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