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