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