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

Commit a46cc490 authored by Jakub Pawlowski's avatar Jakub Pawlowski Committed by android-build-merger
Browse files

Merge "Bluetooth 5 periodc scan API (1/2)"

am: e3bcff43

Change-Id: I7b68746e3dc02a12d13380a90bcf26cebfd31f16
parents ca0238aa e3bcff43
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.annotation.SystemApi;
import android.app.ActivityThread;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.PeriodicAdvertisingManager;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanRecord;
@@ -525,6 +526,7 @@ public final class BluetoothAdapter {

    private static BluetoothLeScanner sBluetoothLeScanner;
    private static BluetoothLeAdvertiser sBluetoothLeAdvertiser;
    private static PeriodicAdvertisingManager sPeriodicAdvertisingManager;

    private final IBluetoothManager mManagerService;
    private IBluetooth mService;
@@ -629,6 +631,30 @@ public final class BluetoothAdapter {
        return sBluetoothLeAdvertiser;
    }

    /**
     * Returns a {@link PeriodicAdvertisingManager} object for Bluetooth LE Periodic Advertising
     * operations. Will return null if Bluetooth is turned off or if Bluetooth LE Periodic
     * Advertising is not supported on this device.
     * <p>
     * Use {@link #isLePeriodicAdvertisingSupported()} to check whether LE Periodic Advertising is
     * supported on this device before calling this method.
     */
    public PeriodicAdvertisingManager getPeriodicAdvertisingManager() {
      if (!getLeAccess())
        return null;

      if (!isLePeriodicAdvertisingSupported())
        return null;

      synchronized (mLock) {
        if (sPeriodicAdvertisingManager == null) {
          sPeriodicAdvertisingManager =
              new PeriodicAdvertisingManager(mManagerService);
        }
      }
      return sPeriodicAdvertisingManager;
    }

    /**
     * Returns a {@link BluetoothLeScanner} object for Bluetooth LE scan operations.
     */
+5 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.bluetooth.BluetoothGattService;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.bluetooth.le.ResultStorageDescriptor;
import android.os.ParcelUuid;
@@ -29,6 +30,7 @@ import android.os.WorkSource;
import android.bluetooth.IBluetoothGattCallback;
import android.bluetooth.IBluetoothGattServerCallback;
import android.bluetooth.le.IAdvertiserCallback;
import android.bluetooth.le.IPeriodicAdvertisingCallback;
import android.bluetooth.le.IScannerCallback;

/**
@@ -53,6 +55,9 @@ interface IBluetoothGatt {
                               in AdvertiseSettings settings);
    void stopMultiAdvertising(in int advertiserId);

    void registerSync(in ScanResult scanResult, in int skip, in int timeout, in IPeriodicAdvertisingCallback callback);
    void unregisterSync(in IPeriodicAdvertisingCallback callback);

    void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback);
    void unregisterClient(in int clientIf);
    void clientConnect(in int clientIf, in String address, in boolean isDirect, in int transport);
+31 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.bluetooth.le;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.PeriodicAdvertisingReport;

/**
 * Callback definitions for interacting with Periodic Advertising
 * @hide
 */
oneway interface IPeriodicAdvertisingCallback {

  void onSyncEstablished(in int syncHandle, in BluetoothDevice device, in int advertisingSid,
                         in int skip, in int timeout, in int status);
  void onPeriodicAdvertisingReport(in PeriodicAdvertisingReport report);
  void onSyncLost(in int syncHandle);
}
+77 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.bluetooth.le;

import android.bluetooth.BluetoothDevice;

/**
 * Bluetooth LE periodic advertising callbacks, used to deliver periodic
 * advertising operation status.
 *
 * @see PeriodicAdvertisingManager#createSync
 */
public abstract class PeriodicAdvertisingCallback {

    /**
     * The requested operation was successful.
     *
     * @hide
     */
    public static final int SYNC_SUCCESS = 0;

    /**
     * Sync failed to be established because remote device did not respond.
     */
    public static final int SYNC_NO_RESPONSE = 1;

    /**
     *  Sync failed to be established because controller can't support more syncs.
     */
    public static final int SYNC_NO_RESOURCES = 2;


    /**
     * Callback when synchronization was established.
     *
     * @param syncHandle handle used to identify this synchronization.
     * @param device remote device.
     * @param advertisingSid synchronized advertising set id.
     * @param skip  The number of periodic advertising packets that can be skipped
     * after a successful receive in force. @see PeriodicAdvertisingManager#createSync
     * @param timeout Synchronization timeout for the periodic advertising in force. One
     * unit is 10ms. @see PeriodicAdvertisingManager#createSync
     * @param timeout
     * @param status operation status.
     */
    public void onSyncEstablished(int syncHandle, BluetoothDevice device,
                                  int advertisingSid, int skip, int timeout,
                                  int status) {}

    /**
     * Callback when periodic advertising report is received.
     *
     * @param report periodic advertising report.
     */
    public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) {}

    /**
     * Callback when periodic advertising synchronization was lost.
     *
     * @param syncHandle handle used to identify this synchronization.
     */
    public void onSyncLost(int syncHandle) {}
}
+237 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package android.bluetooth.le;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.IBluetoothGatt;
import android.bluetooth.IBluetoothManager;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import java.util.IdentityHashMap;
import java.util.Map;

