Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bc283ba1 authored by Wei Wang's avatar Wei Wang
Browse files

Implementation of batch scan.

Change-Id: I247a1676d20131ca05674da44a06d8c21785d71d
parent 9506e02e
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -1706,7 +1706,6 @@ static JNINativeMethod sStateMachineMethods[] = {
    {"gattClientConfigBatchScanStorageNative", "(IIII)V",(void *) gattClientConfigBatchScanStorageNative},
    {"gattClientStartBatchScanNative", "(IIIIII)V", (void *) gattClientStartBatchScanNative},
    {"gattClientStopBatchScanNative", "(I)V", (void *) gattClientStopBatchScanNative},
    {"gattClientReadScanReportsNative", "(II)V", (void *) gattClientReadScanReportsNative},
    {"gattClientScanFilterParamAddNative", "(IIIIIIIIIII)V", (void *) gattClientScanFilterParamAddNative},
    {"gattClientScanFilterParamDeleteNative", "(II)V", (void *) gattClientScanFilterParamDeleteNative},
    {"gattClientScanFilterParamClearAllNative", "(I)V", (void *) gattClientScanFilterParamClearAllNative},
@@ -1761,6 +1760,7 @@ static JNINativeMethod sMethods[] = {

    {"gattSetAdvDataNative", "(IZZZIII[B[B[B)V", (void *) gattSetAdvDataNative},
    {"gattSetScanParametersNative", "(II)V", (void *) gattSetScanParametersNative},
    {"gattClientReadScanReportsNative", "(II)V", (void *) gattClientReadScanReportsNative},
    {"gattTestNative", "(IJJLjava/lang/String;IIIII)V", (void *) gattTestNative},
};

+121 −11
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import com.android.bluetooth.btservice.ProfileService;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -97,6 +98,12 @@ public class GattService extends ProfileService {
    private static final int SCAN_MODE_LOW_LATENCY_WINDOW_MS = 5000;
    private static final int SCAN_MODE_LOW_LATENCY_INTERVAL_MS = 5000;

    private static final int MAC_ADDRESS_LENGTH = 6;
    // Batch scan related constants.
    private static final int TRUNCATED_RESULT_SIZE = 11;
    private static final int TIME_STAMP_LENGTH = 2;


    /**
     * Search queue to serialize remote onbject inspection.
     */
@@ -351,6 +358,13 @@ public class GattService extends ProfileService {
            service.stopScan(appIf, isServer);
        }

        @Override
        public void flushPendingBatchResults(int appIf, boolean isServer) {
            GattService service = getService();
            if (service == null) return;
            service.flushPendingBatchResults(appIf, isServer);
        }

        public void clientConnect(int clientIf, String address, boolean isDirect, int transport) {
            GattService service = getService();
            if (service == null) return;
@@ -662,9 +676,9 @@ public class GattService extends ProfileService {
                if (app != null) {
                    BluetoothDevice device = BluetoothAdapter.getDefaultAdapter()
                            .getRemoteDevice(address);
                    long scanTimeMicros =
                    long scanTimeNanos =
                            TimeUnit.NANOSECONDS.toMicros(SystemClock.elapsedRealtimeNanos());
                    ScanResult result = new ScanResult(device, adv_data, rssi, scanTimeMicros);
                    ScanResult result = new ScanResult(device, adv_data, rssi, scanTimeNanos);
                    if (matchesFilters(client, result)) {
                        try {
                            app.callback.onScanResult(address, rssi, adv_data);
@@ -1037,7 +1051,7 @@ public class GattService extends ProfileService {
        if (DBG) {
            Log.d(TAG, "onBatchScanStorageConfigured() - clientIf="+ clientIf + ", status=" + status);
        }

        mStateMachine.callbackDone();
    }

    // TODO: split into two different callbacks : onBatchScanStarted and onBatchScanStopped.
@@ -1046,20 +1060,100 @@ public class GattService extends ProfileService {
            Log.d(TAG, "onBatchScanStartStopped() - clientIf=" + clientIf
                    + ", status=" + status + ", startStopAction=" + startStopAction);
        }
        mStateMachine.callbackDone();
    }

    void onBatchScanReports(int status, int clientIf, int reportType, int numRecords,
            byte[] recordData) {
            byte[] recordData) throws RemoteException {
        if (DBG) {
            Log.d(TAG, "onBatchScanReports() - clientIf=" + clientIf + ", status=" + status
                    + ", reportType=" + reportType + ", numRecords=" + numRecords);
        }
        ClientMap.App app = mClientMap.getById(clientIf);
        if (app == null) return;
        Set<ScanResult> results = parseBatchScanResults(numRecords, reportType, recordData);
        app.callback.onBatchScanResults(new ArrayList<ScanResult>(results));
    }

    private Set<ScanResult> parseBatchScanResults(int numRecords, int reportType,
            byte[] batchRecord) {
        if (numRecords == 0) {
            return Collections.emptySet();
        }
        if (DBG) Log.d(TAG, "current time is " + SystemClock.elapsedRealtimeNanos());
        if (reportType == GattServiceStateMachine.SCAN_RESULT_TYPE_TRUNCATED) {
            return parseTruncatedResults(numRecords, batchRecord);
        } else {
            return parseFullResults(numRecords, batchRecord);
        }
    }

    private Set<ScanResult> parseTruncatedResults(int numRecords, byte[] batchRecord) {
        Set<ScanResult> results = new HashSet<ScanResult>(numRecords);
        for (int i = 0; i < numRecords; ++i) {
            byte[] record = extractBytes(batchRecord, i * TRUNCATED_RESULT_SIZE,
                    TRUNCATED_RESULT_SIZE);
            byte[] address = extractBytes(batchRecord, 0, 6);
            BluetoothDevice device = mAdapter.getRemoteDevice(address);
            int rssi = record[8];
            // Timestamp is in every 50 ms.
            long timestampNanos = parseTimestampNanos(extractBytes(record, 9, 2));
            results.add(new ScanResult(device, new byte[0], rssi, timestampNanos));
        }
        return results;
    }

    private long parseTimestampNanos(byte[] data) {
        long timestampUnit = data[1] & 0xFF << 8 + data[0];
        long timestampNanos = SystemClock.elapsedRealtimeNanos() -
                TimeUnit.MILLISECONDS.toNanos(timestampUnit * 50);
        return timestampNanos;
    }

    private Set<ScanResult> parseFullResults(int numRecords, byte[] batchRecord) {
        Set<ScanResult> results = new HashSet<ScanResult>(numRecords);
        int position = 0;
        while (position < batchRecord.length) {
            byte[] address = extractBytes(batchRecord, position, 6);
            BluetoothDevice device = mAdapter.getRemoteDevice(address);
            position += 6;
            // Skip address type.
            position++;
            // Skip tx power level.
            position++;
            int rssi = batchRecord[position++];
            long timestampNanos = parseTimestampNanos(extractBytes(batchRecord, position, 2));
            position += 2;

            // Combine advertise packet and scan response packet.
            int advertisePacketLen = batchRecord[position++];
            byte[] advertiseBytes = extractBytes(batchRecord, position, advertisePacketLen);
            position += advertisePacketLen;
            int scanResponsePacketLen = batchRecord[position++];
            byte[] scanResponseBytes = extractBytes(batchRecord, position, scanResponsePacketLen);
            position += scanResponsePacketLen;
            byte[] scanRecord = new byte[advertisePacketLen + scanResponsePacketLen];
            System.arraycopy(advertiseBytes, 0, scanRecord, 0, advertisePacketLen);
            System.arraycopy(scanResponseBytes, 0, scanRecord,
                    advertisePacketLen, scanResponsePacketLen);
            results.add(new ScanResult(device, scanRecord, rssi, timestampNanos));
        }
        return results;
    }

    // Helper method to extract bytes from byte array.
    private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
        byte[] bytes = new byte[length];
        System.arraycopy(scanRecord, start, bytes, 0, length);
        return bytes;
    }

    void onBatchScanThresholdCrossed(int clientIf) {
        if (DBG) {
            Log.d(TAG, "onBatchScanThresholdCrossed() - clientIf=" + clientIf);
        }
        boolean isServer = false;
        flushPendingBatchResults(clientIf, isServer);
    }

    void onTrackAdvFoundLost(int filterIndex, int addrType, String address, int advState,
@@ -1289,10 +1383,24 @@ public class GattService extends ProfileService {
                if (DBG) Log.d(TAG, "startScan() - adding client=" + appIf);
                mScanQueue.add(new ScanClient(appIf, isServer, settings, filters));
            }
            sendStartScanMessage(appIf, new HashSet<ScanFilter>(filters));
            sendStartScanMessage(appIf, getScanClient(appIf, isServer));
        }
    }

    void flushPendingBatchResults(int clientIf, boolean isServer) {
        if (DBG) Log.d(TAG, "flushPendingBatchResults - clientIf=" + clientIf +
                ", isServer=" + isServer);
        ScanClient scanClient = getScanClient(clientIf, isServer);
        if (scanClient == null || scanClient.settings == null
                || scanClient.settings.getReportDelayNanos() == 0) {
            // Not a batch scan client.
            Log.e(TAG, "called flushPendingBatchResults without a proper app!");
            return;
        }
        int resultType = mStateMachine.getResultType(scanClient.settings);
        gattClientReadScanReportsNative(clientIf, resultType);
    }

    void configureScanParams(int appIf) {
        if (DBG) Log.d(TAG, "configureScanParams() - queue=" + mScanQueue.size());
        int curScanSetting = Integer.MIN_VALUE;
@@ -1345,10 +1453,10 @@ public class GattService extends ProfileService {
        }
    }

    private void sendStartScanMessage(int clientIf, Set<ScanFilter> filters) {
    private void sendStartScanMessage(int clientIf, ScanClient client) {
        Message message = mStateMachine.obtainMessage(GattServiceStateMachine.START_BLE_SCAN);
        message.arg1 = clientIf;
        message.obj = filters;
        message.obj = client;
        mStateMachine.sendMessage(message);
    }

@@ -2471,4 +2579,6 @@ public class GattService extends ProfileService {
    private native void gattServerSendResponseNative (int server_if,
            int conn_id, int trans_id, int status, int handle, int offset,
            byte[] val, int auth_req);

    private native void gattClientReadScanReportsNative(int client_if, int scan_type);
}
+94 −30
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.AdvertisementData;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanSettings;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.RemoteException;
@@ -34,6 +35,7 @@ import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -105,6 +107,15 @@ public class GattServiceStateMachine extends StateMachine {
    private static final int ADVERTISING_EVENT_TYPE_SCANNABLE = 2;
    private static final int ADVERTISING_EVENT_TYPE_NON_CONNECTABLE = 3;

    // Result type defined in bt stack.
    static final int SCAN_RESULT_TYPE_TRUNCATED = 1;
    static final int SCAN_RESULT_TYPE_FULL = 2;

    // Delivery mode defined in bt stack.
    private static final int DELIVERY_MODE_IMMEDIATE = 0;
    private static final int DELIVERY_MODE_ON_FOUND = 1;
    private static final int DELIVERY_MODE_BATCH = 2;

    private final GattService mService;
    private final Map<Integer, AdvertiseClient> mAdvertiseClients;
    // Keep track of whether scan filters exist.
@@ -114,6 +125,7 @@ public class GattServiceStateMachine extends StateMachine {
    private final Idle mIdle;
    private final ScanStarting mScanStarting;
    private final AdvertiseStarting mAdvertiseStarting;
    // A count down latch used to block on stack callback. MUST reset before use.
    private CountDownLatch mCallbackLatch;

    // It's sad we need to maintain this.
@@ -160,9 +172,8 @@ public class GattServiceStateMachine extends StateMachine {
    }

    void initFilterIndexStack() {
        int maxFiltersSupported = 16;
                // AdapterService.getAdapterService().getNumOfOffloadedScanFilterSupported();
        log("maxFiltersSupported = " + maxFiltersSupported);
        int maxFiltersSupported =
                AdapterService.getAdapterService().getNumOfOffloadedScanFilterSupported();
        for (int i = 1; i < maxFiltersSupported; ++i) {
            mFilterIndexStack.add(i);
        }
@@ -198,7 +209,6 @@ public class GattServiceStateMachine extends StateMachine {
                    transitionTo(mScanStarting);
                    break;
                case STOP_BLE_SCAN:
                    // Note this should only happen no client is doing scans any more.
                    int clientIf = message.arg1;
                    resetCallbackLatch();
                    gattClientScanFilterParamClearAllNative(clientIf);
@@ -295,8 +305,12 @@ public class GattServiceStateMachine extends StateMachine {
                    resetCallbackLatch();
                    gattClientScanFilterEnableNative(clientIf, true);
                    waitForCallback();
                    Set<ScanFilter> filters = (Set<ScanFilter>) message.obj;
                    if (filters != null && filters.size() <= mFilterIndexStack.size()) {
                    ScanClient client = (ScanClient) message.obj;
                    if (client != null) {
                        Set<ScanFilter> filters = new HashSet<ScanFilter>(client.filters);
                        // TODO: add ALLOW_ALL filter.
                        if (filters != null && !filters.isEmpty() &&
                                filters.size() <= mFilterIndexStack.size()) {
                            Deque<Integer> clientFilterIndices = new ArrayDeque<Integer>();
                            for (ScanFilter filter : filters) {
                                ScanFilterQueue queue = new ScanFilterQueue();
@@ -309,18 +323,26 @@ public class GattServiceStateMachine extends StateMachine {
                                    waitForCallback();
                                }
                                resetCallbackLatch();
                                int deliveryMode = getDeliveryMode(client.settings);
                                logd("deliveryMode : " + deliveryMode);
                                int listLogicType = 0x1111111;
                                int filterLogicType = 1;
                                int rssiThreshold = Byte.MIN_VALUE;
                                gattClientScanFilterParamAddNative(
                                    clientIf, filterIndex, featureSelection, 0x1111111, 1, -127,
                                    -127, 0, 0, 0, 0);
                                        clientIf, filterIndex, featureSelection, listLogicType,
                                        filterLogicType, rssiThreshold, rssiThreshold, deliveryMode,
                                        0, 0, 0);
                                waitForCallback();
                                clientFilterIndices.add(filterIndex);
                            }
                            mClientFilterIndexMap.put(clientIf, clientFilterIndices);
                        }
                    sendMessage(ENABLE_BLE_SCAN);
                    }
                    sendMessage(ENABLE_BLE_SCAN, client);
                    break;
                case ENABLE_BLE_SCAN:
                    gattClientScanNative(true);
                    client = (ScanClient) message.obj;
                    enableBleScan(client);
                    removeMessages(OPERATION_TIMEOUT);
                    transitionTo(mIdle);
                    break;
@@ -335,6 +357,33 @@ public class GattServiceStateMachine extends StateMachine {

    }

    private void enableBleScan(ScanClient client) {
        if (client == null || client.settings == null
                || client.settings.getReportDelayNanos() == 0) {
            logd("enabling ble scan, appIf " + client.appIf);
            gattClientScanNative(true);
            return;
        }
        int fullScanPercent = 20;
        int notifyThreshold = 95;
        resetCallbackLatch();
        if (DBG)
            logd("configuring batch scan storage, appIf " + client.appIf);
        gattClientConfigBatchScanStorageNative(client.appIf, fullScanPercent,
                100 - fullScanPercent, notifyThreshold);
        waitForCallback();
        int scanMode = getResultType(client.settings);
        // TODO: configure scan parameters.
        int scanIntervalUnit = 8;
        int scanWindowUnit = 8;
        int discardRule = 2;
        int addressType = 0;
        logd("Starting BLE batch scan");
        gattClientStartBatchScanNative(client.appIf, scanMode, scanIntervalUnit, scanWindowUnit,
                addressType,
                discardRule);
    }

    private void resetCallbackLatch() {
        mCallbackLatch = new CountDownLatch(1);
    }
@@ -355,21 +404,18 @@ public class GattServiceStateMachine extends StateMachine {
        log("addFilterToController: " + entry.type);
        switch (entry.type) {
            case ScanFilterQueue.TYPE_DEVICE_ADDRESS:
                // TBD appropriate params need to be passed here
                log("add address " + entry.address);
                gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 0,
                        "", entry.address, (byte) 0, new byte[0], new byte[0]);
                break;

            case ScanFilterQueue.TYPE_SERVICE_DATA:
                // TBD appropriate params need to be passed here
                gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 0,
                        "", "", (byte) 0, entry.data, entry.data_mask);
                break;

            case ScanFilterQueue.TYPE_SERVICE_UUID:
            case ScanFilterQueue.TYPE_SOLICIT_UUID:
                // TBD appropriate params need to be passed here
                gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0,
                        entry.uuid.getLeastSignificantBits(),
                        entry.uuid.getMostSignificantBits(),
@@ -379,7 +425,6 @@ public class GattServiceStateMachine extends StateMachine {
                break;

            case ScanFilterQueue.TYPE_LOCAL_NAME:
                // TBD appropriate params need to be passed here
                loge("adding filters: " + entry.name);
                gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 0,
                        entry.name, "", (byte) 0, new byte[0], new byte[0]);
@@ -390,7 +435,6 @@ public class GattServiceStateMachine extends StateMachine {
                int len = entry.data.length;
                if (entry.data_mask.length != len)
                    return;
                // TBD appropriate params need to be passed here
                gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, entry.company,
                        entry.company_mask, 0, 0, 0, 0, "", "", (byte) 0,
                        entry.data, entry.data_mask);
@@ -545,6 +589,28 @@ public class GattServiceStateMachine extends StateMachine {
        }
    }

    /**
     * Return batch scan result type value defined in bt stack.
     */
    int getResultType(ScanSettings settings) {
        return settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_FULL ?
                SCAN_RESULT_TYPE_FULL : SCAN_RESULT_TYPE_TRUNCATED;
    }

    // Get delivery mode based on scan settings.
    private int getDeliveryMode(ScanSettings settings) {
        if (settings == null) {
            return DELIVERY_MODE_IMMEDIATE;
        }
        // TODO: double check whether it makes sense to use the same delivery mode for found and
        // lost.
        if (settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ON_FOUND ||
                settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ON_LOST) {
            return DELIVERY_MODE_ON_FOUND;
        }
        return settings.getReportDelayNanos() == 0 ? DELIVERY_MODE_IMMEDIATE : DELIVERY_MODE_BATCH;
    }

    private long millsToUnit(int millisecond) {
        return TimeUnit.MILLISECONDS.toMicros(millisecond) / ADVERTISING_INTERVAL_MICROS_PER_UNIT;
    }
@@ -605,6 +671,4 @@ public class GattServiceStateMachine extends StateMachine {
            int scan_interval_unit, int scan_window_unit, int address_type, int discard_rule);

    private native void gattClientStopBatchScanNative(int client_if);

    private native void gattClientReadScanReportsNative(int client_if, int scan_type);
}