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