Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2017 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.settings.bluetooth;
     17 
     18 import android.bluetooth.BluetoothDevice;
     19 import android.content.Context;
     20 import android.os.Bundle;
     21 import android.os.SystemProperties;
     22 import android.support.annotation.VisibleForTesting;
     23 import android.support.v7.preference.Preference;
     24 import android.util.Log;
     25 
     26 import com.android.settings.R;
     27 import com.android.settings.connecteddevice.DevicePreferenceCallback;
     28 import com.android.settings.core.SubSettingLauncher;
     29 import com.android.settings.dashboard.DashboardFragment;
     30 import com.android.settings.widget.GearPreference;
     31 import com.android.settingslib.bluetooth.BluetoothCallback;
     32 import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
     33 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
     34 import com.android.settingslib.bluetooth.LocalBluetoothManager;
     35 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
     36 
     37 import java.util.Collection;
     38 import java.util.HashMap;
     39 import java.util.Map;
     40 
     41 /**
     42  * Update the bluetooth devices. It gets bluetooth event from {@link LocalBluetoothManager} using
     43  * {@link BluetoothCallback}. It notifies the upper level whether to add/remove the preference
     44  * through {@link DevicePreferenceCallback}
     45  *
     46  * In {@link BluetoothDeviceUpdater}, it uses {@link BluetoothDeviceFilter.Filter} to detect
     47  * whether the {@link CachedBluetoothDevice} is relevant.
     48  */
     49 public abstract class BluetoothDeviceUpdater implements BluetoothCallback,
     50         LocalBluetoothProfileManager.ServiceListener {
     51     private static final String TAG = "BluetoothDeviceUpdater";
     52     private static final String BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY =
     53             "persist.bluetooth.showdeviceswithoutnames";
     54 
     55     protected final LocalBluetoothManager mLocalManager;
     56     protected final DevicePreferenceCallback mDevicePreferenceCallback;
     57     protected final Map<BluetoothDevice, Preference> mPreferenceMap;
     58     protected Context mPrefContext;
     59     protected DashboardFragment mFragment;
     60 
     61     private final boolean mShowDeviceWithoutNames;
     62 
     63     @VisibleForTesting
     64     final GearPreference.OnGearClickListener mDeviceProfilesListener = pref -> {
     65         launchDeviceDetails(pref);
     66     };
     67 
     68     public BluetoothDeviceUpdater(Context context, DashboardFragment fragment,
     69             DevicePreferenceCallback devicePreferenceCallback) {
     70         this(fragment, devicePreferenceCallback, Utils.getLocalBtManager(context));
     71     }
     72 
     73     @VisibleForTesting
     74     BluetoothDeviceUpdater(DashboardFragment fragment,
     75             DevicePreferenceCallback devicePreferenceCallback, LocalBluetoothManager localManager) {
     76         mFragment = fragment;
     77         mDevicePreferenceCallback = devicePreferenceCallback;
     78         mShowDeviceWithoutNames = SystemProperties.getBoolean(
     79                 BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false);
     80         mPreferenceMap = new HashMap<>();
     81         mLocalManager = localManager;
     82     }
     83 
     84     /**
     85      * Register the bluetooth event callback and update the list
     86      */
     87     public void registerCallback() {
     88         mLocalManager.setForegroundActivity(mFragment.getContext());
     89         mLocalManager.getEventManager().registerCallback(this);
     90         mLocalManager.getProfileManager().addServiceListener(this);
     91         forceUpdate();
     92     }
     93 
     94     /**
     95      * Unregister the bluetooth event callback
     96      */
     97     public void unregisterCallback() {
     98         mLocalManager.setForegroundActivity(null);
     99         mLocalManager.getEventManager().unregisterCallback(this);
    100         mLocalManager.getProfileManager().removeServiceListener(this);
    101     }
    102 
    103     /**
    104      * Force to update the list of bluetooth devices
    105      */
    106     public void forceUpdate() {
    107         Collection<CachedBluetoothDevice> cachedDevices =
    108                 mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
    109         for (CachedBluetoothDevice cachedBluetoothDevice : cachedDevices) {
    110             update(cachedBluetoothDevice);
    111         }
    112     }
    113 
    114     @Override
    115     public void onBluetoothStateChanged(int bluetoothState) {
    116         forceUpdate();
    117     }
    118 
    119     @Override
    120     public void onScanningStateChanged(boolean started) {}
    121 
    122     @Override
    123     public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
    124         update(cachedDevice);
    125     }
    126 
    127     @Override
    128     public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
    129         // Used to combine the hearing aid entries just after pairing. Once both the hearing aids
    130         // get connected and their hiSyncId gets populated, this gets called for one of the
    131         // 2 hearing aids so that only one entry in the connected devices list will be seen.
    132         removePreference(cachedDevice);
    133     }
    134 
    135     @Override
    136     public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
    137         update(cachedDevice);
    138     }
    139 
    140     @Override
    141     public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {}
    142 
    143     @Override
    144     public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
    145     }
    146 
    147     @Override
    148     public void onAudioModeChanged() {
    149     }
    150 
    151     @Override
    152     public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state,
    153             int bluetoothProfile) {
    154     }
    155 
    156     @Override
    157     public void onServiceConnected() {
    158         // When bluetooth service connected update the UI
    159         forceUpdate();
    160     }
    161 
    162     @Override
    163     public void onServiceDisconnected() {
    164 
    165     }
    166 
    167     /**
    168      * Set the context to generate the {@link Preference}, so it could get the correct theme.
    169      */
    170     public void setPrefContext(Context context) {
    171         mPrefContext = context;
    172     }
    173 
    174     /**
    175      * Return {@code true} if {@code cachedBluetoothDevice} matches this
    176      * {@link BluetoothDeviceUpdater} and should stay in the list, otherwise return {@code false}
    177      */
    178     public abstract boolean isFilterMatched(CachedBluetoothDevice cachedBluetoothDevice);
    179 
    180     /**
    181      * Update whether to show {@link CachedBluetoothDevice} in the list.
    182      */
    183     protected void update(CachedBluetoothDevice cachedBluetoothDevice) {
    184         if (isFilterMatched(cachedBluetoothDevice)) {
    185             // Add the preference if it is new one
    186             addPreference(cachedBluetoothDevice);
    187         } else {
    188             removePreference(cachedBluetoothDevice);
    189         }
    190     }
    191 
    192     /**
    193      * Add the {@link Preference} that represents the {@code cachedDevice}
    194      */
    195     protected void addPreference(CachedBluetoothDevice cachedDevice) {
    196         final BluetoothDevice device = cachedDevice.getDevice();
    197         if (!mPreferenceMap.containsKey(device)) {
    198             BluetoothDevicePreference btPreference =
    199                     new BluetoothDevicePreference(mPrefContext, cachedDevice,
    200                             mShowDeviceWithoutNames);
    201             btPreference.setOnGearClickListener(mDeviceProfilesListener);
    202             if (this instanceof Preference.OnPreferenceClickListener) {
    203                 btPreference.setOnPreferenceClickListener(
    204                         (Preference.OnPreferenceClickListener)this);
    205             }
    206             mPreferenceMap.put(device, btPreference);
    207             mDevicePreferenceCallback.onDeviceAdded(btPreference);
    208         }
    209     }
    210 
    211     /**
    212      * Remove the {@link Preference} that represents the {@code cachedDevice}
    213      */
    214     protected void removePreference(CachedBluetoothDevice cachedDevice) {
    215         final BluetoothDevice device = cachedDevice.getDevice();
    216         if (mPreferenceMap.containsKey(device)) {
    217             mDevicePreferenceCallback.onDeviceRemoved(mPreferenceMap.get(device));
    218             mPreferenceMap.remove(device);
    219         }
    220     }
    221 
    222     /**
    223      * Get {@link CachedBluetoothDevice} from {@link Preference} and it is used to init
    224      * {@link SubSettingLauncher} to launch {@link BluetoothDeviceDetailsFragment}
    225      */
    226     protected void launchDeviceDetails(Preference preference) {
    227         final CachedBluetoothDevice device =
    228                 ((BluetoothDevicePreference) preference).getBluetoothDevice();
    229         if (device == null) {
    230             return;
    231         }
    232         final Bundle args = new Bundle();
    233         args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS,
    234                 device.getDevice().getAddress());
    235 
    236         new SubSettingLauncher(mFragment.getContext())
    237                 .setDestination(BluetoothDeviceDetailsFragment.class.getName())
    238                 .setArguments(args)
    239                 .setTitle(R.string.device_details_title)
    240                 .setSourceMetricsCategory(mFragment.getMetricsCategory())
    241                 .launch();
    242     }
    243 
    244     /**
    245      * @return {@code true} if {@code cachedBluetoothDevice} is connected
    246      * and the bond state is bonded.
    247      */
    248     public boolean isDeviceConnected(CachedBluetoothDevice cachedDevice) {
    249         if (cachedDevice == null) {
    250             return false;
    251         }
    252         final BluetoothDevice device = cachedDevice.getDevice();
    253         return device.getBondState() == BluetoothDevice.BOND_BONDED && device.isConnected();
    254     }
    255 }
    256