blob: f39598aff4df429a4f1efd12467f24c19bac8fe8 [file] [log] [blame]
/*
* Copyright 2019 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.settings.bluetooth;
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.car.drivingstate.CarUxRestrictions;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import androidx.preference.PreferenceGroup;
import com.android.car.settings.common.FragmentController;
import com.android.car.settings.common.Logger;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
/**
* Controller which sets the Bluetooth adapter to discovery mode and begins scanning for
* discoverable devices for as long as the preference group is shown. Discovery
* and scanning are halted while any device is pairing. Users with the {@link
* DISALLOW_CONFIG_BLUETOOTH} restriction cannot scan for devices, so only cached devices will be
* shown.
*/
public abstract class BluetoothScanningDevicesGroupPreferenceController extends
BluetoothDevicesGroupPreferenceController {
private static final Logger LOG = new Logger(
BluetoothScanningDevicesGroupPreferenceController.class);
private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
private final AlwaysDiscoverable mAlwaysDiscoverable;
private boolean mIsScanningEnabled;
public BluetoothScanningDevicesGroupPreferenceController(Context context, String preferenceKey,
FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
super(context, preferenceKey, fragmentController, uxRestrictions);
mAlwaysDiscoverable = new AlwaysDiscoverable(context, mBluetoothAdapter);
}
@Override
protected final void onDeviceClicked(CachedBluetoothDevice cachedDevice) {
LOG.d("onDeviceClicked: " + cachedDevice);
disableScanning();
onDeviceClickedInternal(cachedDevice);
}
/**
* Called when the user selects a device in the group.
*
* @param cachedDevice the device represented by the selected preference.
*/
protected abstract void onDeviceClickedInternal(CachedBluetoothDevice cachedDevice);
@Override
protected void onStopInternal() {
super.onStopInternal();
disableScanning();
getBluetoothManager().getCachedDeviceManager().clearNonBondedDevices();
getPreferenceMap().clear();
getPreference().removeAll();
}
@Override
protected void updateState(PreferenceGroup preferenceGroup) {
super.updateState(preferenceGroup);
if (shouldEnableScanning()) {
enableScanning();
} else {
disableScanning();
}
}
private boolean shouldEnableScanning() {
for (CachedBluetoothDevice device : getPreferenceMap().keySet()) {
if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
return false;
}
}
// Users who cannot configure Bluetooth cannot scan.
return !getCarUserManagerHelper().isCurrentProcessUserHasRestriction(
DISALLOW_CONFIG_BLUETOOTH);
}
/**
* Starts scanning for devices which will be displayed in the group for a user to select.
* Calls are idempotent.
*/
private void enableScanning() {
mIsScanningEnabled = true;
if (!mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.startDiscovery();
}
mAlwaysDiscoverable.start();
getPreference().setEnabled(true);
}
/** Stops scanning for devices and disables interaction. Calls are idempotent. */
private void disableScanning() {
mIsScanningEnabled = false;
getPreference().setEnabled(false);
mAlwaysDiscoverable.stop();
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
}
@Override
public void onScanningStateChanged(boolean started) {
LOG.d("onScanningStateChanged started: " + started + " mIsScanningEnabled: "
+ mIsScanningEnabled);
if (!started && mIsScanningEnabled) {
enableScanning();
}
}
@Override
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
LOG.d("onDeviceBondStateChanged device: " + cachedDevice + " state: " + bondState);
refreshUi();
}
/**
* Helper class to keep the {@link BluetoothAdapter} in discoverable mode indefinitely. By
* default, setting the scan mode to BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE will
* timeout, but for pairing, we want to keep the device discoverable as long as the page is
* scanning.
*/
private static final class AlwaysDiscoverable extends BroadcastReceiver {
private final Context mContext;
private final BluetoothAdapter mAdapter;
private final IntentFilter mIntentFilter = new IntentFilter(
BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
private boolean mStarted;
AlwaysDiscoverable(Context context, BluetoothAdapter adapter) {
mContext = context;
mAdapter = adapter;
}
/**
* Sets the adapter scan mode to
* {@link BluetoothAdapter#SCAN_MODE_CONNECTABLE_DISCOVERABLE}. {@link #start()} calls
* should have a matching calls to {@link #stop()} when discover mode is no longer needed.
*/
void start() {
if (mStarted) {
return;
}
mContext.registerReceiver(this, mIntentFilter);
mStarted = true;
setDiscoverable();
}
void stop() {
if (!mStarted) {
return;
}
mContext.unregisterReceiver(this);
mStarted = false;
mAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
}
@Override
public void onReceive(Context context, Intent intent) {
setDiscoverable();
}
private void setDiscoverable() {
if (mAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
mAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
}
}
}
}