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 */ 50 PeriodicScanManager(AdapterService adapterService) { 51 if (DBG) { 52 Log.d(TAG, "advertise manager created"); 53 } 54 mAdapterService = adapterService; 55 } 56 57 void start() { 58 initializeNative(); 59 } 60 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 77 SyncInfo(Integer id, SyncDeathRecipient deathRecipient, 78 IPeriodicAdvertisingCallback callback) { 79 this.id = id; 80 this.deathRecipient = deathRecipient; 81 this.callback = callback; 82 } 83 } 84 85 IBinder toBinder(IPeriodicAdvertisingCallback e) { 86 return ((IInterface) e).asBinder(); 87 } 88 89 class SyncDeathRecipient implements IBinder.DeathRecipient { 90 public IPeriodicAdvertisingCallback callback; 91 92 SyncDeathRecipient(IPeriodicAdvertisingCallback callback) { 93 this.callback = callback; 94 } 95 96 @Override 97 public void binderDied() { 98 if (DBG) { 99 Log.d(TAG, "Binder is dead - unregistering advertising set"); 100 } 101 stopSync(callback); 102 } 103 } 104 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 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 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 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 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 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 { 227 classInitNative(); 228 } 229 230 private static native void classInitNative(); 231 232 private native void initializeNative(); 233 234 private native void cleanupNative(); 235 236 private native void startSyncNative(int sid, String address, int skip, int timeout, int regId); 237 238 private native void stopSyncNative(int syncHandle); 239 } 240