diff --git a/res/values/arrays.xml b/res/values/arrays.xml index e4bd5501319..f0de6d05d9c 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -265,6 +265,23 @@ 5 + + + Decide automatically + Play on hearing device + Play on phone speaker + + + + + + 0 + + 1 + + 2 + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 83698eb8e79..31dd0b8ccd3 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4545,6 +4545,8 @@ Pair new device + Hearing devices + Saved devices Hearing device controls @@ -4559,7 +4561,7 @@ Pair hearing device - Available devices + Available hearing devices Don\u2019t see your hearing device? diff --git a/res/xml/accessibility_hearing_aids.xml b/res/xml/accessibility_hearing_aids.xml index f5e65aef155..76910a03d0f 100644 --- a/res/xml/accessibility_hearing_aids.xml +++ b/res/xml/accessibility_hearing_aids.xml @@ -19,13 +19,25 @@ android:key="accessibility_hearing_devices_screen" android:title="@string/accessibility_hearingaid_title"> + + + settings:useAdminDisabledSummary="true" + settings:controller="com.android.settings.connecteddevice.AddDevicePreferenceController"/> + + + + + + + + + + + + + + + + + diff --git a/res/xml/bluetooth_pairing_detail.xml b/res/xml/bluetooth_pairing_detail.xml index 86fb9e43d44..460a7083fc8 100644 --- a/res/xml/bluetooth_pairing_detail.xml +++ b/res/xml/bluetooth_pairing_detail.xml @@ -27,7 +27,7 @@ + android:title="@string/bluetooth_preference_found_media_devices"/> diff --git a/res/xml/hearing_device_pairing_detail.xml b/res/xml/hearing_device_pairing_detail.xml new file mode 100644 index 00000000000..65cc6c72a13 --- /dev/null +++ b/res/xml/hearing_device_pairing_detail.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java index cec48bbd523..cd76b47c22b 100644 --- a/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java +++ b/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java @@ -29,7 +29,6 @@ import android.content.IntentFilter; import android.os.Bundle; import android.text.TextUtils; import android.util.FeatureFlagUtils; -import android.util.Log; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.FragmentManager; @@ -55,8 +54,6 @@ import com.android.settingslib.core.lifecycle.events.OnStop; import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; import java.util.stream.Collectors; /** @@ -84,7 +81,8 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC public AccessibilityHearingAidPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); - mLocalBluetoothManager = getLocalBluetoothManager(); + mLocalBluetoothManager = com.android.settings.bluetooth.Utils.getLocalBluetoothManager( + context); mProfileManager = mLocalBluetoothManager.getProfileManager(); mCachedDeviceManager = mLocalBluetoothManager.getCachedDeviceManager(); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); @@ -269,19 +267,6 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC return false; } - private LocalBluetoothManager getLocalBluetoothManager() { - final FutureTask localBtManagerFutureTask = new FutureTask<>( - // Avoid StrictMode ThreadPolicy violation - () -> com.android.settings.bluetooth.Utils.getLocalBtManager(mContext)); - try { - localBtManagerFutureTask.run(); - return localBtManagerFutureTask.get(); - } catch (InterruptedException | ExecutionException e) { - Log.w(TAG, "Error getting LocalBluetoothManager.", e); - return null; - } - } - @VisibleForTesting(otherwise = VisibleForTesting.NONE) void setPreference(Preference preference) { mHearingAidPreference = preference; diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java index 5df8afb425c..85783b73a77 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,13 @@ public class AccessibilityHearingAidsFragment extends AccessibilityShortcutPrefe super(DISALLOW_CONFIG_BLUETOOTH); } + @Override + public void onAttach(Context context) { + super.onAttach(context); + use(AvailableHearingDevicePreferenceController.class).init(this); + 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/AvailableHearingDevicePreferenceController.java b/src/com/android/settings/accessibility/AvailableHearingDevicePreferenceController.java new file mode 100644 index 00000000000..076432c9b57 --- /dev/null +++ b/src/com/android/settings/accessibility/AvailableHearingDevicePreferenceController.java @@ -0,0 +1,109 @@ +/* + * 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.BluetoothProfile; +import android.content.Context; + +import androidx.fragment.app.FragmentManager; +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.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +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 update the {@link androidx.preference.PreferenceCategory} for all + * connected hearing devices, including ASHA and HAP profile. + * Parent class {@link BaseBluetoothDevicePreferenceController} will use + * {@link DevicePreferenceCallback} to add/remove {@link Preference}. + */ +public class AvailableHearingDevicePreferenceController extends + BaseBluetoothDevicePreferenceController implements LifecycleObserver, OnStart, OnStop, + BluetoothCallback { + + private static final String TAG = "AvailableHearingDevicePreferenceController"; + + private BluetoothDeviceUpdater mAvailableHearingDeviceUpdater; + private final LocalBluetoothManager mLocalBluetoothManager; + private FragmentManager mFragmentManager; + + public AvailableHearingDevicePreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + mLocalBluetoothManager = com.android.settings.bluetooth.Utils.getLocalBluetoothManager( + context); + } + + /** + * 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 (mAvailableHearingDeviceUpdater != null) { + throw new IllegalStateException("Should not call init() more than 1 time."); + } + mAvailableHearingDeviceUpdater = new AvailableHearingDeviceUpdater(fragment.getContext(), + this, fragment.getMetricsCategory()); + mFragmentManager = fragment.getParentFragmentManager(); + } + + @Override + public void onStart() { + mAvailableHearingDeviceUpdater.registerCallback(); + mAvailableHearingDeviceUpdater.refreshPreference(); + mLocalBluetoothManager.getEventManager().registerCallback(this); + } + + @Override + public void onStop() { + mAvailableHearingDeviceUpdater.unregisterCallback(); + mLocalBluetoothManager.getEventManager().unregisterCallback(this); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + if (isAvailable()) { + final Context context = screen.getContext(); + mAvailableHearingDeviceUpdater.setPrefContext(context); + mAvailableHearingDeviceUpdater.forceUpdate(); + } + } + + @Override + public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) { + if (activeDevice == null) { + return; + } + + if (bluetoothProfile == BluetoothProfile.HEARING_AID) { + HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, activeDevice); + } + } +} diff --git a/src/com/android/settings/accessibility/AvailableHearingDeviceUpdater.java b/src/com/android/settings/accessibility/AvailableHearingDeviceUpdater.java new file mode 100644 index 00000000000..b3d371528f6 --- /dev/null +++ b/src/com/android/settings/accessibility/AvailableHearingDeviceUpdater.java @@ -0,0 +1,51 @@ +/* + * 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.AvailableMediaBluetoothDeviceUpdater; +import com.android.settings.connecteddevice.DevicePreferenceCallback; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; + +/** + * Maintains and updates connected hearing devices, including ASHA and HAP profile. + */ +public class AvailableHearingDeviceUpdater extends AvailableMediaBluetoothDeviceUpdater { + + private static final String PREF_KEY = "connected_hearing_device"; + + public AvailableHearingDeviceUpdater(Context context, + DevicePreferenceCallback devicePreferenceCallback, int metricsCategory) { + super(context, devicePreferenceCallback, metricsCategory); + } + + @Override + public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) { + final BluetoothDevice device = cachedDevice.getDevice(); + final boolean isConnectedHearingAidDevice = (cachedDevice.isConnectedHearingAidDevice() + && (device.getBondState() == BluetoothDevice.BOND_BONDED)); + + return isConnectedHearingAidDevice && isDeviceInCachedDevicesList(cachedDevice); + } + + @Override + protected String getPreferenceKey() { + return PREF_KEY; + } +} 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/HearingDevicePairingDetail.java b/src/com/android/settings/accessibility/HearingDevicePairingDetail.java new file mode 100644 index 00000000000..aa9b587d818 --- /dev/null +++ b/src/com/android/settings/accessibility/HearingDevicePairingDetail.java @@ -0,0 +1,82 @@ +/* + * 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.bluetooth.BluetoothUuid; +import android.bluetooth.le.ScanFilter; + +import androidx.annotation.VisibleForTesting; + +import com.android.settings.R; +import com.android.settings.bluetooth.BluetoothDevicePairingDetailBase; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; + +import java.util.Collections; + +/** + * HearingDevicePairingDetail is a page to scan hearing devices. This page shows scanning icons and + * pairing them. + */ +public class HearingDevicePairingDetail extends BluetoothDevicePairingDetailBase { + + private static final String TAG = "HearingDevicePairingDetail"; + @VisibleForTesting + static final String KEY_AVAILABLE_HEARING_DEVICES = "available_hearing_devices"; + + public HearingDevicePairingDetail() { + super(); + final ScanFilter filter = new ScanFilter.Builder() + .setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}, new byte[]{0}) + .build(); + setFilter(Collections.singletonList(filter)); + } + + @Override + public void onStart() { + super.onStart(); + mAvailableDevicesCategory.setProgress(mBluetoothAdapter.isEnabled()); + } + + @Override + public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { + super.onDeviceBondStateChanged(cachedDevice, bondState); + + mAvailableDevicesCategory.setProgress(bondState == BluetoothDevice.BOND_NONE); + } + + @Override + public int getMetricsCategory() { + // TODO(b/262839191): To be updated settings_enums.proto + return 0; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.hearing_device_pairing_detail; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + public String getDeviceListKey() { + return KEY_AVAILABLE_HEARING_DEVICES; + } +} 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/BluetoothDetailsAudioRoutingController.java b/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingController.java index 509a9b0c05d..4e40323c0d8 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingController.java @@ -17,10 +17,13 @@ package com.android.settings.bluetooth; import static com.android.settings.bluetooth.BluetoothDeviceDetailsFragment.FEATURE_AUDIO_ROUTING_ORDER; +import static com.android.settings.bluetooth.BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS; import android.content.Context; +import android.os.Bundle; import android.util.FeatureFlagUtils; +import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceFragmentCompat; @@ -36,7 +39,8 @@ import com.android.settingslib.core.lifecycle.Lifecycle; public class BluetoothDetailsAudioRoutingController extends BluetoothDetailsController { private static final String KEY_FEATURE_CONTROLS_GROUP = "feature_controls_group"; - private static final String KEY_AUDIO_ROUTING = "audio_routing"; + @VisibleForTesting + static final String KEY_AUDIO_ROUTING = "audio_routing"; public BluetoothDetailsAudioRoutingController(Context context, PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle) { @@ -71,9 +75,13 @@ public class BluetoothDetailsAudioRoutingController extends BluetoothDetailsCont private Preference createAudioRoutingPreference(Context context) { final Preference preference = new Preference(context); + preference.setKey(KEY_AUDIO_ROUTING); preference.setTitle(context.getString(R.string.bluetooth_audio_routing_title)); preference.setSummary(context.getString(R.string.bluetooth_audio_routing_summary)); + final Bundle extras = preference.getExtras(); + extras.putString(KEY_DEVICE_ADDRESS, mCachedDevice.getAddress()); + preference.setFragment(BluetoothDetailsAudioRoutingFragment.class.getName()); return preference; } diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragment.java b/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragment.java new file mode 100644 index 00000000000..691aceeab1c --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragment.java @@ -0,0 +1,86 @@ +/* + * 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.bluetooth; + +import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; + +import static com.android.settings.bluetooth.BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; + +import com.android.settings.R; +import com.android.settings.dashboard.RestrictedDashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.search.SearchIndexable; + +/** Settings fragment containing bluetooth audio routing. */ +@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) +public class BluetoothDetailsAudioRoutingFragment extends RestrictedDashboardFragment { + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.bluetooth_audio_routing_fragment); + private static final String TAG = "BluetoothDetailsAudioRoutingFragment"; + @VisibleForTesting + CachedBluetoothDevice mCachedDevice; + + public BluetoothDetailsAudioRoutingFragment() { + super(DISALLOW_CONFIG_BLUETOOTH); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + final LocalBluetoothManager localBtMgr = Utils.getLocalBtManager(context); + final CachedBluetoothDeviceManager cachedDeviceMgr = localBtMgr.getCachedDeviceManager(); + final BluetoothDevice bluetoothDevice = localBtMgr.getBluetoothAdapter().getRemoteDevice( + getArguments().getString(KEY_DEVICE_ADDRESS)); + + mCachedDevice = cachedDeviceMgr.findDevice(bluetoothDevice); + if (mCachedDevice == null) { + // Close this page if device is null with invalid device mac address + Log.w(TAG, "onAttach() CachedDevice is null! Can not find address: " + + bluetoothDevice.getAnonymizedAddress()); + finish(); + return; + } + + // TODO: mCachedDevice will pass to control in next CLs. + } + + @Override + public int getMetricsCategory() { + // TODO(b/262839191): To be updated settings_enums.proto + return 0; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.bluetooth_audio_routing_fragment; + } + + @Override + protected String getLogTag() { + return TAG; + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java new file mode 100644 index 00000000000..c71decb0ab8 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java @@ -0,0 +1,200 @@ +/* + * 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.bluetooth; + +import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import com.android.settings.R; +import com.android.settings.accessibility.AccessibilityStatsLogUtils; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HearingAidStatsLogUtils; + +/** + * Abstract class for providing basic interaction for a list of Bluetooth devices in bluetooth + * device pairing detail page. + */ +public abstract class BluetoothDevicePairingDetailBase extends DeviceListPreferenceFragment { + + protected boolean mInitialScanStarted; + @VisibleForTesting + protected BluetoothProgressCategory mAvailableDevicesCategory; + + public BluetoothDevicePairingDetailBase() { + super(DISALLOW_CONFIG_BLUETOOTH); + } + + @Override + public void initPreferencesFromPreferenceScreen() { + mAvailableDevicesCategory = findPreference(getDeviceListKey()); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + mInitialScanStarted = false; + super.onViewCreated(view, savedInstanceState); + } + + @Override + public void onStart() { + super.onStart(); + if (mLocalManager == null) { + Log.e(getLogTag(), "Bluetooth is not supported on this device"); + return; + } + updateBluetooth(); + } + + @Override + public void onStop() { + super.onStop(); + if (mLocalManager == null) { + Log.e(getLogTag(), "Bluetooth is not supported on this device"); + return; + } + disableScanning(); + } + + @Override + public void onBluetoothStateChanged(int bluetoothState) { + super.onBluetoothStateChanged(bluetoothState); + updateContent(bluetoothState); + if (bluetoothState == BluetoothAdapter.STATE_ON) { + showBluetoothTurnedOnToast(); + } + } + + @Override + public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { + if (bondState == BluetoothDevice.BOND_BONDED) { + // If one device is connected(bonded), then close this fragment. + finish(); + return; + } else if (bondState == BluetoothDevice.BOND_BONDING) { + // Set the bond entry where binding process starts for logging hearing aid device info + final int pageId = FeatureFactory.getFactory( + getContext()).getMetricsFeatureProvider().getAttribution(getActivity()); + final int bondEntry = AccessibilityStatsLogUtils.convertToHearingAidInfoBondEntry( + pageId); + HearingAidStatsLogUtils.setBondEntryForDevice(bondEntry, cachedDevice); + } + if (mSelectedDevice != null && cachedDevice != null) { + BluetoothDevice device = cachedDevice.getDevice(); + if (device != null && mSelectedDevice.equals(device) + && bondState == BluetoothDevice.BOND_NONE) { + // If currently selected device failed to bond, restart scanning + enableScanning(); + } + } + } + + @Override + public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state, + int bluetoothProfile) { + // This callback is used to handle the case that bonded device is connected in pairing list. + // 1. If user selected multiple bonded devices in pairing list, after connected + // finish this page. + // 2. If the bonded devices auto connected in paring list, after connected it will be + // removed from paring list. + if (cachedDevice != null && cachedDevice.isConnected()) { + final BluetoothDevice device = cachedDevice.getDevice(); + if (device != null && mSelectedList.contains(device)) { + finish(); + } else if (mDevicePreferenceMap.containsKey(cachedDevice)) { + onDeviceDeleted(cachedDevice); + } + } + } + + @Override + public void enableScanning() { + // Clear all device states before first scan + if (!mInitialScanStarted) { + if (mAvailableDevicesCategory != null) { + removeAllDevices(); + } + mLocalManager.getCachedDeviceManager().clearNonBondedDevices(); + mInitialScanStarted = true; + } + super.enableScanning(); + } + + @Override + public void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { + disableScanning(); + super.onDevicePreferenceClick(btPreference); + } + + @VisibleForTesting + void updateBluetooth() { + if (mBluetoothAdapter.isEnabled()) { + updateContent(mBluetoothAdapter.getState()); + } else { + // Turn on bluetooth if it is disabled + mBluetoothAdapter.enable(); + } + } + + /** + * Enables the scanning when {@code bluetoothState} is on, or finish the page when + * {@code bluetoothState} is off. + * + * @param bluetoothState the current Bluetooth state, the possible values that will handle here: + * {@link android.bluetooth.BluetoothAdapter#STATE_OFF}, + * {@link android.bluetooth.BluetoothAdapter#STATE_ON}, + */ + @VisibleForTesting + public void updateContent(int bluetoothState) { + switch (bluetoothState) { + case BluetoothAdapter.STATE_ON: + mDevicePreferenceMap.clear(); + clearPreferenceGroupCache(); + mBluetoothAdapter.enable(); + enableScanning(); + break; + + case BluetoothAdapter.STATE_OFF: + finish(); + break; + } + } + + /** + * Clears all cached preferences in {@code preferenceGroup}. + */ + private void clearPreferenceGroupCache() { + cacheRemoveAllPrefs(mAvailableDevicesCategory); + removeCachedPrefs(mAvailableDevicesCategory); + } + + @VisibleForTesting + void showBluetoothTurnedOnToast() { + Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast, + Toast.LENGTH_SHORT).show(); + } +} 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/BluetoothPairingDetail.java b/src/com/android/settings/bluetooth/BluetoothPairingDetail.java index 9a92783e18a..a78bf27101c 100644 --- a/src/com/android/settings/bluetooth/BluetoothPairingDetail.java +++ b/src/com/android/settings/bluetooth/BluetoothPairingDetail.java @@ -16,31 +16,25 @@ package com.android.settings.bluetooth; -import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; - import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; import android.content.Context; import android.os.Bundle; -import android.util.Log; -import android.widget.Toast; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.settings.R; -import com.android.settings.accessibility.AccessibilityStatsLogUtils; -import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.bluetooth.BluetoothDeviceFilter; -import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.settingslib.bluetooth.HearingAidStatsLogUtils; import com.android.settingslib.search.Indexable; import com.android.settingslib.widget.FooterPreference; /** * BluetoothPairingDetail is a page to scan bluetooth devices and pair them. */ -public class BluetoothPairingDetail extends DeviceListPreferenceFragment implements +public class BluetoothPairingDetail extends BluetoothDevicePairingDetailBase implements Indexable { private static final String TAG = "BluetoothPairingDetail"; @@ -49,35 +43,13 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme @VisibleForTesting static final String KEY_FOOTER_PREF = "footer_preference"; - @VisibleForTesting - BluetoothProgressCategory mAvailableDevicesCategory; @VisibleForTesting FooterPreference mFooterPreference; @VisibleForTesting AlwaysDiscoverable mAlwaysDiscoverable; - private boolean mInitialScanStarted; - public BluetoothPairingDetail() { - super(DISALLOW_CONFIG_BLUETOOTH); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - mInitialScanStarted = false; - mAlwaysDiscoverable = new AlwaysDiscoverable(getContext()); - } - - @Override - public void onStart() { - super.onStart(); - if (mLocalManager == null){ - Log.e(TAG, "Bluetooth is not supported on this device"); - return; - } - updateBluetooth(); - mAvailableDevicesCategory.setProgress(mBluetoothAdapter.isDiscovering()); + super(); } @Override @@ -86,32 +58,29 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme use(BluetoothDeviceRenamePreferenceController.class).setFragment(this); } - @VisibleForTesting - void updateBluetooth() { - if (mBluetoothAdapter.isEnabled()) { - updateContent(mBluetoothAdapter.getState()); - } else { - // Turn on bluetooth if it is disabled - mBluetoothAdapter.enable(); - } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mAlwaysDiscoverable = new AlwaysDiscoverable(getContext()); + } + + @Override + public void onStart() { + super.onStart(); + mAvailableDevicesCategory.setProgress(mBluetoothAdapter.isDiscovering()); } @Override public void onStop() { super.onStop(); - if (mLocalManager == null){ - Log.e(TAG, "Bluetooth is not supported on this device"); - return; - } // Make the device only visible to connected devices. mAlwaysDiscoverable.stop(); - disableScanning(); } @Override - void initPreferencesFromPreferenceScreen() { - mAvailableDevicesCategory = (BluetoothProgressCategory) findPreference(KEY_AVAIL_DEVICES); - mFooterPreference = (FooterPreference) findPreference(KEY_FOOTER_PREF); + public void initPreferencesFromPreferenceScreen() { + super.initPreferencesFromPreferenceScreen(); + mFooterPreference = findPreference(KEY_FOOTER_PREF); mFooterPreference.setSelectable(false); } @@ -120,23 +89,25 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme return SettingsEnums.BLUETOOTH_PAIRING; } + /** + * {@inheritDoc} + * + * Will update footer and keep the device discoverable as long as the page is visible. + */ + @VisibleForTesting @Override - void enableScanning() { - // Clear all device states before first scan - if (!mInitialScanStarted) { - if (mAvailableDevicesCategory != null) { - removeAllDevices(); + public void updateContent(int bluetoothState) { + super.updateContent(bluetoothState); + if (bluetoothState == BluetoothAdapter.STATE_ON) { + if (mInitialScanStarted) { + // Don't show bonded devices when screen turned back on + setFilter(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER); + addCachedDevices(); } - mLocalManager.getCachedDeviceManager().clearNonBondedDevices(); - mInitialScanStarted = true; + setFilter(BluetoothDeviceFilter.ALL_FILTER); + updateFooterPreference(mFooterPreference); + mAlwaysDiscoverable.start(); } - super.enableScanning(); - } - - @Override - void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { - disableScanning(); - super.onDevicePreferenceClick(btPreference); } @Override @@ -146,78 +117,6 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme mAvailableDevicesCategory.setProgress(started); } - @VisibleForTesting - void updateContent(int bluetoothState) { - switch (bluetoothState) { - case BluetoothAdapter.STATE_ON: - mDevicePreferenceMap.clear(); - mBluetoothAdapter.enable(); - - addDeviceCategory(mAvailableDevicesCategory, - R.string.bluetooth_preference_found_media_devices, - BluetoothDeviceFilter.ALL_FILTER, mInitialScanStarted); - updateFooterPreference(mFooterPreference); - mAlwaysDiscoverable.start(); - enableScanning(); - break; - - case BluetoothAdapter.STATE_OFF: - finish(); - break; - } - } - - @Override - public void onBluetoothStateChanged(int bluetoothState) { - super.onBluetoothStateChanged(bluetoothState); - updateContent(bluetoothState); - if (bluetoothState == BluetoothAdapter.STATE_ON) { - showBluetoothTurnedOnToast(); - } - } - - @Override - public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { - if (bondState == BluetoothDevice.BOND_BONDED) { - // If one device is connected(bonded), then close this fragment. - finish(); - return; - } else if (bondState == BluetoothDevice.BOND_BONDING) { - // Set the bond entry where binding process starts for logging hearing aid device info - final int pageId = FeatureFactory.getFactory( - getContext()).getMetricsFeatureProvider().getAttribution(getActivity()); - final int bondEntry = AccessibilityStatsLogUtils.convertToHearingAidInfoBondEntry( - pageId); - HearingAidStatsLogUtils.setBondEntryForDevice(bondEntry, cachedDevice); - } - if (mSelectedDevice != null && cachedDevice != null) { - BluetoothDevice device = cachedDevice.getDevice(); - if (device != null && mSelectedDevice.equals(device) - && bondState == BluetoothDevice.BOND_NONE) { - // If currently selected device failed to bond, restart scanning - enableScanning(); - } - } - } - - @Override - public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state, - int bluetoothProfile) { - // This callback is used to handle the case that bonded device is connected in pairing list. - // 1. If user selected multiple bonded devices in pairing list, after connected - // finish this page. - // 2. If the bonded devices auto connected in paring list, after connected it will be - // removed from paring list. - if (cachedDevice != null && cachedDevice.isConnected()) { - final BluetoothDevice device = cachedDevice.getDevice(); - if (device != null && mSelectedList.contains(device)) { - finish(); - } else if (mDevicePreferenceMap.containsKey(cachedDevice)) { - onDeviceDeleted(cachedDevice); - } - } - } - @Override public int getHelpResource() { return R.string.help_url_bluetooth; @@ -237,10 +136,4 @@ public class BluetoothPairingDetail extends DeviceListPreferenceFragment impleme public String getDeviceListKey() { return KEY_AVAIL_DEVICES; } - - @VisibleForTesting - void showBluetoothTurnedOnToast() { - Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast, - Toast.LENGTH_SHORT).show(); - } } 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/DeviceListPreferenceFragment.java b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java index 21813099237..522b5cb10b0 100644 --- a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java +++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java @@ -18,6 +18,11 @@ package com.android.settings.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; import android.os.Bundle; import android.os.SystemProperties; import android.text.BidiFormatter; @@ -33,6 +38,7 @@ import com.android.settings.dashboard.RestrictedDashboardFragment; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.BluetoothDeviceFilter; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; import java.util.ArrayList; @@ -59,37 +65,51 @@ public abstract class DeviceListPreferenceFragment extends "persist.bluetooth.showdeviceswithoutnames"; private BluetoothDeviceFilter.Filter mFilter; + private List mLeScanFilters; + private ScanCallback mScanCallback; @VisibleForTesting - boolean mScanEnabled; + protected boolean mScanEnabled; - BluetoothDevice mSelectedDevice; + protected BluetoothDevice mSelectedDevice; - BluetoothAdapter mBluetoothAdapter; - LocalBluetoothManager mLocalManager; + protected BluetoothAdapter mBluetoothAdapter; + protected LocalBluetoothManager mLocalManager; + protected CachedBluetoothDeviceManager mCachedDeviceManager; @VisibleForTesting - PreferenceGroup mDeviceListGroup; + protected PreferenceGroup mDeviceListGroup; - final HashMap mDevicePreferenceMap = + protected final HashMap mDevicePreferenceMap = new HashMap<>(); - final List mSelectedList = new ArrayList<>(); + protected final List mSelectedList = new ArrayList<>(); - boolean mShowDevicesWithoutNames; + protected boolean mShowDevicesWithoutNames; - DeviceListPreferenceFragment(String restrictedKey) { + public DeviceListPreferenceFragment(String restrictedKey) { super(restrictedKey); mFilter = BluetoothDeviceFilter.ALL_FILTER; } - final void setFilter(BluetoothDeviceFilter.Filter filter) { + protected final void setFilter(BluetoothDeviceFilter.Filter filter) { mFilter = filter; } - final void setFilter(int filterType) { + protected final void setFilter(int filterType) { mFilter = BluetoothDeviceFilter.getFilter(filterType); } + /** + * Sets the bluetooth device scanning filter with {@link ScanFilter}s. It will change to start + * {@link BluetoothLeScanner} which will scan BLE device only. + * + * @param leScanFilters list of settings to filter scan result + */ + protected void setFilter(List leScanFilters) { + mFilter = null; + mLeScanFilters = leScanFilters; + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -100,6 +120,7 @@ public abstract class DeviceListPreferenceFragment extends return; } mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mCachedDeviceManager = mLocalManager.getCachedDeviceManager(); mShowDevicesWithoutNames = SystemProperties.getBoolean( BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false); @@ -109,7 +130,7 @@ public abstract class DeviceListPreferenceFragment extends } /** find and update preference that already existed in preference screen */ - abstract void initPreferencesFromPreferenceScreen(); + protected abstract void initPreferencesFromPreferenceScreen(); @Override public void onStart() { @@ -139,7 +160,7 @@ public abstract class DeviceListPreferenceFragment extends void addCachedDevices() { Collection cachedDevices = - mLocalManager.getCachedDeviceManager().getCachedDevicesCopy(); + mCachedDeviceManager.getCachedDevicesCopy(); for (CachedBluetoothDevice cachedDevice : cachedDevices) { onDeviceAdded(cachedDevice); } @@ -164,7 +185,7 @@ public abstract class DeviceListPreferenceFragment extends return super.onPreferenceTreeClick(preference); } - void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { + protected void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { btPreference.onClicked(); } @@ -177,7 +198,8 @@ public abstract class DeviceListPreferenceFragment extends // Prevent updates while the list shows one of the state messages if (mBluetoothAdapter.getState() != BluetoothAdapter.STATE_ON) return; - if (mFilter.matches(cachedDevice.getDevice())) { + if (mLeScanFilters != null + || (mFilter != null && mFilter.matches(cachedDevice.getDevice()))) { createDevicePreference(cachedDevice); } } @@ -227,7 +249,7 @@ public abstract class DeviceListPreferenceFragment extends } @VisibleForTesting - void enableScanning() { + protected void enableScanning() { // BluetoothAdapter already handles repeated scan requests if (!mScanEnabled) { startScanning(); @@ -236,7 +258,7 @@ public abstract class DeviceListPreferenceFragment extends } @VisibleForTesting - void disableScanning() { + protected void disableScanning() { if (mScanEnabled) { stopScanning(); mScanEnabled = false; @@ -250,31 +272,6 @@ public abstract class DeviceListPreferenceFragment extends } } - /** - * Add bluetooth device preferences to {@code preferenceGroup} which satisfy the {@code filter} - * - * This method will also (1) set the title for {@code preferenceGroup} and (2) change the - * default preferenceGroup and filter - * @param preferenceGroup - * @param titleId - * @param filter - * @param addCachedDevices - */ - public void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId, - BluetoothDeviceFilter.Filter filter, boolean addCachedDevices) { - cacheRemoveAllPrefs(preferenceGroup); - preferenceGroup.setTitle(titleId); - mDeviceListGroup = preferenceGroup; - if (addCachedDevices) { - // Don't show bonded devices when screen turned back on - setFilter(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER); - addCachedDevices(); - } - setFilter(filter); - preferenceGroup.setEnabled(true); - removeCachedPrefs(preferenceGroup); - } - /** * Return the key of the {@link PreferenceGroup} that contains the bluetooth devices */ @@ -284,15 +281,65 @@ public abstract class DeviceListPreferenceFragment extends return mShowDevicesWithoutNames; } + @VisibleForTesting void startScanning() { + if (mFilter != null) { + startClassicScanning(); + } else if (mLeScanFilters != null) { + startLeScanning(); + } + + } + + @VisibleForTesting + void stopScanning() { + if (mFilter != null) { + stopClassicScanning(); + } else if (mLeScanFilters != null) { + stopLeScanning(); + } + } + + private void startClassicScanning() { if (!mBluetoothAdapter.isDiscovering()) { mBluetoothAdapter.startDiscovery(); } } - void stopScanning() { + private void stopClassicScanning() { if (mBluetoothAdapter.isDiscovering()) { mBluetoothAdapter.cancelDiscovery(); } } + + private void startLeScanning() { + final BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); + final ScanSettings settings = new ScanSettings.Builder() + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .build(); + mScanCallback = new ScanCallback() { + @Override + public void onScanResult(int callbackType, ScanResult result) { + final BluetoothDevice device = result.getDevice(); + CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(device); + if (cachedDevice == null) { + cachedDevice = mCachedDeviceManager.addDevice(device); + } + onDeviceAdded(cachedDevice); + } + + @Override + public void onScanFailed(int errorCode) { + Log.w(TAG, "BLE Scan failed with error code " + errorCode); + } + }; + scanner.startScan(mLeScanFilters, settings, mScanCallback); + } + + private void stopLeScanning() { + final BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); + if (scanner != null) { + scanner.stopScan(mScanCallback); + } + } } diff --git a/src/com/android/settings/bluetooth/DevicePickerFragment.java b/src/com/android/settings/bluetooth/DevicePickerFragment.java index e8adac01e4f..2e810620e7c 100644 --- a/src/com/android/settings/bluetooth/DevicePickerFragment.java +++ b/src/com/android/settings/bluetooth/DevicePickerFragment.java @@ -27,8 +27,8 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.UserManager; -import android.util.Log; import android.text.TextUtils; +import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -68,7 +68,7 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { } @Override - void initPreferencesFromPreferenceScreen() { + public void initPreferencesFromPreferenceScreen() { Intent intent = getActivity().getIntent(); mNeedAuth = intent.getBooleanExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false); setFilter(intent.getIntExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE, @@ -136,7 +136,7 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { } @Override - void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { + public void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { disableScanning(); LocalBluetoothPreferences.persistSelectedDeviceInPicker( getActivity(), mSelectedDevice.getAddress()); 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/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java index 37c826f6127..46d8e70a416 100644 --- a/src/com/android/settings/bluetooth/Utils.java +++ b/src/com/android/settings/bluetooth/Utils.java @@ -42,6 +42,9 @@ import com.android.settingslib.bluetooth.BluetoothUtils.ErrorListener; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + /** * Utils is a helper class that contains constants for various * Android resource IDs, debug logging flags, and static methods @@ -132,6 +135,24 @@ public final class Utils { return LocalBluetoothManager.getInstance(context, mOnInitCallback); } + /** + * Obtains a {@link LocalBluetoothManager}. + * + * To avoid StrictMode ThreadPolicy violation, will get it in another thread. + */ + public static LocalBluetoothManager getLocalBluetoothManager(Context context) { + final FutureTask localBtManagerFutureTask = new FutureTask<>( + // Avoid StrictMode ThreadPolicy violation + () -> getLocalBtManager(context)); + try { + localBtManagerFutureTask.run(); + return localBtManagerFutureTask.get(); + } catch (InterruptedException | ExecutionException e) { + Log.w(TAG, "Error getting LocalBluetoothManager.", e); + return null; + } + } + public static String createRemoteName(Context context, BluetoothDevice device) { String mRemoteName = device != null ? device.getAlias() : null; 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/AvailableHearingDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/accessibility/AvailableHearingDeviceUpdaterTest.java new file mode 100644 index 00000000000..6305014a6e3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/AvailableHearingDeviceUpdaterTest.java @@ -0,0 +1,123 @@ +/* + * 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 AvailableHearingDeviceUpdater}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothUtils.class}) +public class AvailableHearingDeviceUpdaterTest { + @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 AvailableHearingDeviceUpdater mUpdater; + + @Before + public void setUp() { + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; + mLocalBluetoothManager = Utils.getLocalBtManager(mContext); + when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + mUpdater = new AvailableHearingDeviceUpdater(mContext, + mDevicePreferenceCallback, /* metricsCategory= */ 0); + } + + @Test + public void isFilterMatch_connectedHearingDevice_returnTrue() { + CachedBluetoothDevice connectedHearingDevice = mCachedBluetoothDevice; + when(connectedHearingDevice.isConnectedHearingAidDevice()).thenReturn(true); + doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState(); + when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn( + new ArrayList<>(List.of(connectedHearingDevice))); + + assertThat(mUpdater.isFilterMatched(connectedHearingDevice)).isEqualTo(true); + } + + @Test + public void isFilterMatch_nonConnectedHearingDevice_returnFalse() { + CachedBluetoothDevice nonConnectedHearingDevice = mCachedBluetoothDevice; + when(nonConnectedHearingDevice.isConnectedHearingAidDevice()).thenReturn(false); + doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState(); + when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn( + new ArrayList<>(List.of(nonConnectedHearingDevice))); + + assertThat(mUpdater.isFilterMatched(nonConnectedHearingDevice)).isEqualTo(false); + } + + @Test + public void isFilterMatch_connectedBondingHearingDevice_returnFalse() { + CachedBluetoothDevice connectedBondingHearingDevice = mCachedBluetoothDevice; + when(connectedBondingHearingDevice.isHearingAidDevice()).thenReturn(true); + doReturn(BluetoothDevice.BOND_BONDING).when(mBluetoothDevice).getBondState(); + when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn( + new ArrayList<>(List.of(connectedBondingHearingDevice))); + + assertThat(mUpdater.isFilterMatched(connectedBondingHearingDevice)).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/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/HearingDevicePairingDetailTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingDevicePairingDetailTest.java new file mode 100644 index 00000000000..e1651d9d4ed --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/HearingDevicePairingDetailTest.java @@ -0,0 +1,115 @@ +/* + * 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.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.bluetooth.BluetoothProgressCategory; +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; + +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 org.robolectric.shadow.api.Shadow; + +/** Tests for {@link HearingDevicePairingDetail}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothAdapter.class}) +public class HearingDevicePairingDetailTest { + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + private final Context mContext = ApplicationProvider.getApplicationContext(); + + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + private BluetoothProgressCategory mProgressCategory; + private TestHearingDevicePairingDetail mFragment; + + @Before + public void setUp() { + final BluetoothAdapter bluetoothAdapter = spy(BluetoothAdapter.getDefaultAdapter()); + final ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract( + BluetoothAdapter.getDefaultAdapter()); + shadowBluetoothAdapter.setEnabled(true); + + mProgressCategory = spy(new BluetoothProgressCategory(mContext)); + mFragment = spy(new TestHearingDevicePairingDetail()); + when(mFragment.getContext()).thenReturn(mContext); + when(mFragment.findPreference( + HearingDevicePairingDetail.KEY_AVAILABLE_HEARING_DEVICES)).thenReturn( + mProgressCategory); + mFragment.setBluetoothAdapter(bluetoothAdapter); + + } + + @Test + public void getDeviceListKey_expectedKey() { + assertThat(mFragment.getDeviceListKey()).isEqualTo( + HearingDevicePairingDetail.KEY_AVAILABLE_HEARING_DEVICES); + } + + @Test + public void onDeviceBondStateChanged_bondNone_setProgressFalse() { + mFragment.initPreferencesFromPreferenceScreen(); + + mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_NONE); + + verify(mProgressCategory).setProgress(true); + } + + @Test + public void onDeviceBondStateChanged_bonding_setProgressTrue() { + mFragment.initPreferencesFromPreferenceScreen(); + + mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDING); + + verify(mProgressCategory).setProgress(false); + } + + private static class TestHearingDevicePairingDetail extends HearingDevicePairingDetail { + TestHearingDevicePairingDetail() { + super(); + } + + public void setBluetoothAdapter(BluetoothAdapter bluetoothAdapter) { + this.mBluetoothAdapter = bluetoothAdapter; + } + + public void enableScanning() { + super.enableScanning(); + } + } +} 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/BluetoothDetailsAudioRoutingControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingControllerTest.java index 92174f30222..ea65856de61 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingControllerTest.java @@ -16,12 +16,17 @@ package com.android.settings.bluetooth; +import static com.android.settings.bluetooth.BluetoothDetailsAudioRoutingController.KEY_AUDIO_ROUTING; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; import android.util.FeatureFlagUtils; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; + import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,6 +41,8 @@ public class BluetoothDetailsAudioRoutingControllerTest extends @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + private static final String TEST_ADDRESS = "55:66:77:88:99:AA"; + private BluetoothDetailsAudioRoutingController mController; @Override @@ -44,7 +51,9 @@ public class BluetoothDetailsAudioRoutingControllerTest extends mController = new BluetoothDetailsAudioRoutingController(mContext, mFragment, mCachedDevice, mLifecycle); - mController.init(mScreen); + final PreferenceCategory preferenceCategory = new PreferenceCategory(mContext); + preferenceCategory.setKey(mController.getPreferenceKey()); + mScreen.addPreference(preferenceCategory); } @Test @@ -64,4 +73,20 @@ public class BluetoothDetailsAudioRoutingControllerTest extends assertThat(mController.isAvailable()).isFalse(); } + + @Test + public void init_isHearingAidDevice_expectedAudioRoutingPreference() { + when(mCachedDevice.isHearingAidDevice()).thenReturn(true); + when(mCachedDevice.getAddress()).thenReturn(TEST_ADDRESS); + + mController.init(mScreen); + final Preference preference = mScreen.findPreference(KEY_AUDIO_ROUTING); + final String address = preference.getExtras().getString( + BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS); + final String fragment = preference.getFragment(); + + assertThat(address).isEqualTo(TEST_ADDRESS); + assertThat(fragment).isEqualTo(BluetoothDetailsAudioRoutingFragment.class.getName()); + + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragmentTest.java new file mode 100644 index 00000000000..b2da5798ead --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragmentTest.java @@ -0,0 +1,111 @@ +/* + * 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.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.os.Bundle; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settings.testutils.XmlTestUtils; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.LocalBluetoothAdapter; +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.List; + +/** Tests for {@link BluetoothDetailsAudioRoutingFragment}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothUtils.class}) +public class BluetoothDetailsAudioRoutingFragmentTest { + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + + private static final String TEST_ADDRESS = "55:66:77:88:99:AA"; + + private final Context mContext = ApplicationProvider.getApplicationContext(); + + private BluetoothDetailsAudioRoutingFragment mFragment; + @Mock + private LocalBluetoothManager mLocalBluetoothManager; + @Mock + private CachedBluetoothDeviceManager mCachedDeviceManager; + @Mock + private LocalBluetoothAdapter mLocalBluetoothAdapter; + @Mock + private BluetoothDevice mBluetoothDevice; + @Mock + private CachedBluetoothDevice mCachedDevice; + + @Before + public void setUp() { + setupEnvironment(); + + when(mLocalBluetoothAdapter.getRemoteDevice(TEST_ADDRESS)).thenReturn(mBluetoothDevice); + when(mCachedDevice.getAddress()).thenReturn(TEST_ADDRESS); + when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedDevice); + + mFragment = new BluetoothDetailsAudioRoutingFragment(); + } + + @Test + public void onAttach_setArgumentsWithAddress_expectedCachedDeviceWithAddress() { + final Bundle args = new Bundle(); + args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS, TEST_ADDRESS); + mFragment.setArguments(args); + + mFragment.onAttach(mContext); + + assertThat(mFragment.mCachedDevice.getAddress()).isEqualTo(TEST_ADDRESS); + } + + @Test + public void getNonIndexableKeys_existInXmlLayout() { + final List niks = BluetoothDetailsAudioRoutingFragment.SEARCH_INDEX_DATA_PROVIDER + .getNonIndexableKeys(mContext); + final List keys = + XmlTestUtils.getKeysFromPreferenceXml(mContext, + R.xml.bluetooth_audio_routing_fragment); + + assertThat(keys).containsAtLeastElementsIn(niks); + } + + private void setupEnvironment() { + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; + when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); + when(mLocalBluetoothManager.getBluetoothAdapter()).thenReturn(mLocalBluetoothAdapter); + } +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java new file mode 100644 index 00000000000..184f5212e77 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java @@ -0,0 +1,266 @@ +/* + * 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.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.Pair; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +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.Answers; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; + +/** Tests for {@link BluetoothDevicePairingDetailBase}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothAdapter.class}) +public class BluetoothDevicePairingDetailBaseTest { + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + public static final String KEY_DEVICE_LIST_GROUP = "test_key"; + + private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; + private static final String TEST_DEVICE_ADDRESS_B = "00:B1:B1:B1:B1:B1"; + private final Context mContext = ApplicationProvider.getApplicationContext(); + + @Mock + private Resources mResource; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private LocalBluetoothManager mLocalManager; + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private Drawable mDrawable; + private BluetoothAdapter mBluetoothAdapter; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; + private BluetoothProgressCategory mAvailableDevicesCategory; + private BluetoothDevice mBluetoothDevice; + private TestBluetoothDevicePairingDetailBase mFragment; + + @Before + public void setUp() { + mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext)); + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); + when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); + final Pair pairs = new Pair<>(mDrawable, "fake_device"); + when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs); + mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); + + mFragment = spy(new TestBluetoothDevicePairingDetailBase()); + when(mFragment.findPreference(KEY_DEVICE_LIST_GROUP)).thenReturn(mAvailableDevicesCategory); + doReturn(mContext).when(mFragment).getContext(); + doReturn(mResource).when(mFragment).getResources(); + mFragment.mDeviceListGroup = mAvailableDevicesCategory; + mFragment.mLocalManager = mLocalManager; + mFragment.mBluetoothAdapter = mBluetoothAdapter; + mFragment.initPreferencesFromPreferenceScreen(); + + } + + @Test + public void startScanning_startScanAndRemoveDevices() { + mFragment.enableScanning(); + + verify(mFragment).startScanning(); + verify(mAvailableDevicesCategory).removeAll(); + } + + @Test + public void updateContent_stateOn() { + mFragment.updateContent(BluetoothAdapter.STATE_ON); + + assertThat(mBluetoothAdapter.isEnabled()).isTrue(); + verify(mFragment).enableScanning(); + } + + @Test + public void updateContent_stateOff_finish() { + mFragment.updateContent(BluetoothAdapter.STATE_OFF); + + verify(mFragment).finish(); + } + + @Test + public void updateBluetooth_bluetoothOff_turnOnBluetooth() { + mShadowBluetoothAdapter.setEnabled(false); + + mFragment.updateBluetooth(); + + assertThat(mBluetoothAdapter.isEnabled()).isTrue(); + } + + @Test + public void updateBluetooth_bluetoothOn_updateState() { + mShadowBluetoothAdapter.setEnabled(true); + doNothing().when(mFragment).updateContent(anyInt()); + + mFragment.updateBluetooth(); + + verify(mFragment).updateContent(anyInt()); + } + + @Test + public void onBluetoothStateChanged_whenTurnedOnBTShowToast() { + doNothing().when(mFragment).updateContent(anyInt()); + + mFragment.onBluetoothStateChanged(BluetoothAdapter.STATE_ON); + + verify(mFragment).showBluetoothTurnedOnToast(); + } + + @Test + public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish() { + final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); + mFragment.mSelectedList.add(mBluetoothDevice); + mFragment.mSelectedList.add(device); + + when(mCachedBluetoothDevice.isConnected()).thenReturn(true); + when(mCachedBluetoothDevice.getDevice()).thenReturn(device); + + mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, + BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); + + verify(mFragment).finish(); + } + + @Test + public void onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing() { + final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); + mFragment.mSelectedList.add(device); + + when(mCachedBluetoothDevice.isConnected()).thenReturn(true); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + + mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, + BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); + + // not crash + } + + @Test + public void onProfileConnectionStateChanged_deviceDisconnected_doNothing() { + final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); + mFragment.mSelectedList.add(mBluetoothDevice); + mFragment.mSelectedList.add(device); + + when(mCachedBluetoothDevice.isConnected()).thenReturn(false); + when(mCachedBluetoothDevice.getDevice()).thenReturn(device); + + mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, + BluetoothProfile.A2DP, BluetoothAdapter.STATE_DISCONNECTED); + + // not crash + } + + @Test + public void onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed() { + final BluetoothDevicePreference preference = + new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, + true, BluetoothDevicePreference.SortType.TYPE_FIFO); + final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); + mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference); + + when(mCachedBluetoothDevice.isConnected()).thenReturn(true); + when(mCachedBluetoothDevice.getDevice()).thenReturn(device); + + mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, + BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); + + assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0); + } + + @Test + public void onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing() { + final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); + final BluetoothDevicePreference preference = + new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, + true, BluetoothDevicePreference.SortType.TYPE_FIFO); + final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); + final BluetoothDevice device2 = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); + mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference); + + when(mCachedBluetoothDevice.isConnected()).thenReturn(true); + when(mCachedBluetoothDevice.getDevice()).thenReturn(device); + when(cachedDevice.isConnected()).thenReturn(true); + when(cachedDevice.getDevice()).thenReturn(device2); + when(cachedDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS_B); + when(cachedDevice.getIdentityAddress()).thenReturn(TEST_DEVICE_ADDRESS_B); + + mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothProfile.A2DP, + BluetoothAdapter.STATE_CONNECTED); + + // not crash + } + + private static class TestBluetoothDevicePairingDetailBase extends + BluetoothDevicePairingDetailBase { + + TestBluetoothDevicePairingDetailBase() { + super(); + } + + @Override + public int getMetricsCategory() { + return 0; + } + + @Override + public String getDeviceListKey() { + return KEY_DEVICE_LIST_GROUP; + } + + @Override + protected int getPreferenceScreenResId() { + return 0; + } + + @Override + protected String getLogTag() { + return "test_tag"; + } + } +} 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/BluetoothPairingDetailTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java index bac868f4585..865ed81e367 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java @@ -18,166 +18,86 @@ package com.android.settings.bluetooth; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothProfile; import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.util.Pair; +import android.os.Bundle; -import androidx.preference.PreferenceGroup; +import androidx.test.core.app.ApplicationProvider; -import com.android.settings.R; -import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; -import com.android.settingslib.bluetooth.BluetoothDeviceFilter; -import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.widget.FooterPreference; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; -import org.robolectric.shadow.api.Shadow; @RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowBluetoothAdapter.class}) public class BluetoothPairingDetailTest { - private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; - private static final String TEST_DEVICE_ADDRESS_B = "00:B1:B1:B1:B1:B1"; + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + private final Context mContext = ApplicationProvider.getApplicationContext(); - @Mock - private Resources mResource; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private LocalBluetoothManager mLocalManager; - @Mock - private PreferenceGroup mPreferenceGroup; - @Mock - private CachedBluetoothDevice mCachedBluetoothDevice; - @Mock - private Drawable mDrawable; - private BluetoothPairingDetail mFragment; - private Context mContext; private BluetoothProgressCategory mAvailableDevicesCategory; private FooterPreference mFooterPreference; private BluetoothAdapter mBluetoothAdapter; - private ShadowBluetoothAdapter mShadowBluetoothAdapter; - private BluetoothDevice mBluetoothDevice; @Before public void setUp() { - MockitoAnnotations.initMocks(this); - - Pair pairs = new Pair<>(mDrawable, "fake_device"); - mContext = RuntimeEnvironment.application; mFragment = spy(new BluetoothPairingDetail()); doReturn(mContext).when(mFragment).getContext(); - doReturn(mResource).when(mFragment).getResources(); - when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); - when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs); - mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext)); mFooterPreference = new FooterPreference(mContext); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); - mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); + + doReturn(mAvailableDevicesCategory).when(mFragment) + .findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES); + doReturn(mFooterPreference).when(mFragment) + .findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF); mFragment.mBluetoothAdapter = mBluetoothAdapter; mFragment.mLocalManager = mLocalManager; - mFragment.mDeviceListGroup = mPreferenceGroup; - mFragment.mAlwaysDiscoverable = new AlwaysDiscoverable(mContext); + mFragment.mDeviceListGroup = mAvailableDevicesCategory; + mFragment.onViewCreated(mFragment.getView(), Bundle.EMPTY); } - +// @Test public void initPreferencesFromPreferenceScreen_findPreferences() { - doReturn(mAvailableDevicesCategory).when(mFragment) - .findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES); - doReturn(mFooterPreference).when(mFragment) - .findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF); - mFragment.initPreferencesFromPreferenceScreen(); assertThat(mFragment.mAvailableDevicesCategory).isEqualTo(mAvailableDevicesCategory); assertThat(mFragment.mFooterPreference).isEqualTo(mFooterPreference); } - @Test - public void startScanning_startScanAndRemoveDevices() { - mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory; - mFragment.mDeviceListGroup = mAvailableDevicesCategory; - - mFragment.enableScanning(); - - verify(mFragment).startScanning(); - verify(mAvailableDevicesCategory).removeAll(); - } - @Test public void updateContent_stateOn_addDevices() { - mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory; - mFragment.mFooterPreference = mFooterPreference; - doNothing().when(mFragment).addDeviceCategory(any(), anyInt(), any(), anyBoolean()); + mFragment.initPreferencesFromPreferenceScreen(); mFragment.updateContent(BluetoothAdapter.STATE_ON); - verify(mFragment).addDeviceCategory(mAvailableDevicesCategory, - R.string.bluetooth_preference_found_media_devices, - BluetoothDeviceFilter.ALL_FILTER, false); + assertThat(mFragment.mAlwaysDiscoverable.mStarted).isEqualTo(true); assertThat(mBluetoothAdapter.getScanMode()) .isEqualTo(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); } - @Test - public void updateContent_stateOff_finish() { - mFragment.updateContent(BluetoothAdapter.STATE_OFF); - - verify(mFragment).finish(); - } - - @Test - public void updateBluetooth_bluetoothOff_turnOnBluetooth() { - mShadowBluetoothAdapter.setEnabled(false); - - mFragment.updateBluetooth(); - - assertThat(mBluetoothAdapter.isEnabled()).isTrue(); - } - - @Test - public void updateBluetooth_bluetoothOn_updateState() { - mShadowBluetoothAdapter.setEnabled(true); - doNothing().when(mFragment).updateContent(anyInt()); - - mFragment.updateBluetooth(); - - verify(mFragment).updateContent(anyInt()); - } - @Test public void onScanningStateChanged_restartScanAfterInitialScanning() { - mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory; - mFragment.mFooterPreference = mFooterPreference; - mFragment.mDeviceListGroup = mAvailableDevicesCategory; - doNothing().when(mFragment).addDeviceCategory(any(), anyInt(), any(), anyBoolean()); + mFragment.initPreferencesFromPreferenceScreen(); // Initial Bluetooth ON will trigger scan enable, list clear and scan start mFragment.updateContent(BluetoothAdapter.STATE_ON); @@ -219,97 +139,4 @@ public class BluetoothPairingDetailTest { // Verify that clean up only happen once at initialization verify(mAvailableDevicesCategory, times(1)).removeAll(); } - - @Test - public void onBluetoothStateChanged_whenTurnedOnBTShowToast() { - doNothing().when(mFragment).updateContent(anyInt()); - - mFragment.onBluetoothStateChanged(BluetoothAdapter.STATE_ON); - - verify(mFragment).showBluetoothTurnedOnToast(); - } - - @Test - public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish() { - final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); - mFragment.mSelectedList.add(mBluetoothDevice); - mFragment.mSelectedList.add(device); - - when(mCachedBluetoothDevice.isConnected()).thenReturn(true); - when(mCachedBluetoothDevice.getDevice()).thenReturn(device); - - mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, - BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); - - verify(mFragment).finish(); - } - - @Test - public void onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing() { - final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); - mFragment.mSelectedList.add(device); - - when(mCachedBluetoothDevice.isConnected()).thenReturn(true); - when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); - - mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, - BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); - - // not crash - } - - @Test - public void onProfileConnectionStateChanged_deviceDisconnected_doNothing() { - final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); - mFragment.mSelectedList.add(mBluetoothDevice); - mFragment.mSelectedList.add(device); - - when(mCachedBluetoothDevice.isConnected()).thenReturn(false); - when(mCachedBluetoothDevice.getDevice()).thenReturn(device); - - mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, - BluetoothProfile.A2DP, BluetoothAdapter.STATE_DISCONNECTED); - - // not crash - } - - @Test - public void onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed() { - final BluetoothDevicePreference preference = - new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, - true, BluetoothDevicePreference.SortType.TYPE_FIFO); - final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); - mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference); - - when(mCachedBluetoothDevice.isConnected()).thenReturn(true); - when(mCachedBluetoothDevice.getDevice()).thenReturn(device); - - mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice, - BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED); - - assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0); - } - - @Test - public void onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing() { - final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); - final BluetoothDevicePreference preference = - new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, - true, BluetoothDevicePreference.SortType.TYPE_FIFO); - final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); - final BluetoothDevice device2 = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B); - mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference); - - when(mCachedBluetoothDevice.isConnected()).thenReturn(true); - when(mCachedBluetoothDevice.getDevice()).thenReturn(device); - when(cachedDevice.isConnected()).thenReturn(true); - when(cachedDevice.getDevice()).thenReturn(device2); - when(cachedDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS_B); - when(cachedDevice.getIdentityAddress()).thenReturn(TEST_DEVICE_ADDRESS_B); - - mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothProfile.A2DP, - BluetoothAdapter.STATE_CONNECTED); - - // not crash - } } \ No newline at end of file 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/DeviceListPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/DeviceListPreferenceFragmentTest.java index 19de2e4b125..4f46ce93d03 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/DeviceListPreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/DeviceListPreferenceFragmentTest.java @@ -26,6 +26,11 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanSettings; import android.content.Context; import android.content.res.Resources; @@ -45,6 +50,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import java.util.Collections; import java.util.List; @RunWith(RobolectricTestRunner.class) @@ -57,10 +63,14 @@ public class DeviceListPreferenceFragmentTest { private Resources mResource; @Mock private Context mContext; + @Mock + private BluetoothLeScanner mBluetoothLeScanner; private TestFragment mFragment; private Preference mMyDevicePreference; + + private BluetoothAdapter mBluetoothAdapter; @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -68,7 +78,8 @@ public class DeviceListPreferenceFragmentTest { mFragment = spy(new TestFragment()); doReturn(mContext).when(mFragment).getContext(); doReturn(mResource).when(mFragment).getResources(); - mFragment.mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mBluetoothAdapter = spy(BluetoothAdapter.getDefaultAdapter()); + mFragment.mBluetoothAdapter = mBluetoothAdapter; mMyDevicePreference = new Preference(RuntimeEnvironment.application); } @@ -169,6 +180,20 @@ public class DeviceListPreferenceFragmentTest { verify(mFragment, times(1)).startScanning(); } + @Test + public void startScanning_setLeScanFilter_shouldStartLeScan() { + final ScanFilter leScanFilter = new ScanFilter.Builder() + .setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}, new byte[]{0}) + .build(); + doReturn(mBluetoothLeScanner).when(mBluetoothAdapter).getBluetoothLeScanner(); + + mFragment.setFilter(Collections.singletonList(leScanFilter)); + mFragment.startScanning(); + + verify(mBluetoothLeScanner).startScan(eq(Collections.singletonList(leScanFilter)), + any(ScanSettings.class), any(ScanCallback.class)); + } + /** * Fragment to test since {@code DeviceListPreferenceFragment} is abstract */ @@ -187,7 +212,7 @@ public class DeviceListPreferenceFragmentTest { public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {} @Override - void initPreferencesFromPreferenceScreen() {} + protected void initPreferencesFromPreferenceScreen() {} @Override public String getDeviceListKey() { 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;