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