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 17 package com.android.car.settings.bluetooth; 18 19 import android.annotation.CallSuper; 20 import android.bluetooth.BluetoothAdapter; 21 import android.car.drivingstate.CarUxRestrictions; 22 import android.content.Context; 23 import android.os.UserManager; 24 25 import androidx.annotation.VisibleForTesting; 26 import androidx.preference.Preference; 27 import androidx.preference.PreferenceGroup; 28 29 import com.android.car.settings.common.FragmentController; 30 import com.android.settingslib.bluetooth.BluetoothCallback; 31 import com.android.settingslib.bluetooth.BluetoothDeviceFilter; 32 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 33 import com.android.settingslib.bluetooth.LocalBluetoothManager; 34 35 import java.util.Collection; 36 import java.util.HashMap; 37 import java.util.HashSet; 38 import java.util.Map; 39 import java.util.Set; 40 41 /** 42 * Manages a group of Bluetooth devices by adding preferences for devices that pass a subclass 43 * defined filter and removing preferences for devices that no longer pass. Subclasses are 44 * dispatched click events on individual preferences to customize the behavior. 45 * 46 * <p>Note: {@link #refreshUi()} is called whenever a device is added or removed with {@link 47 * #onDeviceAdded(CachedBluetoothDevice)} or {@link #onDeviceDeleted(CachedBluetoothDevice)}. 48 * Subclasses should listen to state changes (and possibly override additional {@link 49 * BluetoothCallback} methods) and call {@link #refreshUi()} for changes which affect their 50 * implementation of {@link #getDeviceFilter()}. 51 */ 52 public abstract class BluetoothDevicesGroupPreferenceController extends 53 BluetoothPreferenceController<PreferenceGroup> { 54 55 private final Map<CachedBluetoothDevice, BluetoothDevicePreference> mPreferenceMap = 56 new HashMap<>(); 57 private final Preference.OnPreferenceClickListener mDevicePreferenceClickListener = 58 preference -> { 59 onDeviceClicked(((BluetoothDevicePreference) preference).getCachedDevice()); 60 return true; 61 }; 62 BluetoothDevicesGroupPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)63 public BluetoothDevicesGroupPreferenceController(Context context, String preferenceKey, 64 FragmentController fragmentController, CarUxRestrictions uxRestrictions) { 65 super(context, preferenceKey, fragmentController, uxRestrictions); 66 } 67 68 @VisibleForTesting BluetoothDevicesGroupPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions, LocalBluetoothManager localBluetoothManager, UserManager userManager)69 BluetoothDevicesGroupPreferenceController(Context context, String preferenceKey, 70 FragmentController fragmentController, CarUxRestrictions uxRestrictions, 71 LocalBluetoothManager localBluetoothManager, UserManager userManager) { 72 super(context, preferenceKey, fragmentController, uxRestrictions, localBluetoothManager, 73 userManager); 74 } 75 76 @Override getPreferenceType()77 protected Class<PreferenceGroup> getPreferenceType() { 78 return PreferenceGroup.class; 79 } 80 81 /** 82 * Returns a filter for which devices should be included in the group. Devices that do not 83 * pass the filter will not be added. Added devices that no longer pass the filter will be 84 * removed. 85 */ getDeviceFilter()86 protected abstract BluetoothDeviceFilter.Filter getDeviceFilter(); 87 88 /** 89 * Returns a newly created {@link BluetoothDevicePreference} for the given {@link 90 * CachedBluetoothDevice}. Subclasses may override this method to customize how devices are 91 * represented in the group. 92 */ createDevicePreference(CachedBluetoothDevice cachedDevice)93 protected BluetoothDevicePreference createDevicePreference(CachedBluetoothDevice cachedDevice) { 94 return new BluetoothDevicePreference(getContext(), cachedDevice, 95 shouldShowDisconnectedStateSubtitle()); 96 } 97 98 /** 99 * Returns whether or not the created bluetooth device preferences should display the 100 * disconnected state subtitle. 101 */ shouldShowDisconnectedStateSubtitle()102 protected boolean shouldShowDisconnectedStateSubtitle() { 103 return true; 104 } 105 106 /** 107 * Called when a preference in the group is clicked. 108 * 109 * @param cachedDevice the device represented by the clicked preference. 110 */ onDeviceClicked(CachedBluetoothDevice cachedDevice)111 protected abstract void onDeviceClicked(CachedBluetoothDevice cachedDevice); 112 113 /** 114 * Returns a mapping of all {@link CachedBluetoothDevice} instances represented by this group 115 * and their associated preferences. 116 */ getPreferenceMap()117 protected Map<CachedBluetoothDevice, BluetoothDevicePreference> getPreferenceMap() { 118 return mPreferenceMap; 119 } 120 121 @Override 122 @CallSuper updateState(PreferenceGroup preferenceGroup)123 protected void updateState(PreferenceGroup preferenceGroup) { 124 Collection<CachedBluetoothDevice> cachedDevices = 125 getBluetoothManager().getCachedDeviceManager().getCachedDevicesCopy(); 126 127 Set<CachedBluetoothDevice> devicesToRemove = new HashSet<>(mPreferenceMap.keySet()); 128 devicesToRemove.removeAll(cachedDevices); 129 for (CachedBluetoothDevice deviceToRemove : devicesToRemove) { 130 removePreference(deviceToRemove); 131 } 132 133 for (CachedBluetoothDevice cachedDevice : cachedDevices) { 134 if (getDeviceFilter().matches(cachedDevice.getDevice())) { 135 addPreference(cachedDevice); 136 } else { 137 removePreference(cachedDevice); 138 } 139 } 140 141 preferenceGroup.setVisible(preferenceGroup.getPreferenceCount() > 0); 142 } 143 144 @Override onBluetoothStateChanged(int bluetoothState)145 public final void onBluetoothStateChanged(int bluetoothState) { 146 super.onBluetoothStateChanged(bluetoothState); 147 if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) { 148 // Cleanup the UI so that we don't have stale representations when the adapter turns 149 // on again. This can happen if Bluetooth crashes and restarts. 150 getPreference().removeAll(); 151 mPreferenceMap.clear(); 152 } 153 } 154 155 @Override onDeviceAdded(CachedBluetoothDevice cachedDevice)156 public final void onDeviceAdded(CachedBluetoothDevice cachedDevice) { 157 refreshUi(); 158 } 159 160 @Override onDeviceDeleted(CachedBluetoothDevice cachedDevice)161 public final void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { 162 refreshUi(); 163 } 164 addPreference(CachedBluetoothDevice cachedDevice)165 private void addPreference(CachedBluetoothDevice cachedDevice) { 166 if (!mPreferenceMap.containsKey(cachedDevice)) { 167 BluetoothDevicePreference devicePreference = createDevicePreference(cachedDevice); 168 devicePreference.setOnPreferenceClickListener(mDevicePreferenceClickListener); 169 mPreferenceMap.put(cachedDevice, devicePreference); 170 getPreference().addPreference(devicePreference); 171 } 172 } 173 removePreference(CachedBluetoothDevice cachedDevice)174 private void removePreference(CachedBluetoothDevice cachedDevice) { 175 if (mPreferenceMap.containsKey(cachedDevice)) { 176 getPreference().removePreference(mPreferenceMap.get(cachedDevice)); 177 mPreferenceMap.remove(cachedDevice); 178 } 179 } 180 } 181