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 } 96 97 /** 98 * Called when a preference in the group is clicked. 99 * 100 * @param cachedDevice the device represented by the clicked preference. 101 */ onDeviceClicked(CachedBluetoothDevice cachedDevice)102 protected abstract void onDeviceClicked(CachedBluetoothDevice cachedDevice); 103 104 /** 105 * Returns a mapping of all {@link CachedBluetoothDevice} instances represented by this group 106 * and their associated preferences. 107 */ getPreferenceMap()108 protected Map<CachedBluetoothDevice, BluetoothDevicePreference> getPreferenceMap() { 109 return mPreferenceMap; 110 } 111 112 @Override 113 @CallSuper updateState(PreferenceGroup preferenceGroup)114 protected void updateState(PreferenceGroup preferenceGroup) { 115 Collection<CachedBluetoothDevice> cachedDevices = 116 getBluetoothManager().getCachedDeviceManager().getCachedDevicesCopy(); 117 118 Set<CachedBluetoothDevice> devicesToRemove = new HashSet<>(mPreferenceMap.keySet()); 119 devicesToRemove.removeAll(cachedDevices); 120 for (CachedBluetoothDevice deviceToRemove : devicesToRemove) { 121 removePreference(deviceToRemove); 122 } 123 124 for (CachedBluetoothDevice cachedDevice : cachedDevices) { 125 if (getDeviceFilter().matches(cachedDevice.getDevice())) { 126 addPreference(cachedDevice); 127 } else { 128 removePreference(cachedDevice); 129 } 130 } 131 132 preferenceGroup.setVisible(preferenceGroup.getPreferenceCount() > 0); 133 } 134 135 @Override onBluetoothStateChanged(int bluetoothState)136 public final void onBluetoothStateChanged(int bluetoothState) { 137 super.onBluetoothStateChanged(bluetoothState); 138 if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) { 139 // Cleanup the UI so that we don't have stale representations when the adapter turns 140 // on again. This can happen if Bluetooth crashes and restarts. 141 getPreference().removeAll(); 142 mPreferenceMap.clear(); 143 } 144 } 145 146 @Override onDeviceAdded(CachedBluetoothDevice cachedDevice)147 public final void onDeviceAdded(CachedBluetoothDevice cachedDevice) { 148 refreshUi(); 149 } 150 151 @Override onDeviceDeleted(CachedBluetoothDevice cachedDevice)152 public final void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { 153 refreshUi(); 154 } 155 addPreference(CachedBluetoothDevice cachedDevice)156 private void addPreference(CachedBluetoothDevice cachedDevice) { 157 if (!mPreferenceMap.containsKey(cachedDevice)) { 158 BluetoothDevicePreference devicePreference = createDevicePreference(cachedDevice); 159 devicePreference.setOnPreferenceClickListener(mDevicePreferenceClickListener); 160 mPreferenceMap.put(cachedDevice, devicePreference); 161 getPreference().addPreference(devicePreference); 162 } 163 } 164 removePreference(CachedBluetoothDevice cachedDevice)165 private void removePreference(CachedBluetoothDevice cachedDevice) { 166 if (mPreferenceMap.containsKey(cachedDevice)) { 167 getPreference().removePreference(mPreferenceMap.get(cachedDevice)); 168 mPreferenceMap.remove(cachedDevice); 169 } 170 } 171 } 172