From 88579e2558b95107c3179060cc4ec93b33fe1ff5 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Thu, 16 Nov 2017 10:14:41 -0800 Subject: [PATCH] Add infrastructure for connected device group. The core thing is to find out a way to update group when devices (bt device and usb device)are updated. The infrastructure contains three parts: 1. ConnectedDeviceController Normal PreferenceController. Get info from sub controller and update the preferenceGroup. 2. BluetoothDeviceUpdater Listen to bluetooth callback, decide whether to add/remove devices 3. DevicePreferenceCallback Interface to add/remove preference. This interface will be used in ConntectedDeviceController and future SavedDeviceController Bug: 69333961 Test: RunsettingsRoboTests Change-Id: I85a9ef216a801d5f0dd1cf0130d53850a68be4bd --- .../bluetooth/BluetoothDeviceUpdater.java | 181 ++++++++++++++++++ .../ConnectedDeviceGroupController.java | 78 ++++++++ .../DevicePreferenceCallback.java | 36 ++++ .../bluetooth/BluetoothDeviceUpdaterTest.java | 127 ++++++++++++ 4 files changed, 422 insertions(+) create mode 100644 src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java create mode 100644 src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java create mode 100644 src/com/android/settings/connecteddevice/DevicePreferenceCallback.java create mode 100644 tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java new file mode 100644 index 00000000000..e053bc9df25 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java @@ -0,0 +1,181 @@ +/* + * 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.BluetoothDevice; +import android.content.Context; +import android.os.Bundle; +import android.os.SystemProperties; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.connecteddevice.DevicePreferenceCallback; +import com.android.settings.widget.GearPreference; +import com.android.settingslib.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.BluetoothDeviceFilter; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * Update the bluetooth devices. It gets bluetooth event from {@link LocalBluetoothManager} using + * {@link BluetoothCallback}. It notifies the upper level whether to add/remove the preference + * through {@link DevicePreferenceCallback} + * + * In {@link BluetoothDeviceUpdater}, it uses {@link BluetoothDeviceFilter.Filter} to detect + * whether the {@link CachedBluetoothDevice} is relevant. + */ +public abstract class BluetoothDeviceUpdater implements BluetoothCallback { + private static final String BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY = + "persist.bluetooth.showdeviceswithoutnames"; + + protected final LocalBluetoothManager mLocalManager; + protected final DevicePreferenceCallback mDevicePreferenceCallback; + protected final Map mPreferenceMap; + protected Context mPrefContext; + + private final boolean mShowDeviceWithoutNames; + private DashboardFragment mFragment; + + @VisibleForTesting + final GearPreference.OnGearClickListener mDeviceProfilesListener = pref -> { + final CachedBluetoothDevice device = + ((BluetoothDevicePreference) pref).getBluetoothDevice(); + if (device == null) { + return; + } + final Bundle args = new Bundle(); + args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS, + device.getDevice().getAddress()); + final SettingsActivity activity = (SettingsActivity) mFragment.getActivity(); + activity.startPreferencePanel(mFragment, + BluetoothDeviceDetailsFragment.class.getName(), args, + R.string.device_details_title, null, null, 0); + + }; + + public BluetoothDeviceUpdater(DashboardFragment fragment, + DevicePreferenceCallback devicePreferenceCallback) { + this(fragment, devicePreferenceCallback, Utils.getLocalBtManager(fragment.getContext())); + } + + @VisibleForTesting + BluetoothDeviceUpdater(DashboardFragment fragment, + DevicePreferenceCallback devicePreferenceCallback, LocalBluetoothManager localManager) { + mFragment = fragment; + mDevicePreferenceCallback = devicePreferenceCallback; + mShowDeviceWithoutNames = SystemProperties.getBoolean( + BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false); + mPreferenceMap = new HashMap<>(); + mLocalManager = localManager; + } + + /** + * Register the bluetooth event callback and update the list + */ + public void registerCallback() { + mLocalManager.setForegroundActivity(mFragment.getContext()); + mLocalManager.getEventManager().registerCallback(this); + forceUpdate(); + } + + /** + * Unregister the bluetooth event callback + */ + public void unregisterCallback() { + mLocalManager.setForegroundActivity(null); + mLocalManager.getEventManager().unregisterCallback(this); + } + + /** + * Force to update the list of bluetooth devices + */ + public void forceUpdate() { + Collection cachedDevices = + mLocalManager.getCachedDeviceManager().getCachedDevicesCopy(); + for (CachedBluetoothDevice cachedBluetoothDevice : cachedDevices) { + update(cachedBluetoothDevice); + } + } + + @Override + public void onBluetoothStateChanged(int bluetoothState) { + forceUpdate(); + } + + @Override + public void onScanningStateChanged(boolean started) {} + + @Override + public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { + update(cachedDevice); + } + + @Override + public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {} + + @Override + public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { + update(cachedDevice); + } + + @Override + public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {} + + /** + * Set the context to generate the {@link Preference}, so it could get the correct theme. + */ + public void setPrefContext(Context context) { + mPrefContext = context; + } + + /** + * Update whether to show {@cde cachedBluetoothDevice} in the list. + */ + abstract public void update(CachedBluetoothDevice cachedBluetoothDevice); + + /** + * Add the {@link Preference} that represents the {@code cachedDevice} + */ + protected void addPreference(CachedBluetoothDevice cachedDevice) { + final BluetoothDevice device = cachedDevice.getDevice(); + if (!mPreferenceMap.containsKey(device)) { + BluetoothDevicePreference btPreference = + new BluetoothDevicePreference(mPrefContext, cachedDevice, + mShowDeviceWithoutNames); + btPreference.setOnGearClickListener(mDeviceProfilesListener); + mPreferenceMap.put(device, btPreference); + mDevicePreferenceCallback.onDeviceAdded(btPreference); + } + } + + /** + * Remove the {@link Preference} that represents the {@code cachedDevice} + */ + protected void removePreference(CachedBluetoothDevice cachedDevice) { + final BluetoothDevice device = cachedDevice.getDevice(); + if (mPreferenceMap.containsKey(device)) { + mDevicePreferenceCallback.onDeviceRemoved(mPreferenceMap.get(device)); + mPreferenceMap.remove(device); + } + } +} diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java b/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java new file mode 100644 index 00000000000..0eab26d45ae --- /dev/null +++ b/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java @@ -0,0 +1,78 @@ +/* + * 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.connecteddevice; + +import android.content.Context; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.bluetooth.BluetoothDeviceUpdater; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +/** + * Controller to maintain the {@link android.support.v7.preference.PreferenceGroup} for all + * connected devices. It uses {@link DevicePreferenceCallback} to add/remove {@link Preference} + * + * TODO(b/69333961): add real impl + */ +public class ConnectedDeviceGroupController extends AbstractPreferenceController + implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop, + DevicePreferenceCallback { + private BluetoothDeviceUpdater mBluetoothController; + + public ConnectedDeviceGroupController(Context context) { + super(context); + } + + @Override + public void onStart() { + mBluetoothController.registerCallback(); + } + + @Override + public void onStop() { + mBluetoothController.unregisterCallback(); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + + } + + @Override + public boolean isAvailable() { + return false; + } + + @Override + public String getPreferenceKey() { + return null; + } + + @Override + public void onDeviceAdded(Preference preference) { + + } + + @Override + public void onDeviceRemoved(Preference preference) { + + } +} diff --git a/src/com/android/settings/connecteddevice/DevicePreferenceCallback.java b/src/com/android/settings/connecteddevice/DevicePreferenceCallback.java new file mode 100644 index 00000000000..5f0470068a6 --- /dev/null +++ b/src/com/android/settings/connecteddevice/DevicePreferenceCallback.java @@ -0,0 +1,36 @@ +/* + * 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.connecteddevice; + +import android.support.v7.preference.Preference; + +/** + * Callback to add or remove {@link Preference} in device group. + */ +public interface DevicePreferenceCallback { + /** + * Called when a device(i.e. bluetooth, usb) is added + * @param preference present the device + */ + void onDeviceAdded(Preference preference); + + /** + * Called when a device(i.e. bluetooth, usb) is removed + * @param preference present the device + */ + void onDeviceRemoved(Preference preference); +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java new file mode 100644 index 00000000000..525f70e4da4 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java @@ -0,0 +1,127 @@ +/* + * 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.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.support.v7.preference.Preference; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.TestConfig; +import com.android.settings.connecteddevice.DevicePreferenceCallback; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class BluetoothDeviceUpdaterTest { + @Mock + private DashboardFragment mDashboardFragment; + @Mock + private DevicePreferenceCallback mDevicePreferenceCallback; + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private BluetoothDevice mBluetoothDevice; + @Mock + private SettingsActivity mSettingsActivity; + + private Context mContext; + private BluetoothDeviceUpdater mBluetoothDeviceUpdater; + private BluetoothDevicePreference mPreference; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + doReturn(mContext).when(mDashboardFragment).getContext(); + doReturn(mBluetoothDevice).when(mCachedBluetoothDevice).getDevice(); + + mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, false); + mBluetoothDeviceUpdater = new BluetoothDeviceUpdater(mDashboardFragment, + mDevicePreferenceCallback, null) { + @Override + public void update(CachedBluetoothDevice cachedBluetoothDevice) { + // do nothing + } + }; + mBluetoothDeviceUpdater.setPrefContext(mContext); + } + + @Test + public void testAddPreference_deviceExist_doNothing() { + mBluetoothDeviceUpdater.mPreferenceMap.put(mBluetoothDevice, mPreference); + + mBluetoothDeviceUpdater.addPreference(mCachedBluetoothDevice); + + verify(mDevicePreferenceCallback, never()).onDeviceAdded(any(Preference.class)); + } + + @Test + public void testAddPreference_deviceNotExist_addPreference() { + mBluetoothDeviceUpdater.addPreference(mCachedBluetoothDevice); + + verify(mDevicePreferenceCallback).onDeviceAdded(any(Preference.class)); + assertThat(mBluetoothDeviceUpdater.mPreferenceMap.containsKey(mBluetoothDevice)).isTrue(); + } + + @Test + public void testRemovePreference_deviceExist_removePreference() { + mBluetoothDeviceUpdater.mPreferenceMap.put(mBluetoothDevice, mPreference); + + mBluetoothDeviceUpdater.removePreference(mCachedBluetoothDevice); + + verify(mDevicePreferenceCallback).onDeviceRemoved(mPreference); + assertThat(mBluetoothDeviceUpdater.mPreferenceMap.containsKey(mBluetoothDevice)).isFalse(); + } + + @Test + public void testRemovePreference_deviceNotExist_doNothing() { + mBluetoothDeviceUpdater.removePreference(mCachedBluetoothDevice); + + verify(mDevicePreferenceCallback, never()).onDeviceRemoved(any(Preference.class)); + } + + @Test + public void testDeviceProfilesListener_click_startBluetoothDeviceDetailPage() { + doReturn(mSettingsActivity).when(mDashboardFragment).getActivity(); + + mBluetoothDeviceUpdater.mDeviceProfilesListener.onGearClick(mPreference); + + verify(mSettingsActivity).startPreferencePanel(eq(mDashboardFragment), + eq(BluetoothDeviceDetailsFragment.class.getName()), any(), + eq(R.string.device_details_title), eq(null), eq(null), eq(0)); + } +}