Merge "Stay discoverable in Bluetooth settings and pairing pages" into oc-dr1-dev

This commit is contained in:
TreeHugger Robot
2017-08-10 07:23:29 +00:00
committed by Android (Google) Code Review
5 changed files with 223 additions and 6 deletions

View File

@@ -0,0 +1,87 @@
/*
* 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.settings.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import java.util.Timer;
import java.util.TimerTask;
/** Helper class, intended to be used by an Activity, to keep the local Bluetooth adapter in
* discoverable mode indefinitely. By default setting the scan mode to
* BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE will time out after some time, but some
* Bluetooth settings pages would like to keep the device discoverable as long as the page is
* visible. */
public class AlwaysDiscoverable extends BroadcastReceiver {
private static final String TAG = "AlwaysDiscoverable";
private Context mContext;
private LocalBluetoothAdapter mLocalAdapter;
private IntentFilter mIntentFilter;
@VisibleForTesting
boolean mStarted;
public AlwaysDiscoverable(Context context, LocalBluetoothAdapter localAdapter) {
mContext = context;
mLocalAdapter = localAdapter;
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
}
/** After calling start(), consumers should make a matching call to stop() when they no longer
* wish to enforce discoverable mode. */
public void start() {
if (mStarted) {
return;
}
mContext.registerReceiver(this, mIntentFilter);
mStarted = true;
if (mLocalAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
}
}
public void stop() {
if (!mStarted) {
return;
}
mContext.unregisterReceiver(this);
mStarted = false;
mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action != BluetoothAdapter.ACTION_SCAN_MODE_CHANGED) {
return;
}
if (mLocalAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
}
}
}

View File

@@ -53,6 +53,8 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme
BluetoothProgressCategory mAvailableDevicesCategory;
@VisibleForTesting
FooterPreference mFooterPreference;
@VisibleForTesting
AlwaysDiscoverable mAlwaysDiscoverable;
private boolean mInitialScanStarted;
@@ -64,6 +66,7 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mInitialScanStarted = false;
mAlwaysDiscoverable = new AlwaysDiscoverable(getContext(), mLocalAdapter);
}
@Override
@@ -79,7 +82,7 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme
super.onStop();
// Make the device only visible to connected devices.
mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
mAlwaysDiscoverable.stop();
disableScanning();
}
@@ -132,9 +135,7 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme
R.string.bluetooth_preference_found_devices,
BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER, mInitialScanStarted);
updateFooterPreference(mFooterPreference);
// mLocalAdapter.setScanMode is internally synchronized so it is okay for multiple
// threads to execute.
mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
mAlwaysDiscoverable.start();
enableScanning();
break;

View File

@@ -84,6 +84,7 @@ public class BluetoothSettings extends DeviceListPreferenceFragment implements I
FooterPreference mFooterPreference;
private Preference mPairingPreference;
private BluetoothEnabler mBluetoothEnabler;
private AlwaysDiscoverable mAlwaysDiscoverable;
private SwitchBar mSwitchBar;
@@ -115,6 +116,9 @@ public class BluetoothSettings extends DeviceListPreferenceFragment implements I
mMetricsFeatureProvider, Utils.getLocalBtManager(activity),
MetricsEvent.ACTION_BLUETOOTH_TOGGLE);
mBluetoothEnabler.setupSwitchController();
if (mLocalAdapter != null) {
mAlwaysDiscoverable = new AlwaysDiscoverable(getContext(), mLocalAdapter);
}
}
@Override
@@ -161,7 +165,9 @@ public class BluetoothSettings extends DeviceListPreferenceFragment implements I
}
// Make the device only visible to connected devices.
mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
if (mAlwaysDiscoverable != null) {
mAlwaysDiscoverable.stop();
}
if (isUiRestricted()) {
return;
@@ -192,7 +198,9 @@ public class BluetoothSettings extends DeviceListPreferenceFragment implements I
mPairedDevicesCategory.addPreference(mPairingPreference);
updateFooterPreference(mFooterPreference);
mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
if (mAlwaysDiscoverable != null) {
mAlwaysDiscoverable.start();
}
return; // not break
case BluetoothAdapter.STATE_TURNING_OFF:

View File

@@ -0,0 +1,120 @@
/*
* 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.settings.bluetooth;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class AlwaysDiscoverableTest {
@Mock
private LocalBluetoothAdapter mLocalAdapter;
@Mock
private Context mContext;
private AlwaysDiscoverable mAlwaysDiscoverable;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mAlwaysDiscoverable = new AlwaysDiscoverable(mContext, mLocalAdapter);
}
@Test
public void isStartedWithoutStart() {
assertThat(mAlwaysDiscoverable.mStarted).isFalse();
}
@Test
public void isStartedWithStart() {
mAlwaysDiscoverable.start();
assertThat(mAlwaysDiscoverable.mStarted).isTrue();
}
@Test
public void isStartedWithStartStop() {
mAlwaysDiscoverable.start();
mAlwaysDiscoverable.stop();
assertThat(mAlwaysDiscoverable.mStarted).isFalse();
}
@Test
public void stopWithoutStart() {
mAlwaysDiscoverable.stop();
// expect no crash
verify(mLocalAdapter, never()).setScanMode(anyInt());
}
@Test
public void startSetsModeAndRegistersReceiver() {
when(mLocalAdapter.getScanMode()).thenReturn(BluetoothAdapter.SCAN_MODE_NONE);
mAlwaysDiscoverable.start();
verify(mLocalAdapter).setScanMode(eq(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE));
verify(mContext).registerReceiver(eq(mAlwaysDiscoverable), any());
}
@Test
public void stopUnregistersReceiver() {
mAlwaysDiscoverable.start();
mAlwaysDiscoverable.stop();
verify(mContext).unregisterReceiver(mAlwaysDiscoverable);
}
@Test
public void resetsToDiscoverableModeWhenScanModeChanges() {
mAlwaysDiscoverable.start();
verify(mLocalAdapter, times(1)).setScanMode(
BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
sendScanModeChangedIntent(BluetoothAdapter.SCAN_MODE_CONNECTABLE,
BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
verify(mLocalAdapter, times(2)).setScanMode(
BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
}
private void sendScanModeChangedIntent(int newMode, int previousMode) {
when(mLocalAdapter.getScanMode()).thenReturn(newMode);
Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, newMode);
intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_SCAN_MODE, previousMode);
mAlwaysDiscoverable.onReceive(mContext, intent);
}
}

View File

@@ -85,6 +85,7 @@ public class BluetoothPairingDetailTest {
mFragment.mLocalAdapter = mLocalAdapter;
mFragment.mLocalManager = mLocalManager;
mFragment.mDeviceListGroup = mPreferenceGroup;
mFragment.mAlwaysDiscoverable = new AlwaysDiscoverable(mContext, mLocalAdapter);
}
@Test