1 /*
2  * Copyright (C) 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 
17 package com.android.tv.settings.accessories;
18 
19 import android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.BluetoothClass;
21 import android.bluetooth.BluetoothDevice;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.os.Bundle;
27 import android.os.UserHandle;
28 import android.os.UserManager;
29 import android.text.TextUtils;
30 import android.util.ArraySet;
31 import android.util.Log;
32 
33 import androidx.annotation.DrawableRes;
34 import androidx.annotation.Keep;
35 import androidx.annotation.NonNull;
36 import androidx.preference.Preference;
37 import androidx.preference.PreferenceScreen;
38 
39 import com.android.settingslib.RestrictedLockUtils;
40 import com.android.settingslib.RestrictedLockUtilsInternal;
41 import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
42 import com.android.settingslib.bluetooth.LocalBluetoothManager;
43 import com.android.tv.settings.R;
44 import com.android.tv.settings.SettingsPreferenceFragment;
45 
46 import java.util.Set;
47 
48 /**
49  * The "Remotes and Accessories" screen in TV settings.
50  */
51 @Keep
52 public class AccessoriesFragment extends SettingsPreferenceFragment {
53     private static final String TAG = "AccessoriesFragment";
54     private static final String KEY_ADD_ACCESSORY = "add_accessory";
55 
56     private LocalBluetoothManager mLocalBtManager;
57     private Preference mAddAccessory;
58     private final BroadcastReceiver mBCMReceiver = new BroadcastReceiver() {
59         @Override
60         public void onReceive(Context context, Intent intent) {
61             updateAccessories();
62         }
63     };
64 
65     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)66     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
67         setPreferencesFromResource(R.xml.remotes_and_accessories, null);
68         mAddAccessory = findPreference(KEY_ADD_ACCESSORY);
69     }
70 
updateAccessories()71     private void updateAccessories() {
72         PreferenceScreen preferenceScreen = getPreferenceScreen();
73         if (preferenceScreen == null) {
74             return;
75         }
76 
77         LocalBluetoothAdapter btAdapter = mLocalBtManager.getBluetoothAdapter();
78         if (btAdapter == null) {
79             preferenceScreen.removeAll();
80             return;
81         }
82 
83         final Set<BluetoothDevice> bondedDevices = btAdapter.getBondedDevices();
84         if (bondedDevices == null) {
85             preferenceScreen.removeAll();
86             return;
87         }
88 
89         final Context themedContext = getPreferenceManager().getContext();
90 
91         final Set<String> touchedKeys = new ArraySet<>(bondedDevices.size() + 1);
92         if (mAddAccessory != null) {
93             touchedKeys.add(mAddAccessory.getKey());
94         }
95 
96         for (final BluetoothDevice device : bondedDevices) {
97             final String deviceAddress = device.getAddress();
98             if (TextUtils.isEmpty(deviceAddress)) {
99                 Log.w(TAG, "Skipping mysteriously empty bluetooth device");
100                 continue;
101             }
102 
103             final String desc = device.isConnected() ? getString(R.string.accessory_connected) :
104                     null;
105             final String key = "BluetoothDevice:" + deviceAddress;
106             touchedKeys.add(key);
107             Preference preference = preferenceScreen.findPreference(key);
108             if (preference == null) {
109                 preference = new Preference(themedContext);
110                 preference.setKey(key);
111             }
112             final String deviceName = device.getAlias();
113             preference.setTitle(deviceName);
114             preference.setSummary(desc);
115             final int deviceImgId = getImageIdForDevice(device, false);
116             preference.setIcon(deviceImgId);
117 
118             RestrictedLockUtils.EnforcedAdmin admin =
119                     RestrictedLockUtilsInternal.checkIfRestrictionEnforced(preference.getContext(),
120                             UserManager.DISALLOW_CONFIG_BLUETOOTH, UserHandle.myUserId());
121             if (admin == null) {
122                 preference.setFragment(BluetoothAccessoryFragment.class.getName());
123             } else {
124                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), admin);
125             }
126 
127             BluetoothAccessoryFragment.prepareArgs(
128                     preference.getExtras(),
129                     deviceAddress,
130                     deviceName,
131                     deviceImgId);
132             preferenceScreen.addPreference(preference);
133         }
134 
135         for (int i = 0; i < preferenceScreen.getPreferenceCount();) {
136             final Preference preference = preferenceScreen.getPreference(i);
137             if (touchedKeys.contains(preference.getKey())) {
138                 i++;
139             } else {
140                 preferenceScreen.removePreference(preference);
141             }
142         }
143     }
144 
145     @Override
onStart()146     public void onStart() {
147         super.onStart();
148         IntentFilter btChangeFilter = new IntentFilter();
149         btChangeFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
150         btChangeFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
151         btChangeFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
152         getContext().registerReceiver(mBCMReceiver, btChangeFilter);
153     }
154 
155     @Override
onStop()156     public void onStop() {
157         super.onStop();
158         getContext().unregisterReceiver(mBCMReceiver);
159     }
160 
161     @Override
onResume()162     public void onResume() {
163         super.onResume();
164         updateAccessories();
165     }
166 
167     @Override
onCreate(Bundle savedInstanceState)168     public void onCreate(Bundle savedInstanceState) {
169         mLocalBtManager = AccessoryUtils.getLocalBluetoothManager(getContext());
170         super.onCreate(savedInstanceState);
171     }
172 
173 
174     /**
175      * @param dev             the bluetooth device
176      * @param willBeProcessed whether the icon will be processed by Slice
177      * @return the resource id for a bluetooth device's icon based on its device type
178      */
getImageIdForDevice( @onNull BluetoothDevice dev, boolean willBeProcessed)179     static @DrawableRes int getImageIdForDevice(
180         @NonNull BluetoothDevice dev, boolean willBeProcessed) {
181         if (dev == null || dev.getBluetoothClass() == null) {
182             return willBeProcessed ? R.drawable.ic_bluetooth_raw : R.drawable.ic_bluetooth;
183         }
184 
185         final int devClass = dev.getBluetoothClass().getDeviceClass();
186         // The order is critical
187         if (devClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET) {
188             return willBeProcessed ? R.drawable.ic_headset_mic_raw : R.drawable.ic_headset_mic;
189         } else if (devClass == BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES
190             || devClass == BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER
191             || devClass == BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO
192             || devClass == BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO) {
193             return willBeProcessed ? R.drawable.ic_headset_raw : R.drawable.ic_headset;
194         } else if ((devClass & InputDeviceCriteria.MINOR_DEVICE_CLASS_POINTING) != 0) {
195             return willBeProcessed ? R.drawable.ic_mouse_raw : R.drawable.ic_mouse;
196         } else if (AccessoryUtils.isA2dpSource(dev) && willBeProcessed) {
197             return R.drawable.ic_a2dp_raw;
198         } else if ((devClass & InputDeviceCriteria.MINOR_DEVICE_CLASS_REMOTE) != 0) {
199             return willBeProcessed ? R.drawable.ic_games_raw : R.drawable.ic_games;
200         } else if ((devClass & InputDeviceCriteria.MINOR_DEVICE_CLASS_JOYSTICK) != 0) {
201             return willBeProcessed ? R.drawable.ic_games_raw : R.drawable.ic_games;
202         } else if ((devClass & InputDeviceCriteria.MINOR_DEVICE_CLASS_GAMEPAD) != 0) {
203             return willBeProcessed ? R.drawable.ic_games_raw : R.drawable.ic_games;
204         } else if ((devClass & InputDeviceCriteria.MINOR_DEVICE_CLASS_KEYBOARD) != 0) {
205             return willBeProcessed ? R.drawable.ic_keyboard_raw : R.drawable.ic_keyboard;
206         }
207 
208         // Default for now
209         return willBeProcessed ? R.drawable.ic_bluetooth_raw : R.drawable.ic_bluetooth;
210     }
211 }
212