From 4af270b231be69f40f1fb124c92c9a58df276c6a Mon Sep 17 00:00:00 2001 From: Angela Wang Date: Tue, 27 Feb 2024 13:39:37 +0000 Subject: [PATCH 01/10] Separate category controller out of HearingDeviceControlsController We're going to add more different device controls (such as hearing aids presets, volume offset controls or microphone volume controls) into "device_controls_general" PreferenceCategory. It's better to keep the category controller separated from the child controller to better maintain the visibility of the whole category and have a clearer stucture of these controllers. Bug: 300015207 Test: atest BluetoothDetailsHearingDeviceControllerTest Test: atest BluetoothDetailsHearingDeviceSettingsControllerTest Test: atest BluetoothDeviceDetailsFragmentTest Change-Id: I7f35b02a1120aefa8307e500f7abfce3b8055fbf --- res/values/strings.xml | 12 +-- res/xml/bluetooth_device_details_fragment.xml | 2 +- ...uetoothDetailsHearingDeviceController.java | 102 ++++++++++++++++++ ...tailsHearingDeviceSettingsController.java} | 36 ++++--- .../BluetoothDeviceDetailsFragment.java | 20 ++-- ...othDetailsHearingDeviceControllerTest.java | 83 ++++++++++++++ ...sHearingDeviceSettingsControllerTest.java} | 19 ++-- .../BluetoothDeviceDetailsFragmentTest.java | 6 +- 8 files changed, 232 insertions(+), 48 deletions(-) create mode 100644 src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java rename src/com/android/settings/bluetooth/{BluetoothDetailsHearingDeviceControlsController.java => BluetoothDetailsHearingDeviceSettingsController.java} (75%) create mode 100644 tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java rename tests/robotests/src/com/android/settings/bluetooth/{BluetoothDetailsHearingDeviceControlsControllerTest.java => BluetoothDetailsHearingDeviceSettingsControllerTest.java} (81%) diff --git a/res/values/strings.xml b/res/values/strings.xml index cf13dfbf8d2..3f01af0b9cf 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -150,14 +150,10 @@ Pair right ear Pair left ear - - For all available hearing devices - - More hearing device settings - - Change cross-device settings like shortcut, and telecoil controls - - For this device + + Hearing device settings + + Shortcut, hearing aid compatibility Audio output diff --git a/res/xml/bluetooth_device_details_fragment.xml b/res/xml/bluetooth_device_details_fragment.xml index d260554f937..91f73a70b6e 100644 --- a/res/xml/bluetooth_device_details_fragment.xml +++ b/res/xml/bluetooth_device_details_fragment.xml @@ -69,7 +69,7 @@ android:key="device_companion_apps"/> + android:key="hearing_device_group" /> diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java new file mode 100644 index 00000000000..27a4cb17aa1 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceScreen; + +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import com.google.common.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.List; + +/** + * The controller of the hearing device controls. + * + *

Note: It is responsible for creating the sub-controllers inside this preference + * category controller. + */ +public class BluetoothDetailsHearingDeviceController extends BluetoothDetailsController { + static final String KEY_HEARING_DEVICE_GROUP = "hearing_device_group"; + + private final List mControllers = new ArrayList<>(); + private Lifecycle mLifecycle; + + public BluetoothDetailsHearingDeviceController(@NonNull Context context, + @NonNull PreferenceFragmentCompat fragment, + @NonNull CachedBluetoothDevice device, + @NonNull Lifecycle lifecycle) { + super(context, fragment, device, lifecycle); + mLifecycle = lifecycle; + } + + @VisibleForTesting + void setSubControllers( + BluetoothDetailsHearingDeviceSettingsController hearingDeviceSettingsController) { + mControllers.clear(); + mControllers.add(hearingDeviceSettingsController); + } + + @Override + public boolean isAvailable() { + return mControllers.stream().anyMatch(BluetoothDetailsController::isAvailable); + } + + @Override + @NonNull + public String getPreferenceKey() { + return KEY_HEARING_DEVICE_GROUP; + } + + @Override + protected void init(PreferenceScreen screen) { + + } + + @Override + protected void refresh() { + + } + + /** + * Initiates the sub controllers controlled by this group controller. + * + *

Note: The caller must call this method when creating this class. + * + * @param isLaunchFromHearingDevicePage a boolean that determines if the caller is launch from + * hearing device page + */ + void initSubControllers(boolean isLaunchFromHearingDevicePage) { + mControllers.clear(); + // Don't need to show the entrance to hearing device page when launched from the same page + if (!isLaunchFromHearingDevicePage) { + mControllers.add(new BluetoothDetailsHearingDeviceSettingsController(mContext, + mFragment, mCachedDevice, mLifecycle)); + } + } + + @NonNull + public List getSubControllers() { + return mControllers; + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsController.java similarity index 75% rename from src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java rename to src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsController.java index 162abc78aef..b381cc4b2b8 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsController.java @@ -16,6 +16,8 @@ package com.android.settings.bluetooth; +import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP; + import android.content.Context; import android.text.TextUtils; @@ -36,15 +38,13 @@ import com.google.common.annotations.VisibleForTesting; /** * The controller of the hearing device settings to launch Hearing device page. */ -public class BluetoothDetailsHearingDeviceControlsController extends BluetoothDetailsController +public class BluetoothDetailsHearingDeviceSettingsController extends BluetoothDetailsController implements Preference.OnPreferenceClickListener { @VisibleForTesting - static final String KEY_DEVICE_CONTROLS_GENERAL_GROUP = "device_controls_general"; - @VisibleForTesting - static final String KEY_HEARING_DEVICE_CONTROLS = "hearing_device_controls"; + static final String KEY_HEARING_DEVICE_SETTINGS = "hearing_device_settings"; - public BluetoothDetailsHearingDeviceControlsController(Context context, + public BluetoothDetailsHearingDeviceSettingsController(Context context, PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle) { super(context, fragment, device, lifecycle); lifecycle.addObserver(this); @@ -57,37 +57,39 @@ public class BluetoothDetailsHearingDeviceControlsController extends BluetoothDe @Override protected void init(PreferenceScreen screen) { - if (!mCachedDevice.isHearingAidDevice()) { + if (!isAvailable()) { return; } - - final PreferenceCategory prefCategory = screen.findPreference(getPreferenceKey()); - final Preference pref = createHearingDeviceControlsPreference(prefCategory.getContext()); - prefCategory.addPreference(pref); + final PreferenceCategory group = screen.findPreference(KEY_HEARING_DEVICE_GROUP); + final Preference pref = createHearingDeviceSettingsPreference(group.getContext()); + group.addPreference(pref); } @Override - protected void refresh() {} + protected void refresh() { + + } @Override public String getPreferenceKey() { - return KEY_DEVICE_CONTROLS_GENERAL_GROUP; + return KEY_HEARING_DEVICE_SETTINGS; } @Override public boolean onPreferenceClick(Preference preference) { - if (TextUtils.equals(preference.getKey(), KEY_HEARING_DEVICE_CONTROLS)) { + if (TextUtils.equals(preference.getKey(), KEY_HEARING_DEVICE_SETTINGS)) { launchAccessibilityHearingDeviceSettings(); return true; } return false; } - private Preference createHearingDeviceControlsPreference(Context context) { + private Preference createHearingDeviceSettingsPreference(Context context) { final ArrowPreference preference = new ArrowPreference(context); - preference.setKey(KEY_HEARING_DEVICE_CONTROLS); - preference.setTitle(context.getString(R.string.bluetooth_device_controls_title)); - preference.setSummary(context.getString(R.string.bluetooth_device_controls_summary)); + preference.setKey(KEY_HEARING_DEVICE_SETTINGS); + preference.setTitle(context.getString(R.string.bluetooth_hearing_device_settings_title)); + preference.setSummary( + context.getString(R.string.bluetooth_hearing_device_settings_summary)); preference.setOnPreferenceClickListener(this); return preference; diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index 9c68c9cc870..dae1e086115 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -326,16 +326,16 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment lifecycle)); controllers.add(new BluetoothDetailsPairOtherController(context, this, mCachedDevice, lifecycle)); - // Don't need to show hearing device again when launched from the same page. - if (!isLaunchFromHearingDevicePage()) { - controllers.add(new BluetoothDetailsHearingDeviceControlsController(context, this, - mCachedDevice, lifecycle)); - } - controllers.add(new BluetoothDetailsDataSyncController(context, this, - mCachedDevice, lifecycle)); - controllers.add( - new BluetoothDetailsExtraOptionsController( - context, this, mCachedDevice, lifecycle)); + controllers.add(new BluetoothDetailsDataSyncController(context, this, mCachedDevice, + lifecycle)); + controllers.add(new BluetoothDetailsExtraOptionsController(context, this, mCachedDevice, + lifecycle)); + BluetoothDetailsHearingDeviceController hearingDeviceController = + new BluetoothDetailsHearingDeviceController(context, this, mCachedDevice, + lifecycle); + controllers.add(hearingDeviceController); + hearingDeviceController.initSubControllers(isLaunchFromHearingDevicePage()); + controllers.addAll(hearingDeviceController.getSubControllers()); } return controllers; } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java new file mode 100644 index 00000000000..d5284b4c438 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2024 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 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; + +/** Tests for {@link BluetoothDetailsHearingDeviceController}. */ +@RunWith(RobolectricTestRunner.class) +public class BluetoothDetailsHearingDeviceControllerTest extends + BluetoothDetailsControllerTestBase { + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + private BluetoothDetailsHearingDeviceController mHearingDeviceController; + + @Mock + private BluetoothDetailsHearingDeviceSettingsController mHearingDeviceSettingsController; + + @Override + public void setUp() { + super.setUp(); + + mHearingDeviceController = new BluetoothDetailsHearingDeviceController(mContext, + mFragment, mCachedDevice, mLifecycle); + mHearingDeviceController.setSubControllers(mHearingDeviceSettingsController); + } + + @Test + public void isAvailable_hearingDeviceSettingsAvailable_returnTrue() { + when(mHearingDeviceSettingsController.isAvailable()).thenReturn(true); + + assertThat(mHearingDeviceController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_noControllersAvailable_returnFalse() { + when(mHearingDeviceSettingsController.isAvailable()).thenReturn(false); + + assertThat(mHearingDeviceController.isAvailable()).isFalse(); + } + + + @Test + public void initSubControllers_launchFromHearingDevicePage_hearingDeviceSettingsNotExist() { + mHearingDeviceController.initSubControllers(true); + + assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch( + c -> c instanceof BluetoothDetailsHearingDeviceSettingsController)).isFalse(); + } + + @Test + public void initSubControllers_notLaunchFromHearingDevicePage_hearingDeviceSettingsExist() { + mHearingDeviceController.initSubControllers(false); + + assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch( + c -> c instanceof BluetoothDetailsHearingDeviceSettingsController)).isTrue(); + } +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsControllerTest.java similarity index 81% rename from tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsControllerTest.java rename to tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsControllerTest.java index 364d299e519..b420717d397 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsControllerTest.java @@ -39,23 +39,24 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; -/** Tests for {@link BluetoothDetailsHearingDeviceControlsController}. */ +/** Tests for {@link BluetoothDetailsHearingDeviceSettingsController}. */ @RunWith(RobolectricTestRunner.class) -public class BluetoothDetailsHearingDeviceControlsControllerTest extends +public class BluetoothDetailsHearingDeviceSettingsControllerTest extends BluetoothDetailsControllerTestBase { + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); @Captor private ArgumentCaptor mIntentArgumentCaptor; - private BluetoothDetailsHearingDeviceControlsController mController; + private BluetoothDetailsHearingDeviceSettingsController mController; @Override public void setUp() { super.setUp(); FakeFeatureFactory.setupForTest(); - mController = new BluetoothDetailsHearingDeviceControlsController(mActivity, mFragment, + mController = new BluetoothDetailsHearingDeviceSettingsController(mActivity, mFragment, mCachedDevice, mLifecycle); when(mCachedDevice.isHearingAidDevice()).thenReturn(true); } @@ -75,12 +76,12 @@ public class BluetoothDetailsHearingDeviceControlsControllerTest extends } @Test - public void onPreferenceClick_hearingDeviceControlsKey_LaunchExpectedFragment() { - final Preference hearingControlsKeyPreference = new Preference(mContext); - hearingControlsKeyPreference.setKey( - BluetoothDetailsHearingDeviceControlsController.KEY_HEARING_DEVICE_CONTROLS); + public void onPreferenceClick_hearingDeviceSettingsKey_launchExpectedFragment() { + final Preference hearingDeviceSettingsPreference = new Preference(mContext); + hearingDeviceSettingsPreference.setKey( + BluetoothDetailsHearingDeviceSettingsController.KEY_HEARING_DEVICE_SETTINGS); - mController.onPreferenceClick(hearingControlsKeyPreference); + mController.onPreferenceClick(hearingDeviceSettingsPreference); assertStartActivityWithExpectedFragment(mActivity, AccessibilityHearingAidsFragment.class.getName()); diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java index fc72c412b6e..50aa7719ccb 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java @@ -18,7 +18,7 @@ package com.android.settings.bluetooth; import static android.bluetooth.BluetoothDevice.BOND_NONE; -import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceControlsController.KEY_DEVICE_CONTROLS_GENERAL_GROUP; +import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceSettingsController.KEY_HEARING_DEVICE_SETTINGS; import static com.google.common.truth.Truth.assertThat; @@ -237,7 +237,7 @@ public class BluetoothDeviceDetailsFragmentTest { assertThat(controllerList.stream() .anyMatch(controller -> controller.getPreferenceKey().equals( - KEY_DEVICE_CONTROLS_GENERAL_GROUP))).isFalse(); + KEY_HEARING_DEVICE_SETTINGS))).isFalse(); } @Test @@ -253,7 +253,7 @@ public class BluetoothDeviceDetailsFragmentTest { assertThat(controllerList.stream() .anyMatch(controller -> controller.getPreferenceKey().equals( - KEY_DEVICE_CONTROLS_GENERAL_GROUP))).isTrue(); + KEY_HEARING_DEVICE_SETTINGS))).isTrue(); } private InputDevice createInputDeviceWithMatchingBluetoothAddress() { From 82e4ed3bd1a48e9ab73def091ba31b6556ac02ac Mon Sep 17 00:00:00 2001 From: Angela Wang Date: Tue, 27 Feb 2024 13:41:56 +0000 Subject: [PATCH 02/10] Selects presets in device details page (1/2) Enables users to select their presets in Bluetooth device details page if the device supports HAP. This CL only contains the UI elements. The full functionality will be introduce in the next CL. Bug: 300015207 Test: atest BluetoothDetailsHearingDeviceControllerTest Test: atest BluetoothDetailsHearingAidsPresetsControllerTest Change-Id: I1ab4781191b0c9e1033a29c30ca61671878bb7e1 --- res/values/strings.xml | 2 + ...thDetailsHearingAidsPresetsController.java | 204 ++++++++++++++++++ ...uetoothDetailsHearingDeviceController.java | 16 +- ...etailsHearingDeviceSettingsController.java | 2 + .../BluetoothDeviceDetailsFragment.java | 4 +- ...tailsHearingAidsPresetsControllerTest.java | 129 +++++++++++ ...othDetailsHearingDeviceControllerTest.java | 52 ++++- 7 files changed, 403 insertions(+), 6 deletions(-) create mode 100644 src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java create mode 100644 tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 3f01af0b9cf..1fa41eb41b7 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -154,6 +154,8 @@ Hearing device settings Shortcut, hearing aid compatibility + + Presets Audio output diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java new file mode 100644 index 00000000000..208f8d0afbd --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2024 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.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP; +import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.ORDER_HEARING_AIDS_PRESETS; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHapClient; +import android.bluetooth.BluetoothHapPresetInfo; +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HapClientProfile; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.utils.ThreadUtils; + +import java.util.List; + +/** + * The controller of the hearing aid presets. + */ +public class BluetoothDetailsHearingAidsPresetsController extends + BluetoothDetailsController implements Preference.OnPreferenceChangeListener, + BluetoothHapClient.Callback, OnResume, OnPause { + + private static final boolean DEBUG = true; + private static final String TAG = "BluetoothDetailsHearingAidsPresetsController"; + static final String KEY_HEARING_AIDS_PRESETS = "hearing_aids_presets"; + + private final HapClientProfile mHapClientProfile; + @Nullable + private ListPreference mPreference; + + public BluetoothDetailsHearingAidsPresetsController(@NonNull Context context, + @NonNull PreferenceFragmentCompat fragment, + @NonNull LocalBluetoothManager manager, + @NonNull CachedBluetoothDevice device, + @NonNull Lifecycle lifecycle) { + super(context, fragment, device, lifecycle); + mHapClientProfile = manager.getProfileManager().getHapClientProfile(); + } + + @Override + public void onResume() { + super.onResume(); + if (mHapClientProfile != null) { + mHapClientProfile.registerCallback(ThreadUtils.getBackgroundExecutor(), this); + } + } + + @Override + public void onPause() { + if (mHapClientProfile != null) { + mHapClientProfile.unregisterCallback(this); + } + super.onPause(); + } + + @Override + public boolean onPreferenceChange(@NonNull Preference preference, @Nullable Object newValue) { + if (TextUtils.equals(preference.getKey(), getPreferenceKey())) { + // TODO(b/300015207): Update the settings to remote device + return true; + } + return false; + } + + @Nullable + @Override + public String getPreferenceKey() { + return KEY_HEARING_AIDS_PRESETS; + } + + @Override + protected void init(PreferenceScreen screen) { + PreferenceCategory deviceControls = screen.findPreference(KEY_HEARING_DEVICE_GROUP); + if (deviceControls != null) { + mPreference = createPresetPreference(deviceControls.getContext()); + deviceControls.addPreference(mPreference); + } + } + + @Override + protected void refresh() { + if (!isAvailable() || mPreference == null) { + return; + } + mPreference.setEnabled(mCachedDevice.isConnectedHapClientDevice()); + // TODO(b/300015207): Load preset from remote and show in UI + } + + @Override + public boolean isAvailable() { + return false; + } + + @Override + public void onPresetSelected(@NonNull BluetoothDevice device, int presetIndex, int reason) { + if (device.equals(mCachedDevice.getDevice())) { + if (DEBUG) { + Log.d(TAG, "onPresetSelected, device: " + device.getAddress() + + ", presetIndex: " + presetIndex + ", reason: " + reason); + } + // TODO(b/300015207): Update the UI + } + } + + @Override + public void onPresetSelectionFailed(@NonNull BluetoothDevice device, int reason) { + if (device.equals(mCachedDevice.getDevice())) { + if (DEBUG) { + Log.d(TAG, + "onPresetSelectionFailed, device: " + device.getAddress() + + ", reason: " + reason); + } + // TODO(b/300015207): Update the UI + } + } + + @Override + public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) { + if (hapGroupId == mHapClientProfile.getHapGroup(mCachedDevice.getDevice())) { + if (DEBUG) { + Log.d(TAG, "onPresetSelectionForGroupFailed, group: " + hapGroupId + + ", reason: " + reason); + } + // TODO(b/300015207): Update the UI + } + } + + @Override + public void onPresetInfoChanged(@NonNull BluetoothDevice device, + @NonNull List presetInfoList, int reason) { + if (device.equals(mCachedDevice.getDevice())) { + if (DEBUG) { + Log.d(TAG, "onPresetInfoChanged, device: " + device.getAddress() + + ", reason: " + reason + + ", infoList: " + presetInfoList); + } + // TODO(b/300015207): Update the UI + } + } + + @Override + public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int reason) { + if (device.equals(mCachedDevice.getDevice())) { + if (DEBUG) { + Log.d(TAG, + "onSetPresetNameFailed, device: " + device.getAddress() + + ", reason: " + reason); + } + // TODO(b/300015207): Update the UI + } + } + + @Override + public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) { + if (hapGroupId == mHapClientProfile.getHapGroup(mCachedDevice.getDevice())) { + if (DEBUG) { + Log.d(TAG, "onSetPresetNameForGroupFailed, group: " + hapGroupId + + ", reason: " + reason); + } + // TODO(b/300015207): Update the UI + } + } + + private ListPreference createPresetPreference(Context context) { + ListPreference preference = new ListPreference(context); + preference.setKey(KEY_HEARING_AIDS_PRESETS); + preference.setOrder(ORDER_HEARING_AIDS_PRESETS); + preference.setTitle(context.getString(R.string.bluetooth_hearing_aids_presets)); + preference.setOnPreferenceChangeListener(this); + return preference; + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java index 27a4cb17aa1..3703b7180af 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java @@ -22,7 +22,9 @@ import androidx.annotation.NonNull; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceScreen; +import com.android.settings.accessibility.Flags; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.lifecycle.Lifecycle; import com.google.common.annotations.VisibleForTesting; @@ -37,24 +39,32 @@ import java.util.List; * category controller. */ public class BluetoothDetailsHearingDeviceController extends BluetoothDetailsController { + + public static final int ORDER_HEARING_DEVICE_SETTINGS = 1; + public static final int ORDER_HEARING_AIDS_PRESETS = 2; static final String KEY_HEARING_DEVICE_GROUP = "hearing_device_group"; private final List mControllers = new ArrayList<>(); private Lifecycle mLifecycle; + private LocalBluetoothManager mManager; public BluetoothDetailsHearingDeviceController(@NonNull Context context, @NonNull PreferenceFragmentCompat fragment, + @NonNull LocalBluetoothManager manager, @NonNull CachedBluetoothDevice device, @NonNull Lifecycle lifecycle) { super(context, fragment, device, lifecycle); + mManager = manager; mLifecycle = lifecycle; } @VisibleForTesting void setSubControllers( - BluetoothDetailsHearingDeviceSettingsController hearingDeviceSettingsController) { + BluetoothDetailsHearingDeviceSettingsController hearingDeviceSettingsController, + BluetoothDetailsHearingAidsPresetsController presetsController) { mControllers.clear(); mControllers.add(hearingDeviceSettingsController); + mControllers.add(presetsController); } @Override @@ -93,6 +103,10 @@ public class BluetoothDetailsHearingDeviceController extends BluetoothDetailsCon mControllers.add(new BluetoothDetailsHearingDeviceSettingsController(mContext, mFragment, mCachedDevice, mLifecycle)); } + if (Flags.enableHearingAidPresetControl()) { + mControllers.add(new BluetoothDetailsHearingAidsPresetsController(mContext, mFragment, + mManager, mCachedDevice, mLifecycle)); + } } @NonNull diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsController.java index b381cc4b2b8..7e5f3b1a78f 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceSettingsController.java @@ -17,6 +17,7 @@ package com.android.settings.bluetooth; import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP; +import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.ORDER_HEARING_DEVICE_SETTINGS; import android.content.Context; import android.text.TextUtils; @@ -87,6 +88,7 @@ public class BluetoothDetailsHearingDeviceSettingsController extends BluetoothDe private Preference createHearingDeviceSettingsPreference(Context context) { final ArrowPreference preference = new ArrowPreference(context); preference.setKey(KEY_HEARING_DEVICE_SETTINGS); + preference.setOrder(ORDER_HEARING_DEVICE_SETTINGS); preference.setTitle(context.getString(R.string.bluetooth_hearing_device_settings_title)); preference.setSummary( context.getString(R.string.bluetooth_hearing_device_settings_summary)); diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index dae1e086115..87b2c6b65d0 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -331,8 +331,8 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment controllers.add(new BluetoothDetailsExtraOptionsController(context, this, mCachedDevice, lifecycle)); BluetoothDetailsHearingDeviceController hearingDeviceController = - new BluetoothDetailsHearingDeviceController(context, this, mCachedDevice, - lifecycle); + new BluetoothDetailsHearingDeviceController(context, this, mManager, + mCachedDevice, lifecycle); controllers.add(hearingDeviceController); hearingDeviceController.initSubControllers(isLaunchFromHearingDevicePage()); controllers.addAll(hearingDeviceController.getSubControllers()); diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java new file mode 100644 index 00000000000..3a898c150e1 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 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.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP; +import static com.android.settings.bluetooth.BluetoothDetailsHearingAidsPresetsController.KEY_HEARING_AIDS_PRESETS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothHapClient; + +import androidx.preference.ListPreference; +import androidx.preference.PreferenceCategory; + +import com.android.settingslib.bluetooth.HapClientProfile; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; + +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 java.util.List; +import java.util.concurrent.Executor; + +/** Tests for {@link BluetoothDetailsHearingAidsPresetsController}. */ +@RunWith(RobolectricTestRunner.class) +public class BluetoothDetailsHearingAidsPresetsControllerTest extends + BluetoothDetailsControllerTestBase { + + private static final int TEST_PRESET_INDEX = 1; + private static final String TEST_PRESET_NAME = "test_preset"; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private LocalBluetoothManager mLocalManager; + @Mock + private LocalBluetoothProfileManager mProfileManager; + @Mock + private HapClientProfile mHapClientProfile; + + private BluetoothDetailsHearingAidsPresetsController mController; + + @Override + public void setUp() { + super.setUp(); + + when(mLocalManager.getProfileManager()).thenReturn(mProfileManager); + when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile); + when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile)); + PreferenceCategory deviceControls = new PreferenceCategory(mContext); + deviceControls.setKey(KEY_HEARING_DEVICE_GROUP); + mScreen.addPreference(deviceControls); + mController = new BluetoothDetailsHearingAidsPresetsController(mContext, mFragment, + mLocalManager, mCachedDevice, mLifecycle); + mController.init(mScreen); + } + + @Test + public void onResume_registerCallback() { + mController.onResume(); + + verify(mHapClientProfile).registerCallback(any(Executor.class), + any(BluetoothHapClient.Callback.class)); + } + + @Test + public void onPause_unregisterCallback() { + mController.onPause(); + + verify(mHapClientProfile).unregisterCallback(any(BluetoothHapClient.Callback.class)); + } + + + @Test + public void onPreferenceChange_keyMatched_verifyStatusUpdated() { + final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS); + + boolean handled = mController.onPreferenceChange(presetPreference, + String.valueOf(TEST_PRESET_INDEX)); + + assertThat(handled).isTrue(); + } + + @Test + public void onPreferenceChange_keyNotMatched_doNothing() { + final ListPreference presetPreference = getTestPresetPreference("wrong_key"); + + boolean handled = mController.onPreferenceChange( + presetPreference, String.valueOf(TEST_PRESET_INDEX)); + + assertThat(handled).isFalse(); + } + + private ListPreference getTestPresetPreference(String key) { + final ListPreference presetPreference = spy(new ListPreference(mContext)); + when(presetPreference.findIndexOfValue(String.valueOf(TEST_PRESET_INDEX))).thenReturn(0); + when(presetPreference.getEntries()).thenReturn(new CharSequence[]{TEST_PRESET_NAME}); + when(presetPreference.getEntryValues()).thenReturn( + new CharSequence[]{String.valueOf(TEST_PRESET_INDEX)}); + presetPreference.setKey(key); + return presetPreference; + } +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java index d5284b4c438..2a50f892add 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java @@ -20,6 +20,15 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; + +import com.android.settings.accessibility.Flags; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; + import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,11 +42,20 @@ import org.robolectric.RobolectricTestRunner; public class BluetoothDetailsHearingDeviceControllerTest extends BluetoothDetailsControllerTestBase { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + @Mock + private LocalBluetoothManager mLocalManager; + @Mock + private LocalBluetoothProfileManager mProfileManager; + @Mock private BluetoothDetailsHearingDeviceController mHearingDeviceController; - + @Mock + private BluetoothDetailsHearingAidsPresetsController mPresetsController; @Mock private BluetoothDetailsHearingDeviceSettingsController mHearingDeviceSettingsController; @@ -45,9 +63,11 @@ public class BluetoothDetailsHearingDeviceControllerTest extends public void setUp() { super.setUp(); + when(mLocalManager.getProfileManager()).thenReturn(mProfileManager); mHearingDeviceController = new BluetoothDetailsHearingDeviceController(mContext, - mFragment, mCachedDevice, mLifecycle); - mHearingDeviceController.setSubControllers(mHearingDeviceSettingsController); + mFragment, mLocalManager, mCachedDevice, mLifecycle); + mHearingDeviceController.setSubControllers(mHearingDeviceSettingsController, + mPresetsController); } @Test @@ -57,9 +77,17 @@ public class BluetoothDetailsHearingDeviceControllerTest extends assertThat(mHearingDeviceController.isAvailable()).isTrue(); } + @Test + public void isAvailable_presetsControlsAvailable_returnTrue() { + when(mPresetsController.isAvailable()).thenReturn(true); + + assertThat(mHearingDeviceController.isAvailable()).isTrue(); + } + @Test public void isAvailable_noControllersAvailable_returnFalse() { when(mHearingDeviceSettingsController.isAvailable()).thenReturn(false); + when(mPresetsController.isAvailable()).thenReturn(false); assertThat(mHearingDeviceController.isAvailable()).isFalse(); } @@ -80,4 +108,22 @@ public class BluetoothDetailsHearingDeviceControllerTest extends assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch( c -> c instanceof BluetoothDetailsHearingDeviceSettingsController)).isTrue(); } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HEARING_AID_PRESET_CONTROL) + public void initSubControllers_flagEnabled_presetControllerExist() { + mHearingDeviceController.initSubControllers(false); + + assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch( + c -> c instanceof BluetoothDetailsHearingAidsPresetsController)).isTrue(); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_HEARING_AID_PRESET_CONTROL) + public void initSubControllers_flagDisabled_presetControllerNotExist() { + mHearingDeviceController.initSubControllers(false); + + assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch( + c -> c instanceof BluetoothDetailsHearingAidsPresetsController)).isFalse(); + } } From b054b05b788a80093ed57b4cc4e8443a563524c5 Mon Sep 17 00:00:00 2001 From: Angela Wang Date: Mon, 22 Jan 2024 07:11:27 +0000 Subject: [PATCH 03/10] Selects presets in device details page (2/2) Updates UI and sets preset to remote device when corresponding callback is called. Bug: 300015207 Test: atest BluetoothDetailsHearingAidsPresetsControllerTest Change-Id: Ic013b96acaa6161b861fbae32ddfd77387f9bc47 --- res/values/strings.xml | 2 + ...thDetailsHearingAidsPresetsController.java | 124 +++++++++++++-- ...tailsHearingAidsPresetsControllerTest.java | 143 +++++++++++++++++- 3 files changed, 258 insertions(+), 11 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 1fa41eb41b7..92157f3e9b1 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -156,6 +156,8 @@ Shortcut, hearing aid compatibility Presets + + Couldn\u2019t update preset Audio output diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java index 208f8d0afbd..43721f4cf92 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java @@ -19,15 +19,18 @@ package com.android.settings.bluetooth; import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP; import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.ORDER_HEARING_AIDS_PRESETS; +import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHapClient; import android.bluetooth.BluetoothHapPresetInfo; import android.content.Context; import android.text.TextUtils; import android.util.Log; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; @@ -88,8 +91,53 @@ public class BluetoothDetailsHearingAidsPresetsController extends @Override public boolean onPreferenceChange(@NonNull Preference preference, @Nullable Object newValue) { if (TextUtils.equals(preference.getKey(), getPreferenceKey())) { - // TODO(b/300015207): Update the settings to remote device - return true; + if (newValue instanceof final String value + && preference instanceof final ListPreference listPreference) { + final int index = listPreference.findIndexOfValue(value); + final String presetName = listPreference.getEntries()[index].toString(); + final int presetIndex = Integer.parseInt( + listPreference.getEntryValues()[index].toString()); + listPreference.setSummary(presetName); + boolean supportSynchronizedPresets = mHapClientProfile.supportsSynchronizedPresets( + mCachedDevice.getDevice()); + int hapGroupId = mHapClientProfile.getHapGroup(mCachedDevice.getDevice()); + if (supportSynchronizedPresets + && hapGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { + if (DEBUG) { + Log.d(TAG, "onPreferenceChange, selectPresetForGroup " + + ", presetName: " + presetName + + ", presetIndex: " + presetIndex + + ", hapGroupId: " + hapGroupId + + ", device: " + mCachedDevice.getAddress()); + } + mHapClientProfile.selectPresetForGroup(hapGroupId, presetIndex); + } else { + if (DEBUG) { + Log.d(TAG, "onPreferenceChange, selectPreset " + + ", presetName: " + presetName + + ", presetIndex: " + presetIndex + + ", device: " + mCachedDevice.getAddress()); + } + mHapClientProfile.selectPreset(mCachedDevice.getDevice(), presetIndex); + final CachedBluetoothDevice subDevice = mCachedDevice.getSubDevice(); + if (subDevice != null) { + if (DEBUG) { + Log.d(TAG, "onPreferenceChange, selectPreset for subDevice" + + ", device: " + subDevice.getAddress()); + } + mHapClientProfile.selectPreset(subDevice.getDevice(), presetIndex); + } + for (final CachedBluetoothDevice memberDevice : + mCachedDevice.getMemberDevice()) { + if (DEBUG) { + Log.d(TAG, "onPreferenceChange, selectPreset for memberDevice" + + ", device: " + memberDevice.getAddress()); + } + mHapClientProfile.selectPreset(memberDevice.getDevice(), presetIndex); + } + } + return true; + } } return false; } @@ -115,12 +163,29 @@ public class BluetoothDetailsHearingAidsPresetsController extends return; } mPreference.setEnabled(mCachedDevice.isConnectedHapClientDevice()); - // TODO(b/300015207): Load preset from remote and show in UI + + loadAllPresetInfo(); + if (mPreference.getEntries().length == 0) { + mPreference.setEnabled(false); + } else { + int activePresetIndex = mHapClientProfile.getActivePresetIndex( + mCachedDevice.getDevice()); + if (activePresetIndex != BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) { + mPreference.setValue(Integer.toString(activePresetIndex)); + mPreference.setSummary(mPreference.getEntry()); + } else { + mPreference.setSummary(null); + } + } } @Override public boolean isAvailable() { - return false; + if (mHapClientProfile == null) { + return false; + } + return mCachedDevice.getProfiles().stream().anyMatch( + profile -> profile instanceof HapClientProfile); } @Override @@ -130,7 +195,7 @@ public class BluetoothDetailsHearingAidsPresetsController extends Log.d(TAG, "onPresetSelected, device: " + device.getAddress() + ", presetIndex: " + presetIndex + ", reason: " + reason); } - // TODO(b/300015207): Update the UI + mContext.getMainExecutor().execute(this::refresh); } } @@ -142,7 +207,10 @@ public class BluetoothDetailsHearingAidsPresetsController extends "onPresetSelectionFailed, device: " + device.getAddress() + ", reason: " + reason); } - // TODO(b/300015207): Update the UI + mContext.getMainExecutor().execute(() -> { + refresh(); + showErrorToast(); + }); } } @@ -153,7 +221,10 @@ public class BluetoothDetailsHearingAidsPresetsController extends Log.d(TAG, "onPresetSelectionForGroupFailed, group: " + hapGroupId + ", reason: " + reason); } - // TODO(b/300015207): Update the UI + mContext.getMainExecutor().execute(() -> { + refresh(); + showErrorToast(); + }); } } @@ -166,7 +237,7 @@ public class BluetoothDetailsHearingAidsPresetsController extends + ", reason: " + reason + ", infoList: " + presetInfoList); } - // TODO(b/300015207): Update the UI + mContext.getMainExecutor().execute(this::refresh); } } @@ -178,7 +249,10 @@ public class BluetoothDetailsHearingAidsPresetsController extends "onSetPresetNameFailed, device: " + device.getAddress() + ", reason: " + reason); } - // TODO(b/300015207): Update the UI + mContext.getMainExecutor().execute(() -> { + refresh(); + showErrorToast(); + }); } } @@ -189,7 +263,10 @@ public class BluetoothDetailsHearingAidsPresetsController extends Log.d(TAG, "onSetPresetNameForGroupFailed, group: " + hapGroupId + ", reason: " + reason); } - // TODO(b/300015207): Update the UI + mContext.getMainExecutor().execute(() -> { + refresh(); + showErrorToast(); + }); } } @@ -201,4 +278,31 @@ public class BluetoothDetailsHearingAidsPresetsController extends preference.setOnPreferenceChangeListener(this); return preference; } + + private void loadAllPresetInfo() { + if (mPreference == null) { + return; + } + List infoList = mHapClientProfile.getAllPresetInfo( + mCachedDevice.getDevice()); + CharSequence[] presetNames = new CharSequence[infoList.size()]; + CharSequence[] presetIndexes = new CharSequence[infoList.size()]; + for (int i = 0; i < infoList.size(); i++) { + presetNames[i] = infoList.get(i).getName(); + presetIndexes[i] = Integer.toString(infoList.get(i).getIndex()); + } + mPreference.setEntries(presetNames); + mPreference.setEntryValues(presetIndexes); + } + + @VisibleForTesting + @Nullable + ListPreference getPreference() { + return mPreference; + } + + void showErrorToast() { + Toast.makeText(mContext, R.string.bluetooth_hearing_aids_presets_error, + Toast.LENGTH_SHORT).show(); + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java index 3a898c150e1..c08bb98e55b 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsControllerTest.java @@ -16,21 +16,29 @@ package com.android.settings.bluetooth; +import static android.bluetooth.BluetoothCsipSetCoordinator.GROUP_ID_INVALID; +import static android.bluetooth.BluetoothHapClient.PRESET_INDEX_UNAVAILABLE; + import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP; import static com.android.settings.bluetooth.BluetoothDetailsHearingAidsPresetsController.KEY_HEARING_AIDS_PRESETS; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHapClient; +import android.bluetooth.BluetoothHapPresetInfo; import androidx.preference.ListPreference; import androidx.preference.PreferenceCategory; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.HapClientProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; @@ -43,7 +51,9 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; +import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.concurrent.Executor; /** Tests for {@link BluetoothDetailsHearingAidsPresetsController}. */ @@ -53,6 +63,7 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends private static final int TEST_PRESET_INDEX = 1; private static final String TEST_PRESET_NAME = "test_preset"; + private static final int TEST_HAP_GROUP_ID = 1; @Rule public final MockitoRule mockito = MockitoJUnit.rule(); @@ -63,6 +74,10 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends private LocalBluetoothProfileManager mProfileManager; @Mock private HapClientProfile mHapClientProfile; + @Mock + private CachedBluetoothDevice mCachedChildDevice; + @Mock + private BluetoothDevice mChildDevice; private BluetoothDetailsHearingAidsPresetsController mController; @@ -73,6 +88,8 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends when(mLocalManager.getProfileManager()).thenReturn(mProfileManager); when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile); when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile)); + when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true); + when(mCachedChildDevice.getDevice()).thenReturn(mChildDevice); PreferenceCategory deviceControls = new PreferenceCategory(mContext); deviceControls.setKey(KEY_HEARING_DEVICE_GROUP); mScreen.addPreference(deviceControls); @@ -81,6 +98,20 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends mController.init(mScreen); } + @Test + public void isAvailable_supportHap_returnTrue() { + when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile)); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_notSupportHap_returnFalse() { + when(mCachedDevice.getProfiles()).thenReturn(new ArrayList<>()); + + assertThat(mController.isAvailable()).isFalse(); + } + @Test public void onResume_registerCallback() { mController.onResume(); @@ -96,7 +127,6 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends verify(mHapClientProfile).unregisterCallback(any(BluetoothHapClient.Callback.class)); } - @Test public void onPreferenceChange_keyMatched_verifyStatusUpdated() { final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS); @@ -105,6 +135,7 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends String.valueOf(TEST_PRESET_INDEX)); assertThat(handled).isTrue(); + verify(presetPreference).setSummary(TEST_PRESET_NAME); } @Test @@ -115,6 +146,116 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends presetPreference, String.valueOf(TEST_PRESET_INDEX)); assertThat(handled).isFalse(); + verify(presetPreference, never()).setSummary(any()); + } + + @Test + public void onPreferenceChange_supportGroupOperation_validGroupId_verifySelectPresetForGroup() { + final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS); + when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(true); + when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID); + + mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX)); + + verify(mHapClientProfile).selectPresetForGroup(TEST_HAP_GROUP_ID, TEST_PRESET_INDEX); + } + + @Test + public void onPreferenceChange_notSupportGroupOperation_verifySelectPreset() { + final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS); + when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false); + when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID); + + mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX)); + + verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX); + } + + @Test + public void onPreferenceChange_invalidGroupId_verifySelectPreset() { + final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS); + when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(true); + when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(GROUP_ID_INVALID); + + mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX)); + + verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX); + } + + @Test + public void onPreferenceChange_notSupportGroupOperation_hasSubDevice_verifyStatusUpdated() { + final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS); + when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false); + when(mCachedDevice.getSubDevice()).thenReturn(mCachedChildDevice); + + mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX)); + + verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX); + verify(mHapClientProfile).selectPreset(mChildDevice, TEST_PRESET_INDEX); + } + + @Test + public void onPreferenceChange_notSupportGroupOperation_hasMemberDevice_verifyStatusUpdated() { + final ListPreference presetPreference = getTestPresetPreference(KEY_HEARING_AIDS_PRESETS); + when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false); + when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mCachedChildDevice)); + + mController.onPreferenceChange(presetPreference, String.valueOf(TEST_PRESET_INDEX)); + + verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX); + verify(mHapClientProfile).selectPreset(mChildDevice, TEST_PRESET_INDEX); + } + + @Test + public void refresh_emptyPresetInfo_preferenceDisabled() { + when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(new ArrayList<>()); + + mController.refresh(); + + assertThat(mController.getPreference()).isNotNull(); + assertThat(mController.getPreference().isEnabled()).isFalse(); + } + + @Test + public void refresh_validPresetInfo_preferenceEnabled() { + BluetoothHapPresetInfo info = getTestPresetInfo(); + when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info)); + + mController.refresh(); + + assertThat(mController.getPreference()).isNotNull(); + assertThat(mController.getPreference().isEnabled()).isTrue(); + } + + @Test + public void refresh_invalidActivePresetIndex_summaryIsNull() { + BluetoothHapPresetInfo info = getTestPresetInfo(); + when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info)); + when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(PRESET_INDEX_UNAVAILABLE); + + mController.refresh(); + + assertThat(mController.getPreference()).isNotNull(); + assertThat(mController.getPreference().getSummary()).isNull(); + } + + @Test + public void refresh_validActivePresetIndex_summaryIsNotNull() { + BluetoothHapPresetInfo info = getTestPresetInfo(); + when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info)); + when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX); + + mController.refresh(); + + assertThat(mController.getPreference()).isNotNull(); + assertThat(mController.getPreference().getSummary()).isNotNull(); + } + + private BluetoothHapPresetInfo getTestPresetInfo() { + BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class); + when(info.getName()).thenReturn(TEST_PRESET_NAME); + when(info.getIndex()).thenReturn(TEST_PRESET_INDEX); + return info; } private ListPreference getTestPresetPreference(String key) { From f8ffe796695560af6f9712c414567f3b0ca8bba2 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Tue, 5 Mar 2024 16:45:05 +0000 Subject: [PATCH 04/10] Touchpad: add metric for tap dragging setting changes Bug: 321978150 Bug: 324058706 Test: $ m statsd_testdrive $ ./out/host/linux-x86/bin/statsd_testdrive -e 97 Then toggle the setting, press Enter in the terminal, and check the new actions are logged Change-Id: I55b7cc3377e7b27e9570f3f1b79c1f300d5d5686 --- .../TrackpadTapDraggingPreferenceController.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/inputmethod/TrackpadTapDraggingPreferenceController.java b/src/com/android/settings/inputmethod/TrackpadTapDraggingPreferenceController.java index 28c2915e4d7..30253a8a30f 100644 --- a/src/com/android/settings/inputmethod/TrackpadTapDraggingPreferenceController.java +++ b/src/com/android/settings/inputmethod/TrackpadTapDraggingPreferenceController.java @@ -16,16 +16,22 @@ package com.android.settings.inputmethod; +import android.app.settings.SettingsEnums; import android.content.Context; import android.hardware.input.InputSettings; import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; public class TrackpadTapDraggingPreferenceController extends TogglePreferenceController { + private MetricsFeatureProvider mMetricsFeatureProvider; + public TrackpadTapDraggingPreferenceController(Context context, String key) { super(context, key); + mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); } @Override @@ -36,7 +42,8 @@ public class TrackpadTapDraggingPreferenceController extends TogglePreferenceCon @Override public boolean setChecked(boolean isChecked) { InputSettings.setTouchpadTapDragging(mContext, isChecked); - // TODO(b/321978150): add a metric for tap dragging settings changes. + mMetricsFeatureProvider.action( + mContext, SettingsEnums.ACTION_GESTURE_TAP_DRAGGING_CHANGED, isChecked); return true; } From 757633223e9604c0277af79ed4d7e89c3fbdb718 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 28 Feb 2024 17:51:35 +0800 Subject: [PATCH 05/10] Fix AppInfoWithHeaderTest Use any() in getPackageInfoAsUser() to make the tests more robust regarding PackageManager flags change. Bug: 315135755 Test: atest Change-Id: I3857ca3bf2ee23424c78716ed3669fbe87c786f8 --- .../applications/AppInfoWithHeaderTest.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/robotests/src/com/android/settings/applications/AppInfoWithHeaderTest.java b/tests/robotests/src/com/android/settings/applications/AppInfoWithHeaderTest.java index ce520271de6..562212e3569 100644 --- a/tests/robotests/src/com/android/settings/applications/AppInfoWithHeaderTest.java +++ b/tests/robotests/src/com/android/settings/applications/AppInfoWithHeaderTest.java @@ -19,6 +19,7 @@ package com.android.settings.applications; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -48,12 +49,13 @@ import com.android.settingslib.widget.LayoutPreference; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; +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; @@ -62,6 +64,8 @@ import org.robolectric.util.ReflectionHelpers; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowEntityHeaderController.class, ShadowSettingsLibUtils.class}) public class AppInfoWithHeaderTest { + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Mock(answer = Answers.RETURNS_DEEP_STUBS) private EntityHeaderController mHeaderController; @@ -71,7 +75,6 @@ public class AppInfoWithHeaderTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); mFactory = FakeFeatureFactory.setupForTest(); when(mFactory.metricsFeatureProvider.getMetricsCategory(any(Object.class))) .thenReturn(MetricsProto.MetricsEvent.SETTINGS_APP_NOTIF_CATEGORY); @@ -120,7 +123,6 @@ public class AppInfoWithHeaderTest { assertThat(mAppInfoWithHeader.mPackageRemovedCalled).isTrue(); } - @Ignore("b/315135755") @Test public void noExtraUserHandleInIntent_retrieveAppEntryWithMyUserId() throws PackageManager.NameNotFoundException { @@ -133,10 +135,8 @@ public class AppInfoWithHeaderTest { when(mAppInfoWithHeader.mState.getEntry(packageName, UserHandle.myUserId())).thenReturn(entry); - when(mAppInfoWithHeader.mPm.getPackageInfoAsUser(entry.info.packageName, - PackageManager.MATCH_DISABLED_COMPONENTS | - PackageManager.GET_SIGNING_CERTIFICATES | - PackageManager.GET_PERMISSIONS, UserHandle.myUserId())).thenReturn( + when(mAppInfoWithHeader.mPm.getPackageInfoAsUser(eq(entry.info.packageName), + any(), eq(UserHandle.myUserId()))).thenReturn( mAppInfoWithHeader.mPackageInfo); mAppInfoWithHeader.retrieveAppEntry(); @@ -146,7 +146,6 @@ public class AppInfoWithHeaderTest { assertThat(mAppInfoWithHeader.mAppEntry).isNotNull(); } - @Ignore("b/315135755") @Test public void extraUserHandleInIntent_retrieveAppEntryWithMyUserId() throws PackageManager.NameNotFoundException { @@ -161,10 +160,8 @@ public class AppInfoWithHeaderTest { entry.info.packageName = packageName; when(mAppInfoWithHeader.mState.getEntry(packageName, USER_ID)).thenReturn(entry); - when(mAppInfoWithHeader.mPm.getPackageInfoAsUser(entry.info.packageName, - PackageManager.MATCH_DISABLED_COMPONENTS | - PackageManager.GET_SIGNING_CERTIFICATES | - PackageManager.GET_PERMISSIONS, USER_ID)).thenReturn( + when(mAppInfoWithHeader.mPm.getPackageInfoAsUser(eq(entry.info.packageName), + any(), eq(USER_ID))).thenReturn( mAppInfoWithHeader.mPackageInfo); mAppInfoWithHeader.retrieveAppEntry(); @@ -232,6 +229,8 @@ public class AppInfoWithHeaderTest { } @Override - protected Intent getIntent() { return mIntent; } + protected Intent getIntent() { + return mIntent; + } } } From 65e283dca7ac2d353fe36d95bfd133767b2b347f Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Fri, 8 Mar 2024 08:54:59 +0800 Subject: [PATCH 06/10] Enable android:restoreAnyVersion Bug: 317149339 Test: Manual tests Change-Id: If1f37519b59159c5cd6fa49a3e23dcc85348336e --- AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index aed51f3e0eb..5ffbbf84d01 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -153,6 +153,7 @@ android:requiredForAllUsers="true" android:supportsRtl="true" android:backupAgent="com.android.settings.backup.SettingsBackupHelper" + android:restoreAnyVersion="true" android:usesCleartextTraffic="true" android:defaultToDeviceProtectedStorage="true" android:directBootAware="true" From aef8284b9d7ba1582b7aae3b72d48c5df94b8fca Mon Sep 17 00:00:00 2001 From: Roshan Pius Date: Mon, 11 Mar 2024 00:48:37 +0000 Subject: [PATCH 07/10] settings(dev): Remove NFC stack logging control resources Was not fully removed in ag/26414526. Bug: 327517842 Change-Id: Ia147e754547a73ff63d877eb6ef86794f3dd0250 Test: Compiles --- res/values/strings.xml | 5 ----- res/xml/development_settings.xml | 5 ----- 2 files changed, 10 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 2037323c8d4..23d45f59743 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1847,11 +1847,6 @@ Select maximum number of connected Bluetooth audio devices - - NFC stack debug log - - Increase NFC stack logging level - NFC verbose vendor debug log diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index b1ebbb82ed5..23eb1f25241 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -469,11 +469,6 @@ android:entries="@array/bluetooth_max_connected_audio_devices" android:entryValues="@array/bluetooth_max_connected_audio_devices_values" /> - - Date: Mon, 11 Mar 2024 12:27:06 +0800 Subject: [PATCH 08/10] Restrict SimPreference Check policy UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS. Bug: 329000499 Test: manual - on Mobile Settings Change-Id: I37687299a9ec5c77b59cb231969180872271f4a6 --- src/com/android/settings/spa/network/SimsSection.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/com/android/settings/spa/network/SimsSection.kt b/src/com/android/settings/spa/network/SimsSection.kt index 334ca61bc9b..9e4cf9f4c12 100644 --- a/src/com/android/settings/spa/network/SimsSection.kt +++ b/src/com/android/settings/spa/network/SimsSection.kt @@ -36,10 +36,10 @@ import com.android.settings.network.telephony.isSubscriptionEnabledFlow import com.android.settings.network.telephony.phoneNumberFlow import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel -import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference import com.android.settingslib.spa.widget.ui.SettingsIcon import com.android.settingslib.spaprivileged.model.enterprise.Restrictions import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference +import com.android.settingslib.spaprivileged.template.preference.RestrictedTwoTargetSwitchPreference @Composable fun SimsSection(subscriptionInfoList: List) { @@ -61,9 +61,8 @@ private fun SimPreference(subInfo: SubscriptionInfo) { val phoneNumber = remember(subInfo) { context.phoneNumberFlow(subInfo) }.collectAsStateWithLifecycle(initialValue = null) - //TODO: Add the Restricted TwoTargetSwitchPreference in SPA - TwoTargetSwitchPreference( - object : SwitchPreferenceModel { + RestrictedTwoTargetSwitchPreference( + model = object : SwitchPreferenceModel { override val title = subInfo.displayName.toString() override val summary = { phoneNumber.value ?: "" } override val checked = { checked.value } @@ -74,7 +73,8 @@ private fun SimPreference(subInfo: SubscriptionInfo) { newChecked, ) } - } + }, + restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)), ) { MobileNetworkUtils.launchMobileNetworkSettings(context, subInfo) } From ef8a51fac85eedee8f9509b9f7c002281c1ae093 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Fri, 8 Mar 2024 19:36:14 +0800 Subject: [PATCH 09/10] Reset app preferences does not trigger backup for App battery usages Bug: 328712606 Fix: 328712606 Test: UT && Verify logcat when change/reset App battery usages Change-Id: Ia3917c8dc2654185f5f048c048362fd47379b7d1 --- .../fuelgauge/AdvancedPowerUsageDetail.java | 9 ------ .../fuelgauge/BatteryOptimizeUtils.java | 12 +++++++ .../fuelgauge/PowerBackgroundUsageDetail.java | 9 ------ .../AdvancedPowerUsageDetailTest.java | 31 ------------------- .../fuelgauge/BatteryOptimizeUtilsTest.java | 15 +++++++++ 5 files changed, 27 insertions(+), 49 deletions(-) diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java index 6ef5aa82a49..42e6d9c4b68 100644 --- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -52,7 +52,6 @@ import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.instrumentation.Instrumentable; -import com.android.settingslib.datastore.ChangeReason; import com.android.settingslib.widget.LayoutPreference; import java.util.ArrayList; @@ -272,7 +271,6 @@ public class AdvancedPowerUsageDetail extends DashboardFragment public void onPause() { super.onPause(); - notifyBackupManager(); final int currentOptimizeMode = mBatteryOptimizeUtils.getAppOptimizationMode(); mLogStringBuilder.append(", onPause mode = ").append(currentOptimizeMode); logMetricCategory(currentOptimizeMode); @@ -289,13 +287,6 @@ public class AdvancedPowerUsageDetail extends DashboardFragment Log.d(TAG, "Leave with mode: " + currentOptimizeMode); } - @VisibleForTesting - void notifyBackupManager() { - if (mOptimizationMode != mBatteryOptimizeUtils.getAppOptimizationMode()) { - BatterySettingsStorage.get(getContext()).notifyChange(ChangeReason.UPDATE); - } - } - @VisibleForTesting void initHeader() { final View appSnippet = mHeaderPreference.findViewById(R.id.entity_header); diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java index dc4aade4545..001876c394b 100644 --- a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java @@ -33,6 +33,7 @@ import androidx.annotation.VisibleForTesting; import com.android.settings.R; import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action; +import com.android.settingslib.datastore.ChangeReason; import com.android.settingslib.fuelgauge.PowerAllowlistBackend; import java.lang.annotation.Retention; @@ -222,6 +223,10 @@ public class BatteryOptimizeUtils { return; } + // App preferences are already clear when code reach here, and there may be no + // setAppUsageStateInternal call to notifyChange. So always trigger notifyChange here. + BatterySettingsStorage.get(context).notifyChange(ChangeReason.DELETE); + allowlistBackend.refreshList(); // Resets optimization mode for each application. for (ApplicationInfo info : applications) { @@ -351,6 +356,9 @@ public class BatteryOptimizeUtils { } BatteryOptimizeLogUtils.writeLog( context, action, packageNameKey, createLogEvent(appStandbyMode, allowListed)); + if (action != Action.RESET) { // reset has been notified in resetAppOptimizationMode + BatterySettingsStorage.get(context).notifyChange(toChangeReason(action)); + } } private static String createLogEvent(int appStandbyMode, boolean allowListed) { @@ -362,4 +370,8 @@ public class BatteryOptimizeUtils { allowListed, getAppOptimizationMode(appStandbyMode, allowListed)); } + + private static @ChangeReason int toChangeReason(Action action) { + return action == Action.RESTORE ? ChangeReason.RESTORE : ChangeReason.UPDATE; + } } diff --git a/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java b/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java index 56702cf5c2a..b662d3ef908 100644 --- a/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java +++ b/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java @@ -41,7 +41,6 @@ import com.android.settingslib.HelpUtils; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.datastore.ChangeReason; import com.android.settingslib.widget.FooterPreference; import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.MainSwitchPreference; @@ -116,7 +115,6 @@ public class PowerBackgroundUsageDetail extends DashboardFragment public void onPause() { super.onPause(); - notifyBackupManager(); final int currentOptimizeMode = mBatteryOptimizeUtils.getAppOptimizationMode(); mLogStringBuilder.append(", onPause mode = ").append(currentOptimizeMode); logMetricCategory(currentOptimizeMode); @@ -183,13 +181,6 @@ public class PowerBackgroundUsageDetail extends DashboardFragment onRadioButtonClicked(isEnabled ? mOptimizePreference : null); } - @VisibleForTesting - void notifyBackupManager() { - if (mOptimizationMode != mBatteryOptimizeUtils.getAppOptimizationMode()) { - BatterySettingsStorage.get(getContext()).notifyChange(ChangeReason.UPDATE); - } - } - @VisibleForTesting int getSelectedPreference() { if (!mMainSwitchPreference.isChecked()) { diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java index 0648de4ad69..80739e9d47a 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java @@ -60,12 +60,8 @@ import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.instantapps.InstantAppDataProvider; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; -import com.android.settingslib.datastore.ChangeReason; -import com.android.settingslib.datastore.Observer; import com.android.settingslib.widget.LayoutPreference; -import com.google.common.util.concurrent.MoreExecutors; - import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -119,10 +115,8 @@ public class AdvancedPowerUsageDetailTest { @Mock private AppOpsManager mAppOpsManager; @Mock private LoaderManager mLoaderManager; @Mock private BatteryOptimizeUtils mBatteryOptimizeUtils; - @Mock private Observer mObserver; private Context mContext; - private BatterySettingsStorage mBatterySettingsStorage; private PrimarySwitchPreference mAllowBackgroundUsagePreference; private AdvancedPowerUsageDetail mFragment; private SettingsActivity mTestActivity; @@ -134,7 +128,6 @@ public class AdvancedPowerUsageDetailTest { @Before public void setUp() { mContext = spy(ApplicationProvider.getApplicationContext()); - mBatterySettingsStorage = BatterySettingsStorage.get(mContext); when(mContext.getPackageName()).thenReturn("foo"); mFeatureFactory = FakeFeatureFactory.setupForTest(); mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider; @@ -448,28 +441,4 @@ public class AdvancedPowerUsageDetailTest { TimeUnit.SECONDS.sleep(1); verifyNoInteractions(mMetricsFeatureProvider); } - - @Test - public void notifyBackupManager_optimizationModeIsNotChanged_notInvokeDataChanged() { - mBatterySettingsStorage.addObserver(mObserver, MoreExecutors.directExecutor()); - final int mode = BatteryOptimizeUtils.MODE_RESTRICTED; - mFragment.mOptimizationMode = mode; - when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(mode); - - mFragment.notifyBackupManager(); - - verifyNoInteractions(mObserver); - } - - @Test - public void notifyBackupManager_optimizationModeIsChanged_invokeDataChanged() { - mBatterySettingsStorage.addObserver(mObserver, MoreExecutors.directExecutor()); - mFragment.mOptimizationMode = BatteryOptimizeUtils.MODE_RESTRICTED; - when(mBatteryOptimizeUtils.getAppOptimizationMode()) - .thenReturn(BatteryOptimizeUtils.MODE_UNRESTRICTED); - - mFragment.notifyBackupManager(); - - verify(mObserver).onChanged(ChangeReason.UPDATE); - } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java index 3551eeb431e..6085b9a3ce4 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java @@ -49,8 +49,12 @@ import android.os.UserManager; import android.util.ArraySet; import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action; +import com.android.settingslib.datastore.ChangeReason; +import com.android.settingslib.datastore.Observer; import com.android.settingslib.fuelgauge.PowerAllowlistBackend; +import com.google.common.util.concurrent.MoreExecutors; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -74,14 +78,18 @@ public class BatteryOptimizeUtilsTest { @Mock private PowerAllowlistBackend mMockBackend; @Mock private IPackageManager mMockIPackageManager; @Mock private UserManager mMockUserManager; + @Mock private Observer mObserver; private Context mContext; private BatteryOptimizeUtils mBatteryOptimizeUtils; + private BatterySettingsStorage mBatterySettingsStorage; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + mBatterySettingsStorage = BatterySettingsStorage.get(mContext); + mBatterySettingsStorage.addObserver(mObserver, MoreExecutors.directExecutor()); mBatteryOptimizeUtils = spy(new BatteryOptimizeUtils(mContext, UID, PACKAGE_NAME)); mBatteryOptimizeUtils.mAppOpsManager = mMockAppOpsManager; mBatteryOptimizeUtils.mBatteryUtils = mMockBatteryUtils; @@ -156,6 +164,7 @@ public class BatteryOptimizeUtilsTest { TimeUnit.SECONDS.sleep(1); verifySetAppOptimizationMode(AppOpsManager.MODE_IGNORED, /* allowListed */ false); + verify(mObserver).onChanged(ChangeReason.UPDATE); } @Test @@ -169,6 +178,7 @@ public class BatteryOptimizeUtilsTest { TimeUnit.SECONDS.sleep(1); verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ true); + verify(mObserver).onChanged(ChangeReason.UPDATE); } @Test @@ -182,6 +192,7 @@ public class BatteryOptimizeUtilsTest { TimeUnit.SECONDS.sleep(1); verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false); + verify(mObserver).onChanged(ChangeReason.UPDATE); } @Test @@ -197,6 +208,7 @@ public class BatteryOptimizeUtilsTest { verify(mMockBatteryUtils, never()).setForceAppStandby(anyInt(), anyString(), anyInt()); verify(mMockBackend, never()).addApp(anyString()); verify(mMockBackend, never()).removeApp(anyString()); + verifyNoInteractions(mObserver); } @Test @@ -288,6 +300,7 @@ public class BatteryOptimizeUtilsTest { inOrder.verify(mMockBackend).isAllowlisted(PACKAGE_NAME, UID); inOrder.verify(mMockBackend).isSysAllowlisted(PACKAGE_NAME); verifyNoMoreInteractions(mMockBackend); + verify(mObserver).onChanged(ChangeReason.DELETE); } @Test @@ -298,6 +311,7 @@ public class BatteryOptimizeUtilsTest { /* isSystemOrDefaultApp */ false); verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false); + verify(mObserver).onChanged(ChangeReason.DELETE); } @Test @@ -308,6 +322,7 @@ public class BatteryOptimizeUtilsTest { /* isSystemOrDefaultApp */ false); verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false); + verify(mObserver).onChanged(ChangeReason.DELETE); } private void runTestForResetWithMode( From 59c8ba7fd03b247a0fc47eea68736fd92cba0dc4 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 11 Mar 2024 15:43:03 +0800 Subject: [PATCH 10/10] Use SubscriptionUtil.getFormattedPhoneNumber For Primary SIM. Bug: 318310357 Test: manual - on Mobile Settings Change-Id: Ic8085532fd025bb1e0b6897f5509f485c37a6d56 --- .../network/NetworkCellularGroupProvider.kt | 163 +++++++----------- .../spa/network/PrimarySimRepository.kt | 62 +++++++ .../spa/network/SimOnboardingPrimarySim.kt | 18 +- 3 files changed, 144 insertions(+), 99 deletions(-) create mode 100644 src/com/android/settings/spa/network/PrimarySimRepository.kt diff --git a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt index a0c363a206b..5a2a3947621 100644 --- a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt +++ b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt @@ -44,13 +44,13 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.android.settings.R import com.android.settings.network.SubscriptionInfoListViewModel import com.android.settings.network.telephony.MobileNetworkUtils +import com.android.settings.spa.network.PrimarySimRepository.PrimarySimInfo import com.android.settings.wifi.WifiPickerTrackerHelper import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle -import com.android.settingslib.spa.widget.preference.ListPreferenceOption import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreference @@ -173,27 +173,23 @@ fun PageImpl( ) { val selectableSubscriptionInfoList by selectableSubscriptionInfoListFlow .collectAsStateWithLifecycle(initialValue = emptyList()) - val activeSubscriptionInfoList: List = - selectableSubscriptionInfoList.filter { subscriptionInfo -> - subscriptionInfo.simSlotIndex != -1 - } val stringSims = stringResource(R.string.provider_network_settings_title) RegularScaffold(title = stringSims) { SimsSection(selectableSubscriptionInfoList) PrimarySimSectionImpl( - activeSubscriptionInfoList, - defaultVoiceSubId, - defaultSmsSubId, - defaultDataSubId, - nonDds + selectableSubscriptionInfoListFlow, + defaultVoiceSubId, + defaultSmsSubId, + defaultDataSubId, + nonDds ) } } @Composable fun PrimarySimImpl( - subscriptionInfoList: List, + primarySimInfo: PrimarySimInfo, callsSelectedId: MutableIntState, textsSelectedId: MutableIntState, mobileDataSelectedId: MutableIntState, @@ -237,108 +233,83 @@ fun PrimarySimImpl( } }, ) { - var state = rememberSaveable { mutableStateOf(false) } - var callsAndSmsList = remember { - mutableListOf(ListPreferenceOption(id = -1, text = "Loading")) - } - var dataList = remember { - mutableListOf(ListPreferenceOption(id = -1, text = "Loading")) + val telephonyManagerForNonDds: TelephonyManager? = + context.getSystemService(TelephonyManager::class.java) + ?.createForSubscriptionId(nonDds.intValue) + val automaticDataChecked = rememberSaveable() { + mutableStateOf(false) } - if (subscriptionInfoList.size >= 2) { - state.value = true - callsAndSmsList.clear() - dataList.clear() - for (info in subscriptionInfoList) { - var item = ListPreferenceOption( - id = info.subscriptionId, - text = "${info.displayName}", - summary = "${info.number}" - ) - callsAndSmsList.add(item) - dataList.add(item) - } - callsAndSmsList.add( - ListPreferenceOption( - id = SubscriptionManager.INVALID_SUBSCRIPTION_ID, - text = stringResource(id = R.string.sim_calls_ask_first_prefs_title) - ) - ) - } else { - // hide the primary sim - state.value = false - Log.d(NetworkCellularGroupProvider.name, "Hide primary sim") - } + CreatePrimarySimListPreference( + stringResource(id = R.string.primary_sim_calls_title), + primarySimInfo.callsAndSmsList, + callsSelectedId, + ImageVector.vectorResource(R.drawable.ic_phone), + actionSetCalls + ) + CreatePrimarySimListPreference( + stringResource(id = R.string.primary_sim_texts_title), + primarySimInfo.callsAndSmsList, + textsSelectedId, + Icons.AutoMirrored.Outlined.Message, + actionSetTexts + ) + CreatePrimarySimListPreference( + stringResource(id = R.string.mobile_data_settings_title), + primarySimInfo.dataList, + mobileDataSelectedId, + Icons.Outlined.DataUsage, + actionSetMobileData + ) - if (state.value) { - val telephonyManagerForNonDds: TelephonyManager? = - context.getSystemService(TelephonyManager::class.java) - ?.createForSubscriptionId(nonDds.intValue) - val automaticDataChecked = rememberSaveable() { - mutableStateOf(false) - } - - CreatePrimarySimListPreference( - stringResource(id = R.string.primary_sim_calls_title), - callsAndSmsList, - callsSelectedId, - ImageVector.vectorResource(R.drawable.ic_phone), - actionSetCalls - ) - CreatePrimarySimListPreference( - stringResource(id = R.string.primary_sim_texts_title), - callsAndSmsList, - textsSelectedId, - Icons.AutoMirrored.Outlined.Message, - actionSetTexts - ) - CreatePrimarySimListPreference( - stringResource(id = R.string.mobile_data_settings_title), - dataList, - mobileDataSelectedId, - Icons.Outlined.DataUsage, - actionSetMobileData - ) - - val autoDataTitle = stringResource(id = R.string.primary_sim_automatic_data_title) - val autoDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg) - SwitchPreference( - object : SwitchPreferenceModel { - override val title = autoDataTitle - override val summary = { autoDataSummary } - override val checked = { - if (nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - coroutineScope.launch { - automaticDataChecked.value = getAutomaticData(telephonyManagerForNonDds) - Log.d( - NetworkCellularGroupProvider.name, - "NonDds:${nonDds.intValue}" + - "getAutomaticData:${automaticDataChecked.value}" - ) - } + val autoDataTitle = stringResource(id = R.string.primary_sim_automatic_data_title) + val autoDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg) + SwitchPreference( + object : SwitchPreferenceModel { + override val title = autoDataTitle + override val summary = { autoDataSummary } + override val checked = { + if (nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + coroutineScope.launch { + automaticDataChecked.value = getAutomaticData(telephonyManagerForNonDds) + Log.d( + NetworkCellularGroupProvider.name, + "NonDds:${nonDds.intValue}" + + "getAutomaticData:${automaticDataChecked.value}" + ) } - automaticDataChecked.value - } - override val onCheckedChange: ((Boolean) -> Unit)? = { - automaticDataChecked.value = it - actionSetAutoDataSwitch(it) } + automaticDataChecked.value } - ) - } + override val onCheckedChange: ((Boolean) -> Unit)? = { + automaticDataChecked.value = it + actionSetAutoDataSwitch(it) + } + } + ) } @Composable fun PrimarySimSectionImpl( - subscriptionInfoList: List, + subscriptionInfoListFlow: Flow>, callsSelectedId: MutableIntState, textsSelectedId: MutableIntState, mobileDataSelectedId: MutableIntState, nonDds: MutableIntState, ) { + val context = LocalContext.current + val primarySimInfo = remember(subscriptionInfoListFlow) { + subscriptionInfoListFlow + .map { subscriptionInfoList -> + subscriptionInfoList.filter { subInfo -> subInfo.simSlotIndex != -1 } + } + .map(PrimarySimRepository(context)::getPrimarySimInfo) + .flowOn(Dispatchers.Default) + }.collectAsStateWithLifecycle(initialValue = null).value ?: return + Category(title = stringResource(id = R.string.primary_sim_title)) { PrimarySimImpl( - subscriptionInfoList, + primarySimInfo, callsSelectedId, textsSelectedId, mobileDataSelectedId, diff --git a/src/com/android/settings/spa/network/PrimarySimRepository.kt b/src/com/android/settings/spa/network/PrimarySimRepository.kt new file mode 100644 index 00000000000..b9eb3ffcb0a --- /dev/null +++ b/src/com/android/settings/spa/network/PrimarySimRepository.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 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.spa.network + +import android.content.Context +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.util.Log +import com.android.settings.R +import com.android.settings.network.SubscriptionUtil +import com.android.settingslib.spa.widget.preference.ListPreferenceOption + +class PrimarySimRepository(private val context: Context) { + + data class PrimarySimInfo( + val callsAndSmsList: List, + val dataList: List, + ) + + fun getPrimarySimInfo(selectableSubscriptionInfoList: List): PrimarySimInfo? { + if (selectableSubscriptionInfoList.size < 2) { + Log.d(TAG, "Hide primary sim") + return null + } + + val callsAndSmsList = mutableListOf() + val dataList = mutableListOf() + for (info in selectableSubscriptionInfoList) { + val item = ListPreferenceOption( + id = info.subscriptionId, + text = "${info.displayName}", + summary = SubscriptionUtil.getFormattedPhoneNumber(context, info) ?: "", + ) + callsAndSmsList += item + dataList += item + } + callsAndSmsList += ListPreferenceOption( + id = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + text = context.getString(R.string.sim_calls_ask_first_prefs_title), + ) + + return PrimarySimInfo(callsAndSmsList, dataList) + } + + private companion object { + private const val TAG = "PrimarySimRepository" + } +} diff --git a/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt b/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt index b9849666e53..a8c0575e3e8 100644 --- a/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt +++ b/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt @@ -24,10 +24,13 @@ import androidx.compose.material.icons.outlined.SignalCellularAlt import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableIntState import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settings.R import com.android.settings.network.SimOnboardingService import com.android.settingslib.spa.framework.theme.SettingsDimension @@ -38,6 +41,9 @@ import com.android.settingslib.spa.widget.scaffold.BottomAppBarButton import com.android.settingslib.spa.widget.scaffold.SuwScaffold import com.android.settingslib.spa.widget.ui.SettingsBody import com.android.settingslib.spa.widget.ui.SettingsIcon +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn /** * the sim onboarding primary sim compose @@ -77,13 +83,19 @@ fun SimOnboardingPrimarySimImpl( SettingsBody(stringResource(id = R.string.sim_onboarding_primary_sim_msg)) } - var selectedSubscriptionInfoList = - onboardingService.getSelectedSubscriptionInfoListWithRenaming() + val context = LocalContext.current + val primarySimInfo = remember { + flow { + val selectableSubInfoList = + onboardingService.getSelectedSubscriptionInfoListWithRenaming() + emit(PrimarySimRepository(context).getPrimarySimInfo(selectableSubInfoList)) + }.flowOn(Dispatchers.Default) + }.collectAsStateWithLifecycle(initialValue = null).value ?: return@SuwScaffold callsSelectedId.intValue = onboardingService.targetPrimarySimCalls textsSelectedId.intValue = onboardingService.targetPrimarySimTexts mobileDataSelectedId.intValue = onboardingService.targetPrimarySimMobileData PrimarySimImpl( - subscriptionInfoList = selectedSubscriptionInfoList, + primarySimInfo = primarySimInfo, callsSelectedId = callsSelectedId, textsSelectedId = textsSelectedId, mobileDataSelectedId = mobileDataSelectedId,