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