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