Home | History | Annotate | Download | only in media
      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 android.app.Notification;
     19 import android.bluetooth.BluetoothProfile;
     20 import android.content.Context;
     21 import android.util.Log;
     22 
     23 import androidx.annotation.IntDef;
     24 
     25 import com.android.internal.annotations.VisibleForTesting;
     26 import com.android.settingslib.bluetooth.BluetoothCallback;
     27 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
     28 import com.android.settingslib.bluetooth.LocalBluetoothManager;
     29 
     30 import java.lang.annotation.Retention;
     31 import java.lang.annotation.RetentionPolicy;
     32 import java.util.ArrayList;
     33 import java.util.Collection;
     34 import java.util.Collections;
     35 import java.util.Comparator;
     36 import java.util.List;
     37 
     38 /**
     39  * LocalMediaManager provide interface to get MediaDevice list and transfer media to MediaDevice.
     40  */
     41 public class LocalMediaManager implements BluetoothCallback {
     42     private static final Comparator<MediaDevice> COMPARATOR = Comparator.naturalOrder();
     43     private static final String TAG = "LocalMediaManager";
     44 
     45     @Retention(RetentionPolicy.SOURCE)
     46     @IntDef({MediaDeviceState.STATE_CONNECTED,
     47             MediaDeviceState.STATE_CONNECTING,
     48             MediaDeviceState.STATE_DISCONNECTED})
     49     public @interface MediaDeviceState {
     50         int STATE_CONNECTED = 1;
     51         int STATE_CONNECTING = 2;
     52         int STATE_DISCONNECTED = 3;
     53     }
     54 
     55     private final Collection<DeviceCallback> mCallbacks = new ArrayList<>();
     56     @VisibleForTesting
     57     final MediaDeviceCallback mMediaDeviceCallback = new MediaDeviceCallback();
     58 
     59     private Context mContext;
     60     private BluetoothMediaManager mBluetoothMediaManager;
     61     private LocalBluetoothManager mLocalBluetoothManager;
     62 
     63     @VisibleForTesting
     64     List<MediaDevice> mMediaDevices = new ArrayList<>();
     65     @VisibleForTesting
     66     MediaDevice mPhoneDevice;
     67     @VisibleForTesting
     68     MediaDevice mCurrentConnectedDevice;
     69 
     70     /**
     71      * Register to start receiving callbacks for MediaDevice events.
     72      */
     73     public void registerCallback(DeviceCallback callback) {
     74         synchronized (mCallbacks) {
     75             mCallbacks.add(callback);
     76         }
     77     }
     78 
     79     /**
     80      * Unregister to stop receiving callbacks for MediaDevice events
     81      */
     82     public void unregisterCallback(DeviceCallback callback) {
     83         synchronized (mCallbacks) {
     84             mCallbacks.remove(callback);
     85         }
     86     }
     87 
     88     public LocalMediaManager(Context context, String packageName, Notification notification) {
     89         mContext = context;
     90         mLocalBluetoothManager =
     91                 LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null);
     92         if (mLocalBluetoothManager == null) {
     93             Log.e(TAG, "Bluetooth is not supported on this device");
     94             return;
     95         }
     96 
     97         mBluetoothMediaManager =
     98                 new BluetoothMediaManager(context, mLocalBluetoothManager, notification);
     99     }
    100 
    101     @VisibleForTesting
    102     LocalMediaManager(Context context, LocalBluetoothManager localBluetoothManager,
    103             BluetoothMediaManager bluetoothMediaManager, InfoMediaManager infoMediaManager) {
    104         mContext = context;
    105         mLocalBluetoothManager = localBluetoothManager;
    106         mBluetoothMediaManager = bluetoothMediaManager;
    107     }
    108 
    109     /**
    110      * Connect the MediaDevice to transfer media
    111      * @param connectDevice the MediaDevice
    112      */
    113     public void connectDevice(MediaDevice connectDevice) {
    114         final MediaDevice device = getMediaDeviceById(mMediaDevices, connectDevice.getId());
    115         if (device instanceof BluetoothMediaDevice) {
    116             final CachedBluetoothDevice cachedDevice =
    117                     ((BluetoothMediaDevice) device).getCachedDevice();
    118             if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) {
    119                 cachedDevice.connect(true);
    120                 return;
    121             }
    122         }
    123 
    124         if (device == mCurrentConnectedDevice) {
    125             Log.d(TAG, "connectDevice() this device all ready connected! : " + device.getName());
    126             return;
    127         }
    128 
    129         //TODO(b/121083246): Update it once remote media API is ready.
    130         if (mCurrentConnectedDevice != null && !(connectDevice instanceof InfoMediaDevice)) {
    131             mCurrentConnectedDevice.disconnect();
    132         }
    133 
    134         final boolean isConnected = device.connect();
    135         if (isConnected) {
    136             mCurrentConnectedDevice = device;
    137         }
    138 
    139         final int state = isConnected
    140                 ? MediaDeviceState.STATE_CONNECTED
    141                 : MediaDeviceState.STATE_DISCONNECTED;
    142         dispatchSelectedDeviceStateChanged(device, state);
    143     }
    144 
    145     void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) {
    146         synchronized (mCallbacks) {
    147             for (DeviceCallback callback : mCallbacks) {
    148                 callback.onSelectedDeviceStateChanged(device, state);
    149             }
    150         }
    151     }
    152 
    153     /**
    154      * Start scan connected MediaDevice
    155      */
    156     public void startScan() {
    157         mMediaDevices.clear();
    158         mBluetoothMediaManager.registerCallback(mMediaDeviceCallback);
    159         mBluetoothMediaManager.startScan();
    160     }
    161 
    162     private void addPhoneDeviceIfNecessary() {
    163         // add phone device to list if there have any Bluetooth device and cast device.
    164         if (mMediaDevices.size() > 0 && !mMediaDevices.contains(mPhoneDevice)) {
    165             if (mPhoneDevice == null) {
    166                 mPhoneDevice = new PhoneMediaDevice(mContext, mLocalBluetoothManager);
    167             }
    168             mMediaDevices.add(mPhoneDevice);
    169         }
    170     }
    171 
    172     private void removePhoneMediaDeviceIfNecessary() {
    173         // if PhoneMediaDevice is the last item in the list, remove it.
    174         if (mMediaDevices.size() == 1 && mMediaDevices.contains(mPhoneDevice)) {
    175             mMediaDevices.clear();
    176         }
    177     }
    178 
    179     void dispatchDeviceListUpdate() {
    180         synchronized (mCallbacks) {
    181             Collections.sort(mMediaDevices, COMPARATOR);
    182             for (DeviceCallback callback : mCallbacks) {
    183                 callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
    184             }
    185         }
    186     }
    187 
    188     /**
    189      * Stop scan MediaDevice
    190      */
    191     public void stopScan() {
    192         mBluetoothMediaManager.unregisterCallback(mMediaDeviceCallback);
    193         mBluetoothMediaManager.stopScan();
    194     }
    195 
    196     /**
    197      * Find the MediaDevice through id.
    198      *
    199      * @param devices the list of MediaDevice
    200      * @param id the unique id of MediaDevice
    201      * @return MediaDevice
    202      */
    203     public MediaDevice getMediaDeviceById(List<MediaDevice> devices, String id) {
    204         for (MediaDevice mediaDevice : devices) {
    205             if (mediaDevice.getId().equals(id)) {
    206                 return mediaDevice;
    207             }
    208         }
    209         Log.i(TAG, "getMediaDeviceById() can't found device");
    210         return null;
    211     }
    212 
    213     /**
    214      * Find the current connected MediaDevice.
    215      *
    216      * @return MediaDevice
    217      */
    218     public MediaDevice getCurrentConnectedDevice() {
    219         return mCurrentConnectedDevice;
    220     }
    221 
    222     private MediaDevice updateCurrentConnectedDevice() {
    223         for (MediaDevice device : mMediaDevices) {
    224             if (device instanceof  BluetoothMediaDevice) {
    225                 if (isConnected(((BluetoothMediaDevice) device).getCachedDevice())) {
    226                     return device;
    227                 }
    228             }
    229         }
    230         return mMediaDevices.contains(mPhoneDevice) ? mPhoneDevice : null;
    231     }
    232 
    233     private boolean isConnected(CachedBluetoothDevice device) {
    234         return device.isActiveDevice(BluetoothProfile.A2DP)
    235                 || device.isActiveDevice(BluetoothProfile.HEARING_AID);
    236     }
    237 
    238     class MediaDeviceCallback implements MediaManager.MediaDeviceCallback {
    239         @Override
    240         public void onDeviceAdded(MediaDevice device) {
    241             if (!mMediaDevices.contains(device)) {
    242                 mMediaDevices.add(device);
    243                 addPhoneDeviceIfNecessary();
    244                 dispatchDeviceListUpdate();
    245             }
    246         }
    247 
    248         @Override
    249         public void onDeviceListAdded(List<MediaDevice> devices) {
    250             for (MediaDevice device : devices) {
    251                 if (getMediaDeviceById(mMediaDevices, device.getId()) == null) {
    252                     mMediaDevices.add(device);
    253                 }
    254             }
    255             addPhoneDeviceIfNecessary();
    256             mCurrentConnectedDevice = updateCurrentConnectedDevice();
    257             updatePhoneMediaDeviceSummary();
    258             dispatchDeviceListUpdate();
    259         }
    260 
    261         private void updatePhoneMediaDeviceSummary() {
    262             if (mPhoneDevice != null) {
    263                 ((PhoneMediaDevice) mPhoneDevice)
    264                         .updateSummary(mCurrentConnectedDevice == mPhoneDevice);
    265             }
    266         }
    267 
    268         @Override
    269         public void onDeviceRemoved(MediaDevice device) {
    270             if (mMediaDevices.contains(device)) {
    271                 mMediaDevices.remove(device);
    272                 removePhoneMediaDeviceIfNecessary();
    273                 dispatchDeviceListUpdate();
    274             }
    275         }
    276 
    277         @Override
    278         public void onDeviceListRemoved(List<MediaDevice> devices) {
    279             mMediaDevices.removeAll(devices);
    280             removePhoneMediaDeviceIfNecessary();
    281             dispatchDeviceListUpdate();
    282         }
    283 
    284         @Override
    285         public void onConnectedDeviceChanged(String id) {
    286             final MediaDevice connectDevice = getMediaDeviceById(mMediaDevices, id);
    287 
    288             if (connectDevice == mCurrentConnectedDevice) {
    289                 Log.d(TAG, "onConnectedDeviceChanged() this device all ready connected!");
    290                 return;
    291             }
    292             mCurrentConnectedDevice = connectDevice;
    293             updatePhoneMediaDeviceSummary();
    294             dispatchDeviceListUpdate();
    295         }
    296 
    297         @Override
    298         public void onDeviceAttributesChanged() {
    299             addPhoneDeviceIfNecessary();
    300             removePhoneMediaDeviceIfNecessary();
    301             dispatchDeviceListUpdate();
    302         }
    303     }
    304 
    305 
    306     /**
    307      * Callback for notifying device information updating
    308      */
    309     public interface DeviceCallback {
    310         /**
    311          * Callback for notifying device list updated.
    312          *
    313          * @param devices MediaDevice list
    314          */
    315         void onDeviceListUpdate(List<MediaDevice> devices);
    316 
    317         /**
    318          * Callback for notifying the connected device is changed.
    319          *
    320          * @param device the changed connected MediaDevice
    321          * @param state the current MediaDevice state, the possible values are:
    322          * {@link MediaDeviceState#STATE_CONNECTED},
    323          * {@link MediaDeviceState#STATE_CONNECTING},
    324          * {@link MediaDeviceState#STATE_DISCONNECTED}
    325          */
    326         void onSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state);
    327     }
    328 }
    329