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 Log.i(TAG, "bonded " + device); 138 mHandler.post(() -> onDeviceUpdated(device)); 139 } 140 break; 141 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: 142 final int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 143 mHandler.post(() -> { 144 onA2dpConnectionStateChanged(device.getName(), state); 145 if (state == BluetoothProfile.STATE_CONNECTED 146 || state == BluetoothProfile.STATE_DISCONNECTED) { 147 onDeviceUpdated(device); 148 } 149 }); 150 break; 151 case BluetoothDevice.ACTION_ACL_CONNECTED: 152 Log.i(TAG, "acl connected " + device); 153 if (device.getBondState() == BluetoothDevice.BOND_BONDED) { 154 mHandler.post(() -> onDeviceUpdated(device)); 155 } 156 break; 157 case BluetoothDevice.ACTION_ACL_DISCONNECTED: 158 Log.i(TAG, "acl disconnected " + device); 159 mHandler.post(() -> onDeviceUpdated(device)); 160 break; 161 case BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED: 162 Log.i(TAG, "acl disconnect requested: " + device); 163 break; 164 } 165 } 166 } 167 }; 168 169 @Override onBind(Intent intent)170 public IBinder onBind(Intent intent) { 171 return mBinder; 172 } 173 174 @Override onCreate()175 public void onCreate() { 176 super.onCreate(); 177 if (DEBUG) { 178 Log.e(TAG, "onCreate"); 179 } 180 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED); 181 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED); 182 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 183 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 184 filter.addAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED); 185 filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); // Headset connection 186 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); // Bluetooth toggle 187 registerReceiver(mBluetoothReceiver, filter); 188 } 189 190 @Override onDestroy()191 public void onDestroy() { 192 if (DEBUG) { 193 Log.e(TAG, "onDestroy"); 194 } 195 unregisterReceiver(mBluetoothReceiver); 196 mHandler.removeCallbacksAndMessages(null); 197 super.onDestroy(); 198 } 199 200 @Override dump(FileDescriptor fd, PrintWriter writer, String[] args)201 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 202 for (BluetoothDevice device: getDevices()) { 203 if (!device.isConnected()) { 204 continue; 205 } 206 writer.printf("%s (%s):%n", device.getName(), device.getAddress()); 207 } 208 } 209 connectDevice(BluetoothDevice device)210 private void connectDevice(BluetoothDevice device) { 211 if (device != null) { 212 CachedBluetoothDevice cachedDevice = 213 AccessoryUtils.getCachedBluetoothDevice(this, device); 214 if (cachedDevice != null) { 215 Log.i(TAG, "connectDevice: " + device); 216 cachedDevice.connect(); 217 } 218 } 219 } 220 disconnectDevice(BluetoothDevice device)221 private void disconnectDevice(BluetoothDevice device) { 222 if (device != null) { 223 CachedBluetoothDevice cachedDevice = 224 AccessoryUtils.getCachedBluetoothDevice(this, device); 225 if (cachedDevice != null) { 226 Log.i(TAG, "disconnectDevice: " + device); 227 cachedDevice.disconnect(); 228 } 229 } 230 } 231 forgetDevice(BluetoothDevice device)232 private static void forgetDevice(BluetoothDevice device) { 233 if (device == null || !device.removeBond()) { 234 Log.w(TAG, "failed to remove bond: " + device); 235 } 236 } 237 renameDevice(BluetoothDevice device, String newName)238 private void renameDevice(BluetoothDevice device, String newName) { 239 if (device != null) { 240 device.setAlias(newName); 241 mHandler.post(() -> onDeviceUpdated(device)); 242 } 243 } 244 onA2dpConnectionStateChanged(String deviceName, int connectionStatus)245 private void onA2dpConnectionStateChanged(String deviceName, int connectionStatus) { 246 String resStr; 247 String text; 248 switch (connectionStatus) { 249 case BluetoothProfile.STATE_CONNECTED: 250 resStr = getResources().getString(R.string.bluetooth_device_connected_toast); 251 text = String.format(resStr, deviceName); 252 Toast.makeText(BluetoothDevicesService.this.getApplicationContext(), 253 text, Toast.LENGTH_SHORT).show(); 254 Log.d(TAG, "onA2dpConnectionStateChanged: Connected, toasting: " + text); 255 break; 256 case BluetoothProfile.STATE_DISCONNECTED: 257 resStr = getResources().getString(R.string.bluetooth_device_disconnected_toast); 258 text = String.format(resStr, deviceName); 259 Toast.makeText(BluetoothDevicesService.this.getApplicationContext(), 260 text, Toast.LENGTH_SHORT).show(); 261 Log.d(TAG, "onA2dpConnectionStateChanged: Disconnected, toasting: " + text); 262 break; 263 case BluetoothProfile.STATE_CONNECTING: 264 Log.d(TAG, "onA2dpConnectionStateChanged: Connecting"); 265 break; 266 case BluetoothProfile.STATE_DISCONNECTING: 267 Log.d(TAG, "onA2dpConnectionStateChanged: Disconnecting"); 268 break; 269 default: 270 Log.d(TAG, "onA2dpConnectionStateChanged: " + connectionStatus); 271 break; 272 } 273 } 274 onDeviceUpdated(BluetoothDevice device)275 private void onDeviceUpdated(BluetoothDevice device) { 276 mListeners.forEach(listener -> listener.onDeviceUpdated(device)); 277 } 278 279 /** Returns the BluetoothDevice object with the input address. */ findDevice(String address)280 public static BluetoothDevice findDevice(String address) { 281 List<BluetoothDevice> devices = getDevices(); 282 BluetoothDevice curDevice = null; 283 for (BluetoothDevice device: devices) { 284 if (address.equals(device.getAddress())) { 285 curDevice = device; 286 break; 287 } 288 } 289 return curDevice; 290 } 291 getDevices()292 private static List<BluetoothDevice> getDevices() { 293 final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); 294 if (btAdapter != null) { 295 return new ArrayList<>(btAdapter.getBondedDevices()); 296 } 297 return new ArrayList<>(); // Empty list 298 } 299 } 300