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 if (call.getState() == Call.STATE_DISCONNECTED) { 178 internalRemoveCall(call); 179 } 180 } else { 181 Log.w(this, "Call %s added, but it was already present", call.internalGetCallId()); 182 checkCallTree(parcelableCall); 183 call.internalUpdate(parcelableCall, mCallByTelecomCallId); 184 } 185 } 186 internalRemoveCall(Call call)187 final void internalRemoveCall(Call call) { 188 synchronized (mLock) { 189 mCallByTelecomCallId.remove(call.internalGetCallId()); 190 mCalls.remove(call); 191 } 192 193 InCallService.VideoCall videoCall = call.getVideoCall(); 194 if (videoCall != null) { 195 videoCall.destroy(); 196 } 197 fireCallRemoved(call); 198 } 199 internalUpdateCall(ParcelableCall parcelableCall)200 final void internalUpdateCall(ParcelableCall parcelableCall) { 201 if (mTargetSdkVersion < SDK_VERSION_R 202 && parcelableCall.getState() == Call.STATE_AUDIO_PROCESSING) { 203 Log.i(this, "removing audio processing call during update for sdk compatibility"); 204 Call call = getCallById(parcelableCall.getId()); 205 if (call != null) { 206 internalRemoveCall(call); 207 } 208 return; 209 } 210 211 Call call = getCallById(parcelableCall.getId()); 212 if (call != null) { 213 checkCallTree(parcelableCall); 214 call.internalUpdate(parcelableCall, mCallByTelecomCallId); 215 } else { 216 // This call may have come out of audio processing. Try adding it if our target sdk 217 // version is low enough. 218 // The only two allowable states coming out of audio processing are ACTIVE and 219 // SIMULATED_RINGING. 220 if (mTargetSdkVersion < SDK_VERSION_R && (parcelableCall.getState() == Call.STATE_ACTIVE 221 || parcelableCall.getState() == Call.STATE_SIMULATED_RINGING)) { 222 Log.i(this, "adding call during update for sdk compatibility"); 223 internalAddCall(parcelableCall); 224 } 225 } 226 } 227 getCallById(String callId)228 Call getCallById(String callId) { 229 synchronized (mLock) { 230 return mCallByTelecomCallId.get(callId); 231 } 232 } 233 internalSetPostDialWait(String telecomId, String remaining)234 final void internalSetPostDialWait(String telecomId, String remaining) { 235 Call call = getCallById(telecomId); 236 if (call != null) { 237 call.internalSetPostDialWait(remaining); 238 } 239 } 240 internalCallAudioStateChanged(CallAudioState callAudioState)241 final void internalCallAudioStateChanged(CallAudioState callAudioState) { 242 if (!Objects.equals(mCallAudioState, callAudioState)) { 243 mCallAudioState = callAudioState; 244 fireCallAudioStateChanged(callAudioState); 245 } 246 } 247 internalGetCallByTelecomId(String telecomId)248 final Call internalGetCallByTelecomId(String telecomId) { 249 return getCallById(telecomId); 250 } 251 internalBringToForeground(boolean showDialpad)252 final void internalBringToForeground(boolean showDialpad) { 253 fireBringToForeground(showDialpad); 254 } 255 internalSetCanAddCall(boolean canAddCall)256 final void internalSetCanAddCall(boolean canAddCall) { 257 if (mCanAddCall != canAddCall) { 258 mCanAddCall = canAddCall; 259 fireCanAddCallChanged(canAddCall); 260 } 261 } 262 internalSilenceRinger()263 final void internalSilenceRinger() { 264 fireSilenceRinger(); 265 } 266 internalOnConnectionEvent(String telecomId, String event, Bundle extras)267 final void internalOnConnectionEvent(String telecomId, String event, Bundle extras) { 268 Call call = getCallById(telecomId); 269 if (call != null) { 270 call.internalOnConnectionEvent(event, extras); 271 } 272 } 273 internalOnRttUpgradeRequest(String callId, int requestId)274 final void internalOnRttUpgradeRequest(String callId, int requestId) { 275 Call call = getCallById(callId); 276 if (call != null) { 277 call.internalOnRttUpgradeRequest(requestId); 278 } 279 } 280 internalOnRttInitiationFailure(String callId, int reason)281 final void internalOnRttInitiationFailure(String callId, int reason) { 282 Call call = getCallById(callId); 283 if (call != null) { 284 call.internalOnRttInitiationFailure(reason); 285 } 286 } 287 internalOnHandoverFailed(String callId, int error)288 final void internalOnHandoverFailed(String callId, int error) { 289 Call call = getCallById(callId); 290 if (call != null) { 291 call.internalOnHandoverFailed(error); 292 } 293 } 294 internalOnHandoverComplete(String callId)295 final void internalOnHandoverComplete(String callId) { 296 Call call = getCallById(callId); 297 if (call != null) { 298 call.internalOnHandoverComplete(); 299 } 300 } 301 302 /** 303 * Called to destroy the phone and cleanup any lingering calls. 304 */ destroy()305 final void destroy() { 306 for (Call call : mCalls) { 307 InCallService.VideoCall videoCall = call.getVideoCall(); 308 if (videoCall != null) { 309 videoCall.destroy(); 310 } 311 if (call.getState() != Call.STATE_DISCONNECTED) { 312 call.internalSetDisconnected(); 313 } 314 } 315 } 316 317 /** 318 * Adds a listener to this {@code Phone}. 319 * 320 * @param listener A {@code Listener} object. 321 */ addListener(Listener listener)322 public final void addListener(Listener listener) { 323 mListeners.add(listener); 324 } 325 326 /** 327 * Removes a listener from this {@code Phone}. 328 * 329 * @param listener A {@code Listener} object. 330 */ removeListener(Listener listener)331 public final void removeListener(Listener listener) { 332 if (listener != null) { 333 mListeners.remove(listener); 334 } 335 } 336 337 /** 338 * Obtains the current list of {@code Call}s to be displayed by this in-call experience. 339 * 340 * @return A list of the relevant {@code Call}s. 341 */ getCalls()342 public final List<Call> getCalls() { 343 return mUnmodifiableCalls; 344 } 345 346 /** 347 * Returns if the {@code Phone} can support additional calls. 348 * 349 * @return Whether the phone supports adding more calls. 350 */ canAddCall()351 public final boolean canAddCall() { 352 return mCanAddCall; 353 } 354 355 /** 356 * Sets the microphone mute state. When this request is honored, there will be change to 357 * the {@link #getAudioState()}. 358 * 359 * @param state {@code true} if the microphone should be muted; {@code false} otherwise. 360 */ setMuted(boolean state)361 public final void setMuted(boolean state) { 362 mInCallAdapter.mute(state); 363 } 364 365 /** 366 * Sets the audio route (speaker, bluetooth, etc...). When this request is honored, there will 367 * be change to the {@link #getAudioState()}. 368 * 369 * @param route The audio route to use. 370 */ setAudioRoute(int route)371 public final void setAudioRoute(int route) { 372 mInCallAdapter.setAudioRoute(route); 373 } 374 375 /** 376 * Request audio routing to a specific bluetooth device. Calling this method may result in 377 * the device routing audio to a different bluetooth device than the one specified. A list of 378 * available devices can be obtained via {@link CallAudioState#getSupportedBluetoothDevices()} 379 * 380 * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by 381 * {@link BluetoothDevice#getAddress()}, or {@code null} if no device is preferred. 382 */ requestBluetoothAudio(String bluetoothAddress)383 public void requestBluetoothAudio(String bluetoothAddress) { 384 mInCallAdapter.requestBluetoothAudio(bluetoothAddress); 385 } 386 387 /** 388 * Request audio routing to a specific CallEndpoint. When this request is honored, there will 389 * be change to the {@link #getCurrentCallEndpoint()}. 390 * 391 * @param endpoint The call endpoint to use. 392 * @param executor The executor of where the callback will execute. 393 * @param callback The callback to notify the result of the endpoint change. 394 * @hide 395 */ requestCallEndpointChange(@onNull CallEndpoint endpoint, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, CallEndpointException> callback)396 public void requestCallEndpointChange(@NonNull CallEndpoint endpoint, 397 @NonNull @CallbackExecutor Executor executor, 398 @NonNull OutcomeReceiver<Void, CallEndpointException> callback) { 399 mInCallAdapter.requestCallEndpointChange(endpoint, executor, callback); 400 } 401 402 /** 403 * Turns the proximity sensor on. When this request is made, the proximity sensor will 404 * become active, and the touch screen and display will be turned off when the user's face 405 * is detected to be in close proximity to the screen. This operation is a no-op on devices 406 * that do not have a proximity sensor. 407 * <p> 408 * This API does not actually turn on the proximity sensor; apps should do this on their own if 409 * required. 410 * @hide 411 */ 412 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 127403196) setProximitySensorOn()413 public final void setProximitySensorOn() { 414 mInCallAdapter.turnProximitySensorOn(); 415 } 416 417 /** 418 * Turns the proximity sensor off. When this request is made, the proximity sensor will 419 * become inactive, and no longer affect the touch screen and display. This operation is a 420 * no-op on devices that do not have a proximity sensor. 421 * 422 * @param screenOnImmediately If true, the screen will be turned on immediately if it was 423 * previously off. Otherwise, the screen will only be turned on after the proximity sensor 424 * is no longer triggered. 425 * <p> 426 * This API does not actually turn of the proximity sensor; apps should do this on their own if 427 * required. 428 * @hide 429 */ 430 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 127403196) setProximitySensorOff(boolean screenOnImmediately)431 public final void setProximitySensorOff(boolean screenOnImmediately) { 432 mInCallAdapter.turnProximitySensorOff(screenOnImmediately); 433 } 434 435 /** 436 * Obtains the current phone call audio state of the {@code Phone}. 437 * 438 * @return An object encapsulating the audio state. 439 * @deprecated Use {@link #getCallAudioState()} instead. 440 */ 441 @Deprecated getAudioState()442 public final AudioState getAudioState() { 443 return new AudioState(mCallAudioState); 444 } 445 446 /** 447 * Obtains the current phone call audio state of the {@code Phone}. 448 * 449 * @return An object encapsulating the audio state. 450 */ getCallAudioState()451 public final CallAudioState getCallAudioState() { 452 return mCallAudioState; 453 } 454 fireCallAdded(Call call)455 private void fireCallAdded(Call call) { 456 for (Listener listener : mListeners) { 457 listener.onCallAdded(this, call); 458 } 459 } 460 fireCallRemoved(Call call)461 private void fireCallRemoved(Call call) { 462 for (Listener listener : mListeners) { 463 listener.onCallRemoved(this, call); 464 } 465 } 466 fireCallAudioStateChanged(CallAudioState audioState)467 private void fireCallAudioStateChanged(CallAudioState audioState) { 468 for (Listener listener : mListeners) { 469 listener.onCallAudioStateChanged(this, audioState); 470 listener.onAudioStateChanged(this, new AudioState(audioState)); 471 } 472 } 473 fireBringToForeground(boolean showDialpad)474 private void fireBringToForeground(boolean showDialpad) { 475 for (Listener listener : mListeners) { 476 listener.onBringToForeground(this, showDialpad); 477 } 478 } 479 fireCanAddCallChanged(boolean canAddCall)480 private void fireCanAddCallChanged(boolean canAddCall) { 481 for (Listener listener : mListeners) { 482 listener.onCanAddCallChanged(this, canAddCall); 483 } 484 } 485 fireSilenceRinger()486 private void fireSilenceRinger() { 487 for (Listener listener : mListeners) { 488 listener.onSilenceRinger(this); 489 } 490 } 491 checkCallTree(ParcelableCall parcelableCall)492 private void checkCallTree(ParcelableCall parcelableCall) { 493 if (parcelableCall.getChildCallIds() != null) { 494 for (int i = 0; i < parcelableCall.getChildCallIds().size(); i++) { 495 if (!mCallByTelecomCallId.containsKey(parcelableCall.getChildCallIds().get(i))) { 496 Log.wtf(this, "ParcelableCall %s has nonexistent child %s", 497 parcelableCall.getId(), parcelableCall.getChildCallIds().get(i)); 498 } 499 } 500 } 501 } 502 } 503