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 package com.android.settings.connecteddevice;
17 
18 import android.bluetooth.BluetoothAdapter;
19 import android.bluetooth.BluetoothDevice;
20 import android.bluetooth.BluetoothManager;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.pm.PackageManager;
26 import android.util.Log;
27 
28 import androidx.annotation.VisibleForTesting;
29 import androidx.preference.Preference;
30 import androidx.preference.PreferenceGroup;
31 import androidx.preference.PreferenceScreen;
32 
33 import com.android.settings.R;
34 import com.android.settings.bluetooth.BluetoothDevicePreference;
35 import com.android.settings.bluetooth.BluetoothDeviceUpdater;
36 import com.android.settings.bluetooth.SavedBluetoothDeviceUpdater;
37 import com.android.settings.connecteddevice.dock.DockUpdater;
38 import com.android.settings.core.BasePreferenceController;
39 import com.android.settings.dashboard.DashboardFragment;
40 import com.android.settings.overlay.FeatureFactory;
41 import com.android.settingslib.core.lifecycle.LifecycleObserver;
42 import com.android.settingslib.core.lifecycle.events.OnStart;
43 import com.android.settingslib.core.lifecycle.events.OnStop;
44 
45 import java.util.ArrayList;
46 import java.util.HashMap;
47 import java.util.List;
48 import java.util.Map;
49 
50 public class PreviouslyConnectedDevicePreferenceController extends BasePreferenceController
51         implements LifecycleObserver, OnStart, OnStop, DevicePreferenceCallback {
52 
53     private static final String TAG = "PreviouslyDevicePreController";
54     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
55 
56     private static final int MAX_DEVICE_NUM = 3;
57     private static final int DOCK_DEVICE_INDEX = 9;
58     private static final String KEY_SEE_ALL = "previously_connected_devices_see_all";
59 
60     private final List<Preference> mDevicesList = new ArrayList<>();
61     private final List<Preference> mDockDevicesList = new ArrayList<>();
62     private final Map<BluetoothDevice, Preference> mDevicePreferenceMap = new HashMap<>();
63     private final BluetoothAdapter mBluetoothAdapter;
64 
65     private PreferenceGroup mPreferenceGroup;
66     private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
67     private DockUpdater mSavedDockUpdater;
68 
69     @VisibleForTesting
70     Preference mSeeAllPreference;
71     @VisibleForTesting
72     IntentFilter mIntentFilter;
73 
74     @VisibleForTesting
75     BroadcastReceiver mReceiver = new BroadcastReceiver() {
76         @Override
77         public void onReceive(Context context, Intent intent) {
78             updatePreferenceVisibility();
79         }
80     };
81 
PreviouslyConnectedDevicePreferenceController(Context context, String preferenceKey)82     public PreviouslyConnectedDevicePreferenceController(Context context, String preferenceKey) {
83         super(context, preferenceKey);
84 
85         mSavedDockUpdater = FeatureFactory.getFeatureFactory().getDockUpdaterFeatureProvider()
86                 .getSavedDockUpdater(context, this);
87         mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
88         mBluetoothAdapter = context.getSystemService(BluetoothManager.class).getAdapter();
89     }
90 
91     @Override
getAvailabilityStatus()92     public int getAvailabilityStatus() {
93         return (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)
94                 || mSavedDockUpdater != null)
95                 ? AVAILABLE
96                 : CONDITIONALLY_UNAVAILABLE;
97     }
98 
99     @Override
displayPreference(PreferenceScreen screen)100     public void displayPreference(PreferenceScreen screen) {
101         super.displayPreference(screen);
102         mPreferenceGroup = screen.findPreference(getPreferenceKey());
103         mSeeAllPreference = mPreferenceGroup.findPreference(KEY_SEE_ALL);
104         updatePreferenceVisibility();
105 
106         if (isAvailable()) {
107             final Context context = screen.getContext();
108             mBluetoothDeviceUpdater.setPrefContext(context);
109             mSavedDockUpdater.setPreferenceContext(context);
110             mBluetoothDeviceUpdater.forceUpdate();
111         }
112     }
113 
114     @Override
onStart()115     public void onStart() {
116         mBluetoothDeviceUpdater.registerCallback();
117         mSavedDockUpdater.registerCallback();
118         mContext.registerReceiver(mReceiver, mIntentFilter,
119                 Context.RECEIVER_EXPORTED_UNAUDITED);
120         mBluetoothDeviceUpdater.refreshPreference();
121         updatePreferenceGroup();
122     }
123 
124     @Override
onStop()125     public void onStop() {
126         mBluetoothDeviceUpdater.unregisterCallback();
127         mSavedDockUpdater.unregisterCallback();
128         mContext.unregisterReceiver(mReceiver);
129     }
130 
init(DashboardFragment fragment)131     public void init(DashboardFragment fragment) {
132         mBluetoothDeviceUpdater = new SavedBluetoothDeviceUpdater(fragment.getContext(),
133                 PreviouslyConnectedDevicePreferenceController.this, /* showConnectedDevice= */
134                 false, fragment.getMetricsCategory());
135     }
136 
137     @Override
onDeviceAdded(Preference preference)138     public void onDeviceAdded(Preference preference) {
139         if (preference instanceof BluetoothDevicePreference) {
140             mDevicePreferenceMap.put(
141                     ((BluetoothDevicePreference) preference).getBluetoothDevice().getDevice(),
142                     preference);
143         } else {
144             mDockDevicesList.add(preference);
145         }
146         if (DEBUG) {
147             Log.d(TAG, "onDeviceAdded() " + preference.getTitle());
148         }
149         updatePreferenceGroup();
150     }
151 
addPreference(int index, Preference preference)152     private void addPreference(int index, Preference preference) {
153         if (preference instanceof BluetoothDevicePreference) {
154             if (index >= 0 && mDevicesList.size() >= index) {
155                 mDevicesList.add(index, preference);
156             } else {
157                 mDevicesList.add(preference);
158             }
159         } else {
160             mDockDevicesList.add(preference);
161         }
162         addPreference();
163     }
164 
addPreference()165     private void addPreference() {
166         mPreferenceGroup.removeAll();
167         mPreferenceGroup.addPreference(mSeeAllPreference);
168         final int size = getDeviceListSize();
169         for (int i = 0; i < size; i++) {
170             if (DEBUG) {
171                 Log.d(TAG, "addPreference() add device : " + mDevicesList.get(i).getTitle());
172             }
173             mDevicesList.get(i).setOrder(i);
174             mPreferenceGroup.addPreference(mDevicesList.get(i));
175         }
176         if (mDockDevicesList.size() > 0) {
177             for (int i = 0; i < getDockDeviceListSize(MAX_DEVICE_NUM - size); i++) {
178                 if (DEBUG) {
179                     Log.d(TAG, "addPreference() add dock device : "
180                             + mDockDevicesList.get(i).getTitle());
181                 }
182                 mDockDevicesList.get(i).setOrder(DOCK_DEVICE_INDEX);
183                 mPreferenceGroup.addPreference(mDockDevicesList.get(i));
184             }
185         }
186     }
187 
getDeviceListSize()188     private int getDeviceListSize() {
189         return mDevicesList.size() >= MAX_DEVICE_NUM
190                 ? MAX_DEVICE_NUM : mDevicesList.size();
191     }
192 
getDockDeviceListSize(int availableSize)193     private int getDockDeviceListSize(int availableSize) {
194         return mDockDevicesList.size() >= availableSize
195                 ? availableSize : mDockDevicesList.size();
196     }
197 
198     @Override
onDeviceRemoved(Preference preference)199     public void onDeviceRemoved(Preference preference) {
200         if (preference instanceof BluetoothDevicePreference) {
201             mDevicePreferenceMap.remove(
202                     ((BluetoothDevicePreference) preference).getBluetoothDevice().getDevice(),
203                     preference);
204         } else {
205             mDockDevicesList.remove(preference);
206         }
207         if (DEBUG) {
208             Log.d(TAG, "onDeviceRemoved() " + preference.getTitle());
209         }
210         updatePreferenceGroup();
211     }
212 
213     /** Sort the preferenceGroup by most recently used. */
updatePreferenceGroup()214     public void updatePreferenceGroup() {
215         mPreferenceGroup.removeAll();
216         mPreferenceGroup.addPreference(mSeeAllPreference);
217         if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
218             // Bluetooth is supported
219             int order = 0;
220             for (BluetoothDevice device : mBluetoothAdapter.getMostRecentlyConnectedDevices()) {
221                 Preference preference = mDevicePreferenceMap.getOrDefault(device, null);
222                 if (preference != null) {
223                     preference.setOrder(order);
224                     mPreferenceGroup.addPreference(preference);
225                     order += 1;
226                 }
227                 if (order == MAX_DEVICE_NUM) {
228                     break;
229                 }
230             }
231             for (Preference preference : mDockDevicesList) {
232                 if (order == MAX_DEVICE_NUM) {
233                     break;
234                 }
235                 preference.setOrder(order);
236                 mPreferenceGroup.addPreference(preference);
237                 order += 1;
238             }
239         }
240         updatePreferenceVisibility();
241     }
242 
243     @VisibleForTesting
setBluetoothDeviceUpdater(BluetoothDeviceUpdater bluetoothDeviceUpdater)244     void setBluetoothDeviceUpdater(BluetoothDeviceUpdater bluetoothDeviceUpdater) {
245         mBluetoothDeviceUpdater = bluetoothDeviceUpdater;
246     }
247 
248     @VisibleForTesting
setSavedDockUpdater(DockUpdater savedDockUpdater)249     void setSavedDockUpdater(DockUpdater savedDockUpdater) {
250         mSavedDockUpdater = savedDockUpdater;
251     }
252 
253     @VisibleForTesting
setPreferenceGroup(PreferenceGroup preferenceGroup)254     void setPreferenceGroup(PreferenceGroup preferenceGroup) {
255         mPreferenceGroup = preferenceGroup;
256     }
257 
258     @VisibleForTesting
updatePreferenceVisibility()259     void updatePreferenceVisibility() {
260         if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
261             mSeeAllPreference.setSummary("");
262         } else {
263             mSeeAllPreference.setSummary(
264                     mContext.getString(R.string.connected_device_see_all_summary));
265         }
266     }
267 }
268