1 /* 2 * Copyright (C) 2020 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 static com.android.tv.settings.accessories.ConnectedDevicesSliceUtils.GENERAL_SLICE_URI; 20 21 import android.app.Service; 22 import android.bluetooth.BluetoothA2dp; 23 import android.bluetooth.BluetoothAdapter; 24 import android.bluetooth.BluetoothDevice; 25 import android.bluetooth.BluetoothHidHost; 26 import android.bluetooth.BluetoothProfile; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.os.Binder; 32 import android.os.Handler; 33 import android.os.IBinder; 34 import android.os.Looper; 35 import android.util.Log; 36 import android.widget.Toast; 37 38 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 39 import com.android.tv.settings.R; 40 41 import java.io.FileDescriptor; 42 import java.io.PrintWriter; 43 import java.util.ArrayList; 44 import java.util.List; 45 46 /** The Service for handling Bluetooth-related logic. */ 47 public class BluetoothDevicesService extends Service { 48 49 private static final boolean DEBUG = false; 50 private static final String TAG = "BtDevicesServices"; 51 52 private final List<BluetoothDeviceProvider.Listener> mListeners = new ArrayList<>(); 53 private final Binder mBinder = new LocalBinder(); 54 protected final Handler mHandler = new Handler(Looper.getMainLooper()); 55 56 /** Binder in BluetoothDeviceService. */ 57 public class LocalBinder extends Binder implements BluetoothDeviceProvider { 58 getDevices()59 public List<BluetoothDevice> getDevices() { 60 return BluetoothDevicesService.this.getDevices(); 61 } 62 63 @Override addListener(BluetoothDeviceProvider.Listener listener)64 public void addListener(BluetoothDeviceProvider.Listener listener) { 65 mHandler.post(() -> { 66 mListeners.add(listener); 67 68 // Trigger first update after listener callback is registered. 69 for (BluetoothDevice device : getDevices()) { 70 if (device.isConnected()) { 71 listener.onDeviceUpdated(device); 72 } 73 } 74 }); 75 } 76 77 @Override removeListener(BluetoothDeviceProvider.Listener listener)78 public void removeListener(BluetoothDeviceProvider.Listener listener) { 79 mHandler.post(() -> mListeners.remove(listener)); 80 } 81 82 @Override connectDevice(BluetoothDevice device)83 public void connectDevice(BluetoothDevice device) { 84 BluetoothDevicesService.this.connectDevice(device); 85 } 86 87 @Override disconnectDevice(BluetoothDevice device)88 public void disconnectDevice(BluetoothDevice device) { 89 BluetoothDevicesService.this.disconnectDevice(device); 90 } 91 92 @Override forgetDevice(BluetoothDevice device)93 public void forgetDevice(BluetoothDevice device) { 94 BluetoothDevicesService.this.forgetDevice(device); 95 } 96 97 @Override renameDevice(BluetoothDevice device, String newName)98 public void renameDevice(BluetoothDevice device, String newName) { 99 BluetoothDevicesService.this.renameDevice(device, newName); 100 } 101 } 102 103 BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() { 104 @Override 105 public void onReceive(Context context, Intent intent) { 106 final String action = intent.getAction(); 107 final BluetoothDevice device = 108 intent == null ? null : intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 109 // The sequence of a typical connection is: acl connected, bonding, bonded, profile 110 // connecting, profile connected. 111 if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) { 112 final int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); 113 switch (state) { 114 case BluetoothDevice.BOND_BONDED: 115 break; 116 case BluetoothDevice.BOND_NONE: 117 mHandler.post(() -> onDeviceUpdated(device)); 118 break; 119 case BluetoothDevice.BOND_BONDING: 120 break; 121 default: 122 if (DEBUG) { 123 Log.e(TAG, "unknown state " + state + " " + device); 124 } 125 } 126 } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { 127 final int state = 128 intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 129 // Actively refresh the connected devices slice. 130 if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_OFF) { 131 getContentResolver().notifyChange(GENERAL_SLICE_URI, null); 132 } 133 } else { 134 switch(action) { 135 case BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED: 136 if (device.getBondState() == BluetoothDevice.BOND_BONDED) { 137 mHandler.post(() -> onDeviceUpdated(device)); 138 } 139 break; 140 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: 141 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 142 mHandler.post(() -> onA2dpConnectionStateChanged(device.getName(), state)); 143 break; 144 case BluetoothDevice.ACTION_ACL_CONNECTED: 145 Log.i(TAG, "acl connected " + device); 146 if (device.getBondState() == BluetoothDevice.BOND_BONDED) { 147 mHandler.post(() -> onDeviceUpdated(device)); 148 } 149 break; 150 case BluetoothDevice.ACTION_ACL_DISCONNECTED: 151 Log.i(TAG, "acl disconnected " + device); 152 mHandler.post(() -> onDeviceUpdated(device)); 153 break; 154 case BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED: 155 Log.i(TAG, "acl disconnect requested: " + device); 156 break; 157 } 158 } 159 } 160 }; 161 162 @Override onBind(Intent intent)163 public IBinder onBind(Intent intent) { 164 return mBinder; 165 } 166 167 @Override onCreate()168 public void onCreate() { 169 super.onCreate(); 170 if (DEBUG) { 171 Log.e(TAG, "onCreate"); 172 } 173 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED); 174 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED); 175 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 176 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 177 filter.addAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED); 178 filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); // Headset connection 179 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); // Bluetooth toggle 180 registerReceiver(mBluetoothReceiver, filter); 181 } 182 183 @Override onDestroy()184 public void onDestroy() { 185 if (DEBUG) { 186 Log.e(TAG, "onDestroy"); 187 } 188 unregisterReceiver(mBluetoothReceiver); 189 mHandler.removeCallbacksAndMessages(null); 190 super.onDestroy(); 191 } 192 193 @Override dump(FileDescriptor fd, PrintWriter writer, String[] args)194 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 195 for (BluetoothDevice device: getDevices()) { 196 if (!device.isConnected()) { 197 continue; 198 } 199 writer.printf("%s (%s):%n", device.getName(), device.getAddress()); 200 } 201 } 202 connectDevice(BluetoothDevice device)203 private void connectDevice(BluetoothDevice device) { 204 if (device != null) { 205 CachedBluetoothDevice cachedDevice = 206 AccessoryUtils.getCachedBluetoothDevice(this, device); 207 if (cachedDevice != null) { 208 cachedDevice.connect(); 209 } 210 } 211 } 212 disconnectDevice(BluetoothDevice device)213 private void disconnectDevice(BluetoothDevice device) { 214 if (device != null) { 215 CachedBluetoothDevice cachedDevice = 216 AccessoryUtils.getCachedBluetoothDevice(this, device); 217 if (cachedDevice != null) { 218 cachedDevice.disconnect(); 219 } 220 } 221 } 222 forgetDevice(BluetoothDevice device)223 private static void forgetDevice(BluetoothDevice device) { 224 if (device == null || !device.removeBond()) { 225 Log.w(TAG, "failed to remove bond: " + device); 226 } 227 } 228 renameDevice(BluetoothDevice device, String newName)229 private void renameDevice(BluetoothDevice device, String newName) { 230 if (device != null) { 231 device.setAlias(newName); 232 mHandler.post(() -> onDeviceUpdated(device)); 233 } 234 } 235 onA2dpConnectionStateChanged(String deviceName, int connectionStatus)236 private void onA2dpConnectionStateChanged(String deviceName, int connectionStatus) { 237 String resStr; 238 String text; 239 switch (connectionStatus) { 240 case BluetoothProfile.STATE_CONNECTED: 241 resStr = getResources().getString(R.string.bluetooth_device_connected_toast); 242 text = String.format(resStr, deviceName); 243 Toast.makeText(BluetoothDevicesService.this.getApplicationContext(), 244 text, Toast.LENGTH_SHORT).show(); 245 break; 246 case BluetoothProfile.STATE_DISCONNECTED: 247 resStr = getResources().getString(R.string.bluetooth_device_disconnected_toast); 248 text = String.format(resStr, deviceName); 249 Toast.makeText(BluetoothDevicesService.this.getApplicationContext(), 250 text, Toast.LENGTH_SHORT).show(); 251 break; 252 case BluetoothProfile.STATE_CONNECTING: 253 case BluetoothProfile.STATE_DISCONNECTING: 254 default: 255 break; 256 } 257 } 258 onDeviceUpdated(BluetoothDevice device)259 private void onDeviceUpdated(BluetoothDevice device) { 260 mListeners.forEach(listener -> listener.onDeviceUpdated(device)); 261 } 262 263 /** Returns the BluetoothDevice object with the input address. */ findDevice(String address)264 public static BluetoothDevice findDevice(String address) { 265 List<BluetoothDevice> devices = getDevices(); 266 BluetoothDevice curDevice = null; 267 for (BluetoothDevice device: devices) { 268 if (address.equals(device.getAddress())) { 269 curDevice = device; 270 break; 271 } 272 } 273 return curDevice; 274 } 275 getDevices()276 private static List<BluetoothDevice> getDevices() { 277 final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); 278 if (btAdapter != null) { 279 return new ArrayList<>(btAdapter.getBondedDevices()); 280 } 281 return new ArrayList<>(); // Empty list 282 } 283 } 284