Home | History | Annotate | Download | only in gatt
      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