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