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