• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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 package com.android.settingslib.media;
17 
18 import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
19 import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
20 import static android.media.MediaRoute2Info.TYPE_DOCK;
21 import static android.media.MediaRoute2Info.TYPE_GROUP;
22 import static android.media.MediaRoute2Info.TYPE_HDMI;
23 import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
24 import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
25 import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
26 import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
27 import static android.media.MediaRoute2Info.TYPE_USB_ACCESSORY;
28 import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
29 import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
30 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
31 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
32 
33 import android.content.Context;
34 import android.content.res.ColorStateList;
35 import android.graphics.PorterDuff;
36 import android.graphics.PorterDuffColorFilter;
37 import android.graphics.drawable.Drawable;
38 import android.media.MediaRoute2Info;
39 import android.media.MediaRouter2Manager;
40 import android.text.TextUtils;
41 
42 import androidx.annotation.IntDef;
43 import androidx.annotation.VisibleForTesting;
44 
45 import com.android.settingslib.R;
46 
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 import java.util.List;
50 
51 /**
52  * MediaDevice represents a media device(such like Bluetooth device, cast device and phone device).
53  */
54 public abstract class MediaDevice implements Comparable<MediaDevice> {
55     private static final String TAG = "MediaDevice";
56 
57     @Retention(RetentionPolicy.SOURCE)
58     @IntDef({MediaDeviceType.TYPE_UNKNOWN,
59             MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE,
60             MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE,
61             MediaDeviceType.TYPE_FAST_PAIR_BLUETOOTH_DEVICE,
62             MediaDeviceType.TYPE_BLUETOOTH_DEVICE,
63             MediaDeviceType.TYPE_CAST_DEVICE,
64             MediaDeviceType.TYPE_CAST_GROUP_DEVICE,
65             MediaDeviceType.TYPE_PHONE_DEVICE})
66     public @interface MediaDeviceType {
67         int TYPE_UNKNOWN = 0;
68         int TYPE_USB_C_AUDIO_DEVICE = 1;
69         int TYPE_3POINT5_MM_AUDIO_DEVICE = 2;
70         int TYPE_FAST_PAIR_BLUETOOTH_DEVICE = 3;
71         int TYPE_BLUETOOTH_DEVICE = 4;
72         int TYPE_CAST_DEVICE = 5;
73         int TYPE_CAST_GROUP_DEVICE = 6;
74         int TYPE_PHONE_DEVICE = 7;
75     }
76 
77     @VisibleForTesting
78     int mType;
79 
80     private int mConnectedRecord;
81     private int mState;
82 
83     protected final Context mContext;
84     protected final MediaRoute2Info mRouteInfo;
85     protected final MediaRouter2Manager mRouterManager;
86     protected final String mPackageName;
87 
MediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName)88     MediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
89             String packageName) {
90         mContext = context;
91         mRouteInfo = info;
92         mRouterManager = routerManager;
93         mPackageName = packageName;
94         setType(info);
95     }
96 
setType(MediaRoute2Info info)97     private void setType(MediaRoute2Info info) {
98         if (info == null) {
99             mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
100             return;
101         }
102 
103         switch (info.getType()) {
104             case TYPE_GROUP:
105                 mType = MediaDeviceType.TYPE_CAST_GROUP_DEVICE;
106                 break;
107             case TYPE_BUILTIN_SPEAKER:
108                 mType = MediaDeviceType.TYPE_PHONE_DEVICE;
109                 break;
110             case TYPE_WIRED_HEADSET:
111             case TYPE_WIRED_HEADPHONES:
112                 mType = MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE;
113                 break;
114             case TYPE_USB_DEVICE:
115             case TYPE_USB_HEADSET:
116             case TYPE_USB_ACCESSORY:
117             case TYPE_DOCK:
118             case TYPE_HDMI:
119                 mType = MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE;
120                 break;
121             case TYPE_HEARING_AID:
122             case TYPE_BLUETOOTH_A2DP:
123                 mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
124                 break;
125             case TYPE_UNKNOWN:
126             case TYPE_REMOTE_TV:
127             case TYPE_REMOTE_SPEAKER:
128             default:
129                 mType = MediaDeviceType.TYPE_CAST_DEVICE;
130                 break;
131         }
132     }
133 
initDeviceRecord()134     void initDeviceRecord() {
135         ConnectionRecordManager.getInstance().fetchLastSelectedDevice(mContext);
136         mConnectedRecord = ConnectionRecordManager.getInstance().fetchConnectionRecord(mContext,
137                 getId());
138     }
139 
setColorFilter(Drawable drawable)140     void setColorFilter(Drawable drawable) {
141         final ColorStateList list =
142                 mContext.getResources().getColorStateList(
143                         R.color.advanced_icon_color, mContext.getTheme());
144         drawable.setColorFilter(new PorterDuffColorFilter(list.getDefaultColor(),
145                 PorterDuff.Mode.SRC_IN));
146     }
147 
148     /**
149      * Get name from MediaDevice.
150      *
151      * @return name of MediaDevice.
152      */
getName()153     public abstract String getName();
154 
155     /**
156      * Get summary from MediaDevice.
157      *
158      * @return summary of MediaDevice.
159      */
getSummary()160     public abstract String getSummary();
161 
162     /**
163      * Get icon of MediaDevice.
164      *
165      * @return drawable of icon.
166      */
getIcon()167     public abstract Drawable getIcon();
168 
169     /**
170      * Get icon of MediaDevice without background.
171      *
172      * @return drawable of icon
173      */
getIconWithoutBackground()174     public abstract Drawable getIconWithoutBackground();
175 
176     /**
177      * Get unique ID that represent MediaDevice
178      * @return unique id of MediaDevice
179      */
getId()180     public abstract String getId();
181 
setConnectedRecord()182     void setConnectedRecord() {
183         mConnectedRecord++;
184         ConnectionRecordManager.getInstance().setConnectionRecord(mContext, getId(),
185                 mConnectedRecord);
186     }
187 
188     /**
189      * According the MediaDevice type to check whether we are connected to this MediaDevice.
190      *
191      * @return Whether it is connected.
192      */
isConnected()193     public abstract boolean isConnected();
194 
195     /**
196      * Request to set volume.
197      *
198      * @param volume is the new value.
199      */
200 
requestSetVolume(int volume)201     public void requestSetVolume(int volume) {
202         mRouterManager.setRouteVolume(mRouteInfo, volume);
203     }
204 
205     /**
206      * Get max volume from MediaDevice.
207      *
208      * @return max volume.
209      */
getMaxVolume()210     public int getMaxVolume() {
211         return mRouteInfo.getVolumeMax();
212     }
213 
214     /**
215      * Get current volume from MediaDevice.
216      *
217      * @return current volume.
218      */
getCurrentVolume()219     public int getCurrentVolume() {
220         return mRouteInfo.getVolume();
221     }
222 
223     /**
224      * Get application package name.
225      *
226      * @return package name.
227      */
getClientPackageName()228     public String getClientPackageName() {
229         return mRouteInfo.getClientPackageName();
230     }
231 
232     /**
233      * Get application label from MediaDevice.
234      *
235      * @return application label.
236      */
getDeviceType()237     public int getDeviceType() {
238         return mType;
239     }
240 
241     /**
242      * Transfer MediaDevice for media
243      *
244      * @return result of transfer media
245      */
connect()246     public boolean connect() {
247         setConnectedRecord();
248         mRouterManager.selectRoute(mPackageName, mRouteInfo);
249         return true;
250     }
251 
252     /**
253      * Stop transfer MediaDevice
254      */
disconnect()255     public void disconnect() {
256     }
257 
258     /**
259      * Set current device's state
260      */
setState(@ocalMediaManager.MediaDeviceState int state)261     public void setState(@LocalMediaManager.MediaDeviceState int state) {
262         mState = state;
263     }
264 
265     /**
266      * Get current device's state
267      *
268      * @return state of device
269      */
getState()270     public @LocalMediaManager.MediaDeviceState int getState() {
271         return mState;
272     }
273 
274     /**
275      * Rules:
276      * 1. If there is one of the connected devices identified as a carkit or fast pair device,
277      * the fast pair device will be always on the first of the device list and carkit will be
278      * second. Rule 2 and Rule 3 can’t overrule this rule.
279      * 2. For devices without any usage data yet
280      * WiFi device group sorted by alphabetical order + BT device group sorted by alphabetical
281      * order + phone speaker
282      * 3. For devices with usage record.
283      * The most recent used one + device group with usage info sorted by how many times the
284      * device has been used.
285      * 4. The order is followed below rule:
286      *    1. USB-C audio device
287      *    2. 3.5 mm audio device
288      *    3. Bluetooth device
289      *    4. Cast device
290      *    5. Cast group device
291      *    6. Phone
292      *
293      * So the device list will look like 5 slots ranked as below.
294      * Rule 4 + Rule 1 + the most recently used device + Rule 3 + Rule 2
295      * Any slot could be empty. And available device will belong to one of the slots.
296      *
297      * @return a negative integer, zero, or a positive integer
298      * as this object is less than, equal to, or greater than the specified object.
299      */
300     @Override
compareTo(MediaDevice another)301     public int compareTo(MediaDevice another) {
302         // Check Bluetooth device is have same connection state
303         if (isConnected() ^ another.isConnected()) {
304             if (isConnected()) {
305                 return -1;
306             } else {
307                 return 1;
308             }
309         }
310 
311         if (mType == another.mType) {
312             // Check fast pair device
313             if (isFastPairDevice()) {
314                 return -1;
315             } else if (another.isFastPairDevice()) {
316                 return 1;
317             }
318 
319             // Check carkit
320             if (isCarKitDevice()) {
321                 return -1;
322             } else if (another.isCarKitDevice()) {
323                 return 1;
324             }
325 
326             // Set last used device at the first item
327             final String lastSelectedDevice = ConnectionRecordManager.getInstance()
328                     .getLastSelectedDevice();
329             if (TextUtils.equals(lastSelectedDevice, getId())) {
330                 return -1;
331             } else if (TextUtils.equals(lastSelectedDevice, another.getId())) {
332                 return 1;
333             }
334             // Sort by how many times the device has been used if there is usage record
335             if ((mConnectedRecord != another.mConnectedRecord)
336                     && (another.mConnectedRecord > 0 || mConnectedRecord > 0)) {
337                 return (another.mConnectedRecord - mConnectedRecord);
338             }
339 
340             // Both devices have never been used
341             // To devices with the same type, sort by alphabetical order
342             final String s1 = getName();
343             final String s2 = another.getName();
344             return s1.compareToIgnoreCase(s2);
345         } else {
346             // Both devices have never been used, the priority is:
347             // 1. USB-C audio device
348             // 2. 3.5 mm audio device
349             // 3. Bluetooth device
350             // 4. Cast device
351             // 5. Cast group device
352             // 6. Phone
353             return mType < another.mType ? -1 : 1;
354         }
355     }
356 
357     /**
358      * Gets the supported features of the route.
359      */
getFeatures()360     public List<String> getFeatures() {
361         return mRouteInfo.getFeatures();
362     }
363 
364     /**
365      * Check if it is CarKit device
366      * @return true if it is CarKit device
367      */
isCarKitDevice()368     protected boolean isCarKitDevice() {
369         return false;
370     }
371 
372     /**
373      * Check if it is FastPair device
374      * @return {@code true} if it is FastPair device, otherwise return {@code false}
375      */
isFastPairDevice()376     protected boolean isFastPairDevice() {
377         return false;
378     }
379 
380     @Override
equals(Object obj)381     public boolean equals(Object obj) {
382         if (!(obj instanceof MediaDevice)) {
383             return false;
384         }
385         final MediaDevice otherDevice = (MediaDevice) obj;
386         return otherDevice.getId().equals(getId());
387     }
388 }
389