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

Commit aa07cb29 authored by Wei Wang's avatar Wei Wang Committed by Matthew Xie
Browse files

BLE peripheral mode (3/4): Add peripheral mode API.

Change-Id: Id9d2f566b6d9ed0fffe73b67efad2e3d045360b4

Conflicts:
	core/java/android/bluetooth/BluetoothAdapter.java
	core/java/android/bluetooth/BluetoothGatt.java

Conflicts:
	core/java/android/bluetooth/BluetoothAdapter.java
parent 85c4f02c
Loading
Loading
Loading
Loading
+199 −11
Original line number Diff line number Diff line
@@ -27,14 +27,14 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.util.Pair;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
@@ -182,6 +182,43 @@ public final class BluetoothAdapter {
    public static final String EXTRA_DISCOVERABLE_DURATION =
            "android.bluetooth.adapter.extra.DISCOVERABLE_DURATION";

    /**
     * Activity Action: Show a system activity to request BLE advertising.<br>
     * If the device is not doing BLE advertising, this activity will start BLE advertising for the
     * device, otherwise it will continue BLE advertising using the current
     * {@link BluetoothAdvScanData}. <br>
     * Note this activity will also request the user to turn on Bluetooth if it's not currently
     * enabled.
     * @hide
     */
    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    public static final String ACTION_START_ADVERTISING =
        "android.bluetooth.adapter.action.START_ADVERTISING";

    /**
     * Activity Action: Stop the current BLE advertising.
     * @hide
     */
    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    public static final String ACTION_STOP_ADVERTISING =
        "android.bluetooth.adapter.action.STOP_ADVERTISING";

    /**
     * Broadcast Action: Indicate BLE Advertising is started.
     * @hide
     */
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_BLUETOOTH_ADVERTISING_STARTED =
        "android.bluetooth.adapter.action.ADVERTISING_STARTED";

    /**
     * Broadcast Action: Indicated BLE Advertising is stopped.
     * @hide
     */
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_BLUETOOTH_ADVERTISING_STOPPED =
        "android.bluetooth.adapter.action.ADVERTISING_STOPPED";

    /**
     * Activity Action: Show a system activity that allows the user to turn on
     * Bluetooth.
@@ -251,7 +288,6 @@ public final class BluetoothAdapter {
     */
    public static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE = 23;


    /**
     * Broadcast Action: The local Bluetooth adapter has started the remote
     * device discovery process.
@@ -365,6 +401,8 @@ public final class BluetoothAdapter {
    private IBluetooth mService;

    private final Map<LeScanCallback, GattCallbackWrapper> mLeScanClients;
    private BluetoothAdvScanData mBluetoothAdvScanData = null;
    private GattCallbackWrapper mAdvertisingCallback;

    /**
     * Get a handle to the default local Bluetooth adapter.
@@ -437,6 +475,97 @@ public final class BluetoothAdapter {
                address[0], address[1], address[2], address[3], address[4], address[5]));
    }

    /**
     * Returns a {@link BluetoothAdvScanData} object representing advertising data.
     * @hide
     */
    public BluetoothAdvScanData getAdvScanData() {
      try {
          IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
          if (iGatt == null) {
              // BLE is not supported
              Log.e(TAG, "failed to start, iGatt null");
              return null;
          }
          if (mBluetoothAdvScanData == null) {
              mBluetoothAdvScanData = new BluetoothAdvScanData(iGatt, BluetoothAdvScanData.AD);
          }
          return mBluetoothAdvScanData;
      } catch (RemoteException e) {
          Log.e(TAG, "failed to get advScanData, error: " + e);
          return null;
      }
    }


    /**
     * Start BLE advertising using current {@link BluetoothAdvScanData}.
     * An app should start advertising by requesting
     * {@link BluetoothAdapter#ACTION_START_ADVERTISING} instead of calling this method directly.
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}
     *
     * @return true if BLE avertising succeeds, false otherwise.
     * @hide
     */
    public boolean startAdvertising() {
        if (getState() != STATE_ON) return false;

        try {
            IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
            if (iGatt == null) {
                // BLE is not supported.
                return false;
            }
            // Restart/reset advertising packets if advertising is in progress.
            if (isAdvertising()) {
                // Invalid advertising callback.
                if (mAdvertisingCallback == null || mAdvertisingCallback.mLeHandle == -1) {
                    Log.e(TAG, "failed to restart advertising, invalid callback");
                    return false;
                }
                iGatt.startAdvertising(mAdvertisingCallback.mLeHandle);
                return true;
            }
            UUID uuid = UUID.randomUUID();
            GattCallbackWrapper wrapper =
                new GattCallbackWrapper(this, null, null, GattCallbackWrapper.CALLBACK_TYPE_ADV);
            iGatt.registerClient(new ParcelUuid(uuid), wrapper);
            mAdvertisingCallback = wrapper;
            return true;
        } catch (RemoteException e) {
            Log.e(TAG, "", e);
            return false;
        }
    }

    /**
     * Stop BLE advertising.
     * An app should stop advertising by requesting
     * {@link BluetoothAdapter#ACTION_STOP_ADVERTISING} instead of calling this method directly.
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}
     * @return true if BLE advertising stops, false otherwise.
     * @hide
     */
    public boolean stopAdvertisting() {
        try {
            IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
            if (iGatt == null) {
                // BLE is not supported
                return false;
            }
            if (mAdvertisingCallback == null) {
                // no callback.
                return false;
            }
            mAdvertisingCallback.stopAdvertising();
            mAdvertisingCallback = null;
            return true;
        } catch (RemoteException e) {
            Log.e(TAG, "", e);
            return false;
        }
    }

    /**
     * Return true if Bluetooth is currently enabled and ready for use.
     * <p>Equivalent to:
@@ -848,6 +977,23 @@ public final class BluetoothAdapter {
        return false;
    }

    /**
     * Returns whether BLE is currently advertising.
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
     *
     * @hide
     */
    public boolean isAdvertising() {
        if (getState() != STATE_ON) return false;
        try {
            IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
            return iGatt.isAdvertising();
        } catch (RemoteException e) {
            Log.e(TAG, "", e);
        }
        return false;
    }

    /**
     * Return the set of {@link BluetoothDevice} objects that are bonded
     * (paired) to the local adapter.
@@ -1546,8 +1692,12 @@ public final class BluetoothAdapter {
    private static class GattCallbackWrapper extends IBluetoothGattCallback.Stub {
        private static final int LE_CALLBACK_REG_TIMEOUT = 2000;
        private static final int LE_CALLBACK_REG_WAIT_COUNT = 5;
        private static final int CALLBACK_TYPE_SCAN = 0;
        private static final int CALLBACK_TYPE_ADV = 1;

        private final LeScanCallback mLeScanCb;
        private int mCallbackType;

        // mLeHandle 0: not registered
        //           -1: scan stopped
        //           >0: registered and scan started
@@ -1561,6 +1711,16 @@ public final class BluetoothAdapter {
            mLeScanCb = leScanCb;
            mScanFilter = uuid;
            mLeHandle = 0;
            mCallbackType = CALLBACK_TYPE_SCAN;
        }

        public GattCallbackWrapper(BluetoothAdapter bluetoothAdapter, LeScanCallback leScanCb,
            UUID[] uuid, int type) {
          mBluetoothAdapter = new WeakReference<BluetoothAdapter>(bluetoothAdapter);
          mLeScanCb = leScanCb;
          mScanFilter = uuid;
          mLeHandle = 0;
          mCallbackType = type;
        }

        public boolean scanStarted() {
@@ -1583,6 +1743,30 @@ public final class BluetoothAdapter {
            return started;
        }

        public void stopAdvertising() {
            synchronized (this) {
                if (mLeHandle <= 0) {
                    Log.e(TAG, "Error state, mLeHandle: " + mLeHandle);
                    return;
                }
                BluetoothAdapter adapter = mBluetoothAdapter.get();
                if (adapter != null) {
                    try {
                        IBluetoothGatt iGatt = adapter.getBluetoothManager().getBluetoothGatt();
                        iGatt.stopAdvertising();
                        Log.d(TAG, "unregeistering client " + mLeHandle);
                        iGatt.unregisterClient(mLeHandle);
                    } catch (RemoteException e) {
                        Log.e(TAG, "Failed to stop advertising and unregister" + e);
                    }
                } else {
                    Log.e(TAG, "stopAdvertising, BluetoothAdapter is null");
                }
                mLeHandle = -1;
                notifyAll();
            }
        }

        public void stopLeScan() {
            synchronized(this) {
                if (mLeHandle <= 0) {
@@ -1624,6 +1808,9 @@ public final class BluetoothAdapter {
                        BluetoothAdapter adapter = mBluetoothAdapter.get();
                        if (adapter != null) {
                            iGatt = adapter.getBluetoothManager().getBluetoothGatt();
                            if (mCallbackType == CALLBACK_TYPE_ADV) {
                                iGatt.startAdvertising(mLeHandle);
                            } else {
                              if (mScanFilter == null) {
                                  iGatt.startScan(mLeHandle, false);
                              } else {
@@ -1633,6 +1820,7 @@ public final class BluetoothAdapter {
                                  }
                                  iGatt.startScanWithUuids(mLeHandle, false, uuids);
                              }
                            }
                        } else {
                            Log.e(TAG, "onClientRegistered, BluetoothAdapter null");
                            mLeHandle = -1;
@@ -1642,7 +1830,7 @@ public final class BluetoothAdapter {
                        mLeHandle = -1;
                    }
                    if (mLeHandle == -1) {
                        // registration succeeded but start scan failed
                        // registration succeeded but start scan or advertise failed
                        if (iGatt != null) {
                            try {
                                iGatt.unregisterClient(mLeHandle);
+147 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 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;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.util.Log;

import java.util.Collections;
import java.util.List;


/**
 * This class provides the public APIs to set advertising and scan response data when BLE device
 * operates in peripheral mode. <br>
 * The exact format is defined in Bluetooth 4.0 specification, Volume 3, Part C, Section 11
 * @hide
 */
public final class BluetoothAdvScanData {

  /**
   * Available data types of {@link BluetoothAdvScanData}.
   */
  public static final int AD = 0;  // Advertising Data
  public static final int SCAN_RESPONSE = 1;  // Scan Response
  public static final int EIR = 2;  // Extended Inquiry Response

  private static final String TAG = "BluetoothAdvScanData";

  /**
   * Data type of BluetoothAdvScanData.
   */
  private final int mDataType;
  /**
   * Bluetooth Gatt Service.
   */
  private IBluetoothGatt mBluetoothGatt;

  /**
   * @param mBluetoothGatt
   * @param dataType
   */
  public BluetoothAdvScanData(IBluetoothGatt mBluetoothGatt, int dataType) {
    this.mBluetoothGatt = mBluetoothGatt;
    this.mDataType = dataType;
  }

  /**
   * @return advertising data type.
   */
  public int getDataType() {
    return mDataType;
  }

  /**
   * Set manufactureCode and manufactureData.
   * Returns true if manufacturer data is set, false if there is no enough room to set
   * manufacturer data or the data is already set.
   * @param manufacturerCode - unique identifier for the manufacturer
   * @param manufacturerData - data associated with the specific manufacturer.
   */
  public boolean setManufacturerData(int manufacturerCode, byte[] manufacturerData) {
    if (mDataType != AD) return false;
    try {
      return mBluetoothGatt.setAdvManufacturerCodeAndData(manufacturerCode, manufacturerData);
    } catch (RemoteException e) {
      return false;
    }
  }

  /**
   * Set service data.  Note the service data can only be set when the data type is {@code AD};
   * @param serviceData
   */
  public boolean setServiceData(byte[] serviceData) {

    if (mDataType != AD) return false;
    if (serviceData == null) return false;
    try {
      return mBluetoothGatt.setAdvServiceData(serviceData);
    } catch (RemoteException e) {
      return false;
    }
  }

  /**
   * Returns an immutable list of service uuids that will be advertised.
   */
  public List<ParcelUuid> getServiceUuids() {
    try {
      return Collections.unmodifiableList(mBluetoothGatt.getAdvServiceUuids());
    } catch (RemoteException e) {
      return null;
    }
  }

  /**
   * Returns manufacturer data.
   */
  public byte[] getManufacturerData() {
    if (mBluetoothGatt == null) return null;
    try {
      return mBluetoothGatt.getAdvManufacturerData();
    } catch (RemoteException e) {
      return null;
    }
  }

  /**
   * Returns service data.
   */
  public byte[] getServiceData() {
    if (mBluetoothGatt == null) return null;
    try {
      return mBluetoothGatt.getAdvServiceData();
    } catch (RemoteException e) {
      return null;
    }
  }

  /**
   * Remove manufacturer data based on given manufacturer code.
   * @param manufacturerCode
   */
  public void removeManufacturerCodeAndData(int manufacturerCode) {
    if (mBluetoothGatt != null) {
      try {
        mBluetoothGatt.removeAdvManufacturerCodeAndData(manufacturerCode);
      } catch (RemoteException e) {
        Log.e(TAG, e.toString());
      }
    }
  }
}
+0 −73
Original line number Diff line number Diff line
@@ -553,14 +553,6 @@ public final class BluetoothGatt implements BluetoothProfile {
                    Log.w(TAG, "Unhandled exception in callback", ex);
                }
            }

            /**
             * Listen command status callback
             * @hide
             */
            public void onListen(int status) {
                if (DBG) Log.d(TAG, "onListen() - status=" + status);
            }
        };

    /*package*/ BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device) {
@@ -693,71 +685,6 @@ public final class BluetoothGatt implements BluetoothProfile {
        return true;
    }

   /**
     * Starts or stops sending of advertisement packages to listen for connection
     * requests from a central devices.
     *
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
     *
     * @param start Start or stop advertising
     */
    /*package*/ void listen(boolean start) {
        if (mContext == null || !mContext.getResources().
            getBoolean(com.android.internal.R.bool.config_bluetooth_le_peripheral_mode_supported)) {
            throw new UnsupportedOperationException("BluetoothGatt#listen is blocked");
        }
        if (DBG) Log.d(TAG, "listen() - start: " + start);
        if (mService == null || mClientIf == 0) return;

        try {
            mService.clientListen(mClientIf, start);
        } catch (RemoteException e) {
            Log.e(TAG,"",e);
        }
    }

    /**
     * Sets the advertising data contained in the adv. response packet.
     *
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
     *
     * @param advData true to set adv. data, false to set scan response
     * @param includeName Inlucde the name in the adv. response
     * @param includeTxPower Include TX power value
     * @param minInterval Minimum desired scan interval (optional)
     * @param maxInterval Maximum desired scan interval (optional)
     * @param appearance The appearance flags for the device (optional)
     * @param manufacturerData Manufacturer specific data including company ID (optional)
     */
    /*package*/ void setAdvData(boolean advData, boolean includeName, boolean includeTxPower,
                           Integer minInterval, Integer maxInterval,
                           Integer appearance, Byte[] manufacturerData) {
        if (mContext == null || !mContext.getResources().
            getBoolean(com.android.internal.R.bool.config_bluetooth_le_peripheral_mode_supported)) {
            throw new UnsupportedOperationException("BluetoothGatt#setAdvData is blocked");
        }
        if (DBG) Log.d(TAG, "setAdvData()");
        if (mService == null || mClientIf == 0) return;

        byte[] data = new byte[0];
        if (manufacturerData != null) {
            data = new byte[manufacturerData.length];
            for(int i = 0; i != manufacturerData.length; ++i) {
                data[i] = manufacturerData[i];
            }
        }

        try {
            mService.setAdvData(mClientIf, !advData,
                includeName, includeTxPower,
                minInterval != null ? minInterval : 0,
                maxInterval != null ? maxInterval : 0,
                appearance != null ? appearance : 0, data);
        } catch (RemoteException e) {
            Log.e(TAG,"",e);
        }
    }

    /**
     * Disconnects an established connection, or cancels a connection attempt
     * currently in progress.
+1 −1
Original line number Diff line number Diff line
@@ -537,7 +537,7 @@ public final class BluetoothGattServer implements BluetoothProfile {
        try {
            mService.beginServiceDeclaration(mServerIf, service.getType(),
                service.getInstanceId(), service.getHandles(),
                new ParcelUuid(service.getUuid()));
                new ParcelUuid(service.getUuid()), service.isAdvertisePreferred());

            List<BluetoothGattService> includedServices = service.getIncludedServices();
            for (BluetoothGattService includedService : includedServices) {
+21 −2
Original line number Diff line number Diff line
@@ -15,8 +15,6 @@
 */
package android.bluetooth;

import android.bluetooth.BluetoothDevice;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -81,6 +79,11 @@ public class BluetoothGattService {
     */
    protected List<BluetoothGattService> mIncludedServices;

    /**
     * Whether the service uuid should be advertised.
     */
    private boolean mAdvertisePreferred;

    /**
     * Create a new BluetoothGattService.
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
@@ -263,4 +266,20 @@ public class BluetoothGattService {
        }
        return null;
    }

    /**
     * Returns whether the uuid of the service should be advertised.
     * @hide
     */
    public boolean isAdvertisePreferred() {
      return mAdvertisePreferred;
    }

    /**
     * Set whether the service uuid should be advertised.
     * @hide
     */
    public void setAdvertisePreferred(boolean advertisePreferred) {
      this.mAdvertisePreferred = advertisePreferred;
    }
}
Loading