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