From c1fb0ae24046e71f83a61fdee9172aa666a4535b Mon Sep 17 00:00:00 2001 From: jasonwshsu Date: Fri, 30 Dec 2022 03:11:48 +0800 Subject: [PATCH] [Pair hearing devices] Add "Saved devices" to show bonded but not connected hearing devices * BaseHearingDevicePreferenceController will also be used in "Hearing devices", so extract to parent class first. Bug: 237625815 Test: make RunSettingsRoboTests ROBOTEST_FILTER="(SavedHearingDeviceUpdaterTest|BaseBluetoothDevicePreferenceControllerTest|BluetoothDeviceUpdaterTest|AvailableMediaBluetoothDeviceUpdaterTest|ConnectedBluetoothDeviceUpdaterTest|SavedBluetoothDeviceUpdaterTest)" Change-Id: I8a492866f48e3a664b9ff78bce5a4f082c0dc465 --- res/xml/accessibility_hearing_aids.xml | 5 + .../AccessibilityHearingAidsFragment.java | 7 + ...seBluetoothDevicePreferenceController.java | 73 +++++++++ ...avedHearingDevicePreferenceController.java | 89 +++++++++++ .../SavedHearingDeviceUpdater.java | 53 +++++++ .../AvailableMediaBluetoothDeviceUpdater.java | 9 +- .../bluetooth/BluetoothDeviceUpdater.java | 37 ++--- .../ConnectedBluetoothDeviceUpdater.java | 7 +- .../SavedBluetoothDeviceUpdater.java | 17 +-- .../AvailableMediaDeviceGroupController.java | 2 +- .../ConnectedDeviceGroupController.java | 3 +- ...lyConnectedDevicePreferenceController.java | 3 +- .../SavedDeviceGroupController.java | 3 +- .../slices/BluetoothDevicesSlice.java | 5 +- ...uetoothDevicePreferenceControllerTest.java | 135 +++++++++++++++++ .../SavedHearingDeviceUpdaterTest.java | 138 ++++++++++++++++++ ...ilableMediaBluetoothDeviceUpdaterTest.java | 2 +- .../bluetooth/BluetoothDeviceUpdaterTest.java | 45 +++--- .../ConnectedBluetoothDeviceUpdaterTest.java | 2 +- .../SavedBluetoothDeviceUpdaterTest.java | 4 +- 20 files changed, 573 insertions(+), 66 deletions(-) create mode 100644 src/com/android/settings/accessibility/BaseBluetoothDevicePreferenceController.java create mode 100644 src/com/android/settings/accessibility/SavedHearingDevicePreferenceController.java create mode 100644 src/com/android/settings/accessibility/SavedHearingDeviceUpdater.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/BaseBluetoothDevicePreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/SavedHearingDeviceUpdaterTest.java diff --git a/res/xml/accessibility_hearing_aids.xml b/res/xml/accessibility_hearing_aids.xml index f5e65aef155..5d6cff14ca0 100644 --- a/res/xml/accessibility_hearing_aids.xml +++ b/res/xml/accessibility_hearing_aids.xml @@ -27,6 +27,11 @@ settings:userRestriction="no_config_bluetooth" settings:useAdminDisabledSummary="true" /> + + diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java index 5df8afb425c..519b751421b 100644 --- a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java +++ b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java @@ -19,6 +19,7 @@ package com.android.settings.accessibility; import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; import android.content.ComponentName; +import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -44,6 +45,12 @@ public class AccessibilityHearingAidsFragment extends AccessibilityShortcutPrefe super(DISALLOW_CONFIG_BLUETOOTH); } + @Override + public void onAttach(Context context) { + super.onAttach(context); + use(SavedHearingDevicePreferenceController.class).init(this); + } + @Override public void onCreate(Bundle savedInstanceState) { mFeatureName = getContext().getString(R.string.accessibility_hearingaid_title); diff --git a/src/com/android/settings/accessibility/BaseBluetoothDevicePreferenceController.java b/src/com/android/settings/accessibility/BaseBluetoothDevicePreferenceController.java new file mode 100644 index 00000000000..c5c62976153 --- /dev/null +++ b/src/com/android/settings/accessibility/BaseBluetoothDevicePreferenceController.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 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.accessibility; + +import android.content.Context; +import android.content.pm.PackageManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.settings.connecteddevice.DevicePreferenceCallback; +import com.android.settings.core.BasePreferenceController; + +/** + * Abstract base class for bluetooth preference controller to handle UI logic, e.g. availability + * status, preference added, and preference removed. + */ +public abstract class BaseBluetoothDevicePreferenceController extends BasePreferenceController + implements DevicePreferenceCallback { + + private PreferenceCategory mPreferenceCategory; + + public BaseBluetoothDevicePreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) + ? AVAILABLE + : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreferenceCategory = screen.findPreference(getPreferenceKey()); + mPreferenceCategory.setVisible(false); + } + + @Override + public void onDeviceAdded(Preference preference) { + if (mPreferenceCategory.getPreferenceCount() == 0) { + mPreferenceCategory.setVisible(true); + } + mPreferenceCategory.addPreference(preference); + } + + @Override + public void onDeviceRemoved(Preference preference) { + mPreferenceCategory.removePreference(preference); + if (mPreferenceCategory.getPreferenceCount() == 0) { + mPreferenceCategory.setVisible(false); + } + } +} diff --git a/src/com/android/settings/accessibility/SavedHearingDevicePreferenceController.java b/src/com/android/settings/accessibility/SavedHearingDevicePreferenceController.java new file mode 100644 index 00000000000..20e227c48bf --- /dev/null +++ b/src/com/android/settings/accessibility/SavedHearingDevicePreferenceController.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2023 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.accessibility; + +import android.content.Context; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.bluetooth.BluetoothDeviceUpdater; +import com.android.settings.connecteddevice.DevicePreferenceCallback; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +/** + * Controller to update the {@link androidx.preference.PreferenceCategory} for all + * saved ((bonded but not connected)) hearing devices, including ASHA and HAP profile. + * Parent class {@link BaseBluetoothDevicePreferenceController} will use + * {@link DevicePreferenceCallback} to add/remove {@link Preference}. + */ +public class SavedHearingDevicePreferenceController extends + BaseBluetoothDevicePreferenceController implements LifecycleObserver, OnStart, OnResume, + OnStop { + + private BluetoothDeviceUpdater mSavedHearingDeviceUpdater; + + public SavedHearingDevicePreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + } + + /** + * Initializes objects in this controller. Need to call this before onStart(). + * + *

Should not call this more than 1 time. + * + * @param fragment The {@link DashboardFragment} uses the controller. + */ + public void init(DashboardFragment fragment) { + if (mSavedHearingDeviceUpdater != null) { + throw new IllegalStateException("Should not call init() more than 1 time."); + } + mSavedHearingDeviceUpdater = new SavedHearingDeviceUpdater(fragment.getContext(), this, + fragment.getMetricsCategory()); + } + + @Override + public void onStart() { + mSavedHearingDeviceUpdater.registerCallback(); + } + + @Override + public void onResume() { + mSavedHearingDeviceUpdater.refreshPreference(); + } + + @Override + public void onStop() { + mSavedHearingDeviceUpdater.unregisterCallback(); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + if (isAvailable()) { + final Context context = screen.getContext(); + mSavedHearingDeviceUpdater.setPrefContext(context); + mSavedHearingDeviceUpdater.forceUpdate(); + } + } +} diff --git a/src/com/android/settings/accessibility/SavedHearingDeviceUpdater.java b/src/com/android/settings/accessibility/SavedHearingDeviceUpdater.java new file mode 100644 index 00000000000..645d09143a7 --- /dev/null +++ b/src/com/android/settings/accessibility/SavedHearingDeviceUpdater.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 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.accessibility; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; + +import com.android.settings.bluetooth.SavedBluetoothDeviceUpdater; +import com.android.settings.connecteddevice.DevicePreferenceCallback; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; + +/** + * Maintains and updates saved (bonded but not connected) hearing devices, including ASHA and HAP + * profile. + */ +public class SavedHearingDeviceUpdater extends SavedBluetoothDeviceUpdater { + + private static final String PREF_KEY = "saved_hearing_device"; + + public SavedHearingDeviceUpdater(Context context, + DevicePreferenceCallback devicePreferenceCallback, int metricsCategory) { + super(context, devicePreferenceCallback, /* showConnectedDevice= */ false, metricsCategory); + } + + @Override + public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) { + final BluetoothDevice device = cachedDevice.getDevice(); + final boolean isSavedHearingAidDevice = cachedDevice.isHearingAidDevice() + && device.getBondState() == BluetoothDevice.BOND_BONDED + && !device.isConnected(); + + return isSavedHearingAidDevice && isDeviceInCachedDevicesList(cachedDevice); + } + + @Override + protected String getPreferenceKey() { + return PREF_KEY; + } +} diff --git a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java index c3d3b82a6f4..ec131d46edd 100644 --- a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java @@ -23,7 +23,6 @@ import android.util.Log; import androidx.preference.Preference; import com.android.settings.connecteddevice.DevicePreferenceCallback; -import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; /** @@ -39,9 +38,9 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater private final AudioManager mAudioManager; - public AvailableMediaBluetoothDeviceUpdater(Context context, DashboardFragment fragment, - DevicePreferenceCallback devicePreferenceCallback) { - super(context, fragment, devicePreferenceCallback); + public AvailableMediaBluetoothDeviceUpdater(Context context, + DevicePreferenceCallback devicePreferenceCallback, int metricsCategory) { + super(context, devicePreferenceCallback, metricsCategory); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); } @@ -102,7 +101,7 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater @Override public boolean onPreferenceClick(Preference preference) { - mMetricsFeatureProvider.logClickedPreference(preference, mFragment.getMetricsCategory()); + mMetricsFeatureProvider.logClickedPreference(preference, mMetricsCategory); final CachedBluetoothDevice device = ((BluetoothDevicePreference) preference) .getBluetoothDevice(); return device.setActive(); diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java index 89346768409..25e5fbb9928 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java @@ -27,11 +27,9 @@ import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.connecteddevice.DevicePreferenceCallback; import com.android.settings.core.SubSettingLauncher; -import com.android.settings.dashboard.DashboardFragment; import com.android.settings.overlay.FeatureFactory; 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 com.android.settingslib.bluetooth.LocalBluetoothProfileManager; @@ -46,39 +44,42 @@ import java.util.Map; * {@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. + * In {@link BluetoothDeviceUpdater}, it uses {@link #isFilterMatched(CachedBluetoothDevice)} to + * detect whether the {@link CachedBluetoothDevice} is relevant. */ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, LocalBluetoothProfileManager.ServiceListener { - private static final String TAG = "BluetoothDeviceUpdater"; - private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); - protected final MetricsFeatureProvider mMetricsFeatureProvider; protected final DevicePreferenceCallback mDevicePreferenceCallback; protected final Map mPreferenceMap; + protected Context mContext; protected Context mPrefContext; - protected DashboardFragment mFragment; @VisibleForTesting protected LocalBluetoothManager mLocalManager; + protected int mMetricsCategory; + + private static final String TAG = "BluetoothDeviceUpdater"; + private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); @VisibleForTesting final GearPreference.OnGearClickListener mDeviceProfilesListener = pref -> { launchDeviceDetails(pref); }; - public BluetoothDeviceUpdater(Context context, DashboardFragment fragment, - DevicePreferenceCallback devicePreferenceCallback) { - this(context, fragment, devicePreferenceCallback, Utils.getLocalBtManager(context)); + public BluetoothDeviceUpdater(Context context, + DevicePreferenceCallback devicePreferenceCallback, int metricsCategory) { + this(context, devicePreferenceCallback, Utils.getLocalBtManager(context), metricsCategory); } @VisibleForTesting - BluetoothDeviceUpdater(Context context, DashboardFragment fragment, - DevicePreferenceCallback devicePreferenceCallback, LocalBluetoothManager localManager) { - mFragment = fragment; + BluetoothDeviceUpdater(Context context, + DevicePreferenceCallback devicePreferenceCallback, LocalBluetoothManager localManager, + int metricsCategory) { + mContext = context; mDevicePreferenceCallback = devicePreferenceCallback; mPreferenceMap = new HashMap<>(); mLocalManager = localManager; + mMetricsCategory = metricsCategory; mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); } @@ -90,7 +91,7 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, Log.e(TAG, "registerCallback() Bluetooth is not supported on this device"); return; } - mLocalManager.setForegroundActivity(mFragment.getContext()); + mLocalManager.setForegroundActivity(mContext); mLocalManager.getEventManager().registerCallback(this); mLocalManager.getProfileManager().addServiceListener(this); forceUpdate(); @@ -283,7 +284,7 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, * {@link SubSettingLauncher} to launch {@link BluetoothDeviceDetailsFragment} */ protected void launchDeviceDetails(Preference preference) { - mMetricsFeatureProvider.logClickedPreference(preference, mFragment.getMetricsCategory()); + mMetricsFeatureProvider.logClickedPreference(preference, mMetricsCategory); final CachedBluetoothDevice device = ((BluetoothDevicePreference) preference).getBluetoothDevice(); if (device == null) { @@ -293,11 +294,11 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS, device.getDevice().getAddress()); - new SubSettingLauncher(mFragment.getContext()) + new SubSettingLauncher(mContext) .setDestination(BluetoothDeviceDetailsFragment.class.getName()) .setArguments(args) .setTitleRes(R.string.device_details_title) - .setSourceMetricsCategory(mFragment.getMetricsCategory()) + .setSourceMetricsCategory(mMetricsCategory) .launch(); } diff --git a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java index 46c4fc31646..7bb26968d93 100644 --- a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java @@ -24,7 +24,6 @@ import android.util.Log; import androidx.preference.Preference; import com.android.settings.connecteddevice.DevicePreferenceCallback; -import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; /** @@ -39,9 +38,9 @@ public class ConnectedBluetoothDeviceUpdater extends BluetoothDeviceUpdater { private final AudioManager mAudioManager; - public ConnectedBluetoothDeviceUpdater(Context context, DashboardFragment fragment, - DevicePreferenceCallback devicePreferenceCallback) { - super(context, fragment, devicePreferenceCallback); + public ConnectedBluetoothDeviceUpdater(Context context, + DevicePreferenceCallback devicePreferenceCallback, int metricsCategory) { + super(context, devicePreferenceCallback, metricsCategory); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); } diff --git a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java index e7a831718c7..a4a94511ffc 100644 --- a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java @@ -25,8 +25,6 @@ import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import com.android.settings.connecteddevice.DevicePreferenceCallback; -import com.android.settings.connecteddevice.PreviouslyConnectedDeviceDashboardFragment; -import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; @@ -44,15 +42,16 @@ public class SavedBluetoothDeviceUpdater extends BluetoothDeviceUpdater private static final String PREF_KEY = "saved_bt"; - private final boolean mDisplayConnected; + private final boolean mShowConnectedDevice; @VisibleForTesting BluetoothAdapter mBluetoothAdapter; - public SavedBluetoothDeviceUpdater(Context context, DashboardFragment fragment, - DevicePreferenceCallback devicePreferenceCallback) { - super(context, fragment, devicePreferenceCallback); - mDisplayConnected = (fragment instanceof PreviouslyConnectedDeviceDashboardFragment); + public SavedBluetoothDeviceUpdater(Context context, + DevicePreferenceCallback devicePreferenceCallback, boolean showConnectedDevice, + int metricsCategory) { + super(context, devicePreferenceCallback, metricsCategory); + mShowConnectedDevice = showConnectedDevice; mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); } @@ -106,13 +105,13 @@ public class SavedBluetoothDeviceUpdater extends BluetoothDeviceUpdater + cachedDevice.isConnected()); } return device.getBondState() == BluetoothDevice.BOND_BONDED - && (mDisplayConnected || (!device.isConnected() && isDeviceInCachedDevicesList( + && (mShowConnectedDevice || (!device.isConnected() && isDeviceInCachedDevicesList( cachedDevice))); } @Override public boolean onPreferenceClick(Preference preference) { - mMetricsFeatureProvider.logClickedPreference(preference, mFragment.getMetricsCategory()); + mMetricsFeatureProvider.logClickedPreference(preference, mMetricsCategory); final CachedBluetoothDevice device = ((BluetoothDevicePreference) preference) .getBluetoothDevice(); if (device.isConnected()) { diff --git a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java index 6623b979066..a3400155bd9 100644 --- a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java +++ b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java @@ -131,7 +131,7 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle public void init(DashboardFragment fragment) { mFragmentManager = fragment.getParentFragmentManager(); mBluetoothDeviceUpdater = new AvailableMediaBluetoothDeviceUpdater(fragment.getContext(), - fragment, AvailableMediaDeviceGroupController.this); + AvailableMediaDeviceGroupController.this, fragment.getMetricsCategory()); } @VisibleForTesting diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java b/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java index 0d51ebed6ab..f7517b4847f 100644 --- a/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java +++ b/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java @@ -184,7 +184,8 @@ public class ConnectedDeviceGroupController extends BasePreferenceController final DockUpdater connectedDockUpdater = dockUpdaterFeatureProvider.getConnectedDockUpdater(context, this); init(hasBluetoothFeature() - ? new ConnectedBluetoothDeviceUpdater(context, fragment, this) + ? new ConnectedBluetoothDeviceUpdater(context, this, + fragment.getMetricsCategory()) : null, hasUsbFeature() ? new ConnectedUsbDeviceUpdater(context, fragment, this) diff --git a/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java b/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java index afeca512815..5c906fd2e49 100644 --- a/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java +++ b/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java @@ -125,7 +125,8 @@ public class PreviouslyConnectedDevicePreferenceController extends BasePreferenc public void init(DashboardFragment fragment) { mBluetoothDeviceUpdater = new SavedBluetoothDeviceUpdater(fragment.getContext(), - fragment, PreviouslyConnectedDevicePreferenceController.this); + PreviouslyConnectedDevicePreferenceController.this, /* showConnectedDevice= */ + false, fragment.getMetricsCategory()); } @Override diff --git a/src/com/android/settings/connecteddevice/SavedDeviceGroupController.java b/src/com/android/settings/connecteddevice/SavedDeviceGroupController.java index df721f18bcd..3034e2fa785 100644 --- a/src/com/android/settings/connecteddevice/SavedDeviceGroupController.java +++ b/src/com/android/settings/connecteddevice/SavedDeviceGroupController.java @@ -117,7 +117,8 @@ public class SavedDeviceGroupController extends BasePreferenceController public void init(DashboardFragment fragment) { mBluetoothDeviceUpdater = new SavedBluetoothDeviceUpdater(fragment.getContext(), - fragment, SavedDeviceGroupController.this); + SavedDeviceGroupController.this, /* showConnectedDevice= */true, + fragment.getMetricsCategory()); } @VisibleForTesting diff --git a/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java b/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java index 4e276c12c58..f1f9521b96f 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java +++ b/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java @@ -332,12 +332,13 @@ public class BluetoothDevicesSlice implements CustomSliceable { private void lazyInitUpdaters() { if (mAvailableMediaBtDeviceUpdater == null) { mAvailableMediaBtDeviceUpdater = new AvailableMediaBluetoothDeviceUpdater(mContext, - null /* fragment */, null /* devicePreferenceCallback */); + /* devicePreferenceCallback= */ null, /* metricsCategory= */ 0); } if (mSavedBtDeviceUpdater == null) { mSavedBtDeviceUpdater = new SavedBluetoothDeviceUpdater(mContext, - null /* fragment */, null /* devicePreferenceCallback */); + /* devicePreferenceCallback= */ null, /* showConnectedDevice= */ + false, /* metricsCategory= */ 0); } } diff --git a/tests/robotests/src/com/android/settings/accessibility/BaseBluetoothDevicePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/BaseBluetoothDevicePreferenceControllerTest.java new file mode 100644 index 00000000000..7e064c45344 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/BaseBluetoothDevicePreferenceControllerTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2023 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.accessibility; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; + +import android.content.Context; +import android.content.pm.PackageManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.bluetooth.Utils; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +/** Tests for {@link BaseBluetoothDevicePreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothUtils.class}) +public class BaseBluetoothDevicePreferenceControllerTest { + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + + private static final String FAKE_KEY = "fake_key"; + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + + @Mock + private LocalBluetoothManager mLocalBluetoothManager; + @Mock + private PackageManager mPackageManager; + private PreferenceCategory mPreferenceCategory; + private PreferenceManager mPreferenceManager; + private PreferenceScreen mScreen; + private TestBaseBluetoothDevicePreferenceController mController; + + @Before + public void setUp() { + FakeFeatureFactory.setupForTest(); + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; + mPreferenceCategory = new PreferenceCategory(mContext); + mPreferenceCategory.setKey(FAKE_KEY); + mLocalBluetoothManager = Utils.getLocalBtManager(mContext); + mPreferenceManager = new PreferenceManager(mContext); + mScreen = mPreferenceManager.createPreferenceScreen(mContext); + mScreen.addPreference(mPreferenceCategory); + doReturn(mPackageManager).when(mContext).getPackageManager(); + mController = new TestBaseBluetoothDevicePreferenceController(mContext, FAKE_KEY); + } + + @Test + public void getAvailabilityStatus_hasBluetoothFeature_available() { + doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailabilityStatus_noBluetoothFeature_conditionallyUnavailalbe() { + doReturn(false).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void displayPreference_preferenceCategoryInVisible() { + mController.displayPreference(mScreen); + + assertThat(mPreferenceCategory.isVisible()).isFalse(); + } + + @Test + public void onDeviceAdded_preferenceCategoryVisible() { + Preference preference = new Preference(mContext); + mController.displayPreference(mScreen); + + mController.onDeviceAdded(preference); + + assertThat(mPreferenceCategory.isVisible()).isTrue(); + } + + @Test + public void onDeviceRemoved_addedPreferenceFirst_preferenceCategoryInVisible() { + Preference preference = new Preference(mContext); + mController.displayPreference(mScreen); + + mController.onDeviceAdded(preference); + mController.onDeviceRemoved(preference); + + assertThat(mPreferenceCategory.isVisible()).isFalse(); + } + + public static class TestBaseBluetoothDevicePreferenceController extends + BaseBluetoothDevicePreferenceController { + + public TestBaseBluetoothDevicePreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + } + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/SavedHearingDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/accessibility/SavedHearingDeviceUpdaterTest.java new file mode 100644 index 00000000000..9946b9e9ba4 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/SavedHearingDeviceUpdaterTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2023 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.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.bluetooth.Utils; +import com.android.settings.connecteddevice.DevicePreferenceCallback; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; + +/** Tests for {@link SavedHearingDeviceUpdater}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothUtils.class}) +public class SavedHearingDeviceUpdaterTest { + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + + private final Context mContext = ApplicationProvider.getApplicationContext(); + + @Mock + private DevicePreferenceCallback mDevicePreferenceCallback; + @Mock + private CachedBluetoothDeviceManager mCachedDeviceManager; + @Mock + private LocalBluetoothManager mLocalBluetoothManager; + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private BluetoothDevice mBluetoothDevice; + private SavedHearingDeviceUpdater mUpdater; + + @Before + public void setUp() { + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; + mLocalBluetoothManager = Utils.getLocalBtManager(mContext); + when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + mUpdater = new SavedHearingDeviceUpdater(mContext, + mDevicePreferenceCallback, /* metricsCategory= */ 0); + } + + @Test + public void isFilterMatch_savedHearingDevice_returnTrue() { + CachedBluetoothDevice savedHearingDevice = mCachedBluetoothDevice; + when(savedHearingDevice.isHearingAidDevice()).thenReturn(true); + doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState(); + doReturn(false).when(mBluetoothDevice).isConnected(); + when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn( + new ArrayList<>(List.of(savedHearingDevice))); + + assertThat(mUpdater.isFilterMatched(savedHearingDevice)).isEqualTo(true); + } + + @Test + public void isFilterMatch_savedNonHearingDevice_returnFalse() { + CachedBluetoothDevice savedNonHearingDevice = mCachedBluetoothDevice; + when(savedNonHearingDevice.isHearingAidDevice()).thenReturn(false); + doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState(); + doReturn(false).when(mBluetoothDevice).isConnected(); + when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn( + new ArrayList<>(List.of(savedNonHearingDevice))); + + assertThat(mUpdater.isFilterMatched(savedNonHearingDevice)).isEqualTo(false); + } + + @Test + public void isFilterMatch_savedBondingHearingDevice_returnFalse() { + CachedBluetoothDevice savedBondingHearingDevice = mCachedBluetoothDevice; + when(savedBondingHearingDevice.isHearingAidDevice()).thenReturn(true); + doReturn(BluetoothDevice.BOND_BONDING).when(mBluetoothDevice).getBondState(); + doReturn(false).when(mBluetoothDevice).isConnected(); + when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn( + new ArrayList<>(List.of(savedBondingHearingDevice))); + + assertThat(mUpdater.isFilterMatched(savedBondingHearingDevice)).isEqualTo(false); + } + + @Test + public void isFilterMatch_connectedHearingDevice_returnFalse() { + CachedBluetoothDevice connectdHearingDevice = mCachedBluetoothDevice; + when(connectdHearingDevice.isHearingAidDevice()).thenReturn(true); + doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState(); + doReturn(true).when(mBluetoothDevice).isConnected(); + when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn( + new ArrayList<>(List.of(connectdHearingDevice))); + + assertThat(mUpdater.isFilterMatched(connectdHearingDevice)).isEqualTo(false); + } + + @Test + public void isFilterMatch_hearingDeviceNotInCachedDevicesList_returnFalse() { + CachedBluetoothDevice notInCachedDevicesListDevice = mCachedBluetoothDevice; + when(notInCachedDevicesListDevice.isHearingAidDevice()).thenReturn(true); + doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState(); + doReturn(false).when(mBluetoothDevice).isConnected(); + when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(new ArrayList<>()); + + assertThat(mUpdater.isFilterMatched(notInCachedDevicesListDevice)).isEqualTo(false); + } +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java index 0b06f3e42f1..d69b5d4d3bd 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java @@ -96,7 +96,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs); mBluetoothDeviceUpdater = spy(new AvailableMediaBluetoothDeviceUpdater(mContext, - mDashboardFragment, mDevicePreferenceCallback)); + mDevicePreferenceCallback, /* metricsCategory= */ 0)); mBluetoothDeviceUpdater.setPrefContext(mContext); mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, false, BluetoothDevicePreference.SortType.TYPE_DEFAULT); diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java index 6afa56c42fe..d165aa5f6a4 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java @@ -34,7 +34,6 @@ import androidx.preference.Preference; import com.android.settings.SettingsActivity; import com.android.settings.connecteddevice.DevicePreferenceCallback; -import com.android.settings.dashboard.DashboardFragment; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; @@ -62,8 +61,6 @@ public class BluetoothDeviceUpdaterTest { private static final String SUB_MAC_ADDRESS = "05:52:C7:0B:D8:3C"; private static final String TEST_NAME = "test_name"; - @Mock - private DashboardFragment mDashboardFragment; @Mock private DevicePreferenceCallback mDevicePreferenceCallback; @Mock @@ -84,7 +81,7 @@ public class BluetoothDeviceUpdaterTest { private Drawable mDrawable; private Context mContext; - private BluetoothDeviceUpdater mBluetoothDeviceUpdater; + private TestBluetoothDeviceUpdater mBluetoothDeviceUpdater; private BluetoothDevicePreference mPreference; private ShadowBluetoothAdapter mShadowBluetoothAdapter; private List mCachedDevices = new ArrayList<>(); @@ -97,7 +94,6 @@ public class BluetoothDeviceUpdaterTest { mContext = RuntimeEnvironment.application; mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); mCachedDevices.add(mCachedBluetoothDevice); - doReturn(mContext).when(mDashboardFragment).getContext(); when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); when(mSubCachedBluetoothDevice.getDevice()).thenReturn(mSubBluetoothDevice); when(mLocalManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); @@ -107,20 +103,10 @@ public class BluetoothDeviceUpdaterTest { when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs); mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, - false, BluetoothDevicePreference.SortType.TYPE_DEFAULT); - mBluetoothDeviceUpdater = - new BluetoothDeviceUpdater(mContext, mDashboardFragment, mDevicePreferenceCallback, - mLocalManager) { - @Override - public boolean isFilterMatched(CachedBluetoothDevice cachedBluetoothDevice) { - return true; - } - - @Override - protected String getPreferenceKey() { - return "test_bt"; - } - }; + /* showDeviceWithoutNames= */ false, + BluetoothDevicePreference.SortType.TYPE_DEFAULT); + mBluetoothDeviceUpdater = new TestBluetoothDeviceUpdater(mContext, + mDevicePreferenceCallback, mLocalManager, /* metricsCategory= */ 0); mBluetoothDeviceUpdater.setPrefContext(mContext); } @@ -185,7 +171,8 @@ public class BluetoothDeviceUpdaterTest { @Test public void testDeviceProfilesListener_click_startBluetoothDeviceDetailPage() { - doReturn(mSettingsActivity).when(mDashboardFragment).getContext(); + mBluetoothDeviceUpdater = new TestBluetoothDeviceUpdater(mSettingsActivity, + mDevicePreferenceCallback, mLocalManager, /* metricsCategory= */ 0); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); mBluetoothDeviceUpdater.mDeviceProfilesListener.onGearClick(mPreference); @@ -274,4 +261,22 @@ public class BluetoothDeviceUpdaterTest { assertThat(mPreference.getTitle()).isEqualTo(TEST_NAME); } + + public static class TestBluetoothDeviceUpdater extends BluetoothDeviceUpdater { + public TestBluetoothDeviceUpdater(Context context, + DevicePreferenceCallback devicePreferenceCallback, + LocalBluetoothManager localManager, int metricsCategory) { + super(context, devicePreferenceCallback, localManager, metricsCategory); + } + + @Override + public boolean isFilterMatched(CachedBluetoothDevice cachedBluetoothDevice) { + return true; + } + + @Override + protected String getPreferenceKey() { + return "test_bt"; + } + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java index 1f909812bec..00115d7fcea 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java @@ -97,7 +97,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs); mShadowCachedBluetoothDeviceManager.setCachedDevicesCopy(mCachedDevices); mBluetoothDeviceUpdater = spy(new ConnectedBluetoothDeviceUpdater(mContext, - mDashboardFragment, mDevicePreferenceCallback)); + mDevicePreferenceCallback, /* metricsCategory= */ 0)); mBluetoothDeviceUpdater.setPrefContext(mContext); doNothing().when(mBluetoothDeviceUpdater).addPreference(any()); doNothing().when(mBluetoothDeviceUpdater).removePreference(any()); diff --git a/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java index 255a0be5773..c22944905a2 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java @@ -91,8 +91,8 @@ public class SavedBluetoothDeviceUpdaterTest { when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs); - mBluetoothDeviceUpdater = spy(new SavedBluetoothDeviceUpdater(mContext, mDashboardFragment, - mDevicePreferenceCallback)); + mBluetoothDeviceUpdater = spy(new SavedBluetoothDeviceUpdater(mContext, + mDevicePreferenceCallback, false, /* metricsCategory= */ 0)); mBluetoothDeviceUpdater.setPrefContext(mContext); mBluetoothDeviceUpdater.mBluetoothAdapter = mBluetoothAdapter; mBluetoothDeviceUpdater.mLocalManager = mBluetoothManager;