| /* |
| * 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 com.android.bluetooth.gatt; |
| |
| import static android.content.pm.PackageManager.PERMISSION_GRANTED; |
| |
| import android.annotation.Nullable; |
| import android.app.AppOpsManager; |
| import android.app.PendingIntent; |
| import android.app.Service; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothGatt; |
| import android.bluetooth.BluetoothGattCharacteristic; |
| import android.bluetooth.BluetoothGattDescriptor; |
| import android.bluetooth.BluetoothGattService; |
| import android.bluetooth.BluetoothProfile; |
| import android.bluetooth.IBluetoothGatt; |
| import android.bluetooth.IBluetoothGattCallback; |
| import android.bluetooth.IBluetoothGattServerCallback; |
| import android.bluetooth.le.AdvertiseData; |
| import android.bluetooth.le.AdvertisingSetParameters; |
| import android.bluetooth.le.BluetoothLeScanner; |
| import android.bluetooth.le.IAdvertisingSetCallback; |
| import android.bluetooth.le.IPeriodicAdvertisingCallback; |
| import android.bluetooth.le.IScannerCallback; |
| import android.bluetooth.le.PeriodicAdvertisingParameters; |
| import android.bluetooth.le.ResultStorageDescriptor; |
| import android.bluetooth.le.ScanCallback; |
| import android.bluetooth.le.ScanFilter; |
| import android.bluetooth.le.ScanRecord; |
| import android.bluetooth.le.ScanResult; |
| import android.bluetooth.le.ScanSettings; |
| import android.companion.ICompanionDeviceManager; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.Binder; |
| import android.os.IBinder; |
| import android.os.ParcelUuid; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.os.WorkSource; |
| import android.provider.Settings; |
| import android.util.Log; |
| |
| import com.android.bluetooth.BluetoothMetricsProto; |
| import com.android.bluetooth.R; |
| import com.android.bluetooth.Utils; |
| import com.android.bluetooth.btservice.AbstractionLayer; |
| import com.android.bluetooth.btservice.AdapterService; |
| import com.android.bluetooth.btservice.ProfileService; |
| import com.android.bluetooth.util.NumberUtils; |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Provides Bluetooth Gatt profile, as a service in |
| * the Bluetooth application. |
| * @hide |
| */ |
| public class GattService extends ProfileService { |
| private static final boolean DBG = GattServiceConfig.DBG; |
| private static final boolean VDBG = GattServiceConfig.VDBG; |
| private static final String TAG = GattServiceConfig.TAG_PREFIX + "GattService"; |
| |
| static final int SCAN_FILTER_ENABLED = 1; |
| static final int SCAN_FILTER_MODIFIED = 2; |
| |
| 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; |
| |
| // onFoundLost related constants |
| private static final int ADVT_STATE_ONFOUND = 0; |
| private static final int ADVT_STATE_ONLOST = 1; |
| |
| private static final int ET_LEGACY_MASK = 0x10; |
| |
| private static final UUID HID_SERVICE_UUID = |
| UUID.fromString("00001812-0000-1000-8000-00805F9B34FB"); |
| |
| private static final UUID[] HID_UUIDS = { |
| UUID.fromString("00002A4A-0000-1000-8000-00805F9B34FB"), |
| UUID.fromString("00002A4B-0000-1000-8000-00805F9B34FB"), |
| UUID.fromString("00002A4C-0000-1000-8000-00805F9B34FB"), |
| UUID.fromString("00002A4D-0000-1000-8000-00805F9B34FB") |
| }; |
| |
| private static final UUID ANDROID_TV_REMOTE_SERVICE_UUID = |
| UUID.fromString("AB5E0001-5A21-4F05-BC7D-AF01F617B664"); |
| |
| private static final UUID FIDO_SERVICE_UUID = |
| UUID.fromString("0000FFFD-0000-1000-8000-00805F9B34FB"); // U2F |
| |
| /** |
| * Keep the arguments passed in for the PendingIntent. |
| */ |
| class PendingIntentInfo { |
| public PendingIntent intent; |
| public ScanSettings settings; |
| public List<ScanFilter> filters; |
| public String callingPackage; |
| |
| @Override |
| public boolean equals(Object other) { |
| if (!(other instanceof PendingIntentInfo)) { |
| return false; |
| } |
| return intent.equals(((PendingIntentInfo) other).intent); |
| } |
| } |
| |
| /** |
| * List of our registered scanners. |
| */ |
| class ScannerMap extends ContextMap<IScannerCallback, PendingIntentInfo> {} |
| |
| ScannerMap mScannerMap = new ScannerMap(); |
| |
| /** |
| * List of our registered clients. |
| */ |
| class ClientMap extends ContextMap<IBluetoothGattCallback, Void> {} |
| |
| ClientMap mClientMap = new ClientMap(); |
| |
| /** |
| * List of our registered server apps. |
| */ |
| class ServerMap extends ContextMap<IBluetoothGattServerCallback, Void> {} |
| |
| ServerMap mServerMap = new ServerMap(); |
| |
| /** |
| * Server handle map. |
| */ |
| HandleMap mHandleMap = new HandleMap(); |
| private List<UUID> mAdvertisingServiceUuids = new ArrayList<UUID>(); |
| |
| private int mMaxScanFilters; |
| |
| private static final int NUM_SCAN_EVENTS_KEPT = 20; |
| |
| /** |
| * Internal list of scan events to use with the proto |
| */ |
| private final ArrayDeque<BluetoothMetricsProto.ScanEvent> mScanEvents = |
| new ArrayDeque<>(NUM_SCAN_EVENTS_KEPT); |
| |
| /** |
| * Set of restricted (which require a BLUETOOTH_PRIVILEGED permission) handles per connectionId. |
| */ |
| private final Map<Integer, Set<Integer>> mRestrictedHandles = new HashMap<>(); |
| |
| private BluetoothAdapter mAdapter; |
| private AdvertiseManager mAdvertiseManager; |
| private PeriodicScanManager mPeriodicScanManager; |
| private ScanManager mScanManager; |
| private AppOpsManager mAppOps; |
| private ICompanionDeviceManager mCompanionManager; |
| private String mExposureNotificationPackage; |
| |
| private static GattService sGattService; |
| |
| /** |
| * Reliable write queue |
| */ |
| private Set<String> mReliableQueue = new HashSet<String>(); |
| |
| static { |
| classInitNative(); |
| } |
| |
| @Override |
| protected IProfileServiceBinder initBinder() { |
| return new BluetoothGattBinder(this); |
| } |
| |
| @Override |
| protected boolean start() { |
| if (DBG) { |
| Log.d(TAG, "start()"); |
| } |
| mExposureNotificationPackage = getString(R.string.exposure_notification_package); |
| Settings.Global.putInt( |
| getContentResolver(), "bluetooth_sanitized_exposure_notification_supported", 1); |
| initializeNative(); |
| mAdapter = BluetoothAdapter.getDefaultAdapter(); |
| mCompanionManager = ICompanionDeviceManager.Stub.asInterface( |
| ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE)); |
| mAppOps = getSystemService(AppOpsManager.class); |
| mAdvertiseManager = new AdvertiseManager(this, AdapterService.getAdapterService()); |
| mAdvertiseManager.start(); |
| |
| mScanManager = new ScanManager(this); |
| mScanManager.start(); |
| |
| mPeriodicScanManager = new PeriodicScanManager(AdapterService.getAdapterService()); |
| mPeriodicScanManager.start(); |
| |
| setGattService(this); |
| return true; |
| } |
| |
| @Override |
| protected boolean stop() { |
| if (DBG) { |
| Log.d(TAG, "stop()"); |
| } |
| setGattService(null); |
| mScannerMap.clear(); |
| mClientMap.clear(); |
| mServerMap.clear(); |
| mHandleMap.clear(); |
| mReliableQueue.clear(); |
| if (mAdvertiseManager != null) { |
| mAdvertiseManager.cleanup(); |
| } |
| if (mScanManager != null) { |
| mScanManager.cleanup(); |
| } |
| if (mPeriodicScanManager != null) { |
| mPeriodicScanManager.cleanup(); |
| } |
| return true; |
| } |
| |
| @Override |
| protected void cleanup() { |
| if (DBG) { |
| Log.d(TAG, "cleanup()"); |
| } |
| cleanupNative(); |
| if (mAdvertiseManager != null) { |
| mAdvertiseManager.cleanup(); |
| } |
| if (mScanManager != null) { |
| mScanManager.cleanup(); |
| } |
| if (mPeriodicScanManager != null) { |
| mPeriodicScanManager.cleanup(); |
| } |
| } |
| |
| |
| /** |
| * Get the current instance of {@link GattService} |
| * |
| * @return current instance of {@link GattService} |
| */ |
| @VisibleForTesting |
| public static synchronized GattService getGattService() { |
| if (sGattService == null) { |
| Log.w(TAG, "getGattService(): service is null"); |
| return null; |
| } |
| if (!sGattService.isAvailable()) { |
| Log.w(TAG, "getGattService(): service is not available"); |
| return null; |
| } |
| return sGattService; |
| } |
| |
| private static synchronized void setGattService(GattService instance) { |
| if (DBG) { |
| Log.d(TAG, "setGattService(): set to: " + instance); |
| } |
| sGattService = instance; |
| } |
| |
| private boolean permissionCheck(UUID characteristicUuid) { |
| return !isHidCharUuid(characteristicUuid) |
| || (checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED) |
| == PERMISSION_GRANTED); |
| } |
| |
| private boolean permissionCheck(int connId, int handle) { |
| Set<Integer> restrictedHandles = mRestrictedHandles.get(connId); |
| if (restrictedHandles == null || !restrictedHandles.contains(handle)) { |
| return true; |
| } |
| |
| return (checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED) |
| == PERMISSION_GRANTED); |
| } |
| |
| private boolean permissionCheck(ClientMap.App app, int connId, int handle) { |
| Set<Integer> restrictedHandles = mRestrictedHandles.get(connId); |
| if (restrictedHandles == null || !restrictedHandles.contains(handle)) { |
| return true; |
| } |
| |
| if (!app.hasBluetoothPrivilegedPermission |
| && checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)== PERMISSION_GRANTED) { |
| app.hasBluetoothPrivilegedPermission = true; |
| } |
| |
| return app.hasBluetoothPrivilegedPermission; |
| } |
| |
| @Override |
| public int onStartCommand(Intent intent, int flags, int startId) { |
| if (GattDebugUtils.handleDebugAction(this, intent)) { |
| return Service.START_NOT_STICKY; |
| } |
| return super.onStartCommand(intent, flags, startId); |
| } |
| |
| /** |
| * DeathReceipient handlers used to unregister applications that |
| * disconnect ungracefully (ie. crash or forced close). |
| */ |
| |
| class ScannerDeathRecipient implements IBinder.DeathRecipient { |
| int mScannerId; |
| |
| ScannerDeathRecipient(int scannerId) { |
| mScannerId = scannerId; |
| } |
| |
| @Override |
| public void binderDied() { |
| if (DBG) { |
| Log.d(TAG, "Binder is dead - unregistering scanner (" + mScannerId + ")!"); |
| } |
| |
| ScanClient client = getScanClient(mScannerId); |
| if (client != null) { |
| client.appDied = true; |
| stopScan(client.scannerId); |
| } |
| } |
| |
| private ScanClient getScanClient(int clientIf) { |
| for (ScanClient client : mScanManager.getRegularScanQueue()) { |
| if (client.scannerId == clientIf) { |
| return client; |
| } |
| } |
| for (ScanClient client : mScanManager.getBatchScanQueue()) { |
| if (client.scannerId == clientIf) { |
| return client; |
| } |
| } |
| return null; |
| } |
| } |
| |
| class ServerDeathRecipient implements IBinder.DeathRecipient { |
| int mAppIf; |
| |
| ServerDeathRecipient(int appIf) { |
| mAppIf = appIf; |
| } |
| |
| @Override |
| public void binderDied() { |
| if (DBG) { |
| Log.d(TAG, "Binder is dead - unregistering server (" + mAppIf + ")!"); |
| } |
| unregisterServer(mAppIf); |
| } |
| } |
| |
| class ClientDeathRecipient implements IBinder.DeathRecipient { |
| int mAppIf; |
| |
| ClientDeathRecipient(int appIf) { |
| mAppIf = appIf; |
| } |
| |
| @Override |
| public void binderDied() { |
| if (DBG) { |
| Log.d(TAG, "Binder is dead - unregistering client (" + mAppIf + ")!"); |
| } |
| unregisterClient(mAppIf); |
| } |
| } |
| |
| /** |
| * Handlers for incoming service calls |
| */ |
| private static class BluetoothGattBinder extends IBluetoothGatt.Stub |
| implements IProfileServiceBinder { |
| private GattService mService; |
| |
| BluetoothGattBinder(GattService svc) { |
| mService = svc; |
| } |
| |
| @Override |
| public void cleanup() { |
| mService = null; |
| } |
| |
| private GattService getService() { |
| if (mService != null && mService.isAvailable()) { |
| return mService; |
| } |
| Log.e(TAG, "getService() - Service requested, but not available!"); |
| return null; |
| } |
| |
| @Override |
| public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { |
| GattService service = getService(); |
| if (service == null) { |
| return new ArrayList<BluetoothDevice>(); |
| } |
| return service.getDevicesMatchingConnectionStates(states); |
| } |
| |
| @Override |
| public void registerClient(ParcelUuid uuid, IBluetoothGattCallback callback) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.registerClient(uuid.getUuid(), callback); |
| } |
| |
| @Override |
| public void unregisterClient(int clientIf) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.unregisterClient(clientIf); |
| } |
| |
| @Override |
| public void registerScanner(IScannerCallback callback, WorkSource workSource) |
| throws RemoteException { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.registerScanner(callback, workSource); |
| } |
| |
| @Override |
| public void unregisterScanner(int scannerId) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.unregisterScanner(scannerId); |
| } |
| |
| @Override |
| public void startScan(int scannerId, ScanSettings settings, List<ScanFilter> filters, |
| List storages, String callingPackage, String callingFeatureId) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.startScan(scannerId, settings, filters, storages, callingPackage, |
| callingFeatureId); |
| } |
| |
| @Override |
| public void startScanForIntent(PendingIntent intent, ScanSettings settings, |
| List<ScanFilter> filters, String callingPackage, String callingFeatureId) |
| throws RemoteException { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.registerPiAndStartScan(intent, settings, filters, callingPackage, |
| callingFeatureId); |
| } |
| |
| @Override |
| public void stopScanForIntent(PendingIntent intent, String callingPackage) |
| throws RemoteException { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.stopScan(intent, callingPackage); |
| } |
| |
| @Override |
| public void stopScan(int scannerId) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.stopScan(scannerId); |
| } |
| |
| @Override |
| public void flushPendingBatchResults(int scannerId) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.flushPendingBatchResults(scannerId); |
| } |
| |
| @Override |
| public void clientConnect(int clientIf, String address, boolean isDirect, int transport, |
| boolean opportunistic, int phy) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.clientConnect(clientIf, address, isDirect, transport, opportunistic, phy); |
| } |
| |
| @Override |
| public void clientDisconnect(int clientIf, String address) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.clientDisconnect(clientIf, address); |
| } |
| |
| @Override |
| public void clientSetPreferredPhy(int clientIf, String address, int txPhy, int rxPhy, |
| int phyOptions) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.clientSetPreferredPhy(clientIf, address, txPhy, rxPhy, phyOptions); |
| } |
| |
| @Override |
| public void clientReadPhy(int clientIf, String address) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.clientReadPhy(clientIf, address); |
| } |
| |
| @Override |
| public void refreshDevice(int clientIf, String address) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.refreshDevice(clientIf, address); |
| } |
| |
| @Override |
| public void discoverServices(int clientIf, String address) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.discoverServices(clientIf, address); |
| } |
| |
| @Override |
| public void discoverServiceByUuid(int clientIf, String address, ParcelUuid uuid) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.discoverServiceByUuid(clientIf, address, uuid.getUuid()); |
| } |
| |
| @Override |
| public void readCharacteristic(int clientIf, String address, int handle, int authReq) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.readCharacteristic(clientIf, address, handle, authReq); |
| } |
| |
| @Override |
| public void readUsingCharacteristicUuid(int clientIf, String address, ParcelUuid uuid, |
| int startHandle, int endHandle, int authReq) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.readUsingCharacteristicUuid(clientIf, address, uuid.getUuid(), startHandle, |
| endHandle, authReq); |
| } |
| |
| @Override |
| public void writeCharacteristic(int clientIf, String address, int handle, int writeType, |
| int authReq, byte[] value) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.writeCharacteristic(clientIf, address, handle, writeType, authReq, value); |
| } |
| |
| @Override |
| public void readDescriptor(int clientIf, String address, int handle, int authReq) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.readDescriptor(clientIf, address, handle, authReq); |
| } |
| |
| @Override |
| public void writeDescriptor(int clientIf, String address, int handle, int authReq, |
| byte[] value) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.writeDescriptor(clientIf, address, handle, authReq, value); |
| } |
| |
| @Override |
| public void beginReliableWrite(int clientIf, String address) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.beginReliableWrite(clientIf, address); |
| } |
| |
| @Override |
| public void endReliableWrite(int clientIf, String address, boolean execute) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.endReliableWrite(clientIf, address, execute); |
| } |
| |
| @Override |
| public void registerForNotification(int clientIf, String address, int handle, |
| boolean enable) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.registerForNotification(clientIf, address, handle, enable); |
| } |
| |
| @Override |
| public void readRemoteRssi(int clientIf, String address) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.readRemoteRssi(clientIf, address); |
| } |
| |
| @Override |
| public void configureMTU(int clientIf, String address, int mtu) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.configureMTU(clientIf, address, mtu); |
| } |
| |
| @Override |
| public void connectionParameterUpdate(int clientIf, String address, |
| int connectionPriority) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.connectionParameterUpdate(clientIf, address, connectionPriority); |
| } |
| |
| @Override |
| public void leConnectionUpdate(int clientIf, String address, |
| int minConnectionInterval, int maxConnectionInterval, |
| int slaveLatency, int supervisionTimeout, |
| int minConnectionEventLen, int maxConnectionEventLen) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.leConnectionUpdate(clientIf, address, minConnectionInterval, |
| maxConnectionInterval, slaveLatency, |
| supervisionTimeout, minConnectionEventLen, |
| maxConnectionEventLen); |
| } |
| |
| @Override |
| public void registerServer(ParcelUuid uuid, IBluetoothGattServerCallback callback) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.registerServer(uuid.getUuid(), callback); |
| } |
| |
| @Override |
| public void unregisterServer(int serverIf) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.unregisterServer(serverIf); |
| } |
| |
| @Override |
| public void serverConnect(int serverIf, String address, boolean isDirect, int transport) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.serverConnect(serverIf, address, isDirect, transport); |
| } |
| |
| @Override |
| public void serverDisconnect(int serverIf, String address) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.serverDisconnect(serverIf, address); |
| } |
| |
| @Override |
| public void serverSetPreferredPhy(int serverIf, String address, int txPhy, int rxPhy, |
| int phyOptions) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.serverSetPreferredPhy(serverIf, address, txPhy, rxPhy, phyOptions); |
| } |
| |
| @Override |
| public void serverReadPhy(int clientIf, String address) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.serverReadPhy(clientIf, address); |
| } |
| |
| @Override |
| public void addService(int serverIf, BluetoothGattService svc) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| |
| service.addService(serverIf, svc); |
| } |
| |
| @Override |
| public void removeService(int serverIf, int handle) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.removeService(serverIf, handle); |
| } |
| |
| @Override |
| public void clearServices(int serverIf) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.clearServices(serverIf); |
| } |
| |
| @Override |
| public void sendResponse(int serverIf, String address, int requestId, int status, |
| int offset, byte[] value) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.sendResponse(serverIf, address, requestId, status, offset, value); |
| } |
| |
| @Override |
| public void sendNotification(int serverIf, String address, int handle, boolean confirm, |
| byte[] value) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.sendNotification(serverIf, address, handle, confirm, value); |
| } |
| |
| @Override |
| public void startAdvertisingSet(AdvertisingSetParameters parameters, |
| AdvertiseData advertiseData, AdvertiseData scanResponse, |
| PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, |
| int duration, int maxExtAdvEvents, IAdvertisingSetCallback callback) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, |
| periodicData, duration, maxExtAdvEvents, callback); |
| } |
| |
| @Override |
| public void stopAdvertisingSet(IAdvertisingSetCallback callback) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.stopAdvertisingSet(callback); |
| } |
| |
| @Override |
| public void getOwnAddress(int advertiserId) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.getOwnAddress(advertiserId); |
| } |
| |
| @Override |
| public void enableAdvertisingSet(int advertiserId, boolean enable, int duration, |
| int maxExtAdvEvents) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents); |
| } |
| |
| @Override |
| public void setAdvertisingData(int advertiserId, AdvertiseData data) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.setAdvertisingData(advertiserId, data); |
| } |
| |
| @Override |
| public void setScanResponseData(int advertiserId, AdvertiseData data) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.setScanResponseData(advertiserId, data); |
| } |
| |
| @Override |
| public void setAdvertisingParameters(int advertiserId, |
| AdvertisingSetParameters parameters) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.setAdvertisingParameters(advertiserId, parameters); |
| } |
| |
| @Override |
| public void setPeriodicAdvertisingParameters(int advertiserId, |
| PeriodicAdvertisingParameters parameters) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.setPeriodicAdvertisingParameters(advertiserId, parameters); |
| } |
| |
| @Override |
| public void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.setPeriodicAdvertisingData(advertiserId, data); |
| } |
| |
| @Override |
| public void setPeriodicAdvertisingEnable(int advertiserId, boolean enable) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.setPeriodicAdvertisingEnable(advertiserId, enable); |
| } |
| |
| @Override |
| public void registerSync(ScanResult scanResult, int skip, int timeout, |
| IPeriodicAdvertisingCallback callback) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.registerSync(scanResult, skip, timeout, callback); |
| } |
| |
| @Override |
| public void unregisterSync(IPeriodicAdvertisingCallback callback) { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.unregisterSync(callback); |
| } |
| |
| @Override |
| public void disconnectAll() { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.disconnectAll(); |
| } |
| |
| @Override |
| public void unregAll() { |
| GattService service = getService(); |
| if (service == null) { |
| return; |
| } |
| service.unregAll(); |
| } |
| |
| @Override |
| public int numHwTrackFiltersAvailable() { |
| GattService service = getService(); |
| if (service == null) { |
| return 0; |
| } |
| return service.numHwTrackFiltersAvailable(); |
| } |
| } |
| |
| ; |
| |
| /************************************************************************** |
| * Callback functions - CLIENT |
| *************************************************************************/ |
| |
| // EN format defined here: |
| // https://blog.google/documents/70/Exposure_Notification_-_Bluetooth_Specification_v1.2.2.pdf |
| private static final byte[] EXPOSURE_NOTIFICATION_FLAGS_PREAMBLE = new byte[] { |
| // size 2, flag field, flags byte (value is not important) |
| (byte) 0x02, (byte) 0x01 |
| }; |
| private static final int EXPOSURE_NOTIFICATION_FLAGS_LENGTH = 0x2 + 1; |
| private static final byte[] EXPOSURE_NOTIFICATION_PAYLOAD_PREAMBLE = new byte[] { |
| // size 3, complete 16 bit UUID, EN UUID |
| (byte) 0x03, (byte) 0x03, (byte) 0x6F, (byte) 0xFD, |
| // size 23, data for 16 bit UUID, EN UUID |
| (byte) 0x17, (byte) 0x16, (byte) 0x6F, (byte) 0xFD, |
| // ...payload |
| }; |
| private static final int EXPOSURE_NOTIFICATION_PAYLOAD_LENGTH = 0x03 + 0x17 + 2; |
| |
| private static boolean arrayStartsWith(byte[] array, byte[] prefix) { |
| if (array.length < prefix.length) { |
| return false; |
| } |
| for (int i = 0; i < prefix.length; i++) { |
| if (prefix[i] != array[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| ScanResult getSanitizedExposureNotification(ScanResult result) { |
| ScanRecord record = result.getScanRecord(); |
| // Remove the flags part of the payload, if present |
| if (record.getBytes().length > EXPOSURE_NOTIFICATION_FLAGS_LENGTH |
| && arrayStartsWith(record.getBytes(), EXPOSURE_NOTIFICATION_FLAGS_PREAMBLE)) { |
| record = ScanRecord.parseFromBytes( |
| Arrays.copyOfRange( |
| record.getBytes(), |
| EXPOSURE_NOTIFICATION_FLAGS_LENGTH, |
| record.getBytes().length)); |
| } |
| |
| if (record.getBytes().length != EXPOSURE_NOTIFICATION_PAYLOAD_LENGTH) { |
| return null; |
| } |
| if (!arrayStartsWith(record.getBytes(), EXPOSURE_NOTIFICATION_PAYLOAD_PREAMBLE)) { |
| return null; |
| } |
| |
| return new ScanResult(null, 0, 0, 0, 0, 0, result.getRssi(), 0, record, 0); |
| } |
| |
| void onScanResult(int eventType, int addressType, String address, int primaryPhy, |
| int secondaryPhy, int advertisingSid, int txPower, int rssi, int periodicAdvInt, |
| byte[] advData) { |
| if (VDBG) { |
| Log.d(TAG, "onScanResult() - eventType=0x" + Integer.toHexString(eventType) |
| + ", addressType=" + addressType + ", address=" + address + ", primaryPhy=" |
| + primaryPhy + ", secondaryPhy=" + secondaryPhy + ", advertisingSid=0x" |
| + Integer.toHexString(advertisingSid) + ", txPower=" + txPower + ", rssi=" |
| + rssi + ", periodicAdvInt=0x" + Integer.toHexString(periodicAdvInt)); |
| } |
| List<UUID> remoteUuids = parseUuids(advData); |
| |
| byte[] legacyAdvData = Arrays.copyOfRange(advData, 0, 62); |
| |
| for (ScanClient client : mScanManager.getRegularScanQueue()) { |
| if (client.uuids.length > 0) { |
| int matches = 0; |
| for (UUID search : client.uuids) { |
| for (UUID remote : remoteUuids) { |
| if (remote.equals(search)) { |
| ++matches; |
| break; // Only count 1st match in case of duplicates |
| } |
| } |
| } |
| |
| if (matches < client.uuids.length) { |
| continue; |
| } |
| } |
| |
| ScannerMap.App app = mScannerMap.getById(client.scannerId); |
| if (app == null) { |
| continue; |
| } |
| |
| BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); |
| |
| ScanSettings settings = client.settings; |
| byte[] scanRecordData; |
| // This is for compability with applications that assume fixed size scan data. |
| if (settings.getLegacy()) { |
| if ((eventType & ET_LEGACY_MASK) == 0) { |
| // If this is legacy scan, but nonlegacy result - skip. |
| continue; |
| } else { |
| // Some apps are used to fixed-size advertise data. |
| scanRecordData = legacyAdvData; |
| } |
| } else { |
| scanRecordData = advData; |
| } |
| |
| ScanResult result = |
| new ScanResult(device, eventType, primaryPhy, secondaryPhy, advertisingSid, |
| txPower, rssi, periodicAdvInt, |
| ScanRecord.parseFromBytes(scanRecordData), |
| SystemClock.elapsedRealtimeNanos()); |
| boolean hasPermission = hasScanResultPermission(client); |
| if (!hasPermission) { |
| for (String associatedDevice : client.associatedDevices) { |
| if (associatedDevice.equalsIgnoreCase(address)) { |
| hasPermission = true; |
| break; |
| } |
| } |
| } |
| if (!hasPermission && client.eligibleForSanitizedExposureNotification) { |
| ScanResult sanitized = getSanitizedExposureNotification(result); |
| if (sanitized != null) { |
| hasPermission = true; |
| result = sanitized; |
| } |
| } |
| if (!hasPermission || !matchesFilters(client, result)) { |
| continue; |
| } |
| |
| if ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_ALL_MATCHES) == 0) { |
| continue; |
| } |
| |
| try { |
| app.appScanStats.addResult(client.scannerId); |
| if (app.callback != null) { |
| app.callback.onScanResult(result); |
| } else { |
| // Send the PendingIntent |
| ArrayList<ScanResult> results = new ArrayList<>(); |
| results.add(result); |
| sendResultsByPendingIntent(app.info, results, |
| ScanSettings.CALLBACK_TYPE_ALL_MATCHES); |
| } |
| } catch (RemoteException | PendingIntent.CanceledException e) { |
| Log.e(TAG, "Exception: " + e); |
| mScannerMap.remove(client.scannerId); |
| mScanManager.stopScan(client.scannerId); |
| } |
| } |
| } |
| |
| private void sendResultByPendingIntent(PendingIntentInfo pii, ScanResult result, |
| int callbackType, ScanClient client) { |
| ArrayList<ScanResult> results = new ArrayList<>(); |
| results.add(result); |
| try { |
| sendResultsByPendingIntent(pii, results, callbackType); |
| } catch (PendingIntent.CanceledException e) { |
| stopScan(client.scannerId); |
| unregisterScanner(client.scannerId); |
| } |
| } |
| |
| private void sendResultsByPendingIntent(PendingIntentInfo pii, ArrayList<ScanResult> results, |
| int callbackType) throws PendingIntent.CanceledException { |
| Intent extrasIntent = new Intent(); |
| extrasIntent.putParcelableArrayListExtra(BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT, |
| results); |
| extrasIntent.putExtra(BluetoothLeScanner.EXTRA_CALLBACK_TYPE, callbackType); |
| pii.intent.send(this, 0, extrasIntent); |
| } |
| |
| private void sendErrorByPendingIntent(PendingIntentInfo pii, int errorCode) |
| throws PendingIntent.CanceledException { |
| Intent extrasIntent = new Intent(); |
| extrasIntent.putExtra(BluetoothLeScanner.EXTRA_ERROR_CODE, errorCode); |
| pii.intent.send(this, 0, extrasIntent); |
| } |
| |
| void onScannerRegistered(int status, int scannerId, long uuidLsb, long uuidMsb) |
| throws RemoteException { |
| UUID uuid = new UUID(uuidMsb, uuidLsb); |
| if (DBG) { |
| Log.d(TAG, "onScannerRegistered() - UUID=" + uuid + ", scannerId=" + scannerId |
| + ", status=" + status); |
| } |
| |
| // First check the callback map |
| ScannerMap.App cbApp = mScannerMap.getByUuid(uuid); |
| if (cbApp != null) { |
| if (status == 0) { |
| cbApp.id = scannerId; |
| // If app is callback based, setup a death recipient. App will initiate the start. |
| // Otherwise, if PendingIntent based, start the scan directly. |
| if (cbApp.callback != null) { |
| cbApp.linkToDeath(new ScannerDeathRecipient(scannerId)); |
| } else { |
| continuePiStartScan(scannerId, cbApp); |
| } |
| } else { |
| mScannerMap.remove(scannerId); |
| } |
| if (cbApp.callback != null) { |
| cbApp.callback.onScannerRegistered(status, scannerId); |
| } |
| } |
| } |
| |
| /** Determines if the given scan client has the appropriate permissions to receive callbacks. */ |
| private boolean hasScanResultPermission(final ScanClient client) { |
| if (client.hasNetworkSettingsPermission |
| || client.hasNetworkSetupWizardPermission |
| || client.hasScanWithoutLocationPermission) { |
| return true; |
| } |
| return client.hasLocationPermission && !Utils.blockedByLocationOff(this, client.userHandle); |
| } |
| |
| // Check if a scan record matches a specific filters. |
| private boolean matchesFilters(ScanClient client, ScanResult scanResult) { |
| if (client.filters == null || client.filters.isEmpty()) { |
| return true; |
| } |
| for (ScanFilter filter : client.filters) { |
| if (filter.matches(scanResult)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void onClientRegistered(int status, int clientIf, long uuidLsb, long uuidMsb) |
| throws RemoteException { |
| UUID uuid = new UUID(uuidMsb, uuidLsb); |
| if (DBG) { |
| Log.d(TAG, "onClientRegistered() - UUID=" + uuid + ", clientIf=" + clientIf); |
| } |
| ClientMap.App app = mClientMap.getByUuid(uuid); |
| if (app != null) { |
| if (status == 0) { |
| app.id = clientIf; |
| app.linkToDeath(new ClientDeathRecipient(clientIf)); |
| } else { |
| mClientMap.remove(uuid); |
| } |
| app.callback.onClientRegistered(status, clientIf); |
| } |
| } |
| |
| void onConnected(int clientIf, int connId, int status, String address) throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "onConnected() - clientIf=" + clientIf + ", connId=" + connId + ", address=" |
| + address); |
| } |
| |
| if (status == 0) { |
| mClientMap.addConnection(clientIf, connId, address); |
| } |
| ClientMap.App app = mClientMap.getById(clientIf); |
| if (app != null) { |
| app.callback.onClientConnectionState(status, clientIf, |
| (status == BluetoothGatt.GATT_SUCCESS), address); |
| } |
| } |
| |
| void onDisconnected(int clientIf, int connId, int status, String address) |
| throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, |
| "onDisconnected() - clientIf=" + clientIf + ", connId=" + connId + ", address=" |
| + address); |
| } |
| |
| mClientMap.removeConnection(clientIf, connId); |
| ClientMap.App app = mClientMap.getById(clientIf); |
| if (app != null) { |
| app.callback.onClientConnectionState(status, clientIf, false, address); |
| } |
| } |
| |
| void onClientPhyUpdate(int connId, int txPhy, int rxPhy, int status) throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "onClientPhyUpdate() - connId=" + connId + ", status=" + status); |
| } |
| |
| String address = mClientMap.addressByConnId(connId); |
| if (address == null) { |
| return; |
| } |
| |
| ClientMap.App app = mClientMap.getByConnId(connId); |
| if (app == null) { |
| return; |
| } |
| |
| app.callback.onPhyUpdate(address, txPhy, rxPhy, status); |
| } |
| |
| void onClientPhyRead(int clientIf, String address, int txPhy, int rxPhy, int status) |
| throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, |
| "onClientPhyRead() - address=" + address + ", status=" + status + ", clientIf=" |
| + clientIf); |
| } |
| |
| Integer connId = mClientMap.connIdByAddress(clientIf, address); |
| if (connId == null) { |
| Log.d(TAG, "onClientPhyRead() - no connection to " + address); |
| return; |
| } |
| |
| ClientMap.App app = mClientMap.getByConnId(connId); |
| if (app == null) { |
| return; |
| } |
| |
| app.callback.onPhyRead(address, txPhy, rxPhy, status); |
| } |
| |
| void onClientConnUpdate(int connId, int interval, int latency, int timeout, int status) |
| throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "onClientConnUpdate() - connId=" + connId + ", status=" + status); |
| } |
| |
| String address = mClientMap.addressByConnId(connId); |
| if (address == null) { |
| return; |
| } |
| |
| ClientMap.App app = mClientMap.getByConnId(connId); |
| if (app == null) { |
| return; |
| } |
| |
| app.callback.onConnectionUpdated(address, interval, latency, timeout, status); |
| } |
| |
| void onServerPhyUpdate(int connId, int txPhy, int rxPhy, int status) throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "onServerPhyUpdate() - connId=" + connId + ", status=" + status); |
| } |
| |
| String address = mServerMap.addressByConnId(connId); |
| if (address == null) { |
| return; |
| } |
| |
| ServerMap.App app = mServerMap.getByConnId(connId); |
| if (app == null) { |
| return; |
| } |
| |
| app.callback.onPhyUpdate(address, txPhy, rxPhy, status); |
| } |
| |
| void onServerPhyRead(int serverIf, String address, int txPhy, int rxPhy, int status) |
| throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "onServerPhyRead() - address=" + address + ", status=" + status); |
| } |
| |
| Integer connId = mServerMap.connIdByAddress(serverIf, address); |
| if (connId == null) { |
| Log.d(TAG, "onServerPhyRead() - no connection to " + address); |
| return; |
| } |
| |
| ServerMap.App app = mServerMap.getByConnId(connId); |
| if (app == null) { |
| return; |
| } |
| |
| app.callback.onPhyRead(address, txPhy, rxPhy, status); |
| } |
| |
| void onServerConnUpdate(int connId, int interval, int latency, int timeout, int status) |
| throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "onServerConnUpdate() - connId=" + connId + ", status=" + status); |
| } |
| |
| String address = mServerMap.addressByConnId(connId); |
| if (address == null) { |
| return; |
| } |
| |
| ServerMap.App app = mServerMap.getByConnId(connId); |
| if (app == null) { |
| return; |
| } |
| |
| app.callback.onConnectionUpdated(address, interval, latency, timeout, status); |
| } |
| |
| void onSearchCompleted(int connId, int status) throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "onSearchCompleted() - connId=" + connId + ", status=" + status); |
| } |
| // Gatt DB is ready! |
| |
| // This callback was called from the jni_workqueue thread. If we make request to the stack |
| // on the same thread, it might cause deadlock. Schedule request on a new thread instead. |
| Thread t = new Thread(new Runnable() { |
| @Override |
| public void run() { |
| gattClientGetGattDbNative(connId); |
| } |
| }); |
| t.start(); |
| } |
| |
| GattDbElement getSampleGattDbElement() { |
| return new GattDbElement(); |
| } |
| |
| void onGetGattDb(int connId, ArrayList<GattDbElement> db) throws RemoteException { |
| String address = mClientMap.addressByConnId(connId); |
| |
| if (DBG) { |
| Log.d(TAG, "onGetGattDb() - address=" + address); |
| } |
| |
| ClientMap.App app = mClientMap.getByConnId(connId); |
| if (app == null || app.callback == null) { |
| Log.e(TAG, "app or callback is null"); |
| return; |
| } |
| |
| List<BluetoothGattService> dbOut = new ArrayList<BluetoothGattService>(); |
| Set<Integer> restrictedIds = new HashSet<>(); |
| |
| BluetoothGattService currSrvc = null; |
| BluetoothGattCharacteristic currChar = null; |
| boolean isRestrictedSrvc = false; |
| boolean isHidSrvc = false; |
| boolean isRestrictedChar = false; |
| |
| for (GattDbElement el : db) { |
| switch (el.type) { |
| case GattDbElement.TYPE_PRIMARY_SERVICE: |
| case GattDbElement.TYPE_SECONDARY_SERVICE: |
| if (DBG) { |
| Log.d(TAG, "got service with UUID=" + el.uuid + " id: " + el.id); |
| } |
| |
| currSrvc = new BluetoothGattService(el.uuid, el.id, el.type); |
| dbOut.add(currSrvc); |
| isRestrictedSrvc = |
| isFidoSrvcUuid(el.uuid) || isAndroidTvRemoteSrvcUuid(el.uuid); |
| isHidSrvc = isHidSrvcUuid(el.uuid); |
| if (isRestrictedSrvc) { |
| restrictedIds.add(el.id); |
| } |
| break; |
| |
| case GattDbElement.TYPE_CHARACTERISTIC: |
| if (DBG) { |
| Log.d(TAG, "got characteristic with UUID=" + el.uuid + " id: " + el.id); |
| } |
| |
| currChar = new BluetoothGattCharacteristic(el.uuid, el.id, el.properties, 0); |
| currSrvc.addCharacteristic(currChar); |
| isRestrictedChar = isRestrictedSrvc || (isHidSrvc && isHidCharUuid(el.uuid)); |
| if (isRestrictedChar) { |
| restrictedIds.add(el.id); |
| } |
| break; |
| |
| case GattDbElement.TYPE_DESCRIPTOR: |
| if (DBG) { |
| Log.d(TAG, "got descriptor with UUID=" + el.uuid + " id: " + el.id); |
| } |
| |
| currChar.addDescriptor(new BluetoothGattDescriptor(el.uuid, el.id, 0)); |
| if (isRestrictedChar) { |
| restrictedIds.add(el.id); |
| } |
| break; |
| |
| case GattDbElement.TYPE_INCLUDED_SERVICE: |
| if (DBG) { |
| Log.d(TAG, "got included service with UUID=" + el.uuid + " id: " + el.id |
| + " startHandle: " + el.startHandle); |
| } |
| |
| currSrvc.addIncludedService( |
| new BluetoothGattService(el.uuid, el.startHandle, el.type)); |
| break; |
| |
| default: |
| Log.e(TAG, "got unknown element with type=" + el.type + " and UUID=" + el.uuid |
| + " id: " + el.id); |
| } |
| } |
| |
| if (!restrictedIds.isEmpty()) { |
| mRestrictedHandles.put(connId, restrictedIds); |
| } |
| // Search is complete when there was error, or nothing more to process |
| app.callback.onSearchComplete(address, dbOut, 0 /* status */); |
| } |
| |
| void onRegisterForNotifications(int connId, int status, int registered, int handle) { |
| String address = mClientMap.addressByConnId(connId); |
| |
| if (DBG) { |
| Log.d(TAG, "onRegisterForNotifications() - address=" + address + ", status=" + status |
| + ", registered=" + registered + ", handle=" + handle); |
| } |
| } |
| |
| void onNotify(int connId, String address, int handle, boolean isNotify, byte[] data) |
| throws RemoteException { |
| |
| if (VDBG) { |
| Log.d(TAG, "onNotify() - address=" + address + ", handle=" + handle + ", length=" |
| + data.length); |
| } |
| |
| ClientMap.App app = mClientMap.getByConnId(connId); |
| if (app != null) { |
| if (!permissionCheck(app, connId, handle)) { |
| Log.w(TAG, "onNotify() - permission check failed!"); |
| return; |
| } |
| app.callback.onNotify(address, handle, data); |
| } |
| } |
| |
| void onReadCharacteristic(int connId, int status, int handle, byte[] data) |
| throws RemoteException { |
| String address = mClientMap.addressByConnId(connId); |
| |
| if (VDBG) { |
| Log.d(TAG, "onReadCharacteristic() - address=" + address + ", status=" + status |
| + ", length=" + data.length); |
| } |
| |
| ClientMap.App app = mClientMap.getByConnId(connId); |
| if (app != null) { |
| app.callback.onCharacteristicRead(address, status, handle, data); |
| } |
| } |
| |
| void onWriteCharacteristic(int connId, int status, int handle) throws RemoteException { |
| String address = mClientMap.addressByConnId(connId); |
| |
| if (VDBG) { |
| Log.d(TAG, "onWriteCharacteristic() - address=" + address + ", status=" + status); |
| } |
| |
| ClientMap.App app = mClientMap.getByConnId(connId); |
| if (app == null) { |
| return; |
| } |
| |
| if (!app.isCongested) { |
| app.callback.onCharacteristicWrite(address, status, handle); |
| } else { |
| if (status == BluetoothGatt.GATT_CONNECTION_CONGESTED) { |
| status = BluetoothGatt.GATT_SUCCESS; |
| } |
| CallbackInfo callbackInfo = new CallbackInfo(address, status, handle); |
| app.queueCallback(callbackInfo); |
| } |
| } |
| |
| void onExecuteCompleted(int connId, int status) throws RemoteException { |
| String address = mClientMap.addressByConnId(connId); |
| if (VDBG) { |
| Log.d(TAG, "onExecuteCompleted() - address=" + address + ", status=" + status); |
| } |
| |
| ClientMap.App app = mClientMap.getByConnId(connId); |
| if (app != null) { |
| app.callback.onExecuteWrite(address, status); |
| } |
| } |
| |
| void onReadDescriptor(int connId, int status, int handle, byte[] data) throws RemoteException { |
| String address = mClientMap.addressByConnId(connId); |
| |
| if (VDBG) { |
| Log.d(TAG, |
| "onReadDescriptor() - address=" + address + ", status=" + status + ", length=" |
| + data.length); |
| } |
| |
| ClientMap.App app = mClientMap.getByConnId(connId); |
| if (app != null) { |
| app.callback.onDescriptorRead(address, status, handle, data); |
| } |
| } |
| |
| void onWriteDescriptor(int connId, int status, int handle) throws RemoteException { |
| String address = mClientMap.addressByConnId(connId); |
| |
| if (VDBG) { |
| Log.d(TAG, "onWriteDescriptor() - address=" + address + ", status=" + status); |
| } |
| |
| ClientMap.App app = mClientMap.getByConnId(connId); |
| if (app != null) { |
| app.callback.onDescriptorWrite(address, status, handle); |
| } |
| } |
| |
| void onReadRemoteRssi(int clientIf, String address, int rssi, int status) |
| throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, |
| "onReadRemoteRssi() - clientIf=" + clientIf + " address=" + address + ", rssi=" |
| + rssi + ", status=" + status); |
| } |
| |
| ClientMap.App app = mClientMap.getById(clientIf); |
| if (app != null) { |
| app.callback.onReadRemoteRssi(address, rssi, status); |
| } |
| } |
| |
| void onScanFilterEnableDisabled(int action, int status, int clientIf) { |
| if (DBG) { |
| Log.d(TAG, "onScanFilterEnableDisabled() - clientIf=" + clientIf + ", status=" + status |
| + ", action=" + action); |
| } |
| mScanManager.callbackDone(clientIf, status); |
| } |
| |
| void onScanFilterParamsConfigured(int action, int status, int clientIf, int availableSpace) { |
| if (DBG) { |
| Log.d(TAG, |
| "onScanFilterParamsConfigured() - clientIf=" + clientIf + ", status=" + status |
| + ", action=" + action + ", availableSpace=" + availableSpace); |
| } |
| mScanManager.callbackDone(clientIf, status); |
| } |
| |
| void onScanFilterConfig(int action, int status, int clientIf, int filterType, |
| int availableSpace) { |
| if (DBG) { |
| Log.d(TAG, "onScanFilterConfig() - clientIf=" + clientIf + ", action = " + action |
| + " status = " + status + ", filterType=" + filterType + ", availableSpace=" |
| + availableSpace); |
| } |
| |
| mScanManager.callbackDone(clientIf, status); |
| } |
| |
| void onBatchScanStorageConfigured(int status, int clientIf) { |
| if (DBG) { |
| Log.d(TAG, |
| "onBatchScanStorageConfigured() - clientIf=" + clientIf + ", status=" + status); |
| } |
| mScanManager.callbackDone(clientIf, status); |
| } |
| |
| // TODO: split into two different callbacks : onBatchScanStarted and onBatchScanStopped. |
| void onBatchScanStartStopped(int startStopAction, int status, int clientIf) { |
| if (DBG) { |
| Log.d(TAG, "onBatchScanStartStopped() - clientIf=" + clientIf + ", status=" + status |
| + ", startStopAction=" + startStopAction); |
| } |
| mScanManager.callbackDone(clientIf, status); |
| } |
| |
| ScanClient findBatchScanClientById(int scannerId) { |
| for (ScanClient client : mScanManager.getBatchScanQueue()) { |
| if (client.scannerId == scannerId) { |
| return client; |
| } |
| } |
| return null; |
| } |
| |
| void onBatchScanReports(int status, int scannerId, int reportType, int numRecords, |
| byte[] recordData) throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "onBatchScanReports() - scannerId=" + scannerId + ", status=" + status |
| + ", reportType=" + reportType + ", numRecords=" + numRecords); |
| } |
| mScanManager.callbackDone(scannerId, status); |
| Set<ScanResult> results = parseBatchScanResults(numRecords, reportType, recordData); |
| if (reportType == ScanManager.SCAN_RESULT_TYPE_TRUNCATED) { |
| // We only support single client for truncated mode. |
| ScannerMap.App app = mScannerMap.getById(scannerId); |
| if (app == null) { |
| return; |
| } |
| |
| ScanClient client = findBatchScanClientById(scannerId); |
| if (client == null) { |
| return; |
| } |
| |
| ArrayList<ScanResult> permittedResults; |
| if (hasScanResultPermission(client)) { |
| permittedResults = new ArrayList<ScanResult>(results); |
| } else { |
| permittedResults = new ArrayList<ScanResult>(); |
| for (ScanResult scanResult : results) { |
| for (String associatedDevice : client.associatedDevices) { |
| if (associatedDevice.equalsIgnoreCase(scanResult.getDevice() |
| .getAddress())) { |
| permittedResults.add(scanResult); |
| } |
| } |
| } |
| if (permittedResults.isEmpty()) { |
| return; |
| } |
| } |
| |
| if (app.callback != null) { |
| app.callback.onBatchScanResults(permittedResults); |
| } else { |
| // PendingIntent based |
| try { |
| sendResultsByPendingIntent(app.info, permittedResults, |
| ScanSettings.CALLBACK_TYPE_ALL_MATCHES); |
| } catch (PendingIntent.CanceledException e) { |
| } |
| } |
| } else { |
| for (ScanClient client : mScanManager.getFullBatchScanQueue()) { |
| // Deliver results for each client. |
| deliverBatchScan(client, results); |
| } |
| } |
| } |
| |
| private void sendBatchScanResults(ScannerMap.App app, ScanClient client, |
| ArrayList<ScanResult> results) { |
| try { |
| if (app.callback != null) { |
| app.callback.onBatchScanResults(results); |
| } else { |
| sendResultsByPendingIntent(app.info, results, |
| ScanSettings.CALLBACK_TYPE_ALL_MATCHES); |
| } |
| } catch (RemoteException | PendingIntent.CanceledException e) { |
| Log.e(TAG, "Exception: " + e); |
| mScannerMap.remove(client.scannerId); |
| mScanManager.stopScan(client.scannerId); |
| } |
| } |
| |
| // Check and deliver scan results for different scan clients. |
| private void deliverBatchScan(ScanClient client, Set<ScanResult> allResults) |
| throws RemoteException { |
| ScannerMap.App app = mScannerMap.getById(client.scannerId); |
| if (app == null) { |
| return; |
| } |
| |
| ArrayList<ScanResult> permittedResults; |
| if (hasScanResultPermission(client)) { |
| permittedResults = new ArrayList<ScanResult>(allResults); |
| } else { |
| permittedResults = new ArrayList<ScanResult>(); |
| for (ScanResult scanResult : allResults) { |
| for (String associatedDevice : client.associatedDevices) { |
| if (associatedDevice.equalsIgnoreCase(scanResult.getDevice().getAddress())) { |
| permittedResults.add(scanResult); |
| } |
| } |
| } |
| if (permittedResults.isEmpty()) { |
| return; |
| } |
| } |
| |
| if (client.filters == null || client.filters.isEmpty()) { |
| sendBatchScanResults(app, client, permittedResults); |
| // TODO: Question to reviewer: Shouldn't there be a return here? |
| } |
| // Reconstruct the scan results. |
| ArrayList<ScanResult> results = new ArrayList<ScanResult>(); |
| for (ScanResult scanResult : permittedResults) { |
| if (matchesFilters(client, scanResult)) { |
| results.add(scanResult); |
| } |
| } |
| sendBatchScanResults(app, client, 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 == ScanManager.SCAN_RESULT_TYPE_TRUNCATED) { |
| return parseTruncatedResults(numRecords, batchRecord); |
| } else { |
| return parseFullResults(numRecords, batchRecord); |
| } |
| } |
| |
| private Set<ScanResult> parseTruncatedResults(int numRecords, byte[] batchRecord) { |
| if (DBG) { |
| Log.d(TAG, "batch record " + Arrays.toString(batchRecord)); |
| } |
| Set<ScanResult> results = new HashSet<ScanResult>(numRecords); |
| long now = SystemClock.elapsedRealtimeNanos(); |
| for (int i = 0; i < numRecords; ++i) { |
| byte[] record = |
| extractBytes(batchRecord, i * TRUNCATED_RESULT_SIZE, TRUNCATED_RESULT_SIZE); |
| byte[] address = extractBytes(record, 0, 6); |
| reverse(address); |
| BluetoothDevice device = mAdapter.getRemoteDevice(address); |
| int rssi = record[8]; |
| long timestampNanos = now - parseTimestampNanos(extractBytes(record, 9, 2)); |
| results.add(new ScanResult(device, ScanRecord.parseFromBytes(new byte[0]), rssi, |
| timestampNanos)); |
| } |
| return results; |
| } |
| |
| @VisibleForTesting |
| long parseTimestampNanos(byte[] data) { |
| long timestampUnit = NumberUtils.littleEndianByteArrayToInt(data); |
| // Timestamp is in every 50 ms. |
| return TimeUnit.MILLISECONDS.toNanos(timestampUnit * 50); |
| } |
| |
| private Set<ScanResult> parseFullResults(int numRecords, byte[] batchRecord) { |
| if (DBG) { |
| Log.d(TAG, "Batch record : " + Arrays.toString(batchRecord)); |
| } |
| Set<ScanResult> results = new HashSet<ScanResult>(numRecords); |
| int position = 0; |
| long now = SystemClock.elapsedRealtimeNanos(); |
| while (position < batchRecord.length) { |
| byte[] address = extractBytes(batchRecord, position, 6); |
| // TODO: remove temp hack. |
| reverse(address); |
| BluetoothDevice device = mAdapter.getRemoteDevice(address); |
| position += 6; |
| // Skip address type. |
| position++; |
| // Skip tx power level. |
| position++; |
| int rssi = batchRecord[position++]; |
| long timestampNanos = now - 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); |
| if (DBG) { |
| Log.d(TAG, "ScanRecord : " + Arrays.toString(scanRecord)); |
| } |
| results.add(new ScanResult(device, ScanRecord.parseFromBytes(scanRecord), rssi, |
| timestampNanos)); |
| } |
| return results; |
| } |
| |
| // Reverse byte array. |
| private void reverse(byte[] address) { |
| int len = address.length; |
| for (int i = 0; i < len / 2; ++i) { |
| byte b = address[i]; |
| address[i] = address[len - 1 - i]; |
| address[len - 1 - i] = b; |
| } |
| } |
| |
| // 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); |
| } |
| flushPendingBatchResults(clientIf); |
| } |
| |
| AdvtFilterOnFoundOnLostInfo createOnTrackAdvFoundLostObject(int clientIf, int advPktLen, |
| byte[] advPkt, int scanRspLen, byte[] scanRsp, int filtIndex, int advState, |
| int advInfoPresent, String address, int addrType, int txPower, int rssiValue, |
| int timeStamp) { |
| |
| return new AdvtFilterOnFoundOnLostInfo(clientIf, advPktLen, advPkt, scanRspLen, scanRsp, |
| filtIndex, advState, advInfoPresent, address, addrType, txPower, rssiValue, |
| timeStamp); |
| } |
| |
| void onTrackAdvFoundLost(AdvtFilterOnFoundOnLostInfo trackingInfo) throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "onTrackAdvFoundLost() - scannerId= " + trackingInfo.getClientIf() |
| + " address = " + trackingInfo.getAddress() + " adv_state = " |
| + trackingInfo.getAdvState()); |
| } |
| |
| ScannerMap.App app = mScannerMap.getById(trackingInfo.getClientIf()); |
| if (app == null || (app.callback == null && app.info == null)) { |
| Log.e(TAG, "app or callback is null"); |
| return; |
| } |
| |
| BluetoothDevice device = |
| BluetoothAdapter.getDefaultAdapter().getRemoteDevice(trackingInfo.getAddress()); |
| int advertiserState = trackingInfo.getAdvState(); |
| ScanResult result = |
| new ScanResult(device, ScanRecord.parseFromBytes(trackingInfo.getResult()), |
| trackingInfo.getRSSIValue(), SystemClock.elapsedRealtimeNanos()); |
| |
| for (ScanClient client : mScanManager.getRegularScanQueue()) { |
| if (client.scannerId == trackingInfo.getClientIf()) { |
| ScanSettings settings = client.settings; |
| if ((advertiserState == ADVT_STATE_ONFOUND) && ( |
| (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) |
| != 0)) { |
| if (app.callback != null) { |
| app.callback.onFoundOrLost(true, result); |
| } else { |
| sendResultByPendingIntent(app.info, result, |
| ScanSettings.CALLBACK_TYPE_FIRST_MATCH, client); |
| } |
| } else if ((advertiserState == ADVT_STATE_ONLOST) && ( |
| (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST) |
| != 0)) { |
| if (app.callback != null) { |
| app.callback.onFoundOrLost(false, result); |
| } else { |
| sendResultByPendingIntent(app.info, result, |
| ScanSettings.CALLBACK_TYPE_MATCH_LOST, client); |
| } |
| } else { |
| if (DBG) { |
| Log.d(TAG, "Not reporting onlost/onfound : " + advertiserState |
| + " scannerId = " + client.scannerId + " callbackType " |
| + settings.getCallbackType()); |
| } |
| } |
| } |
| } |
| } |
| |
| void onScanParamSetupCompleted(int status, int scannerId) throws RemoteException { |
| ScannerMap.App app = mScannerMap.getById(scannerId); |
| if (app == null || app.callback == null) { |
| Log.e(TAG, "Advertise app or callback is null"); |
| return; |
| } |
| if (DBG) { |
| Log.d(TAG, "onScanParamSetupCompleted : " + status); |
| } |
| } |
| |
| // callback from ScanManager for dispatch of errors apps. |
| void onScanManagerErrorCallback(int scannerId, int errorCode) throws RemoteException { |
| ScannerMap.App app = mScannerMap.getById(scannerId); |
| if (app == null || (app.callback == null && app.info == null)) { |
| Log.e(TAG, "App or callback is null"); |
| return; |
| } |
| if (app.callback != null) { |
| app.callback.onScanManagerErrorCallback(errorCode); |
| } else { |
| try { |
| sendErrorByPendingIntent(app.info, errorCode); |
| } catch (PendingIntent.CanceledException e) { |
| Log.e(TAG, "Error sending error code via PendingIntent:" + e); |
| } |
| } |
| } |
| |
| void onConfigureMTU(int connId, int status, int mtu) throws RemoteException { |
| String address = mClientMap.addressByConnId(connId); |
| |
| if (DBG) { |
| Log.d(TAG, |
| "onConfigureMTU() address=" + address + ", status=" + status + ", mtu=" + mtu); |
| } |
| |
| ClientMap.App app = mClientMap.getByConnId(connId); |
| if (app != null) { |
| app.callback.onConfigureMTU(address, mtu, status); |
| } |
| } |
| |
| void onClientCongestion(int connId, boolean congested) throws RemoteException { |
| if (VDBG) { |
| Log.d(TAG, "onClientCongestion() - connId=" + connId + ", congested=" + congested); |
| } |
| |
| ClientMap.App app = mClientMap.getByConnId(connId); |
| |
| if (app != null) { |
| app.isCongested = congested; |
| while (!app.isCongested) { |
| CallbackInfo callbackInfo = app.popQueuedCallback(); |
| if (callbackInfo == null) { |
| return; |
| } |
| app.callback.onCharacteristicWrite(callbackInfo.address, callbackInfo.status, |
| callbackInfo.handle); |
| } |
| } |
| } |
| |
| /************************************************************************** |
| * GATT Service functions - Shared CLIENT/SERVER |
| *************************************************************************/ |
| |
| List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| Map<BluetoothDevice, Integer> deviceStates = new HashMap<BluetoothDevice, Integer>(); |
| |
| // Add paired LE devices |
| |
| Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); |
| for (BluetoothDevice device : bondedDevices) { |
| if (getDeviceType(device) != AbstractionLayer.BT_DEVICE_TYPE_BREDR) { |
| deviceStates.put(device, BluetoothProfile.STATE_DISCONNECTED); |
| } |
| } |
| |
| // Add connected deviceStates |
| |
| Set<String> connectedDevices = new HashSet<String>(); |
| connectedDevices.addAll(mClientMap.getConnectedDevices()); |
| connectedDevices.addAll(mServerMap.getConnectedDevices()); |
| |
| for (String address : connectedDevices) { |
| BluetoothDevice device = mAdapter.getRemoteDevice(address); |
| if (device != null) { |
| deviceStates.put(device, BluetoothProfile.STATE_CONNECTED); |
| } |
| } |
| |
| // Create matching device sub-set |
| |
| List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(); |
| |
| for (Map.Entry<BluetoothDevice, Integer> entry : deviceStates.entrySet()) { |
| for (int state : states) { |
| if (entry.getValue() == state) { |
| deviceList.add(entry.getKey()); |
| } |
| } |
| } |
| |
| return deviceList; |
| } |
| |
| void registerScanner(IScannerCallback callback, WorkSource workSource) throws RemoteException { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| UUID uuid = UUID.randomUUID(); |
| if (DBG) { |
| Log.d(TAG, "registerScanner() - UUID=" + uuid); |
| } |
| |
| if (workSource != null) { |
| enforceImpersonatationPermission(); |
| } |
| |
| AppScanStats app = mScannerMap.getAppScanStatsByUid(Binder.getCallingUid()); |
| if (app != null && app.isScanningTooFrequently() |
| && checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED) != PERMISSION_GRANTED) { |
| Log.e(TAG, "App '" + app.appName + "' is scanning too frequently"); |
| callback.onScannerRegistered(ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY, -1); |
| return; |
| } |
| |
| mScannerMap.add(uuid, workSource, callback, null, this); |
| mScanManager.registerScanner(uuid); |
| } |
| |
| void unregisterScanner(int scannerId) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "unregisterScanner() - scannerId=" + scannerId); |
| } |
| mScannerMap.remove(scannerId); |
| mScanManager.unregisterScanner(scannerId); |
| } |
| |
| private List<String> getAssociatedDevices(String callingPackage, UserHandle userHandle) { |
| if (mCompanionManager == null) { |
| return new ArrayList<String>(); |
| } |
| long identity = Binder.clearCallingIdentity(); |
| try { |
| return mCompanionManager.getAssociations( |
| callingPackage, userHandle.getIdentifier()); |
| } catch (SecurityException se) { |
| // Not an app with associated devices |
| } catch (RemoteException re) { |
| Log.e(TAG, "Cannot reach companion device service", re); |
| } catch (Exception e) { |
| Log.e(TAG, "Cannot check device associations for " + callingPackage, e); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| return new ArrayList<String>(); |
| } |
| |
| void startScan(int scannerId, ScanSettings settings, List<ScanFilter> filters, |
| List<List<ResultStorageDescriptor>> storages, String callingPackage, |
| @Nullable String callingFeatureId) { |
| if (DBG) { |
| Log.d(TAG, "start scan with filters"); |
| } |
| UserHandle callingUser = UserHandle.of(UserHandle.getCallingUserId()); |
| enforceAdminPermission(); |
| if (needsPrivilegedPermissionForScan(settings)) { |
| enforcePrivilegedPermission(); |
| } |
| final ScanClient scanClient = new ScanClient(scannerId, settings, filters, storages); |
| scanClient.userHandle = UserHandle.of(UserHandle.getCallingUserId()); |
| mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); |
| scanClient.eligibleForSanitizedExposureNotification = |
| callingPackage.equals(mExposureNotificationPackage); |
| scanClient.isQApp = Utils.isQApp(this, callingPackage); |
| if (scanClient.isQApp) { |
| scanClient.hasLocationPermission = Utils.checkCallerHasFineLocation(this, mAppOps, |
| callingPackage, callingFeatureId, scanClient.userHandle); |
| } else { |
| scanClient.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation(this, |
| mAppOps, callingPackage, callingFeatureId, scanClient.userHandle); |
| } |
| scanClient.hasNetworkSettingsPermission = |
| Utils.checkCallerHasNetworkSettingsPermission(this); |
| scanClient.hasNetworkSetupWizardPermission = |
| Utils.checkCallerHasNetworkSetupWizardPermission(this); |
| scanClient.hasScanWithoutLocationPermission = |
| Utils.checkCallerHasScanWithoutLocationPermission(this); |
| scanClient.associatedDevices = getAssociatedDevices(callingPackage, scanClient.userHandle); |
| |
| AppScanStats app = mScannerMap.getAppScanStatsById(scannerId); |
| ScannerMap.App cbApp = mScannerMap.getById(scannerId); |
| if (app != null) { |
| scanClient.stats = app; |
| boolean isFilteredScan = (filters != null) && !filters.isEmpty(); |
| boolean isCallbackScan = false; |
| if (cbApp != null) { |
| isCallbackScan = cbApp.callback != null; |
| } |
| app.recordScanStart(settings, filters, isFilteredScan, isCallbackScan, scannerId); |
| } |
| |
| mScanManager.startScan(scanClient); |
| } |
| |
| void registerPiAndStartScan(PendingIntent pendingIntent, ScanSettings settings, |
| List<ScanFilter> filters, String callingPackage, @Nullable String callingFeatureId) { |
| if (DBG) { |
| Log.d(TAG, "start scan with filters, for PendingIntent"); |
| } |
| enforceAdminPermission(); |
| if (needsPrivilegedPermissionForScan(settings)) { |
| enforcePrivilegedPermission(); |
| } |
| |
| UUID uuid = UUID.randomUUID(); |
| if (DBG) { |
| Log.d(TAG, "startScan(PI) - UUID=" + uuid); |
| } |
| PendingIntentInfo piInfo = new PendingIntentInfo(); |
| piInfo.intent = pendingIntent; |
| piInfo.settings = settings; |
| piInfo.filters = filters; |
| piInfo.callingPackage = callingPackage; |
| |
| // Don't start scan if the Pi scan already in mScannerMap. |
| if (mScannerMap.getByContextInfo(piInfo) != null) { |
| Log.d(TAG, "Don't startScan(PI) since the same Pi scan already in mScannerMap."); |
| return; |
| } |
| |
| ScannerMap.App app = mScannerMap.add(uuid, null, null, piInfo, this); |
| app.mUserHandle = UserHandle.of(UserHandle.getCallingUserId()); |
| mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); |
| app.mEligibleForSanitizedExposureNotification = |
| callingPackage.equals(mExposureNotificationPackage); |
| app.mIsQApp = Utils.isQApp(this, callingPackage); |
| try { |
| if (app.mIsQApp) { |
| app.hasLocationPermission = Utils.checkCallerHasFineLocation( |
| this, mAppOps, callingPackage, callingFeatureId, app.mUserHandle); |
| } else { |
| app.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation( |
| this, mAppOps, callingPackage, callingFeatureId, app.mUserHandle); |
| } |
| } catch (SecurityException se) { |
| // No need to throw here. Just mark as not granted. |
| app.hasLocationPermission = false; |
| } |
| app.mHasNetworkSettingsPermission = |
| Utils.checkCallerHasNetworkSettingsPermission(this); |
| app.mHasNetworkSetupWizardPermission = |
| Utils.checkCallerHasNetworkSetupWizardPermission(this); |
| app.mHasScanWithoutLocationPermission = |
| Utils.checkCallerHasScanWithoutLocationPermission(this); |
| app.mAssociatedDevices = getAssociatedDevices(callingPackage, app.mUserHandle); |
| mScanManager.registerScanner(uuid); |
| } |
| |
| void continuePiStartScan(int scannerId, ScannerMap.App app) { |
| final PendingIntentInfo piInfo = app.info; |
| final ScanClient scanClient = |
| new ScanClient(scannerId, piInfo.settings, piInfo.filters, null); |
| scanClient.hasLocationPermission = app.hasLocationPermission; |
| scanClient.userHandle = app.mUserHandle; |
| scanClient.isQApp = app.mIsQApp; |
| scanClient.eligibleForSanitizedExposureNotification = |
| app.mEligibleForSanitizedExposureNotification; |
| scanClient.hasNetworkSettingsPermission = app.mHasNetworkSettingsPermission; |
| scanClient.hasNetworkSetupWizardPermission = app.mHasNetworkSetupWizardPermission; |
| scanClient.hasScanWithoutLocationPermission = app.mHasScanWithoutLocationPermission; |
| scanClient.associatedDevices = app.mAssociatedDevices; |
| |
| AppScanStats scanStats = mScannerMap.getAppScanStatsById(scannerId); |
| if (scanStats != null) { |
| scanClient.stats = scanStats; |
| boolean isFilteredScan = (piInfo.filters != null) && !piInfo.filters.isEmpty(); |
| scanStats.recordScanStart( |
| piInfo.settings, piInfo.filters, isFilteredScan, false, scannerId); |
| } |
| |
| mScanManager.startScan(scanClient); |
| } |
| |
| void flushPendingBatchResults(int scannerId) { |
| if (DBG) { |
| Log.d(TAG, "flushPendingBatchResults - scannerId=" + scannerId); |
| } |
| mScanManager.flushBatchScanResults(new ScanClient(scannerId)); |
| } |
| |
| void stopScan(int scannerId) { |
| enforceAdminPermission(); |
| int scanQueueSize = |
| mScanManager.getBatchScanQueue().size() + mScanManager.getRegularScanQueue().size(); |
| if (DBG) { |
| Log.d(TAG, "stopScan() - queue size =" + scanQueueSize); |
| } |
| |
| AppScanStats app = null; |
| app = mScannerMap.getAppScanStatsById(scannerId); |
| if (app != null) { |
| app.recordScanStop(scannerId); |
| } |
| |
| mScanManager.stopScan(scannerId); |
| } |
| |
| void stopScan(PendingIntent intent, String callingPackage) { |
| enforceAdminPermission(); |
| PendingIntentInfo pii = new PendingIntentInfo(); |
| pii.intent = intent; |
| ScannerMap.App app = mScannerMap.getByContextInfo(pii); |
| if (VDBG) { |
| Log.d(TAG, "stopScan(PendingIntent): app found = " + app); |
| } |
| if (app != null) { |
| final int scannerId = app.id; |
| stopScan(scannerId); |
| // Also unregister the scanner |
| unregisterScanner(scannerId); |
| } |
| } |
| |
| void disconnectAll() { |
| if (DBG) { |
| Log.d(TAG, "disconnectAll()"); |
| } |
| Map<Integer, String> connMap = mClientMap.getConnectedMap(); |
| for (Map.Entry<Integer, String> entry : connMap.entrySet()) { |
| if (DBG) { |
| Log.d(TAG, "disconnecting addr:" + entry.getValue()); |
| } |
| clientDisconnect(entry.getKey(), entry.getValue()); |
| //clientDisconnect(int clientIf, String address) |
| } |
| } |
| |
| void unregAll() { |
| for (Integer appId : mClientMap.getAllAppsIds()) { |
| if (DBG) { |
| Log.d(TAG, "unreg:" + appId); |
| } |
| unregisterClient(appId); |
| } |
| } |
| |
| /************************************************************************** |
| * PERIODIC SCANNING |
| *************************************************************************/ |
| void registerSync(ScanResult scanResult, int skip, int timeout, |
| IPeriodicAdvertisingCallback callback) { |
| enforceAdminPermission(); |
| mPeriodicScanManager.startSync(scanResult, skip, timeout, callback); |
| } |
| |
| void unregisterSync(IPeriodicAdvertisingCallback callback) { |
| enforceAdminPermission(); |
| mPeriodicScanManager.stopSync(callback); |
| } |
| |
| /************************************************************************** |
| * ADVERTISING SET |
| *************************************************************************/ |
| void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData, |
| AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, |
| AdvertiseData periodicData, int duration, int maxExtAdvEvents, |
| IAdvertisingSetCallback callback) { |
| enforceAdminPermission(); |
| mAdvertiseManager.startAdvertisingSet(parameters, advertiseData, scanResponse, |
| periodicParameters, periodicData, duration, maxExtAdvEvents, callback); |
| } |
| |
| void stopAdvertisingSet(IAdvertisingSetCallback callback) { |
| enforceAdminPermission(); |
| mAdvertiseManager.stopAdvertisingSet(callback); |
| } |
| |
| void getOwnAddress(int advertiserId) { |
| enforcePrivilegedPermission(); |
| mAdvertiseManager.getOwnAddress(advertiserId); |
| } |
| |
| void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents) { |
| enforceAdminPermission(); |
| mAdvertiseManager.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents); |
| } |
| |
| void setAdvertisingData(int advertiserId, AdvertiseData data) { |
| enforceAdminPermission(); |
| mAdvertiseManager.setAdvertisingData(advertiserId, data); |
| } |
| |
| void setScanResponseData(int advertiserId, AdvertiseData data) { |
| enforceAdminPermission(); |
| mAdvertiseManager.setScanResponseData(advertiserId, data); |
| } |
| |
| void setAdvertisingParameters(int advertiserId, AdvertisingSetParameters parameters) { |
| enforceAdminPermission(); |
| mAdvertiseManager.setAdvertisingParameters(advertiserId, parameters); |
| } |
| |
| void setPeriodicAdvertisingParameters(int advertiserId, |
| PeriodicAdvertisingParameters parameters) { |
| enforceAdminPermission(); |
| mAdvertiseManager.setPeriodicAdvertisingParameters(advertiserId, parameters); |
| } |
| |
| void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) { |
| enforceAdminPermission(); |
| mAdvertiseManager.setPeriodicAdvertisingData(advertiserId, data); |
| } |
| |
| void setPeriodicAdvertisingEnable(int advertiserId, boolean enable) { |
| enforceAdminPermission(); |
| mAdvertiseManager.setPeriodicAdvertisingEnable(advertiserId, enable); |
| } |
| |
| /************************************************************************** |
| * GATT Service functions - CLIENT |
| *************************************************************************/ |
| |
| void registerClient(UUID uuid, IBluetoothGattCallback callback) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "registerClient() - UUID=" + uuid); |
| } |
| mClientMap.add(uuid, null, callback, null, this); |
| gattClientRegisterAppNative(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits()); |
| } |
| |
| void unregisterClient(int clientIf) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "unregisterClient() - clientIf=" + clientIf); |
| } |
| mClientMap.remove(clientIf); |
| gattClientUnregisterAppNative(clientIf); |
| } |
| |
| void clientConnect(int clientIf, String address, boolean isDirect, int transport, |
| boolean opportunistic, int phy) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "clientConnect() - address=" + address + ", isDirect=" + isDirect |
| + ", opportunistic=" + opportunistic + ", phy=" + phy); |
| } |
| gattClientConnectNative(clientIf, address, isDirect, transport, opportunistic, phy); |
| } |
| |
| void clientDisconnect(int clientIf, String address) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| Integer connId = mClientMap.connIdByAddress(clientIf, address); |
| if (DBG) { |
| Log.d(TAG, "clientDisconnect() - address=" + address + ", connId=" + connId); |
| } |
| |
| gattClientDisconnectNative(clientIf, address, connId != null ? connId : 0); |
| } |
| |
| void clientSetPreferredPhy(int clientIf, String address, int txPhy, int rxPhy, int phyOptions) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| Integer connId = mClientMap.connIdByAddress(clientIf, address); |
| if (connId == null) { |
| if (DBG) { |
| Log.d(TAG, "clientSetPreferredPhy() - no connection to " + address); |
| } |
| return; |
| } |
| |
| if (DBG) { |
| Log.d(TAG, "clientSetPreferredPhy() - address=" + address + ", connId=" + connId); |
| } |
| gattClientSetPreferredPhyNative(clientIf, address, txPhy, rxPhy, phyOptions); |
| } |
| |
| void clientReadPhy(int clientIf, String address) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| Integer connId = mClientMap.connIdByAddress(clientIf, address); |
| if (connId == null) { |
| if (DBG) { |
| Log.d(TAG, "clientReadPhy() - no connection to " + address); |
| } |
| return; |
| } |
| |
| if (DBG) { |
| Log.d(TAG, "clientReadPhy() - address=" + address + ", connId=" + connId); |
| } |
| gattClientReadPhyNative(clientIf, address); |
| } |
| |
| int numHwTrackFiltersAvailable() { |
| return (AdapterService.getAdapterService().getTotalNumOfTrackableAdvertisements() |
| - mScanManager.getCurrentUsedTrackingAdvertisement()); |
| } |
| |
| synchronized List<ParcelUuid> getRegisteredServiceUuids() { |
| Utils.enforceAdminPermission(this); |
| List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>(); |
| for (HandleMap.Entry entry : mHandleMap.mEntries) { |
| serviceUuids.add(new ParcelUuid(entry.uuid)); |
| } |
| return serviceUuids; |
| } |
| |
| List<String> getConnectedDevices() { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| Set<String> connectedDevAddress = new HashSet<String>(); |
| connectedDevAddress.addAll(mClientMap.getConnectedDevices()); |
| connectedDevAddress.addAll(mServerMap.getConnectedDevices()); |
| List<String> connectedDeviceList = new ArrayList<String>(connectedDevAddress); |
| return connectedDeviceList; |
| } |
| |
| void refreshDevice(int clientIf, String address) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "refreshDevice() - address=" + address); |
| } |
| gattClientRefreshNative(clientIf, address); |
| } |
| |
| void discoverServices(int clientIf, String address) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| Integer connId = mClientMap.connIdByAddress(clientIf, address); |
| if (DBG) { |
| Log.d(TAG, "discoverServices() - address=" + address + ", connId=" + connId); |
| } |
| |
| if (connId != null) { |
| gattClientSearchServiceNative(connId, true, 0, 0); |
| } else { |
| Log.e(TAG, "discoverServices() - No connection for " + address + "..."); |
| } |
| } |
| |
| void discoverServiceByUuid(int clientIf, String address, UUID uuid) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| Integer connId = mClientMap.connIdByAddress(clientIf, address); |
| if (connId != null) { |
| gattClientDiscoverServiceByUuidNative(connId, uuid.getLeastSignificantBits(), |
| uuid.getMostSignificantBits()); |
| } else { |
| Log.e(TAG, "discoverServiceByUuid() - No connection for " + address + "..."); |
| } |
| } |
| |
| void readCharacteristic(int clientIf, String address, int handle, int authReq) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (VDBG) { |
| Log.d(TAG, "readCharacteristic() - address=" + address); |
| } |
| |
| Integer connId = mClientMap.connIdByAddress(clientIf, address); |
| if (connId == null) { |
| Log.e(TAG, "readCharacteristic() - No connection for " + address + "..."); |
| return; |
| } |
| |
| if (!permissionCheck(connId, handle)) { |
| Log.w(TAG, "readCharacteristic() - permission check failed!"); |
| return; |
| } |
| |
| gattClientReadCharacteristicNative(connId, handle, authReq); |
| } |
| |
| void readUsingCharacteristicUuid(int clientIf, String address, UUID uuid, int startHandle, |
| int endHandle, int authReq) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (VDBG) { |
| Log.d(TAG, "readUsingCharacteristicUuid() - address=" + address); |
| } |
| |
| Integer connId = mClientMap.connIdByAddress(clientIf, address); |
| if (connId == null) { |
| Log.e(TAG, "readUsingCharacteristicUuid() - No connection for " + address + "..."); |
| return; |
| } |
| |
| if (!permissionCheck(uuid)) { |
| Log.w(TAG, "readUsingCharacteristicUuid() - permission check failed!"); |
| return; |
| } |
| |
| gattClientReadUsingCharacteristicUuidNative(connId, uuid.getLeastSignificantBits(), |
| uuid.getMostSignificantBits(), startHandle, endHandle, authReq); |
| } |
| |
| void writeCharacteristic(int clientIf, String address, int handle, int writeType, int authReq, |
| byte[] value) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (VDBG) { |
| Log.d(TAG, "writeCharacteristic() - address=" + address); |
| } |
| |
| if (mReliableQueue.contains(address)) { |
| writeType = 3; // Prepared write |
| } |
| |
| Integer connId = mClientMap.connIdByAddress(clientIf, address); |
| if (connId == null) { |
| Log.e(TAG, "writeCharacteristic() - No connection for " + address + "..."); |
| return; |
| } |
| |
| if (!permissionCheck(connId, handle)) { |
| Log.w(TAG, "writeCharacteristic() - permission check failed!"); |
| return; |
| } |
| |
| gattClientWriteCharacteristicNative(connId, handle, writeType, authReq, value); |
| } |
| |
| void readDescriptor(int clientIf, String address, int handle, int authReq) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (VDBG) { |
| Log.d(TAG, "readDescriptor() - address=" + address); |
| } |
| |
| Integer connId = mClientMap.connIdByAddress(clientIf, address); |
| if (connId == null) { |
| Log.e(TAG, "readDescriptor() - No connection for " + address + "..."); |
| return; |
| } |
| |
| if (!permissionCheck(connId, handle)) { |
| Log.w(TAG, "readDescriptor() - permission check failed!"); |
| return; |
| } |
| |
| gattClientReadDescriptorNative(connId, handle, authReq); |
| } |
| |
| ; |
| |
| void writeDescriptor(int clientIf, String address, int handle, int authReq, byte[] value) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| if (VDBG) { |
| Log.d(TAG, "writeDescriptor() - address=" + address); |
| } |
| |
| Integer connId = mClientMap.connIdByAddress(clientIf, address); |
| if (connId == null) { |
| Log.e(TAG, "writeDescriptor() - No connection for " + address + "..."); |
| return; |
| } |
| |
| if (!permissionCheck(connId, handle)) { |
| Log.w(TAG, "writeDescriptor() - permission check failed!"); |
| return; |
| } |
| |
| gattClientWriteDescriptorNative(connId, handle, authReq, value); |
| } |
| |
| void beginReliableWrite(int clientIf, String address) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "beginReliableWrite() - address=" + address); |
| } |
| mReliableQueue.add(address); |
| } |
| |
| void endReliableWrite(int clientIf, String address, boolean execute) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "endReliableWrite() - address=" + address + " execute: " + execute); |
| } |
| mReliableQueue.remove(address); |
| |
| Integer connId = mClientMap.connIdByAddress(clientIf, address); |
| if (connId != null) { |
| gattClientExecuteWriteNative(connId, execute); |
| } |
| } |
| |
| void registerForNotification(int clientIf, String address, int handle, boolean enable) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "registerForNotification() - address=" + address + " enable: " + enable); |
| } |
| |
| Integer connId = mClientMap.connIdByAddress(clientIf, address); |
| if (connId == null) { |
| Log.e(TAG, "registerForNotification() - No connection for " + address + "..."); |
| return; |
| } |
| |
| if (!permissionCheck(connId, handle)) { |
| Log.w(TAG, "registerForNotification() - permission check failed!"); |
| return; |
| } |
| |
| gattClientRegisterForNotificationsNative(clientIf, address, handle, enable); |
| } |
| |
| void readRemoteRssi(int clientIf, String address) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "readRemoteRssi() - address=" + address); |
| } |
| gattClientReadRemoteRssiNative(clientIf, address); |
| } |
| |
| void configureMTU(int clientIf, String address, int mtu) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "configureMTU() - address=" + address + " mtu=" + mtu); |
| } |
| Integer connId = mClientMap.connIdByAddress(clientIf, address); |
| if (connId != null) { |
| gattClientConfigureMTUNative(connId, mtu); |
| } else { |
| Log.e(TAG, "configureMTU() - No connection for " + address + "..."); |
| } |
| } |
| |
| void connectionParameterUpdate(int clientIf, String address, int connectionPriority) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| int minInterval; |
| int maxInterval; |
| |
| // Slave latency |
| int latency; |
| |
| // Link supervision timeout is measured in N * 10ms |
| int timeout = 500; // 5s |
| |
| switch (connectionPriority) { |
| case BluetoothGatt.CONNECTION_PRIORITY_HIGH: |
| minInterval = getResources().getInteger(R.integer.gatt_high_priority_min_interval); |
| maxInterval = getResources().getInteger(R.integer.gatt_high_priority_max_interval); |
| latency = getResources().getInteger(R.integer.gatt_high_priority_latency); |
| break; |
| |
| case BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER: |
| minInterval = getResources().getInteger(R.integer.gatt_low_power_min_interval); |
| maxInterval = getResources().getInteger(R.integer.gatt_low_power_max_interval); |
| latency = getResources().getInteger(R.integer.gatt_low_power_latency); |
| break; |
| |
| default: |
| // Using the values for CONNECTION_PRIORITY_BALANCED. |
| minInterval = |
| getResources().getInteger(R.integer.gatt_balanced_priority_min_interval); |
| maxInterval = |
| getResources().getInteger(R.integer.gatt_balanced_priority_max_interval); |
| latency = getResources().getInteger(R.integer.gatt_balanced_priority_latency); |
| break; |
| } |
| |
| if (DBG) { |
| Log.d(TAG, "connectionParameterUpdate() - address=" + address + "params=" |
| + connectionPriority + " interval=" + minInterval + "/" + maxInterval); |
| } |
| gattConnectionParameterUpdateNative(clientIf, address, minInterval, maxInterval, latency, |
| timeout, 0, 0); |
| } |
| |
| void leConnectionUpdate(int clientIf, String address, int minInterval, |
| int maxInterval, int slaveLatency, |
| int supervisionTimeout, int minConnectionEventLen, |
| int maxConnectionEventLen) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "leConnectionUpdate() - address=" + address + ", intervals=" |
| + minInterval + "/" + maxInterval + ", latency=" + slaveLatency |
| + ", timeout=" + supervisionTimeout + "msec" + ", min_ce=" |
| + minConnectionEventLen + ", max_ce=" + maxConnectionEventLen); |
| |
| |
| } |
| gattConnectionParameterUpdateNative(clientIf, address, minInterval, maxInterval, |
| slaveLatency, supervisionTimeout, |
| minConnectionEventLen, maxConnectionEventLen); |
| } |
| |
| /************************************************************************** |
| * Callback functions - SERVER |
| *************************************************************************/ |
| |
| void onServerRegistered(int status, int serverIf, long uuidLsb, long uuidMsb) |
| throws RemoteException { |
| |
| UUID uuid = new UUID(uuidMsb, uuidLsb); |
| if (DBG) { |
| Log.d(TAG, "onServerRegistered() - UUID=" + uuid + ", serverIf=" + serverIf); |
| } |
| ServerMap.App app = mServerMap.getByUuid(uuid); |
| if (app != null) { |
| app.id = serverIf; |
| app.linkToDeath(new ServerDeathRecipient(serverIf)); |
| app.callback.onServerRegistered(status, serverIf); |
| } |
| } |
| |
| void onServiceAdded(int status, int serverIf, List<GattDbElement> service) |
| throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "onServiceAdded(), status=" + status); |
| } |
| |
| if (status != 0) { |
| return; |
| } |
| |
| GattDbElement svcEl = service.get(0); |
| int srvcHandle = svcEl.attributeHandle; |
| |
| BluetoothGattService svc = null; |
| |
| for (GattDbElement el : service) { |
| if (el.type == GattDbElement.TYPE_PRIMARY_SERVICE) { |
| mHandleMap.addService(serverIf, el.attributeHandle, el.uuid, |
| BluetoothGattService.SERVICE_TYPE_PRIMARY, 0, false); |
| svc = new BluetoothGattService(svcEl.uuid, svcEl.attributeHandle, |
| BluetoothGattService.SERVICE_TYPE_PRIMARY); |
| } else if (el.type == GattDbElement.TYPE_SECONDARY_SERVICE) { |
| mHandleMap.addService(serverIf, el.attributeHandle, el.uuid, |
| BluetoothGattService.SERVICE_TYPE_SECONDARY, 0, false); |
| svc = new BluetoothGattService(svcEl.uuid, svcEl.attributeHandle, |
| BluetoothGattService.SERVICE_TYPE_SECONDARY); |
| } else if (el.type == GattDbElement.TYPE_CHARACTERISTIC) { |
| mHandleMap.addCharacteristic(serverIf, el.attributeHandle, el.uuid, srvcHandle); |
| svc.addCharacteristic( |
| new BluetoothGattCharacteristic(el.uuid, el.attributeHandle, el.properties, |
| el.permissions)); |
| } else if (el.type == GattDbElement.TYPE_DESCRIPTOR) { |
| mHandleMap.addDescriptor(serverIf, el.attributeHandle, el.uuid, srvcHandle); |
| List<BluetoothGattCharacteristic> chars = svc.getCharacteristics(); |
| chars.get(chars.size() - 1) |
| .addDescriptor(new BluetoothGattDescriptor(el.uuid, el.attributeHandle, |
| el.permissions)); |
| } |
| } |
| mHandleMap.setStarted(serverIf, srvcHandle, true); |
| |
| ServerMap.App app = mServerMap.getById(serverIf); |
| if (app != null) { |
| app.callback.onServiceAdded(status, svc); |
| } |
| } |
| |
| void onServiceStopped(int status, int serverIf, int srvcHandle) throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "onServiceStopped() srvcHandle=" + srvcHandle + ", status=" + status); |
| } |
| if (status == 0) { |
| mHandleMap.setStarted(serverIf, srvcHandle, false); |
| } |
| stopNextService(serverIf, status); |
| } |
| |
| void onServiceDeleted(int status, int serverIf, int srvcHandle) { |
| if (DBG) { |
| Log.d(TAG, "onServiceDeleted() srvcHandle=" + srvcHandle + ", status=" + status); |
| } |
| mHandleMap.deleteService(serverIf, srvcHandle); |
| } |
| |
| void onClientConnected(String address, boolean connected, int connId, int serverIf) |
| throws RemoteException { |
| |
| if (DBG) { |
| Log.d(TAG, |
| "onClientConnected() connId=" + connId + ", address=" + address + ", connected=" |
| + connected); |
| } |
| |
| ServerMap.App app = mServerMap.getById(serverIf); |
| if (app == null) { |
| return; |
| } |
| |
| if (connected) { |
| mServerMap.addConnection(serverIf, connId, address); |
| } else { |
| mServerMap.removeConnection(serverIf, connId); |
| } |
| |
| app.callback.onServerConnectionState((byte) 0, serverIf, connected, address); |
| } |
| |
| void onServerReadCharacteristic(String address, int connId, int transId, int handle, int offset, |
| boolean isLong) throws RemoteException { |
| if (VDBG) { |
| Log.d(TAG, "onServerReadCharacteristic() connId=" + connId + ", address=" + address |
| + ", handle=" + handle + ", requestId=" + transId + ", offset=" + offset); |
| } |
| |
| HandleMap.Entry entry = mHandleMap.getByHandle(handle); |
| if (entry == null) { |
| return; |
| } |
| |
| mHandleMap.addRequest(transId, handle); |
| |
| ServerMap.App app = mServerMap.getById(entry.serverIf); |
| if (app == null) { |
| return; |
| } |
| |
| app.callback.onCharacteristicReadRequest(address, transId, offset, isLong, handle); |
| } |
| |
| void onServerReadDescriptor(String address, int connId, int transId, int handle, int offset, |
| boolean isLong) throws RemoteException { |
| if (VDBG) { |
| Log.d(TAG, "onServerReadDescriptor() connId=" + connId + ", address=" + address |
| + ", handle=" + handle + ", requestId=" + transId + ", offset=" + offset); |
| } |
| |
| HandleMap.Entry entry = mHandleMap.getByHandle(handle); |
| if (entry == null) { |
| return; |
| } |
| |
| mHandleMap.addRequest(transId, handle); |
| |
| ServerMap.App app = mServerMap.getById(entry.serverIf); |
| if (app == null) { |
| return; |
| } |
| |
| app.callback.onDescriptorReadRequest(address, transId, offset, isLong, handle); |
| } |
| |
| void onServerWriteCharacteristic(String address, int connId, int transId, int handle, |
| int offset, int length, boolean needRsp, boolean isPrep, byte[] data) |
| throws RemoteException { |
| if (VDBG) { |
| Log.d(TAG, "onServerWriteCharacteristic() connId=" + connId + ", address=" + address |
| + ", handle=" + handle + ", requestId=" + transId + ", isPrep=" + isPrep |
| + ", offset=" + offset); |
| } |
| |
| HandleMap.Entry entry = mHandleMap.getByHandle(handle); |
| if (entry == null) { |
| return; |
| } |
| |
| mHandleMap.addRequest(transId, handle); |
| |
| ServerMap.App app = mServerMap.getById(entry.serverIf); |
| if (app == null) { |
| return; |
| } |
| |
| app.callback.onCharacteristicWriteRequest(address, transId, offset, length, isPrep, needRsp, |
| handle, data); |
| } |
| |
| void onServerWriteDescriptor(String address, int connId, int transId, int handle, int offset, |
| int length, boolean needRsp, boolean isPrep, byte[] data) throws RemoteException { |
| if (VDBG) { |
| Log.d(TAG, "onAttributeWrite() connId=" + connId + ", address=" + address + ", handle=" |
| + handle + ", requestId=" + transId + ", isPrep=" + isPrep + ", offset=" |
| + offset); |
| } |
| |
| HandleMap.Entry entry = mHandleMap.getByHandle(handle); |
| if (entry == null) { |
| return; |
| } |
| |
| mHandleMap.addRequest(transId, handle); |
| |
| ServerMap.App app = mServerMap.getById(entry.serverIf); |
| if (app == null) { |
| return; |
| } |
| |
| app.callback.onDescriptorWriteRequest(address, transId, offset, length, isPrep, needRsp, |
| handle, data); |
| } |
| |
| void onExecuteWrite(String address, int connId, int transId, int execWrite) |
| throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "onExecuteWrite() connId=" + connId + ", address=" + address + ", transId=" |
| + transId); |
| } |
| |
| ServerMap.App app = mServerMap.getByConnId(connId); |
| if (app == null) { |
| return; |
| } |
| |
| app.callback.onExecuteWrite(address, transId, execWrite == 1); |
| } |
| |
| void onResponseSendCompleted(int status, int attrHandle) { |
| if (DBG) { |
| Log.d(TAG, "onResponseSendCompleted() handle=" + attrHandle); |
| } |
| } |
| |
| void onNotificationSent(int connId, int status) throws RemoteException { |
| if (VDBG) { |
| Log.d(TAG, "onNotificationSent() connId=" + connId + ", status=" + status); |
| } |
| |
| String address = mServerMap.addressByConnId(connId); |
| if (address == null) { |
| return; |
| } |
| |
| ServerMap.App app = mServerMap.getByConnId(connId); |
| if (app == null) { |
| return; |
| } |
| |
| if (!app.isCongested) { |
| app.callback.onNotificationSent(address, status); |
| } else { |
| if (status == BluetoothGatt.GATT_CONNECTION_CONGESTED) { |
| status = BluetoothGatt.GATT_SUCCESS; |
| } |
| app.queueCallback(new CallbackInfo(address, status)); |
| } |
| } |
| |
| void onServerCongestion(int connId, boolean congested) throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "onServerCongestion() - connId=" + connId + ", congested=" + congested); |
| } |
| |
| ServerMap.App app = mServerMap.getByConnId(connId); |
| if (app == null) { |
| return; |
| } |
| |
| app.isCongested = congested; |
| while (!app.isCongested) { |
| CallbackInfo callbackInfo = app.popQueuedCallback(); |
| if (callbackInfo == null) { |
| return; |
| } |
| app.callback.onNotificationSent(callbackInfo.address, callbackInfo.status); |
| } |
| } |
| |
| void onMtuChanged(int connId, int mtu) throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "onMtuChanged() - connId=" + connId + ", mtu=" + mtu); |
| } |
| |
| String address = mServerMap.addressByConnId(connId); |
| if (address == null) { |
| return; |
| } |
| |
| ServerMap.App app = mServerMap.getByConnId(connId); |
| if (app == null) { |
| return; |
| } |
| |
| app.callback.onMtuChanged(address, mtu); |
| } |
| |
| /************************************************************************** |
| * GATT Service functions - SERVER |
| *************************************************************************/ |
| |
| void registerServer(UUID uuid, IBluetoothGattServerCallback callback) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "registerServer() - UUID=" + uuid); |
| } |
| mServerMap.add(uuid, null, callback, null, this); |
| gattServerRegisterAppNative(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits()); |
| } |
| |
| void unregisterServer(int serverIf) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "unregisterServer() - serverIf=" + serverIf); |
| } |
| |
| deleteServices(serverIf); |
| |
| mServerMap.remove(serverIf); |
| gattServerUnregisterAppNative(serverIf); |
| } |
| |
| void serverConnect(int serverIf, String address, boolean isDirect, int transport) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "serverConnect() - address=" + address); |
| } |
| gattServerConnectNative(serverIf, address, isDirect, transport); |
| } |
| |
| void serverDisconnect(int serverIf, String address) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| Integer connId = mServerMap.connIdByAddress(serverIf, address); |
| if (DBG) { |
| Log.d(TAG, "serverDisconnect() - address=" + address + ", connId=" + connId); |
| } |
| |
| gattServerDisconnectNative(serverIf, address, connId != null ? connId : 0); |
| } |
| |
| void serverSetPreferredPhy(int serverIf, String address, int txPhy, int rxPhy, int phyOptions) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| Integer connId = mServerMap.connIdByAddress(serverIf, address); |
| if (connId == null) { |
| if (DBG) { |
| Log.d(TAG, "serverSetPreferredPhy() - no connection to " + address); |
| } |
| return; |
| } |
| |
| if (DBG) { |
| Log.d(TAG, "serverSetPreferredPhy() - address=" + address + ", connId=" + connId); |
| } |
| gattServerSetPreferredPhyNative(serverIf, address, txPhy, rxPhy, phyOptions); |
| } |
| |
| void serverReadPhy(int serverIf, String address) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| Integer connId = mServerMap.connIdByAddress(serverIf, address); |
| if (connId == null) { |
| if (DBG) { |
| Log.d(TAG, "serverReadPhy() - no connection to " + address); |
| } |
| return; |
| } |
| |
| if (DBG) { |
| Log.d(TAG, "serverReadPhy() - address=" + address + ", connId=" + connId); |
| } |
| gattServerReadPhyNative(serverIf, address); |
| } |
| |
| void addService(int serverIf, BluetoothGattService service) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "addService() - uuid=" + service.getUuid()); |
| } |
| |
| List<GattDbElement> db = new ArrayList<GattDbElement>(); |
| |
| if (service.getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY) { |
| db.add(GattDbElement.createPrimaryService(service.getUuid())); |
| } else { |
| db.add(GattDbElement.createSecondaryService(service.getUuid())); |
| } |
| |
| for (BluetoothGattService includedService : service.getIncludedServices()) { |
| int inclSrvcHandle = includedService.getInstanceId(); |
| |
| if (mHandleMap.checkServiceExists(includedService.getUuid(), inclSrvcHandle)) { |
| db.add(GattDbElement.createIncludedService(inclSrvcHandle)); |
| } else { |
| Log.e(TAG, |
| "included service with UUID " + includedService.getUuid() + " not found!"); |
| } |
| } |
| |
| for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) { |
| int permission = |
| ((characteristic.getKeySize() - 7) << 12) + characteristic.getPermissions(); |
| db.add(GattDbElement.createCharacteristic(characteristic.getUuid(), |
| characteristic.getProperties(), permission)); |
| |
| for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) { |
| permission = |
| ((characteristic.getKeySize() - 7) << 12) + descriptor.getPermissions(); |
| db.add(GattDbElement.createDescriptor(descriptor.getUuid(), permission)); |
| } |
| } |
| |
| gattServerAddServiceNative(serverIf, db); |
| } |
| |
| void removeService(int serverIf, int handle) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "removeService() - handle=" + handle); |
| } |
| |
| gattServerDeleteServiceNative(serverIf, handle); |
| } |
| |
| void clearServices(int serverIf) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (DBG) { |
| Log.d(TAG, "clearServices()"); |
| } |
| deleteServices(serverIf); |
| } |
| |
| void sendResponse(int serverIf, String address, int requestId, int status, int offset, |
| byte[] value) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (VDBG) { |
| Log.d(TAG, "sendResponse() - address=" + address); |
| } |
| |
| int handle = 0; |
| HandleMap.Entry entry = mHandleMap.getByRequestId(requestId); |
| if (entry != null) { |
| handle = entry.handle; |
| } |
| |
| Integer connId = mServerMap.connIdByAddress(serverIf, address); |
| gattServerSendResponseNative(serverIf, connId != null ? connId : 0, requestId, |
| (byte) status, handle, offset, value, (byte) 0); |
| mHandleMap.deleteRequest(requestId); |
| } |
| |
| void sendNotification(int serverIf, String address, int handle, boolean confirm, byte[] value) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| |
| if (VDBG) { |
| Log.d(TAG, "sendNotification() - address=" + address + " handle=" + handle); |
| } |
| |
| Integer connId = mServerMap.connIdByAddress(serverIf, address); |
| if (connId == null || connId == 0) { |
| return; |
| } |
| |
| if (confirm) { |
| gattServerSendIndicationNative(serverIf, handle, connId, value); |
| } else { |
| gattServerSendNotificationNative(serverIf, handle, connId, value); |
| } |
| } |
| |
| |
| /************************************************************************** |
| * Private functions |
| *************************************************************************/ |
| |
| private boolean isHidSrvcUuid(final UUID uuid) { |
| return HID_SERVICE_UUID.equals(uuid); |
| } |
| |
| private boolean isHidCharUuid(final UUID uuid) { |
| for (UUID hidUuid : HID_UUIDS) { |
| if (hidUuid.equals(uuid)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean isAndroidTvRemoteSrvcUuid(final UUID uuid) { |
| return ANDROID_TV_REMOTE_SERVICE_UUID.equals(uuid); |
| } |
| |
| private boolean isFidoSrvcUuid(final UUID uuid) { |
| return FIDO_SERVICE_UUID.equals(uuid); |
| } |
| |
| private int getDeviceType(BluetoothDevice device) { |
| int type = gattClientGetDeviceTypeNative(device.getAddress()); |
| if (DBG) { |
| Log.d(TAG, "getDeviceType() - device=" + device + ", type=" + type); |
| } |
| return type; |
| } |
| |
| private void enforceAdminPermission() { |
| enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); |
| } |
| |
| private boolean needsPrivilegedPermissionForScan(ScanSettings settings) { |
| BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); |
| // BLE scan only mode needs special permission. |
| if (adapter.getState() != BluetoothAdapter.STATE_ON) { |
| return true; |
| } |
| |
| // Regular scan, no special permission. |
| if (settings == null) { |
| return false; |
| } |
| |
| // Regular scan, no special permission. |
| if (settings.getReportDelayMillis() == 0) { |
| return false; |
| } |
| |
| // Batch scan, truncated mode needs permission. |
| return settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_ABBREVIATED; |
| } |
| |
| // Enforce caller has BLUETOOTH_PRIVILEGED permission. A {@link SecurityException} will be |
| // thrown if the caller app does not have BLUETOOTH_PRIVILEGED permission. |
| private void enforcePrivilegedPermission() { |
| enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, |
| "Need BLUETOOTH_PRIVILEGED permission"); |
| } |
| |
| // Enforce caller has UPDATE_DEVICE_STATS permission, which allows the caller to blame other |
| // apps for Bluetooth usage. A {@link SecurityException} will be thrown if the caller app does |
| // not have UPDATE_DEVICE_STATS permission. |
| private void enforceImpersonatationPermission() { |
| enforceCallingOrSelfPermission(android.Manifest.permission.UPDATE_DEVICE_STATS, |
| "Need UPDATE_DEVICE_STATS permission"); |
| } |
| |
| private void stopNextService(int serverIf, int status) throws RemoteException { |
| if (DBG) { |
| Log.d(TAG, "stopNextService() - serverIf=" + serverIf + ", status=" + status); |
| } |
| |
| if (status == 0) { |
| List<HandleMap.Entry> entries = mHandleMap.getEntries(); |
| for (HandleMap.Entry entry : entries) { |
| if (entry.type != HandleMap.TYPE_SERVICE || entry.serverIf != serverIf |
| || !entry.started) { |
| continue; |
| } |
| |
| gattServerStopServiceNative(serverIf, entry.handle); |
| return; |
| } |
| } |
| } |
| |
| private void deleteServices(int serverIf) { |
| if (DBG) { |
| Log.d(TAG, "deleteServices() - serverIf=" + serverIf); |
| } |
| |
| /* |
| * Figure out which handles to delete. |
| * The handles are copied into a new list to avoid race conditions. |
| */ |
| List<Integer> handleList = new ArrayList<Integer>(); |
| List<HandleMap.Entry> entries = mHandleMap.getEntries(); |
| for (HandleMap.Entry entry : entries) { |
| if (entry.type != HandleMap.TYPE_SERVICE || entry.serverIf != serverIf) { |
| continue; |
| } |
| handleList.add(entry.handle); |
| } |
| |
| /* Now actually delete the services.... */ |
| for (Integer handle : handleList) { |
| gattServerDeleteServiceNative(serverIf, handle); |
| } |
| } |
| |
| private List<UUID> parseUuids(byte[] advData) { |
| List<UUID> uuids = new ArrayList<UUID>(); |
| |
| int offset = 0; |
| while (offset < (advData.length - 2)) { |
| int len = Byte.toUnsignedInt(advData[offset++]); |
| if (len == 0) { |
| break; |
| } |
| |
| int type = advData[offset++]; |
| switch (type) { |
| case 0x02: // Partial list of 16-bit UUIDs |
| case 0x03: // Complete list of 16-bit UUIDs |
| while (len > 1) { |
| int uuid16 = advData[offset++]; |
| uuid16 += (advData[offset++] << 8); |
| len -= 2; |
| uuids.add(UUID.fromString( |
| String.format("%08x-0000-1000-8000-00805f9b34fb", uuid16))); |
| } |
| break; |
| |
| default: |
| offset += (len - 1); |
| break; |
| } |
| } |
| |
| return uuids; |
| } |
| |
| void dumpRegisterId(StringBuilder sb) { |
| sb.append(" Scanner:\n"); |
| for (Integer appId : mScannerMap.getAllAppsIds()) { |
| println(sb, " app_if: " + appId + ", appName: " + mScannerMap.getById(appId).name); |
| } |
| sb.append(" Client:\n"); |
| for (Integer appId : mClientMap.getAllAppsIds()) { |
| println(sb, " app_if: " + appId + ", appName: " + mClientMap.getById(appId).name); |
| } |
| sb.append(" Server:\n"); |
| for (Integer appId : mServerMap.getAllAppsIds()) { |
| println(sb, " app_if: " + appId + ", appName: " + mServerMap.getById(appId).name); |
| } |
| sb.append("\n\n"); |
| } |
| |
| @Override |
| public void dump(StringBuilder sb) { |
| super.dump(sb); |
| println(sb, "mAdvertisingServiceUuids:"); |
| for (UUID uuid : mAdvertisingServiceUuids) { |
| println(sb, " " + uuid); |
| } |
| |
| println(sb, "mMaxScanFilters: " + mMaxScanFilters); |
| |
| sb.append("\nRegistered App\n"); |
| dumpRegisterId(sb); |
| |
| sb.append("GATT Scanner Map\n"); |
| mScannerMap.dump(sb); |
| |
| sb.append("GATT Client Map\n"); |
| mClientMap.dump(sb); |
| |
| sb.append("GATT Server Map\n"); |
| mServerMap.dump(sb); |
| |
| sb.append("GATT Handle Map\n"); |
| mHandleMap.dump(sb); |
| } |
| |
| void addScanEvent(BluetoothMetricsProto.ScanEvent event) { |
| synchronized (mScanEvents) { |
| if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT) { |
| mScanEvents.remove(); |
| } |
| mScanEvents.add(event); |
| } |
| } |
| |
| @Override |
| public void dumpProto(BluetoothMetricsProto.BluetoothLog.Builder builder) { |
| synchronized (mScanEvents) { |
| builder.addAllScanEvent(mScanEvents); |
| } |
| } |
| |
| /************************************************************************** |
| * GATT Test functions |
| *************************************************************************/ |
| |
| void gattTestCommand(int command, UUID uuid1, String bda1, int p1, int p2, int p3, int p4, |
| int p5) { |
| if (bda1 == null) { |
| bda1 = "00:00:00:00:00:00"; |
| } |
| if (uuid1 != null) { |
| gattTestNative(command, uuid1.getLeastSignificantBits(), uuid1.getMostSignificantBits(), |
| bda1, p1, p2, p3, p4, p5); |
| } else { |
| gattTestNative(command, 0, 0, bda1, p1, p2, p3, p4, p5); |
| } |
| } |
| |
| private native void gattTestNative(int command, long uuid1Lsb, long uuid1Msb, String bda1, |
| int p1, int p2, int p3, int p4, int p5); |
| |
| /************************************************************************** |
| * Native functions prototypes |
| *************************************************************************/ |
| |
| private static native void classInitNative(); |
| |
| private native void initializeNative(); |
| |
| private native void cleanupNative(); |
| |
| private native int gattClientGetDeviceTypeNative(String address); |
| |
| private native void gattClientRegisterAppNative(long appUuidLsb, long appUuidMsb); |
| |
| private native void gattClientUnregisterAppNative(int clientIf); |
| |
| private native void gattClientConnectNative(int clientIf, String address, boolean isDirect, |
| int transport, boolean opportunistic, int initiatingPhys); |
| |
| private native void gattClientDisconnectNative(int clientIf, String address, int connId); |
| |
| private native void gattClientSetPreferredPhyNative(int clientIf, String address, int txPhy, |
| int rxPhy, int phyOptions); |
| |
| private native void gattClientReadPhyNative(int clientIf, String address); |
| |
| private native void gattClientRefreshNative(int clientIf, String address); |
| |
| private native void gattClientSearchServiceNative(int connId, boolean searchAll, |
| long serviceUuidLsb, long serviceUuidMsb); |
| |
| private native void gattClientDiscoverServiceByUuidNative(int connId, long serviceUuidLsb, |
| long serviceUuidMsb); |
| |
| private native void gattClientGetGattDbNative(int connId); |
| |
| private native void gattClientReadCharacteristicNative(int connId, int handle, int authReq); |
| |
| private native void gattClientReadUsingCharacteristicUuidNative(int connId, long uuidMsb, |
| long uuidLsb, int sHandle, int eHandle, int authReq); |
| |
| private native void gattClientReadDescriptorNative(int connId, int handle, int authReq); |
| |
| private native void gattClientWriteCharacteristicNative(int connId, int handle, int writeType, |
| int authReq, byte[] value); |
| |
| private native void gattClientWriteDescriptorNative(int connId, int handle, int authReq, |
| byte[] value); |
| |
| private native void gattClientExecuteWriteNative(int connId, boolean execute); |
| |
| private native void gattClientRegisterForNotificationsNative(int clientIf, String address, |
| int handle, boolean enable); |
| |
| private native void gattClientReadRemoteRssiNative(int clientIf, String address); |
| |
| private native void gattClientConfigureMTUNative(int connId, int mtu); |
| |
| private native void gattConnectionParameterUpdateNative(int clientIf, String address, |
| int minInterval, int maxInterval, int latency, int timeout, int minConnectionEventLen, |
| int maxConnectionEventLen); |
| |
| private native void gattServerRegisterAppNative(long appUuidLsb, long appUuidMsb); |
| |
| private native void gattServerUnregisterAppNative(int serverIf); |
| |
| private native void gattServerConnectNative(int serverIf, String address, boolean isDirect, |
| int transport); |
| |
| private native void gattServerDisconnectNative(int serverIf, String address, int connId); |
| |
| private native void gattServerSetPreferredPhyNative(int clientIf, String address, int txPhy, |
| int rxPhy, int phyOptions); |
| |
| private native void gattServerReadPhyNative(int clientIf, String address); |
| |
| private native void gattServerAddServiceNative(int serverIf, List<GattDbElement> service); |
| |
| private native void gattServerStopServiceNative(int serverIf, int svcHandle); |
| |
| private native void gattServerDeleteServiceNative(int serverIf, int svcHandle); |
| |
| private native void gattServerSendIndicationNative(int serverIf, int attrHandle, int connId, |
| byte[] val); |
| |
| private native void gattServerSendNotificationNative(int serverIf, int attrHandle, int connId, |
| byte[] val); |
| |
| private native void gattServerSendResponseNative(int serverIf, int connId, int transId, |
| int status, int handle, int offset, byte[] val, int authReq); |
| } |