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