• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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