• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.google.android.tv.btservices.settings;
18 
19 import static com.google.android.tv.btservices.pairing.BluetoothPairer.STATUS_CANCELLED;
20 import static com.google.android.tv.btservices.pairing.BluetoothPairer.STATUS_CONNECTING;
21 import static com.google.android.tv.btservices.pairing.BluetoothPairer.STATUS_DONE;
22 import static com.google.android.tv.btservices.pairing.BluetoothPairer.STATUS_ERROR;
23 import static com.google.android.tv.btservices.pairing.BluetoothPairer.STATUS_PAIRING;
24 import static com.google.android.tv.btservices.pairing.BluetoothPairer.STATUS_TIMEOUT;
25 
26 import android.bluetooth.BluetoothDevice;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.text.TextUtils;
32 import android.widget.Toast;
33 
34 import androidx.leanback.preference.LeanbackPreferenceFragment;
35 import androidx.preference.Preference;
36 import androidx.preference.PreferenceGroup;
37 import androidx.preference.PreferenceScreen;
38 
39 import com.google.android.tv.btservices.BluetoothUtils;
40 import com.google.android.tv.btservices.R;
41 import com.google.android.tv.btservices.pairing.BluetoothPairingService;
42 
43 public class BluetoothDevicePreferenceFragment extends LeanbackPreferenceFragment
44         implements ResponseFragment.Listener, BluetoothDeviceProvider.Listener,
45         BluetoothPairingService.PairingListener {
46 
47     private static final String TAG = "Atv.BtDevPrefFragment";
48 
49     public interface ServiceProvider {
getBluetoothPairingServiceBinder()50         BluetoothPairingService.LocalBinder getBluetoothPairingServiceBinder();
51     }
52 
53     private PreferenceGroup mPrefGroup;
54     private BluetoothDevice mDevice;
55 
56     private static final String ARG_BT_DEVICE = "bt_device";
57 
58     public static final String KEY_BT_DEVICE_PREF = "key_bt_device";
59     static final String KEY_RENAME = "key_rename";
60     static final String KEY_CONNECT = "key_connect";
61     static final String KEY_DISCONNECT = "key_disconnect";
62     static final String KEY_FORGET = "key_forget";
63     static final String KEY_UPDATE= "key_update";
64     private static final String KEY_RECONNECT = "key_reconnect";
65     static final int YES = R.string.settings_choices_yes;
66     static final int NO = R.string.settings_choices_no;
67     static final int CONTINUE = R.string.settings_continue;
68     static final int CANCEL = R.string.settings_cancel;
69 
70     static final boolean ENABLE_DISCONNECT_OPTION = false;
71     private static final int UPDATE_AFTER_RENAME_MS = 1000;
72 
73     static final int[] YES_NO_ARGS = {YES, NO};
74     static final int[] CONT_CANCEL_ARGS = {CONTINUE, CANCEL};
75 
76     private static final int ORDER_UPDATE = 9;
77     private static final int ORDER_RECONNECT = 10;
78     private static final int ORDER_RENAME = 11;
79     private static final int ORDER_DISCONNECT = 12;
80     private static final int ORDER_FORGET= 13;
81     private static final int ORDER_INFO = 14;
82 
83     private boolean mVisible = false;
84 
85     private final Handler mHandler = new Handler();
86 
87     private BluetoothDeviceInfoPreference mDeviceInfoPreference;
88 
buildArgs(Bundle bundle, BluetoothDevice device)89     public static void buildArgs(Bundle bundle, BluetoothDevice device) {
90         bundle.putParcelable(ARG_BT_DEVICE, device);
91     }
92 
newInstance(BluetoothDevice device)93     public static BluetoothDevicePreferenceFragment newInstance(BluetoothDevice device) {
94         BluetoothDevicePreferenceFragment fragment = new BluetoothDevicePreferenceFragment();
95         Bundle args = new Bundle();
96         args.putParcelable(ARG_BT_DEVICE, device);
97         fragment.setArguments(args);
98         return fragment;
99     }
100 
update()101     private void update() {
102         if (!mVisible) {
103             return;
104         }
105 
106         if (mDevice != null) {
107             mPrefGroup.setTitle(BluetoothUtils.getName(mDevice));
108         }
109 
110         updateRemoteUpdate();
111         updateReconnect();
112 
113         if (mDeviceInfoPreference != null) {
114             mDeviceInfoPreference.update();
115         }
116     }
117 
updateRemoteUpdate()118     private void updateRemoteUpdate() {
119         final Context preferenceContext = getPreferenceManager().getContext();
120         BluetoothUtils.isRemoteClass(mDevice);
121         if (!BluetoothUtils.isRemote(preferenceContext, mDevice)) {
122             return;
123         }
124         BluetoothDeviceProvider btDeviceProvider = getBluetoothDeviceProvider();
125         Preference pref = mPrefGroup.findPreference(KEY_UPDATE);
126         if (pref == null) {
127             pref = new Preference(preferenceContext);
128             pref.setKey(KEY_UPDATE);
129             pref.setOrder(ORDER_UPDATE);
130 
131             ResponseFragment.prepareArgs(
132                 pref.getExtras(),
133                 KEY_UPDATE,
134                 R.string.settings_bt_update,
135                 R.string.settings_bt_update_summary,
136                 0,
137                 CONT_CANCEL_ARGS,
138                 null,
139                 ResponseFragment.DEFAULT_CHOICE_UNDEFINED
140             );
141             pref.setFragment(ResponseFragment.class.getCanonicalName());
142             mPrefGroup.addPreference(pref);
143         }
144 
145         if (btDeviceProvider.hasUpgrade(mDevice)) {
146             pref.setEnabled(true);
147             pref.setSelectable(true);
148             pref.setTitle(R.string.settings_bt_update);
149             if (btDeviceProvider.isBatteryLow(mDevice)) {
150                 pref.setSummary(R.string.settings_bt_battery_low);
151                 pref.setEnabled(false);
152             } else {
153                 pref.setSummary(R.string.settings_bt_update_software_available);
154             }
155         } else {
156             pref.setTitle(R.string.settings_bt_update_not_necessary);
157             pref.setSummary(null);
158             pref.setEnabled(false);
159             pref.setSelectable(false);
160         }
161     }
162 
updateReconnect()163     private void updateReconnect() {
164         final Context preferenceContext = getPreferenceManager().getContext();
165         if (mDevice == null || BluetoothUtils.isRemote(preferenceContext, mDevice)) {
166             return;
167         }
168         Preference reconnectPref = mPrefGroup.findPreference(KEY_RECONNECT);
169         if (getPairingServiceBinder() != null && BluetoothUtils.isBonded(mDevice)) {
170             if (reconnectPref == null) {
171                 reconnectPref = new Preference(preferenceContext);
172                 reconnectPref.setKey(KEY_RECONNECT);
173                 reconnectPref.setTitle(R.string.bluetooth_connect);
174                 reconnectPref.setOrder(ORDER_RECONNECT);
175                 reconnectPref.setOnPreferenceClickListener((pref) -> {
176                     BluetoothPairingService.LocalBinder pairingService = getPairingServiceBinder();
177                     if (pairingService != null) {
178                         pairingService.connectPairedDevice(mDevice);
179                         pref.setEnabled(false);
180                         pref.setTitle(R.string.settings_bt_pair_status_connecting);
181                     }
182                     return true;
183                 });
184                 mPrefGroup.addPreference(reconnectPref);
185             }
186             reconnectPref.setEnabled(true);
187             reconnectPref.setVisible(true);
188         } else if (reconnectPref != null) {
189             reconnectPref.setEnabled(false);
190             reconnectPref.setVisible(false);
191         }
192     }
193 
updatePairingStatusImpl(int status)194     private void updatePairingStatusImpl(int status) {
195         if (!mVisible) {
196             return;
197         }
198         Preference pref = mPrefGroup.findPreference(KEY_RECONNECT);
199         String resStr;
200         String text;
201         switch (status) {
202             case STATUS_PAIRING:
203             case STATUS_CONNECTING:
204                 pref.setEnabled(false);
205                 pref.setTitle(R.string.settings_bt_pair_status_connecting);
206                 break;
207             case STATUS_CANCELLED:
208             case STATUS_TIMEOUT:
209             case STATUS_ERROR:
210                 pref.setEnabled(true);
211                 pref.setTitle(R.string.bluetooth_connect);
212                 resStr = getResources().getString(R.string.settings_bt_pair_toast_fail);
213                 text = String.format(resStr, mDevice.getName());
214                 Toast.makeText(getActivity(), text, Toast.LENGTH_LONG).show();
215                 break;
216             case STATUS_DONE:
217                 pref.setEnabled(true);
218                 pref.setTitle(R.string.bluetooth_connect);
219                 resStr = getResources().getString(R.string.settings_bt_pair_toast_connected);
220                 text = String.format(resStr, mDevice.getName());
221                 Toast.makeText(getActivity(), text, Toast.LENGTH_LONG).show();
222                 break;
223         }
224     }
225 
getBluetoothDeviceProvider()226     private BluetoothDeviceProvider getBluetoothDeviceProvider() {
227         if (!(getTargetFragment() instanceof ConnectedDevicesPreferenceFragment)) {
228             return null;
229         }
230         return ((ConnectedDevicesPreferenceFragment) getTargetFragment())
231                 .getBluetoothDeviceProvider();
232     }
233 
getPairingServiceBinder()234     private BluetoothPairingService.LocalBinder getPairingServiceBinder() {
235         if (!(getActivity() instanceof ServiceProvider)) {
236             return null;
237         }
238         return ((ServiceProvider) getActivity()).getBluetoothPairingServiceBinder();
239     }
240 
getDevice()241     public BluetoothDevice getDevice() {
242         return mDevice;
243     }
244 
245     // ResponseFragment.Listener
246     @Override
onChoice(String key, int choice)247     public void onChoice(String key, int choice) {
248         BluetoothDeviceProvider provider = getBluetoothDeviceProvider();
249         getFragmentManager().popBackStackImmediate();
250 
251         if (provider == null) {
252             return;
253         }
254         if (KEY_DISCONNECT.equals(key)) {
255             if (choice == YES) {
256                 // TODO: disconnect device.
257             }
258         } else if (KEY_FORGET.equals(key)) {
259             if (choice == YES) {
260                 provider.forgetDevice(mDevice);
261             }
262         } else if (KEY_UPDATE.equals(key)) {
263             if (choice == CONTINUE && mDevice != null) {
264                 Context context = getContext();
265                 Intent intent = new Intent(context, RemoteDfuActivity.class);
266                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
267                 intent.putExtra(RemoteDfuActivity.EXTRA_BT_ADDRESS, mDevice.getAddress());
268                 context.startActivity(intent);
269             }
270         }
271     }
272 
273     // ResponseFragment.Listener
274     @Override
onText(String key, String text)275     public void onText(String key, String text) {
276         getFragmentManager().popBackStackImmediate();
277         BluetoothDeviceProvider provider = getBluetoothDeviceProvider();
278         if (provider == null) {
279             return;
280         }
281         if (KEY_RENAME.equals(key)) {
282             if (mDevice != null) {
283                 provider.renameDevice(mDevice, text);
284                 mHandler.postDelayed(this::update, UPDATE_AFTER_RENAME_MS);
285             }
286         }
287     }
288 
289     // BluetoothDeviceProvider.Listener
290     @Override
onDeviceUpdated(BluetoothDevice device)291     public void onDeviceUpdated(BluetoothDevice device) {
292         if (mDevice == null || !TextUtils.equals(mDevice.getAddress(), device.getAddress())) {
293             return;
294         }
295         mHandler.post(this::update);
296     }
297 
298     // BluetoothPairingService.PairingListener implementation
299     @Override
updatePairingStatus(BluetoothDevice device, int status)300     public void updatePairingStatus(BluetoothDevice device, int status) {
301         if (!TextUtils.equals(mDevice.getAddress(), device.getAddress())) {
302             return;
303         }
304         mHandler.post(() -> this.updatePairingStatusImpl(status));
305     }
306 
307     @Override
onCreate(Bundle savedInstanceState)308     public void onCreate(Bundle savedInstanceState) {
309         super.onCreate(savedInstanceState);
310         BluetoothDeviceProvider provider = getBluetoothDeviceProvider();
311         if (provider != null) {
312             provider.addListener(this);
313         }
314         BluetoothPairingService.LocalBinder pairingService = getPairingServiceBinder();
315         if (pairingService != null) {
316             pairingService.addPairingListener(this);
317         }
318     }
319 
320     @Override
onDestroy()321     public void onDestroy() {
322         BluetoothDeviceProvider provider = getBluetoothDeviceProvider();
323         if (provider != null) {
324             provider.removeListener(this);
325         }
326         BluetoothPairingService.LocalBinder pairingService = getPairingServiceBinder();
327         if (pairingService != null) {
328             pairingService.removePairingListener(this);
329         }
330         super.onDestroy();
331     }
332 
333     @Override
onResume()334     public void onResume() {
335         super.onResume();
336         mVisible = true;
337         update();
338     }
339 
340     @Override
onPause()341     public void onPause() {
342         mVisible = false;
343         super.onPause();
344     }
345 
346     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)347     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
348         final Context preferenceContext = getPreferenceManager().getContext();
349         BluetoothDeviceProvider btDeviceProvider = getBluetoothDeviceProvider();
350 
351         Bundle args = getArguments();
352         mDevice = args.getParcelable(ARG_BT_DEVICE);
353         mPrefGroup = getPreferenceManager().createPreferenceScreen(preferenceContext);
354         mPrefGroup.setOrderingAsAdded(false);
355         mPrefGroup.setKey(KEY_BT_DEVICE_PREF);
356 
357         String deviceName = "";
358         if (mDevice != null) {
359             deviceName = BluetoothUtils.getName(mDevice);
360             mPrefGroup.setTitle(deviceName);
361         }
362 
363         updateRemoteUpdate();
364         updateReconnect();
365 
366         Preference renamePref = new Preference(preferenceContext);
367         renamePref.setKey(KEY_RENAME);
368         renamePref.setTitle(R.string.bluetooth_rename);
369         renamePref.setOrder(ORDER_RENAME);
370         ResponseFragment.prepareArgs(
371                 renamePref.getExtras(),
372                 KEY_RENAME,
373                 R.string.settings_bt_rename,
374                 0,
375                 R.drawable.ic_baseline_bluetooth_searching_large,
376                 null,
377                 deviceName,
378                 ResponseFragment.DEFAULT_CHOICE_UNDEFINED
379         );
380         renamePref.setFragment(ResponseFragment.class.getCanonicalName());
381         mPrefGroup.addPreference(renamePref);
382 
383         if (ENABLE_DISCONNECT_OPTION) {
384             Preference disconnectPref = new Preference(preferenceContext);
385             disconnectPref.setKey(KEY_DISCONNECT);
386             disconnectPref.setTitle(R.string.bluetooth_disconnect);
387             disconnectPref.setOrder(ORDER_DISCONNECT);
388             ResponseFragment.prepareArgs(
389                     disconnectPref.getExtras(),
390                     KEY_DISCONNECT,
391                     R.string.settings_bt_disconnect,
392                     0,
393                     R.drawable.ic_baseline_bluetooth_searching_large,
394                     YES_NO_ARGS,
395                     deviceName,
396                     1 /* default to NO (index 1) */
397             );
398             disconnectPref.setFragment(ResponseFragment.class.getCanonicalName());
399             mPrefGroup.addPreference(disconnectPref);
400         }
401 
402         Preference forgetPref = new Preference(preferenceContext);
403         forgetPref.setKey(KEY_FORGET);
404         forgetPref.setTitle(R.string.bluetooth_forget);
405         forgetPref.setOrder(ORDER_FORGET);
406         ResponseFragment.prepareArgs(
407                 forgetPref.getExtras(),
408                 KEY_FORGET,
409                 R.string.settings_bt_forget,
410                 0,
411                 R.drawable.ic_baseline_bluetooth_searching_large,
412                 YES_NO_ARGS,
413                 deviceName,
414                 1 /* default to NO (index 1) */
415         );
416         forgetPref.setFragment(ResponseFragment.class.getCanonicalName());
417         mPrefGroup.addPreference(forgetPref);
418 
419         mDeviceInfoPreference =
420                 new BluetoothDeviceInfoPreference(preferenceContext, btDeviceProvider, mDevice);
421         mDeviceInfoPreference.setOrder(ORDER_INFO);
422         mPrefGroup.addPreference(mDeviceInfoPreference);
423 
424         setPreferenceScreen((PreferenceScreen) mPrefGroup);
425     }
426 }
427