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