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