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