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