• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.google.android.tv.btservices.pairing;
18 
19 import static com.google.android.tv.btservices.pairing.BluetoothPairer.STATUS_CANCELLED;
20 import static com.google.android.tv.btservices.pairing.BluetoothPairer.STATUS_DONE;
21 import static com.google.android.tv.btservices.pairing.BluetoothPairer.STATUS_ERROR;
22 import static com.google.android.tv.btservices.pairing.BluetoothPairer.STATUS_TIMEOUT;
23 
24 import android.app.Service;
25 import android.bluetooth.BluetoothDevice;
26 import android.bluetooth.BluetoothProfile;
27 import android.content.Intent;
28 import android.os.Binder;
29 import android.os.IBinder;
30 import android.os.Handler;
31 import android.util.Log;
32 
33 import com.google.android.tv.btservices.BluetoothUtils;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 
38 public class BluetoothPairingService extends Service implements BluetoothScanner.Listener,
39         BluetoothPairer.Listener {
40 
41     private static final String TAG = "Atv.BtPairingService";
42 
43     private final Binder mBinder = new LocalBinder();
44     private List<ScanningListener> mScanningListenerList = new ArrayList<>();
45     private List<PairingListener> mPairingListenerList = new ArrayList<>();
46     private BluetoothScanner mBluetoothScanner;
47     private BluetoothPairer mBluetoothPairer;
48 
49     private final Handler mHandler = new Handler();
50 
51     public static final int STATUS_FOUND = 1;
52     public static final int STATUS_UPDATED = 2;
53     public static final int STATUS_LOST = 3;
54 
55     private static final long POST_SCANNING_PRE_PAIRING_PAUSE_MS = 500;
56 
57     public interface ScanningListener {
updateScanning(boolean isScanning)58         void updateScanning(boolean isScanning);
updateDevice(BluetoothDevice device, int status)59         void updateDevice(BluetoothDevice device, int status);
60     }
61 
62     public interface PairingListener {
updatePairingStatus(BluetoothDevice device, int status)63         void updatePairingStatus(BluetoothDevice device, int status);
64     }
65 
66     public class LocalBinder extends Binder {
67 
addScanningListener(ScanningListener listener)68         public void addScanningListener(ScanningListener listener) {
69             if (!mScanningListenerList.contains(listener)) {
70                 mScanningListenerList.add(listener);
71                 resetScanning();
72             }
73         }
74 
addPairingListener(PairingListener listener)75         public void addPairingListener(PairingListener listener) {
76             if (!mPairingListenerList.contains(listener)) {
77                 mPairingListenerList.add(listener);
78             }
79         }
80 
removeScanningListener(ScanningListener listener)81         public void removeScanningListener(ScanningListener listener) {
82             mScanningListenerList.remove(listener);
83             if (mScanningListenerList.isEmpty()) {
84                 stopScanning();
85             }
86         }
87 
removePairingListener(PairingListener listener)88         public void removePairingListener(PairingListener listener) {
89             mPairingListenerList.remove(listener);
90         }
91 
restartScanning()92         public void restartScanning() {
93             resetScanning();
94         }
95 
pairDevice(BluetoothDevice device)96         public void pairDevice(BluetoothDevice device) {
97             pair(device, true);
98         }
99 
connectPairedDevice(BluetoothDevice device)100         public void connectPairedDevice(BluetoothDevice device) {
101             // Need to preserve bond for already paired but not connected device
102             pair(device, false);
103         }
104     }
105 
106     @Override
onBind(Intent intent)107     public IBinder onBind(Intent intent) {
108         return mBinder;
109     }
110 
111     @Override
onCreate()112     public void onCreate() {
113         super.onCreate();
114         mBluetoothScanner = new BluetoothScanner(getApplicationContext());
115     }
116 
117     @Override
onDestroy()118     public void onDestroy() {
119         stopScanning();
120         super.onDestroy();
121     }
122 
resetScanning()123     private void resetScanning() {
124         stopScanning();
125         if (pairingInProgress()) {
126             return;
127         }
128         if (!mScanningListenerList.isEmpty()) {
129             mBluetoothScanner.startListening(/* listener= */this);
130             mScanningListenerList.forEach(listener -> listener.updateScanning(true));
131         }
132     }
133 
stopScanning()134     private void stopScanning() {
135         mBluetoothScanner.stopListening(this);
136         mScanningListenerList.forEach(listener -> listener.updateScanning(false));
137     }
138 
pairingInProgress()139     private boolean pairingInProgress() {
140         return mBluetoothPairer != null;
141     }
142 
pair(BluetoothDevice device, boolean unpairOnFail)143     private void pair(BluetoothDevice device, boolean unpairOnFail) {
144         if (pairingInProgress()) {
145             return;
146         }
147         Integer pairingProfile = getPairingProfile(device);
148         if (pairingProfile != null) {
149             stopScanning();
150             mHandler.postDelayed(() -> {
151                 try {
152                     mBluetoothPairer = new BluetoothPairer(this, pairingProfile);
153                 } catch (IllegalArgumentException e) {
154                     Log.e(TAG, "Invalid device type", e);
155                     return;
156                 }
157                 mBluetoothPairer.startPairing(device, this, unpairOnFail);
158             }, POST_SCANNING_PRE_PAIRING_PAUSE_MS);
159         }
160     }
161 
getPairingProfile(BluetoothDevice device)162     private Integer getPairingProfile(BluetoothDevice device) {
163         if (BluetoothUtils.isBluetoothHeadset(device)) {
164             return BluetoothProfile.A2DP;
165         } else if (BluetoothUtils.isRemoteClass(device)) {
166             return BluetoothProfile.HID_HOST;
167         } else {
168             return null;
169         }
170     }
171 
172     /** BluetoothScanner.Listener implementation */
173     @Override
onDeviceAdded(BluetoothDevice device)174     public void onDeviceAdded(BluetoothDevice device) {
175         mScanningListenerList.forEach(listener -> listener.updateDevice(device, STATUS_FOUND));
176     }
177 
178     /** BluetoothScanner.Listener implementation */
179     @Override
onDeviceChanged(BluetoothDevice device)180     public void onDeviceChanged(BluetoothDevice device) {
181         mScanningListenerList.forEach(listener -> listener.updateDevice(device, STATUS_UPDATED));
182     }
183 
184     /** BluetoothScanner.Listener implementation */
185     @Override
onDeviceRemoved(BluetoothDevice device)186     public void onDeviceRemoved(BluetoothDevice device) {
187         mScanningListenerList.forEach(listener -> listener.updateDevice(device, STATUS_LOST));
188     }
189 
190     /** BluetoothPairer.Listener implementation */
191     @Override
onStatusChanged(BluetoothDevice device, int status)192     public void onStatusChanged(BluetoothDevice device, int status) {
193         if (device != null) {
194             mPairingListenerList.forEach(listener -> listener.updatePairingStatus(device, status));
195         }
196         if (mBluetoothPairer != null && (status == STATUS_ERROR || status == STATUS_CANCELLED
197                 || status == STATUS_TIMEOUT || status == STATUS_DONE)) {
198             mBluetoothPairer.dispose();
199             mBluetoothPairer = null;
200             resetScanning();
201         }
202     }
203 }
204