1 /* 2 * Copyright (C) 2017 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.bluetooth.gatt; 18 19 import android.bluetooth.le.IPeriodicAdvertisingCallback; 20 import android.bluetooth.le.PeriodicAdvertisingReport; 21 import android.bluetooth.le.ScanRecord; 22 import android.bluetooth.le.ScanResult; 23 import android.os.IBinder; 24 import android.os.IInterface; 25 import android.os.RemoteException; 26 import android.util.Log; 27 28 import com.android.bluetooth.btservice.AdapterService; 29 30 import java.util.Collections; 31 import java.util.HashMap; 32 import java.util.Map; 33 34 /** 35 * Manages Bluetooth LE Periodic scans 36 * 37 * @hide 38 */ 39 class PeriodicScanManager { 40 private static final boolean DBG = GattServiceConfig.DBG; 41 private static final String TAG = GattServiceConfig.TAG_PREFIX + "SyncManager"; 42 43 private final AdapterService mAdapterService; 44 Map<IBinder, SyncInfo> mSyncs = Collections.synchronizedMap(new HashMap<>()); 45 static int sTempRegistrationId = -1; 46 47 /** 48 * Constructor of {@link SyncManager}. 49 */ PeriodicScanManager(AdapterService adapterService)50 PeriodicScanManager(AdapterService adapterService) { 51 if (DBG) { 52 Log.d(TAG, "advertise manager created"); 53 } 54 mAdapterService = adapterService; 55 } 56 start()57 void start() { 58 initializeNative(); 59 } 60 cleanup()61 void cleanup() { 62 if (DBG) { 63 Log.d(TAG, "cleanup()"); 64 } 65 cleanupNative(); 66 mSyncs.clear(); 67 sTempRegistrationId = -1; 68 } 69 70 class SyncInfo { 71 /* When id is negative, the registration is ongoing. When the registration finishes, id 72 * becomes equal to sync_handle */ 73 public Integer id; 74 public SyncDeathRecipient deathRecipient; 75 public IPeriodicAdvertisingCallback callback; 76 SyncInfo(Integer id, SyncDeathRecipient deathRecipient, IPeriodicAdvertisingCallback callback)77 SyncInfo(Integer id, SyncDeathRecipient deathRecipient, 78 IPeriodicAdvertisingCallback callback) { 79 this.id = id; 80 this.deathRecipient = deathRecipient; 81 this.callback = callback; 82 } 83 } 84 toBinder(IPeriodicAdvertisingCallback e)85 IBinder toBinder(IPeriodicAdvertisingCallback e) { 86 return ((IInterface) e).asBinder(); 87 } 88 89 class SyncDeathRecipient implements IBinder.DeathRecipient { 90 public IPeriodicAdvertisingCallback callback; 91 SyncDeathRecipient(IPeriodicAdvertisingCallback callback)92 SyncDeathRecipient(IPeriodicAdvertisingCallback callback) { 93 this.callback = callback; 94 } 95 96 @Override binderDied()97 public void binderDied() { 98 if (DBG) { 99 Log.d(TAG, "Binder is dead - unregistering advertising set"); 100 } 101 stopSync(callback); 102 } 103 } 104 findSync(int syncHandle)105 Map.Entry<IBinder, SyncInfo> findSync(int syncHandle) { 106 Map.Entry<IBinder, SyncInfo> entry = null; 107 for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) { 108 if (e.getValue().id == syncHandle) { 109 entry = e; 110 break; 111 } 112 } 113 return entry; 114 } 115 onSyncStarted(int regId, int syncHandle, int sid, int addressType, String address, int phy, int interval, int status)116 void onSyncStarted(int regId, int syncHandle, int sid, int addressType, String address, int phy, 117 int interval, int status) throws Exception { 118 if (DBG) { 119 Log.d(TAG, 120 "onSyncStarted() - regId=" + regId + ", syncHandle=" + syncHandle + ", status=" 121 + status); 122 } 123 124 Map.Entry<IBinder, SyncInfo> entry = findSync(regId); 125 if (entry == null) { 126 Log.i(TAG, "onSyncStarted() - no callback found for regId " + regId); 127 // Sync was stopped before it was properly registered. 128 stopSyncNative(syncHandle); 129 return; 130 } 131 132 IPeriodicAdvertisingCallback callback = entry.getValue().callback; 133 if (status == 0) { 134 entry.setValue(new SyncInfo(syncHandle, entry.getValue().deathRecipient, callback)); 135 } else { 136 IBinder binder = entry.getKey(); 137 binder.unlinkToDeath(entry.getValue().deathRecipient, 0); 138 mSyncs.remove(binder); 139 } 140 141 // TODO: fix callback arguments 142 // callback.onSyncStarted(syncHandle, tx_power, status); 143 } 144 onSyncReport(int syncHandle, int txPower, int rssi, int dataStatus, byte[] data)145 void onSyncReport(int syncHandle, int txPower, int rssi, int dataStatus, byte[] data) 146 throws Exception { 147 if (DBG) { 148 Log.d(TAG, "onSyncReport() - syncHandle=" + syncHandle); 149 } 150 151 Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle); 152 if (entry == null) { 153 Log.i(TAG, "onSyncReport() - no callback found for syncHandle " + syncHandle); 154 return; 155 } 156 157 IPeriodicAdvertisingCallback callback = entry.getValue().callback; 158 PeriodicAdvertisingReport report = 159 new PeriodicAdvertisingReport(syncHandle, txPower, rssi, dataStatus, 160 ScanRecord.parseFromBytes(data)); 161 callback.onPeriodicAdvertisingReport(report); 162 } 163 onSyncLost(int syncHandle)164 void onSyncLost(int syncHandle) throws Exception { 165 if (DBG) { 166 Log.d(TAG, "onSyncLost() - syncHandle=" + syncHandle); 167 } 168 169 Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle); 170 if (entry == null) { 171 Log.i(TAG, "onSyncLost() - no callback found for syncHandle " + syncHandle); 172 return; 173 } 174 175 IPeriodicAdvertisingCallback callback = entry.getValue().callback; 176 mSyncs.remove(entry); 177 callback.onSyncLost(syncHandle); 178 } 179 startSync(ScanResult scanResult, int skip, int timeout, IPeriodicAdvertisingCallback callback)180 void startSync(ScanResult scanResult, int skip, int timeout, 181 IPeriodicAdvertisingCallback callback) { 182 SyncDeathRecipient deathRecipient = new SyncDeathRecipient(callback); 183 IBinder binder = toBinder(callback); 184 try { 185 binder.linkToDeath(deathRecipient, 0); 186 } catch (RemoteException e) { 187 throw new IllegalArgumentException("Can't link to periodic scanner death"); 188 } 189 190 String address = scanResult.getDevice().getAddress(); 191 int sid = scanResult.getAdvertisingSid(); 192 193 int cbId = --sTempRegistrationId; 194 mSyncs.put(binder, new SyncInfo(cbId, deathRecipient, callback)); 195 196 if (DBG) { 197 Log.d(TAG, "startSync() - reg_id=" + cbId + ", callback: " + binder); 198 } 199 startSyncNative(sid, address, skip, timeout, cbId); 200 } 201 stopSync(IPeriodicAdvertisingCallback callback)202 void stopSync(IPeriodicAdvertisingCallback callback) { 203 IBinder binder = toBinder(callback); 204 if (DBG) { 205 Log.d(TAG, "stopSync() " + binder); 206 } 207 208 SyncInfo sync = mSyncs.remove(binder); 209 if (sync == null) { 210 Log.e(TAG, "stopSync() - no client found for callback"); 211 return; 212 } 213 214 Integer syncHandle = sync.id; 215 binder.unlinkToDeath(sync.deathRecipient, 0); 216 217 if (syncHandle < 0) { 218 Log.i(TAG, "stopSync() - not finished registration yet"); 219 // Sync will be freed once initiated in onSyncStarted() 220 return; 221 } 222 223 stopSyncNative(syncHandle); 224 } 225 226 static { classInitNative()227 classInitNative(); 228 } 229 classInitNative()230 private static native void classInitNative(); 231 initializeNative()232 private native void initializeNative(); 233 cleanupNative()234 private native void cleanupNative(); 235 startSyncNative(int sid, String address, int skip, int timeout, int regId)236 private native void startSyncNative(int sid, String address, int skip, int timeout, int regId); 237 stopSyncNative(int syncHandle)238 private native void stopSyncNative(int syncHandle); 239 } 240