/* * Copyright 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.car.settings.bluetooth; import android.annotation.CallSuper; import android.bluetooth.BluetoothAdapter; import android.car.drivingstate.CarUxRestrictions; import android.content.Context; import android.os.UserManager; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; import com.android.car.settings.common.FragmentController; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.BluetoothDeviceFilter; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Manages a group of Bluetooth devices by adding preferences for devices that pass a subclass * defined filter and removing preferences for devices that no longer pass. Subclasses are * dispatched click events on individual preferences to customize the behavior. * *

Note: {@link #refreshUi()} is called whenever a device is added or removed with {@link * #onDeviceAdded(CachedBluetoothDevice)} or {@link #onDeviceDeleted(CachedBluetoothDevice)}. * Subclasses should listen to state changes (and possibly override additional {@link * BluetoothCallback} methods) and call {@link #refreshUi()} for changes which affect their * implementation of {@link #getDeviceFilter()}. */ public abstract class BluetoothDevicesGroupPreferenceController extends BluetoothPreferenceController { private final Map mPreferenceMap = new HashMap<>(); private final Preference.OnPreferenceClickListener mDevicePreferenceClickListener = preference -> { onDeviceClicked(((BluetoothDevicePreference) preference).getCachedDevice()); return true; }; public BluetoothDevicesGroupPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions) { super(context, preferenceKey, fragmentController, uxRestrictions); } @VisibleForTesting BluetoothDevicesGroupPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions, LocalBluetoothManager localBluetoothManager, UserManager userManager) { super(context, preferenceKey, fragmentController, uxRestrictions, localBluetoothManager, userManager); } @Override protected Class getPreferenceType() { return PreferenceGroup.class; } /** * Returns a filter for which devices should be included in the group. Devices that do not * pass the filter will not be added. Added devices that no longer pass the filter will be * removed. */ protected abstract BluetoothDeviceFilter.Filter getDeviceFilter(); /** * Returns a newly created {@link BluetoothDevicePreference} for the given {@link * CachedBluetoothDevice}. Subclasses may override this method to customize how devices are * represented in the group. */ protected BluetoothDevicePreference createDevicePreference(CachedBluetoothDevice cachedDevice) { return new BluetoothDevicePreference(getContext(), cachedDevice, shouldShowDisconnectedStateSubtitle()); } /** * Returns whether or not the created bluetooth device preferences should display the * disconnected state subtitle. */ protected boolean shouldShowDisconnectedStateSubtitle() { return true; } /** * Called when a preference in the group is clicked. * * @param cachedDevice the device represented by the clicked preference. */ protected abstract void onDeviceClicked(CachedBluetoothDevice cachedDevice); /** * Returns a mapping of all {@link CachedBluetoothDevice} instances represented by this group * and their associated preferences. */ protected Map getPreferenceMap() { return mPreferenceMap; } @Override @CallSuper protected void updateState(PreferenceGroup preferenceGroup) { Collection cachedDevices = getBluetoothManager().getCachedDeviceManager().getCachedDevicesCopy(); Set devicesToRemove = new HashSet<>(mPreferenceMap.keySet()); devicesToRemove.removeAll(cachedDevices); for (CachedBluetoothDevice deviceToRemove : devicesToRemove) { removePreference(deviceToRemove); } for (CachedBluetoothDevice cachedDevice : cachedDevices) { if (getDeviceFilter().matches(cachedDevice.getDevice())) { addPreference(cachedDevice); } else { removePreference(cachedDevice); } } preferenceGroup.setVisible(preferenceGroup.getPreferenceCount() > 0); } @Override public final void onBluetoothStateChanged(int bluetoothState) { super.onBluetoothStateChanged(bluetoothState); if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) { // Cleanup the UI so that we don't have stale representations when the adapter turns // on again. This can happen if Bluetooth crashes and restarts. getPreference().removeAll(); mPreferenceMap.clear(); } } @Override public final void onDeviceAdded(CachedBluetoothDevice cachedDevice) { refreshUi(); } @Override public final void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { refreshUi(); } private void addPreference(CachedBluetoothDevice cachedDevice) { if (!mPreferenceMap.containsKey(cachedDevice)) { BluetoothDevicePreference devicePreference = createDevicePreference(cachedDevice); devicePreference.setOnPreferenceClickListener(mDevicePreferenceClickListener); mPreferenceMap.put(cachedDevice, devicePreference); getPreference().addPreference(devicePreference); } } private void removePreference(CachedBluetoothDevice cachedDevice) { if (mPreferenceMap.containsKey(cachedDevice)) { getPreference().removePreference(mPreferenceMap.get(cachedDevice)); mPreferenceMap.remove(cachedDevice); } } }