1 /* 2 * Copyright (C) 2014 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.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.TestApi; 23 import android.bluetooth.BluetoothDevice; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 27 import java.lang.annotation.Retention; 28 import java.lang.annotation.RetentionPolicy; 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.Collection; 32 import java.util.Collections; 33 import java.util.List; 34 import java.util.Locale; 35 import java.util.Objects; 36 import java.util.stream.Collectors; 37 38 /** 39 * Encapsulates the telecom audio state, including the current audio routing, supported audio 40 * routing and mute. 41 */ 42 public final class CallAudioState implements Parcelable { 43 /** @hide */ 44 @Retention(RetentionPolicy.SOURCE) 45 @IntDef(value={ROUTE_EARPIECE, ROUTE_BLUETOOTH, ROUTE_WIRED_HEADSET, ROUTE_SPEAKER}, 46 flag=true) 47 public @interface CallAudioRoute {} 48 49 /** Direct the audio stream through the device's earpiece. */ 50 public static final int ROUTE_EARPIECE = 0x00000001; 51 52 /** Direct the audio stream through Bluetooth. */ 53 public static final int ROUTE_BLUETOOTH = 0x00000002; 54 55 /** Direct the audio stream through a wired headset. */ 56 public static final int ROUTE_WIRED_HEADSET = 0x00000004; 57 58 /** Direct the audio stream through the device's speakerphone. */ 59 public static final int ROUTE_SPEAKER = 0x00000008; 60 61 /** 62 * Direct the audio stream through the device's earpiece or wired headset if one is 63 * connected. 64 */ 65 public static final int ROUTE_WIRED_OR_EARPIECE = ROUTE_EARPIECE | ROUTE_WIRED_HEADSET; 66 67 /** 68 * Bit mask of all possible audio routes. 69 * 70 * @hide 71 **/ 72 public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET | 73 ROUTE_SPEAKER; 74 75 private final boolean isMuted; 76 private final int route; 77 private final int supportedRouteMask; 78 private final BluetoothDevice activeBluetoothDevice; 79 private final Collection<BluetoothDevice> supportedBluetoothDevices; 80 81 /** 82 * Constructor for a {@link CallAudioState} object. 83 * 84 * @param muted {@code true} if the call is muted, {@code false} otherwise. 85 * @param route The current audio route being used. 86 * Allowed values: 87 * {@link #ROUTE_EARPIECE} 88 * {@link #ROUTE_BLUETOOTH} 89 * {@link #ROUTE_WIRED_HEADSET} 90 * {@link #ROUTE_SPEAKER} 91 * @param supportedRouteMask Bit mask of all routes supported by this call. This should be a 92 * bitwise combination of the following values: 93 * {@link #ROUTE_EARPIECE} 94 * {@link #ROUTE_BLUETOOTH} 95 * {@link #ROUTE_WIRED_HEADSET} 96 * {@link #ROUTE_SPEAKER} 97 */ CallAudioState(boolean muted, @CallAudioRoute int route, @CallAudioRoute int supportedRouteMask)98 public CallAudioState(boolean muted, @CallAudioRoute int route, 99 @CallAudioRoute int supportedRouteMask) { 100 this(muted, route, supportedRouteMask, null, Collections.emptyList()); 101 } 102 103 /** @hide */ 104 @TestApi CallAudioState(boolean isMuted, @CallAudioRoute int route, @CallAudioRoute int supportedRouteMask, @Nullable BluetoothDevice activeBluetoothDevice, @NonNull Collection<BluetoothDevice> supportedBluetoothDevices)105 public CallAudioState(boolean isMuted, @CallAudioRoute int route, 106 @CallAudioRoute int supportedRouteMask, 107 @Nullable BluetoothDevice activeBluetoothDevice, 108 @NonNull Collection<BluetoothDevice> supportedBluetoothDevices) { 109 this.isMuted = isMuted; 110 this.route = route; 111 this.supportedRouteMask = supportedRouteMask; 112 this.activeBluetoothDevice = activeBluetoothDevice; 113 this.supportedBluetoothDevices = supportedBluetoothDevices; 114 } 115 116 /** @hide */ CallAudioState(CallAudioState state)117 public CallAudioState(CallAudioState state) { 118 isMuted = state.isMuted(); 119 route = state.getRoute(); 120 supportedRouteMask = state.getSupportedRouteMask(); 121 activeBluetoothDevice = state.activeBluetoothDevice; 122 supportedBluetoothDevices = state.getSupportedBluetoothDevices(); 123 } 124 125 /** @hide */ 126 @SuppressWarnings("deprecation") CallAudioState(AudioState state)127 public CallAudioState(AudioState state) { 128 isMuted = state.isMuted(); 129 route = state.getRoute(); 130 supportedRouteMask = state.getSupportedRouteMask(); 131 activeBluetoothDevice = null; 132 supportedBluetoothDevices = Collections.emptyList(); 133 } 134 135 @Override equals(Object obj)136 public boolean equals(Object obj) { 137 if (obj == null) { 138 return false; 139 } 140 if (!(obj instanceof CallAudioState)) { 141 return false; 142 } 143 CallAudioState state = (CallAudioState) obj; 144 if (supportedBluetoothDevices.size() != state.supportedBluetoothDevices.size()) { 145 return false; 146 } 147 for (BluetoothDevice device : supportedBluetoothDevices) { 148 if (!state.supportedBluetoothDevices.contains(device)) { 149 return false; 150 } 151 } 152 return Objects.equals(activeBluetoothDevice, state.activeBluetoothDevice) && isMuted() == 153 state.isMuted() && getRoute() == state.getRoute() && getSupportedRouteMask() == 154 state.getSupportedRouteMask(); 155 } 156 157 @Override toString()158 public String toString() { 159 String bluetoothDeviceList = supportedBluetoothDevices.stream() 160 .map(BluetoothDevice::getAddress).collect(Collectors.joining(", ")); 161 162 return String.format(Locale.US, 163 "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s, " + 164 "activeBluetoothDevice: [%s], supportedBluetoothDevices: [%s]]", 165 isMuted, 166 audioRouteToString(route), 167 audioRouteToString(supportedRouteMask), 168 activeBluetoothDevice, 169 bluetoothDeviceList); 170 } 171 172 /** 173 * @return {@code true} if the call is muted, {@code false} otherwise. 174 */ isMuted()175 public boolean isMuted() { 176 return isMuted; 177 } 178 179 /** 180 * @return The current audio route being used. 181 */ 182 @CallAudioRoute getRoute()183 public int getRoute() { 184 return route; 185 } 186 187 /** 188 * @return Bit mask of all routes supported by this call. 189 */ 190 @CallAudioRoute getSupportedRouteMask()191 public int getSupportedRouteMask() { 192 return supportedRouteMask; 193 } 194 195 /** 196 * @return The {@link BluetoothDevice} through which audio is being routed. 197 * Will not be {@code null} if {@link #getRoute()} returns {@link #ROUTE_BLUETOOTH}. 198 */ getActiveBluetoothDevice()199 public BluetoothDevice getActiveBluetoothDevice() { 200 return activeBluetoothDevice; 201 } 202 203 /** 204 * @return {@link List} of {@link BluetoothDevice}s that can be used for this call. 205 */ getSupportedBluetoothDevices()206 public Collection<BluetoothDevice> getSupportedBluetoothDevices() { 207 return supportedBluetoothDevices; 208 } 209 210 /** 211 * Converts the provided audio route into a human readable string representation. 212 * 213 * @param route to convert into a string. 214 * 215 * @return String representation of the provided audio route. 216 */ audioRouteToString(int route)217 public static String audioRouteToString(int route) { 218 if (route == 0 || (route & ~ROUTE_ALL) != 0x0) { 219 return "UNKNOWN"; 220 } 221 222 StringBuffer buffer = new StringBuffer(); 223 if ((route & ROUTE_EARPIECE) == ROUTE_EARPIECE) { 224 listAppend(buffer, "EARPIECE"); 225 } 226 if ((route & ROUTE_BLUETOOTH) == ROUTE_BLUETOOTH) { 227 listAppend(buffer, "BLUETOOTH"); 228 } 229 if ((route & ROUTE_WIRED_HEADSET) == ROUTE_WIRED_HEADSET) { 230 listAppend(buffer, "WIRED_HEADSET"); 231 } 232 if ((route & ROUTE_SPEAKER) == ROUTE_SPEAKER) { 233 listAppend(buffer, "SPEAKER"); 234 } 235 236 return buffer.toString(); 237 } 238 239 /** 240 * Responsible for creating AudioState objects for deserialized Parcels. 241 */ 242 public static final @android.annotation.NonNull Parcelable.Creator<CallAudioState> CREATOR = 243 new Parcelable.Creator<CallAudioState> () { 244 245 @Override 246 public CallAudioState createFromParcel(Parcel source) { 247 boolean isMuted = source.readByte() == 0 ? false : true; 248 int route = source.readInt(); 249 int supportedRouteMask = source.readInt(); 250 BluetoothDevice activeBluetoothDevice = source.readParcelable( 251 ClassLoader.getSystemClassLoader()); 252 List<BluetoothDevice> supportedBluetoothDevices = new ArrayList<>(); 253 source.readParcelableList(supportedBluetoothDevices, 254 ClassLoader.getSystemClassLoader()); 255 return new CallAudioState(isMuted, route, 256 supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices); 257 } 258 259 @Override 260 public CallAudioState[] newArray(int size) { 261 return new CallAudioState[size]; 262 } 263 }; 264 265 /** 266 * {@inheritDoc} 267 */ 268 @Override describeContents()269 public int describeContents() { 270 return 0; 271 } 272 273 /** 274 * Writes AudioState object into a serializeable Parcel. 275 */ 276 @Override writeToParcel(Parcel destination, int flags)277 public void writeToParcel(Parcel destination, int flags) { 278 destination.writeByte((byte) (isMuted ? 1 : 0)); 279 destination.writeInt(route); 280 destination.writeInt(supportedRouteMask); 281 destination.writeParcelable(activeBluetoothDevice, 0); 282 destination.writeParcelableList(new ArrayList<>(supportedBluetoothDevices), 0); 283 } 284 listAppend(StringBuffer buffer, String str)285 private static void listAppend(StringBuffer buffer, String str) { 286 if (buffer.length() > 0) { 287 buffer.append(", "); 288 } 289 buffer.append(str); 290 } 291 } 292