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