1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.telecom;
18
19import android.annotation.SystemApi;
20import android.os.Bundle;
21import android.util.ArrayMap;
22
23import java.util.Collections;
24import java.util.List;
25import java.util.Map;
26import java.util.Objects;
27import java.util.concurrent.CopyOnWriteArrayList;
28
29/**
30 * A unified virtual device providing a means of voice (and other) communication on a device.
31 *
32 * @hide
33 * @deprecated Use {@link InCallService} directly instead of using this class.
34 */
35@SystemApi
36@Deprecated
37public final class Phone {
38
39    public abstract static class Listener {
40        /**
41         * Called when the audio state changes.
42         *
43         * @param phone The {@code Phone} calling this method.
44         * @param audioState The new {@link AudioState}.
45         *
46         * @deprecated Use {@link #onCallAudioStateChanged(Phone, CallAudioState)} instead.
47         */
48        @Deprecated
49        public void onAudioStateChanged(Phone phone, AudioState audioState) { }
50
51        /**
52         * Called when the audio state changes.
53         *
54         * @param phone The {@code Phone} calling this method.
55         * @param callAudioState The new {@link CallAudioState}.
56         */
57        public void onCallAudioStateChanged(Phone phone, CallAudioState callAudioState) { }
58
59        /**
60         * Called to bring the in-call screen to the foreground. The in-call experience should
61         * respond immediately by coming to the foreground to inform the user of the state of
62         * ongoing {@code Call}s.
63         *
64         * @param phone The {@code Phone} calling this method.
65         * @param showDialpad If true, put up the dialpad when the screen is shown.
66         */
67        public void onBringToForeground(Phone phone, boolean showDialpad) { }
68
69        /**
70         * Called when a {@code Call} has been added to this in-call session. The in-call user
71         * experience should add necessary state listeners to the specified {@code Call} and
72         * immediately start to show the user information about the existence
73         * and nature of this {@code Call}. Subsequent invocations of {@link #getCalls()} will
74         * include this {@code Call}.
75         *
76         * @param phone The {@code Phone} calling this method.
77         * @param call A newly added {@code Call}.
78         */
79        public void onCallAdded(Phone phone, Call call) { }
80
81        /**
82         * Called when a {@code Call} has been removed from this in-call session. The in-call user
83         * experience should remove any state listeners from the specified {@code Call} and
84         * immediately stop displaying any information about this {@code Call}.
85         * Subsequent invocations of {@link #getCalls()} will no longer include this {@code Call}.
86         *
87         * @param phone The {@code Phone} calling this method.
88         * @param call A newly removed {@code Call}.
89         */
90        public void onCallRemoved(Phone phone, Call call) { }
91
92        /**
93         * Called when the {@code Phone} ability to add more calls changes.  If the phone cannot
94         * support more calls then {@code canAddCall} is set to {@code false}.  If it can, then it
95         * is set to {@code true}.
96         *
97         * @param phone The {@code Phone} calling this method.
98         * @param canAddCall Indicates whether an additional call can be added.
99         */
100        public void onCanAddCallChanged(Phone phone, boolean canAddCall) { }
101
102        /**
103         * Called to silence the ringer if a ringing call exists.
104         *
105         * @param phone The {@code Phone} calling this method.
106         */
107        public void onSilenceRinger(Phone phone) { }
108    }
109
110    // A Map allows us to track each Call by its Telecom-specified call ID
111    private final Map<String, Call> mCallByTelecomCallId = new ArrayMap<>();
112
113    // A List allows us to keep the Calls in a stable iteration order so that casually developed
114    // user interface components do not incur any spurious jank
115    private final List<Call> mCalls = new CopyOnWriteArrayList<>();
116
117    // An unmodifiable view of the above List can be safely shared with subclass implementations
118    private final List<Call> mUnmodifiableCalls = Collections.unmodifiableList(mCalls);
119
120    private final InCallAdapter mInCallAdapter;
121
122    private CallAudioState mCallAudioState;
123
124    private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
125
126    private boolean mCanAddCall = true;
127
128    Phone(InCallAdapter adapter) {
129        mInCallAdapter = adapter;
130    }
131
132    final void internalAddCall(ParcelableCall parcelableCall) {
133        Call call = new Call(this, parcelableCall.getId(), mInCallAdapter,
134                parcelableCall.getState());
135        mCallByTelecomCallId.put(parcelableCall.getId(), call);
136        mCalls.add(call);
137        checkCallTree(parcelableCall);
138        call.internalUpdate(parcelableCall, mCallByTelecomCallId);
139        fireCallAdded(call);
140     }
141
142    final void internalRemoveCall(Call call) {
143        mCallByTelecomCallId.remove(call.internalGetCallId());
144        mCalls.remove(call);
145
146        InCallService.VideoCall videoCall = call.getVideoCall();
147        if (videoCall != null) {
148            videoCall.destroy();
149        }
150        fireCallRemoved(call);
151    }
152
153    final void internalUpdateCall(ParcelableCall parcelableCall) {
154         Call call = mCallByTelecomCallId.get(parcelableCall.getId());
155         if (call != null) {
156             checkCallTree(parcelableCall);
157             call.internalUpdate(parcelableCall, mCallByTelecomCallId);
158         }
159     }
160
161    final void internalSetPostDialWait(String telecomId, String remaining) {
162        Call call = mCallByTelecomCallId.get(telecomId);
163        if (call != null) {
164            call.internalSetPostDialWait(remaining);
165        }
166    }
167
168    final void internalCallAudioStateChanged(CallAudioState callAudioState) {
169        if (!Objects.equals(mCallAudioState, callAudioState)) {
170            mCallAudioState = callAudioState;
171            fireCallAudioStateChanged(callAudioState);
172        }
173    }
174
175    final Call internalGetCallByTelecomId(String telecomId) {
176        return mCallByTelecomCallId.get(telecomId);
177    }
178
179    final void internalBringToForeground(boolean showDialpad) {
180        fireBringToForeground(showDialpad);
181    }
182
183    final void internalSetCanAddCall(boolean canAddCall) {
184        if (mCanAddCall != canAddCall) {
185            mCanAddCall = canAddCall;
186            fireCanAddCallChanged(canAddCall);
187        }
188    }
189
190    final void internalSilenceRinger() {
191        fireSilenceRinger();
192    }
193
194    final void internalOnConnectionEvent(String telecomId, String event, Bundle extras) {
195        Call call = mCallByTelecomCallId.get(telecomId);
196        if (call != null) {
197            call.internalOnConnectionEvent(event, extras);
198        }
199    }
200
201    /**
202     * Called to destroy the phone and cleanup any lingering calls.
203     */
204    final void destroy() {
205        for (Call call : mCalls) {
206            InCallService.VideoCall videoCall = call.getVideoCall();
207            if (videoCall != null) {
208                videoCall.destroy();
209            }
210            if (call.getState() != Call.STATE_DISCONNECTED) {
211                call.internalSetDisconnected();
212            }
213        }
214    }
215
216    /**
217     * Adds a listener to this {@code Phone}.
218     *
219     * @param listener A {@code Listener} object.
220     */
221    public final void addListener(Listener listener) {
222        mListeners.add(listener);
223    }
224
225    /**
226     * Removes a listener from this {@code Phone}.
227     *
228     * @param listener A {@code Listener} object.
229     */
230    public final void removeListener(Listener listener) {
231        if (listener != null) {
232            mListeners.remove(listener);
233        }
234    }
235
236    /**
237     * Obtains the current list of {@code Call}s to be displayed by this in-call experience.
238     *
239     * @return A list of the relevant {@code Call}s.
240     */
241    public final List<Call> getCalls() {
242        return mUnmodifiableCalls;
243    }
244
245    /**
246     * Returns if the {@code Phone} can support additional calls.
247     *
248     * @return Whether the phone supports adding more calls.
249     */
250    public final boolean canAddCall() {
251        return mCanAddCall;
252    }
253
254    /**
255     * Sets the microphone mute state. When this request is honored, there will be change to
256     * the {@link #getAudioState()}.
257     *
258     * @param state {@code true} if the microphone should be muted; {@code false} otherwise.
259     */
260    public final void setMuted(boolean state) {
261        mInCallAdapter.mute(state);
262    }
263
264    /**
265     * Sets the audio route (speaker, bluetooth, etc...).  When this request is honored, there will
266     * be change to the {@link #getAudioState()}.
267     *
268     * @param route The audio route to use.
269     */
270    public final void setAudioRoute(int route) {
271        mInCallAdapter.setAudioRoute(route);
272    }
273
274    /**
275     * Turns the proximity sensor on. When this request is made, the proximity sensor will
276     * become active, and the touch screen and display will be turned off when the user's face
277     * is detected to be in close proximity to the screen. This operation is a no-op on devices
278     * that do not have a proximity sensor.
279     *
280     * @hide
281     */
282    public final void setProximitySensorOn() {
283        mInCallAdapter.turnProximitySensorOn();
284    }
285
286    /**
287     * Turns the proximity sensor off. When this request is made, the proximity sensor will
288     * become inactive, and no longer affect the touch screen and display. This operation is a
289     * no-op on devices that do not have a proximity sensor.
290     *
291     * @param screenOnImmediately If true, the screen will be turned on immediately if it was
292     * previously off. Otherwise, the screen will only be turned on after the proximity sensor
293     * is no longer triggered.
294     *
295     * @hide
296     */
297    public final void setProximitySensorOff(boolean screenOnImmediately) {
298        mInCallAdapter.turnProximitySensorOff(screenOnImmediately);
299    }
300
301    /**
302     * Obtains the current phone call audio state of the {@code Phone}.
303     *
304     * @return An object encapsulating the audio state.
305     * @deprecated Use {@link #getCallAudioState()} instead.
306     */
307    @Deprecated
308    public final AudioState getAudioState() {
309        return new AudioState(mCallAudioState);
310    }
311
312    /**
313     * Obtains the current phone call audio state of the {@code Phone}.
314     *
315     * @return An object encapsulating the audio state.
316     */
317    public final CallAudioState getCallAudioState() {
318        return mCallAudioState;
319    }
320
321    private void fireCallAdded(Call call) {
322        for (Listener listener : mListeners) {
323            listener.onCallAdded(this, call);
324        }
325    }
326
327    private void fireCallRemoved(Call call) {
328        for (Listener listener : mListeners) {
329            listener.onCallRemoved(this, call);
330        }
331    }
332
333    private void fireCallAudioStateChanged(CallAudioState audioState) {
334        for (Listener listener : mListeners) {
335            listener.onCallAudioStateChanged(this, audioState);
336            listener.onAudioStateChanged(this, new AudioState(audioState));
337        }
338    }
339
340    private void fireBringToForeground(boolean showDialpad) {
341        for (Listener listener : mListeners) {
342            listener.onBringToForeground(this, showDialpad);
343        }
344    }
345
346    private void fireCanAddCallChanged(boolean canAddCall) {
347        for (Listener listener : mListeners) {
348            listener.onCanAddCallChanged(this, canAddCall);
349        }
350    }
351
352    private void fireSilenceRinger() {
353        for (Listener listener : mListeners) {
354            listener.onSilenceRinger(this);
355        }
356    }
357
358    private void checkCallTree(ParcelableCall parcelableCall) {
359        if (parcelableCall.getChildCallIds() != null) {
360            for (int i = 0; i < parcelableCall.getChildCallIds().size(); i++) {
361                if (!mCallByTelecomCallId.containsKey(parcelableCall.getChildCallIds().get(i))) {
362                    Log.wtf(this, "ParcelableCall %s has nonexistent child %s",
363                            parcelableCall.getId(), parcelableCall.getChildCallIds().get(i));
364                }
365            }
366        }
367    }
368}
369