/**
 * This class provides methods to perform periodic advertising related
 * operations. An application can register for periodic advertisements using
 * {@link PeriodicAdvertisingManager#registerSync}.
 * <p>
 * Use {@link BluetoothAdapter#getPeriodicAdvertisingManager()} to get an
 * instance of {@link PeriodicAdvertisingManager}.
 * <p>
 * <b>Note:</b> Most of the methods here require
 * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
 */
public final class PeriodicAdvertisingManager {

  private static final String TAG = "PeriodicAdvertisingManager";

  private static final int SKIP_MIN = 0;
  private static final int SKIP_MAX = 499;
  private static final int TIMEOUT_MIN = 10;
  private static final int TIMEOUT_MAX = 16384;

  private static final int SYNC_STARTING = -1;

  private final IBluetoothManager mBluetoothManager;
  private BluetoothAdapter mBluetoothAdapter;

  /* maps callback, to callback wrapper and sync handle */
  Map<PeriodicAdvertisingCallback,
      IPeriodicAdvertisingCallback /* callbackWrapper */> callbackWrappers;

  /**
   * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead.
   *
   * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management.
   * @hide
   */
  public PeriodicAdvertisingManager(IBluetoothManager bluetoothManager) {
    mBluetoothManager = bluetoothManager;
    mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    callbackWrappers = new IdentityHashMap<>();
  }

  /**
   * Synchronize with periodic advertising pointed to by the {@code scanResult}.
   * The {@code scanResult} used must contain a valid advertisingSid. First
   * call to registerSync will use the {@code skip} and {@code timeout} provided.
   * Subsequent calls from other apps, trying to sync with same set will reuse
   * existing sync, thus {@code skip} and {@code timeout} values will not take
   * effect. The values in effect will be returned in
   * {@link PeriodicAdvertisingCallback#onSyncEstablished}.
   *
   * @param scanResult Scan result containing advertisingSid.
   * @param skip The number of periodic advertising packets that can be skipped
   * after a successful receive. Must be between 0 and 499.
   * @param timeout Synchronization timeout for the periodic advertising. One
   * unit is 10ms. Must be between 10 (100ms) and 16384 (163.84s).
   * @param callback Callback used to deliver all operations status.
   * @throws IllegalArgumentException if {@code scanResult} is null or {@code
   * skip} is invalid or {@code timeout} is invalid or {@code callback} is null.
   */
  public void registerSync(ScanResult scanResult, int skip, int timeout,
                         PeriodicAdvertisingCallback callback) {
    registerSync(scanResult, skip, timeout, callback, null);
  }

