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