/* * Copyright (C) 2018 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.car.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.os.Handler; import android.util.IndentingPrintWriter; import android.util.Slog; import com.android.car.CarLog; import com.android.car.CarServiceUtils; import com.android.car.R; /** * An advertiser for the Bluetooth LE based Fast Pair service. FastPairProvider enables easy * Bluetooth pairing between a peripheral and a phone participating in the Fast Pair Seeker role. * When the seeker finds a compatible peripheral a notification prompts the user to begin pairing if * desired. A peripheral should call startAdvertising when it is appropriate to pair, and * stopAdvertising when pairing is complete or it is no longer appropriate to pair. */ public class FastPairProvider { private static final String TAG = CarLog.tagFor(FastPairProvider.class); private static final boolean DBG = FastPairUtils.DBG; private final int mModelId; private final String mAntiSpoofKey; private final boolean mAutomaticAcceptance; private final Context mContext; private boolean mStarted; private int mScanMode; private BluetoothAdapter mBluetoothAdapter; private FastPairAdvertiser mFastPairModelAdvertiser; private FastPairAdvertiser mFastPairAccountAdvertiser; private FastPairGattServer mFastPairGattServer; private Handler mFastPairAdvertiserHandler; FastPairAdvertiser.Callbacks mAdvertiserCallbacks = new FastPairAdvertiser.Callbacks() { @Override public void onRpaUpdated(BluetoothDevice device) { mFastPairGattServer.updateLocalRpa(device); } }; FastPairGattServer.Callbacks mGattServerCallbacks = new FastPairGattServer.Callbacks() { @Override public void onPairingCompleted(boolean successful) { if (DBG) { Slog.d(TAG, "onPairingCompleted " + successful); } if (successful || mScanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { advertiseAccountKeys(); } } }; /** * listen for changes in the Bluetooth adapter specifically for the Bluetooth adapter turning * on, turning off, and changes to discoverability */ BroadcastReceiver mDiscoveryModeChanged = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (DBG) { Slog.d(TAG, "onReceive, " + action); } switch (action) { case BluetoothAdapter.ACTION_SCAN_MODE_CHANGED: mScanMode = intent .getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, BluetoothAdapter.SCAN_MODE_NONE); if (DBG) { Slog.d(TAG, "NewScanMode = " + mScanMode); } if (mScanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { if (mBluetoothAdapter.isDiscovering()) { advertiseModelId(); } else { stopAdvertising(); } } else if (mScanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE && mFastPairGattServer != null && !mFastPairGattServer.isConnected()) { // The adapter is no longer discoverable, and the Fast Pair session is // complete advertiseAccountKeys(); } break; case BluetoothAdapter.ACTION_STATE_CHANGED: int state = intent .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF); if (state != BluetoothAdapter.STATE_ON) { if (mFastPairGattServer != null) { mFastPairGattServer.stop(); mFastPairGattServer = null; } } break; } } }; /** * FastPairProvider constructor which loads Fast Pair variables from the device specific * resource overlay. * * @param context user specific context on which all Bluetooth operations shall occur. */ public FastPairProvider(Context context) { mContext = context; Resources res = mContext.getResources(); mModelId = res.getInteger(R.integer.fastPairModelId); mAntiSpoofKey = res.getString(R.string.fastPairAntiSpoofKey); mAutomaticAcceptance = res.getBoolean(R.bool.fastPairAutomaticAcceptance); mBluetoothAdapter = mContext.getSystemService(BluetoothManager.class).getAdapter(); } /** * Start the Fast Pair provider which will register for Bluetooth broadcasts. */ public void start() { if (mModelId == 0) { Slog.w(TAG, "Model ID undefined, disabling"); return; } Slog.d(TAG, "modelId == " + mModelId); mFastPairAdvertiserHandler = new Handler( CarServiceUtils.getHandlerThread(FastPairUtils.THREAD_NAME).getLooper()); IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); mContext.registerReceiver(mDiscoveryModeChanged, filter); mStarted = true; } /** * Stop the Fast Pair provider which will unregister the broadcast receiver. */ public void stop() { if (mStarted) { mContext.unregisterReceiver(mDiscoveryModeChanged); mStarted = false; } } void stopAdvertising() { mFastPairAdvertiserHandler.post(new Runnable() { @Override public void run() { if (mFastPairAccountAdvertiser != null) { mFastPairAccountAdvertiser.stopAdvertising(); } if (mFastPairModelAdvertiser != null) { mFastPairModelAdvertiser.stopAdvertising(); } } }); } void advertiseModelId() { mFastPairAdvertiserHandler.post(new Runnable() { @Override public void run() { if (mFastPairAccountAdvertiser != null) { mFastPairAccountAdvertiser.stopAdvertising(); } if (mFastPairModelAdvertiser == null) { mFastPairModelAdvertiser = new FastPairAdvertiser(mContext, mModelId, mAdvertiserCallbacks); } startGatt(); mFastPairModelAdvertiser.advertiseModelId(); } }); } void advertiseAccountKeys() { mFastPairAdvertiserHandler.post(new Runnable() { @Override public void run() { if (mFastPairModelAdvertiser != null) { mFastPairModelAdvertiser.stopAdvertising(); } if (mFastPairAccountAdvertiser == null) { mFastPairAccountAdvertiser = new FastPairAdvertiser(mContext, mModelId, mAdvertiserCallbacks); } startGatt(); mFastPairAccountAdvertiser.advertiseAccountKeys(); } }); } void startGatt() { if (mFastPairGattServer == null) { mFastPairGattServer = new FastPairGattServer(mContext, mModelId, mAntiSpoofKey, mGattServerCallbacks, mAutomaticAcceptance); mFastPairGattServer.start(); } } /** * Dump current status of the Fast Pair provider * * @param writer */ public void dump(IndentingPrintWriter writer) { writer.println(TAG + " services"); if (mModelId == 0) { writer.increaseIndent(); writer.println("Service Disabled"); writer.decreaseIndent(); return; } writer.increaseIndent(); writer.println("Model ID : " + mModelId); if (mFastPairModelAdvertiser != null) { mFastPairModelAdvertiser.dump(writer); } if (mFastPairAccountAdvertiser != null) { mFastPairAccountAdvertiser.dump(writer); } if (mFastPairGattServer != null) { mFastPairGattServer.dump(writer); } writer.decreaseIndent(); } }