1 /* 2 * Copyright (C) 2023 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.settings.connecteddevice.fastpair; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.content.pm.PackageManager; 25 import android.util.Log; 26 27 import androidx.annotation.NonNull; 28 import androidx.annotation.VisibleForTesting; 29 import androidx.lifecycle.DefaultLifecycleObserver; 30 import androidx.lifecycle.LifecycleOwner; 31 import androidx.preference.Preference; 32 import androidx.preference.PreferenceGroup; 33 import androidx.preference.PreferenceScreen; 34 35 import com.android.settings.connecteddevice.DevicePreferenceCallback; 36 import com.android.settings.core.BasePreferenceController; 37 import com.android.settings.flags.Flags; 38 import com.android.settings.overlay.FeatureFactory; 39 40 import java.util.ArrayList; 41 import java.util.Collections; 42 import java.util.List; 43 44 /** 45 * Controller to maintain the {@link PreferenceGroup} for all Fast Pair devices and a "See all" 46 * Preference. It uses {@link DevicePreferenceCallback} to add/remove {@link Preference}. 47 */ 48 public class FastPairDevicePreferenceController extends BasePreferenceController 49 implements DefaultLifecycleObserver, DevicePreferenceCallback { 50 51 private static final String TAG = "FastPairDevicePrefCtr"; 52 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 53 54 private static final int MAX_DEVICE_NUM = 3; 55 private static final String KEY_SEE_ALL = "fast_pair_devices_see_all"; 56 57 private final List<Preference> mPreferenceList = new ArrayList<>(); 58 59 private PreferenceGroup mPreferenceGroup; 60 private FastPairDeviceUpdater mFastPairDeviceUpdater; 61 private BluetoothAdapter mBluetoothAdapter; 62 63 @VisibleForTesting Preference mSeeAllPreference; 64 @VisibleForTesting IntentFilter mIntentFilter; 65 66 @VisibleForTesting 67 BroadcastReceiver mReceiver = 68 new BroadcastReceiver() { 69 @Override 70 public void onReceive(Context context, Intent intent) { 71 updatePreferenceVisibility(); 72 } 73 }; 74 FastPairDevicePreferenceController(Context context, String preferenceKey)75 public FastPairDevicePreferenceController(Context context, String preferenceKey) { 76 super(context, preferenceKey); 77 78 if (Flags.enableSubsequentPairSettingsIntegration()) { 79 FastPairFeatureProvider fastPairFeatureProvider = 80 FeatureFactory.getFeatureFactory().getFastPairFeatureProvider(); 81 mFastPairDeviceUpdater = 82 fastPairFeatureProvider.getFastPairDeviceUpdater(context, this); 83 } else { 84 Log.d(TAG, "Flag disabled. Ignore."); 85 mFastPairDeviceUpdater = null; 86 } 87 mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); 88 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 89 } 90 91 @Override onStart(@onNull LifecycleOwner owner)92 public void onStart(@NonNull LifecycleOwner owner) { 93 if (mFastPairDeviceUpdater != null) { 94 mFastPairDeviceUpdater.setPreferenceContext(mContext); 95 mFastPairDeviceUpdater.registerCallback(); 96 } else { 97 if (DEBUG) { 98 Log.d(TAG, "Callback register: Fast Pair device updater is null. Ignore."); 99 } 100 } 101 mContext.registerReceiver(mReceiver, mIntentFilter, Context.RECEIVER_EXPORTED_UNAUDITED); 102 } 103 104 @Override onStop(@onNull LifecycleOwner owner)105 public void onStop(@NonNull LifecycleOwner owner) { 106 if (mFastPairDeviceUpdater != null) { 107 mFastPairDeviceUpdater.setPreferenceContext(null); 108 mFastPairDeviceUpdater.unregisterCallback(); 109 } else { 110 if (DEBUG) { 111 Log.d(TAG, "Callback unregister: Fast Pair device updater is null. Ignore."); 112 } 113 } 114 mContext.unregisterReceiver(mReceiver); 115 } 116 117 @Override displayPreference(PreferenceScreen screen)118 public void displayPreference(PreferenceScreen screen) { 119 super.displayPreference(screen); 120 mPreferenceGroup = screen.findPreference(getPreferenceKey()); 121 mSeeAllPreference = mPreferenceGroup.findPreference(KEY_SEE_ALL); 122 updatePreferenceVisibility(); 123 if (isAvailable()) { 124 final Context context = screen.getContext(); 125 mFastPairDeviceUpdater.setPreferenceContext(context); 126 mFastPairDeviceUpdater.forceUpdate(); 127 } 128 } 129 130 @Override getAvailabilityStatus()131 public int getAvailabilityStatus() { 132 return (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH) 133 && mFastPairDeviceUpdater != null) 134 ? AVAILABLE 135 : UNSUPPORTED_ON_DEVICE; 136 } 137 138 @Override onDeviceAdded(Preference preference)139 public void onDeviceAdded(Preference preference) { 140 if (preference == null) { 141 if (DEBUG) { 142 Log.d(TAG, "onDeviceAdd receives null preference. Ignore."); 143 } 144 return; 145 } 146 147 // Keep showing the latest MAX_DEVICE_NUM devices. 148 // The preference for the latest device has top preference order. 149 int idx = Collections.binarySearch(mPreferenceList, preference); 150 // Binary search returns the index of the search key if it is contained in the list; 151 // otherwise, (-(insertion point) - 1). 152 // The insertion point is defined as the point at which the key would be inserted into the 153 // list: the index of the first element greater than the key, or list.size() if all elements 154 // in the list are less than the specified key. 155 if (idx >= 0) { 156 if (DEBUG) { 157 Log.d(TAG, "onDeviceAdd receives duplicate preference. Ignore."); 158 } 159 return; 160 } 161 idx = -1 * (idx + 1); 162 mPreferenceList.add(idx, preference); 163 if (idx < MAX_DEVICE_NUM) { 164 if (mPreferenceList.size() > MAX_DEVICE_NUM) { 165 mPreferenceGroup.removePreference(mPreferenceList.get(MAX_DEVICE_NUM)); 166 } 167 mPreferenceGroup.addPreference(preference); 168 } 169 updatePreferenceVisibility(); 170 } 171 172 @Override onDeviceRemoved(Preference preference)173 public void onDeviceRemoved(Preference preference) { 174 if (preference == null) { 175 if (DEBUG) { 176 Log.d(TAG, "onDeviceRemoved receives null preference. Ignore."); 177 } 178 return; 179 } 180 181 // Keep showing the latest MAX_DEVICE_NUM devices. 182 // The preference for the latest device has top preference order. 183 final int idx = mPreferenceList.indexOf(preference); 184 mPreferenceList.remove(preference); 185 if (idx < MAX_DEVICE_NUM) { 186 mPreferenceGroup.removePreference(preference); 187 if (mPreferenceList.size() >= MAX_DEVICE_NUM) { 188 mPreferenceGroup.addPreference(mPreferenceList.get(MAX_DEVICE_NUM - 1)); 189 } 190 } 191 updatePreferenceVisibility(); 192 } 193 194 @VisibleForTesting setPreferenceGroup(PreferenceGroup preferenceGroup)195 void setPreferenceGroup(PreferenceGroup preferenceGroup) { 196 mPreferenceGroup = preferenceGroup; 197 } 198 199 @VisibleForTesting updatePreferenceVisibility()200 void updatePreferenceVisibility() { 201 if (mBluetoothAdapter != null 202 && mBluetoothAdapter.isEnabled() 203 && mPreferenceList.size() > 0) { 204 mPreferenceGroup.setVisible(true); 205 mSeeAllPreference.setVisible(mPreferenceList.size() > MAX_DEVICE_NUM); 206 } else { 207 mPreferenceGroup.setVisible(false); 208 mSeeAllPreference.setVisible(false); 209 } 210 } 211 } 212