• 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                             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