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