  /**
   * Synchronize with periodic advertising pointed to by the {@code scanResult}.
   * The {@code scanResult} used must contain a valid advertisingSid. First
   * call to registerSync will use the {@code skip} and {@code timeout} provided.
   * Subsequent calls from other apps, trying to sync with same set will reuse
   * existing sync, thus {@code skip} and {@code timeout} values will not take
   * effect. The values in effect will be returned in
   * {@link PeriodicAdvertisingCallback#onSyncEstablished}.
   *
   * @param scanResult Scan result containing advertisingSid.
   * @param skip The number of periodic advertising packets that can be skipped
   * after a successful receive. Must be between 0 and 499.
   * @param timeout Synchronization timeout for the periodic advertising. One
   * unit is 10ms. Must be between 10 (100ms) and 16384 (163.84s).
   * @param callback Callback used to deliver all operations status.
   * @param handler thread upon which the callbacks will be invoked.
   * @throws IllegalArgumentException if {@code scanResult} is null or {@code
   * skip} is invalid or {@code timeout} is invalid or {@code callback} is null.
   */
  public void registerSync(ScanResult scanResult, int skip, int timeout,
                         PeriodicAdvertisingCallback callback, Handler handler) {
    if (callback == null) {
      throw new IllegalArgumentException("callback can't be null");
    }

    if (scanResult == null) {
      throw new IllegalArgumentException("scanResult can't be null");
    }

    if (scanResult.getAdvertisingSid() == ScanResult.SID_NOT_PRESENT) {
      throw new IllegalArgumentException("scanResult must contain a valid sid");
    }

    if (skip < SKIP_MIN || skip > SKIP_MAX) {
      throw new IllegalArgumentException(
          "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX);
    }

    if (timeout < TIMEOUT_MIN || timeout > TIMEOUT_MAX) {
      throw new IllegalArgumentException(
          "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX);
    }

    IBluetoothGatt gatt;
    try {
        gatt = mBluetoothManager.getBluetoothGatt();
    } catch (RemoteException e) {
        Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
        callback.onSyncEstablished(0, scanResult.getDevice(), scanResult.getAdvertisingSid(),
                                   skip, timeout,
                                   PeriodicAdvertisingCallback.SYNC_NO_RESOURCES);
        return;
    }

    if (handler == null)
      handler = new Handler(Looper.getMainLooper());

    IPeriodicAdvertisingCallback wrapped = wrap(callback, handler);
    callbackWrappers.put(callback, wrapped);

    try {
      gatt.registerSync(scanResult, skip, timeout, wrapped);
    } catch (RemoteException e) {
      Log.e(TAG, "Failed to register sync - ", e);
      return;
    }
  }

  /**
   * Cancel pending attempt to create sync, or terminate existing sync.
   *
   * @param callback Callback used to deliver all operations status.
   * @throws IllegalArgumentException if {@code callback} is null, or not a properly
   * registered callback.
   */
  public void unregisterSync(PeriodicAdvertisingCallback callback) {
    if (callback == null) {
      throw new IllegalArgumentException("callback can't be null");
    }

    IBluetoothGatt gatt;
    try {
        gatt = mBluetoothManager.getBluetoothGatt();
    } catch (RemoteException e) {
        Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
        return;
    }

    IPeriodicAdvertisingCallback wrapper = callbackWrappers.remove(callback);
    if (wrapper == null) {
      throw new IllegalArgumentException("callback was not properly registered");
    }

    try {
      gatt.unregisterSync(wrapper);
    } catch (RemoteException e) {
        Log.e(TAG, "Failed to cancel sync creation - ", e);
        return;
    }
  }

  private IPeriodicAdvertisingCallback wrap(PeriodicAdvertisingCallback callback, Handler handler) {
    return new IPeriodicAdvertisingCallback.Stub() {
      public void onSyncEstablished(int syncHandle, BluetoothDevice device,
                                    int advertisingSid, int skip, int timeout, int status) {

          handler.post(new Runnable() {
              @Override
              public void run() {
                  callback.onSyncEstablished(syncHandle, device, advertisingSid, skip, timeout,
                                             status);

                  if (status != PeriodicAdvertisingCallback.SYNC_SUCCESS) {
                      // App can still unregister the sync until notified it failed. Remove callback
                      // after app was notifed.
                      callbackWrappers.remove(callback);
                  }
              }
          });
      }

      public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) {
          handler.post(new Runnable() {
              @Override
              public void run() {
                callback.onPeriodicAdvertisingReport(report);
              }
          });
      }

      public void onSyncLost(int syncHandle) {
          handler.post(new Runnable() {
              @Override
              public void run() {
                callback.onSyncLost(syncHandle);
                // App can still unregister the sync until notified it's lost. Remove callback after
                // app was notifed.
                callbackWrappers.remove(callback);
              }
          });
      }
    };
  }
}
Loading