From 836018788dce949e2708dfcfe14940a6f58e9111 Mon Sep 17 00:00:00 2001 From: Fiona Campbell Date: Mon, 29 Jan 2024 17:27:33 +0000 Subject: [PATCH 1/6] Create EvenDimmerPreferenceController - Create preference controller - linked to even dimmer activated setting Bug: 179428400 Test: EvenDimmerPreferenceControllerTest Change-Id: I3ac6501c3d45399caeda96fe6a7dd4164439d1bd --- res/values/strings.xml | 4 + res/xml/display_settings.xml | 5 + .../EvenDimmerPreferenceController.java | 76 ++++++++++++ .../EvenDimmerPreferenceControllerTest.java | 114 ++++++++++++++++++ 4 files changed, 199 insertions(+) create mode 100644 src/com/android/settings/display/EvenDimmerPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/display/EvenDimmerPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 45305ece4f6..b2073a9e5cb 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2824,6 +2824,10 @@ Dark theme is currently following your Bedtime mode schedule Bedtime mode settings + + Even Dimmer + + Allow device to go dimmer than usual diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml index 2df360d6840..5b4bee8ebb5 100644 --- a/res/xml/display_settings.xml +++ b/res/xml/display_settings.xml @@ -36,6 +36,11 @@ android:title="@string/auto_brightness_title" android:fragment="com.android.settings.display.AutoBrightnessSettings" settings:controller="com.android.settings.display.AutoBrightnessPreferenceController"/> + Date: Fri, 23 Feb 2024 08:07:42 +0000 Subject: [PATCH 2/6] Fixed AutoDataSwitchPreferenceControllerTest The settings Flag is not assigned value, it cause test cases failed Bug: 326543866 Test: have tested the v2/android-settings/apps/robolectric_settings_test at https://android-build.corp.google.com/builds/abtd/run/L92300030002155909 The test cases of com.android.settings.network.telephony are all passed. Change-Id: I5a53fb6de741a7550f03ffca3c519d6b67c7c0fe --- ...utoDataSwitchPreferenceControllerTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/robotests/src/com/android/settings/network/telephony/AutoDataSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/telephony/AutoDataSwitchPreferenceControllerTest.java index 758d6b09b99..29592cf9189 100644 --- a/tests/robotests/src/com/android/settings/network/telephony/AutoDataSwitchPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/telephony/AutoDataSwitchPreferenceControllerTest.java @@ -30,12 +30,19 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; +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 android.telephony.TelephonyManager; import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreference; +import com.android.settings.flags.Flags; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -52,6 +59,9 @@ public class AutoDataSwitchPreferenceControllerTest { private static final int SUB_ID_1 = 111; private static final int SUB_ID_2 = 222; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Mock private TelephonyManager mTelephonyManager; @Mock @@ -79,6 +89,7 @@ public class AutoDataSwitchPreferenceControllerTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED) public void getAvailabilityStatus_noInit_notAvailable() { ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1); AutoDataSwitchPreferenceController controller = @@ -90,6 +101,7 @@ public class AutoDataSwitchPreferenceControllerTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED) public void displayPreference_defaultForData_notAvailable() { ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1); @@ -100,6 +112,7 @@ public class AutoDataSwitchPreferenceControllerTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED) public void displayPreference_notDefaultForData_available() { ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_2); @@ -110,6 +123,7 @@ public class AutoDataSwitchPreferenceControllerTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED) public void onSubscriptionsChanged_becomesDefaultForData_notAvailable() { ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_2); @@ -122,6 +136,7 @@ public class AutoDataSwitchPreferenceControllerTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED) public void onSubscriptionsChanged_noLongerDefaultForData_available() { ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1); @@ -134,6 +149,7 @@ public class AutoDataSwitchPreferenceControllerTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED) public void getAvailabilityStatus_mobileDataChangWithDefaultDataSubId_returnUnavailable() { ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1); @@ -144,6 +160,7 @@ public class AutoDataSwitchPreferenceControllerTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED) public void getAvailabilityStatus_mobileDataChangWithoutDefaultDataSubId_returnAvailable() { ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1); @@ -152,4 +169,16 @@ public class AutoDataSwitchPreferenceControllerTest { assertThat(mController.getAvailabilityStatus(SUB_ID_2)).isEqualTo(AVAILABLE); } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED) + public void getAvailabilityStatus_flagIsDualSimOnboardingEnabledOn_returnUnavailable() { + ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1); + + mController.displayPreference(mPreferenceScreen); + mController.refreshPreference(); + + assertThat(mController.getAvailabilityStatus(SUB_ID_1)) + .isEqualTo(CONDITIONALLY_UNAVAILABLE); + } } From 736df6d2ef7e4659a33e7be52672a25e4f257471 Mon Sep 17 00:00:00 2001 From: Rafael Higuera Silva Date: Wed, 14 Feb 2024 19:36:16 +0000 Subject: [PATCH 3/6] Add new dialogue when user is going to delete sim that use RAC. Test: make Bug: 316419093 Change-Id: Iaed54afa7cfd20c1dd6adbd4d50f54cab3da095d --- AndroidManifest.xml | 4 + .../sim_warning_dialog_wifi_connectivity.xml | 52 ++++++ res/values/strings.xml | 10 ++ .../settings/network/SubscriptionUtil.java | 30 +++- .../EuiccRacConnectivityDialogActivity.java | 95 +++++++++++ .../telephony/WarningDialogFragment.java | 148 ++++++++++++++++++ .../network/SubscriptionUtilRoboTest.java | 82 ++++++++++ .../network/SubscriptionUtilTest.java | 31 +++- 8 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 res/layout/sim_warning_dialog_wifi_connectivity.xml create mode 100644 src/com/android/settings/network/telephony/EuiccRacConnectivityDialogActivity.java create mode 100644 src/com/android/settings/network/telephony/WarningDialogFragment.java create mode 100644 tests/robotests/src/com/android/settings/network/SubscriptionUtilRoboTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6e24863e27d..00d9f08356f 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -818,6 +818,10 @@ android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" android:theme="@style/Theme.AlertDialog.SimConfirmDialog"/> + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 012ddc3498b..db6e30c6dc6 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -11660,6 +11660,16 @@ Something went wrong and this eSIM wasn\'t erased.\n\nRestart your device and try again. + + + Connect to Wi\u2011Fi before erasing + + This makes it easier to use your eSIM again in the future without needing to contact your carrier + + Erase anyway + + OK + Connect to device diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java index 84e4e7543fd..2498ec9407a 100644 --- a/src/com/android/settings/network/SubscriptionUtil.java +++ b/src/com/android/settings/network/SubscriptionUtil.java @@ -17,13 +17,15 @@ package com.android.settings.network; import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX; -import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT; import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING; +import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT; import static com.android.internal.util.CollectionUtils.emptyIfNull; import android.content.Context; import android.content.SharedPreferences; +import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; import android.os.ParcelUuid; import android.provider.Settings; import android.telephony.PhoneNumberUtils; @@ -560,6 +562,7 @@ public class SubscriptionUtil { Log.i(TAG, "Unable to delete subscription due to invalid subscription ID."); return; } + // TODO(b/325693582): Add verification if carrier is RAC and logic for new dialog context.startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(context, subId)); } @@ -832,4 +835,29 @@ public class SubscriptionUtil { } return true; } + + /** + * Returns {@code true} if device is connected to Wi-Fi or mobile data provided by a different + * subId. + * + * @param context context + * @param targetSubId subscription that is going to be deleted + */ + @VisibleForTesting + static boolean isConnectedToWifiOrDifferentSubId(@NonNull Context context, int targetSubId) { + ConnectivityManager connectivityManager = + context.getSystemService(ConnectivityManager.class); + NetworkCapabilities capabilities = + connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork()); + + if (capabilities != null) { + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + // Connected to WiFi + return true; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return targetSubId != SubscriptionManager.getActiveDataSubscriptionId(); + } + } + return false; + } } diff --git a/src/com/android/settings/network/telephony/EuiccRacConnectivityDialogActivity.java b/src/com/android/settings/network/telephony/EuiccRacConnectivityDialogActivity.java new file mode 100644 index 00000000000..cb4ab18dc67 --- /dev/null +++ b/src/com/android/settings/network/telephony/EuiccRacConnectivityDialogActivity.java @@ -0,0 +1,95 @@ +/* + * 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.network.telephony; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.telephony.SubscriptionManager; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.android.settings.R; + +/** This dialog activity advise the user to have connectivity if the eSIM uses a RAC. */ +public class EuiccRacConnectivityDialogActivity extends SubscriptionActionDialogActivity + implements WarningDialogFragment.OnConfirmListener { + + private static final String TAG = "EuiccRacConnectivityDialogActivity"; + // Dialog tags + private static final int DIALOG_TAG_ERASE_ANYWAY_CONFIRMATION = 1; + + private int mSubId; + + /** + * Returns an intent of EuiccRacConnectivityDialogActivity. + * + * @param context The context used to start the EuiccRacConnectivityDialogActivity. + * @param subId The subscription ID of the subscription needs to be deleted. If the subscription + * belongs to a group of subscriptions, all subscriptions from the group will be deleted. + */ + @NonNull + public static Intent getIntent(@NonNull Context context, int subId) { + Intent intent = new Intent(context, EuiccRacConnectivityDialogActivity.class); + intent.putExtra(ARG_SUB_ID, subId); + return intent; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + mSubId = intent.getIntExtra(ARG_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); + + if (savedInstanceState == null) { + showConnectivityWarningDialog(); + } + } + + @Override + public void onConfirm(int tag, boolean confirmed) { + if (!confirmed) { + finish(); + return; + } + + switch (tag) { + case DIALOG_TAG_ERASE_ANYWAY_CONFIRMATION: + finish(); + Log.i(TAG, "Show dialogue activity that handles deleting eSIM profiles"); + startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(this, mSubId)); + break; + default: + Log.e(TAG, "Unrecognized confirmation dialog tag: " + tag); + break; + } + } + + /* Displays warning to have connectivity because subscription is RAC dialog. */ + private void showConnectivityWarningDialog() { + WarningDialogFragment.show( + this, + WarningDialogFragment.OnConfirmListener.class, + DIALOG_TAG_ERASE_ANYWAY_CONFIRMATION, + getString(R.string.wifi_warning_dialog_title), + getString(R.string.wifi_warning_dialog_text), + getString(R.string.wifi_warning_continue_button), + getString(R.string.wifi_warning_return_button)); + } +} diff --git a/src/com/android/settings/network/telephony/WarningDialogFragment.java b/src/com/android/settings/network/telephony/WarningDialogFragment.java new file mode 100644 index 00000000000..58bc1dafb3c --- /dev/null +++ b/src/com/android/settings/network/telephony/WarningDialogFragment.java @@ -0,0 +1,148 @@ +/* + * 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.network.telephony; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; + +import com.android.settings.R; + +/** Fragment to show a warning dialog. The caller should implement onConfirmListener. */ +public class WarningDialogFragment extends BaseDialogFragment + implements DialogInterface.OnClickListener { + private static final String TAG = "WarningDialogFragment"; + private static final String ARG_TITLE = "title"; + private static final String ARG_MSG = "msg"; + private static final String ARG_POS_BUTTON_STRING = "pos_button_string"; + private static final String ARG_NEG_BUTTON_STRING = "neg_button_string"; + + /** + * Interface defining the method that will be invoked when the user has done with the dialog. + */ + public interface OnConfirmListener { + /** + * @param tag The tag in the caller. + * @param confirmed True if the user has clicked the positive button. False if the user has + * clicked the negative button or cancel the dialog. + */ + void onConfirm(int tag, boolean confirmed); + } + + /** Displays a confirmation dialog which has confirm and cancel buttons. */ + static void show( + FragmentActivity activity, + Class callbackInterfaceClass, + int tagInCaller, + String title, + String msg, + String posButtonString, + String negButtonString) { + WarningDialogFragment fragment = new WarningDialogFragment(); + Bundle arguments = new Bundle(); + arguments.putString(ARG_TITLE, title); + arguments.putCharSequence(ARG_MSG, msg); + arguments.putString(ARG_POS_BUTTON_STRING, posButtonString); + arguments.putString(ARG_NEG_BUTTON_STRING, negButtonString); + setListener(activity, null, callbackInterfaceClass, tagInCaller, arguments); + fragment.setArguments(arguments); + fragment.show(activity.getSupportFragmentManager(), TAG); + } + + @Override + @NonNull + public final Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + String title = getArguments().getString(ARG_TITLE); + String message = getArguments().getString(ARG_MSG); + String leftButton = getArguments().getString(ARG_POS_BUTTON_STRING); + String rightButton = getArguments().getString(ARG_NEG_BUTTON_STRING); + + Log.i(TAG, "Showing dialog with title =" + title); + AlertDialog.Builder builder = + new AlertDialog.Builder(getContext()) + .setPositiveButton(rightButton, this) + .setNegativeButton(leftButton, this); + + View content = + LayoutInflater.from(getContext()) + .inflate(R.layout.sim_warning_dialog_wifi_connectivity, null); + + if (content != null) { + TextView dialogTitle = content.findViewById(R.id.title); + if (!TextUtils.isEmpty(title) && dialogTitle != null) { + dialogTitle.setText(title); + dialogTitle.setVisibility(View.VISIBLE); + } + TextView dialogMessage = content.findViewById(R.id.msg); + if (!TextUtils.isEmpty(message) && dialogMessage != null) { + dialogMessage.setText(message); + dialogMessage.setVisibility(View.VISIBLE); + } + + builder.setView(content); + } else { + if (!TextUtils.isEmpty(title)) { + builder.setTitle(title); + } + if (!TextUtils.isEmpty(message)) { + builder.setMessage(message); + } + } + + AlertDialog dialog = builder.create(); + dialog.setCanceledOnTouchOutside(false); + return dialog; + } + + @Override + public void onClick(@NonNull DialogInterface dialog, int which) { + Log.i(TAG, "dialog onClick =" + which); + + // Positions of the buttons have been switch: + // negative button = left button = the button to continue + informCaller(which == DialogInterface.BUTTON_NEGATIVE); + } + + @Override + public void onCancel(@NonNull DialogInterface dialog) { + informCaller(false); + } + + private void informCaller(boolean confirmed) { + OnConfirmListener listener; + try { + listener = getListener(OnConfirmListener.class); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Do nothing and return.", e); + return; + } + if (listener == null) { + return; + } + listener.onConfirm(getTagInCaller(), confirmed); + } +} diff --git a/tests/robotests/src/com/android/settings/network/SubscriptionUtilRoboTest.java b/tests/robotests/src/com/android/settings/network/SubscriptionUtilRoboTest.java new file mode 100644 index 00000000000..2595510a540 --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/SubscriptionUtilRoboTest.java @@ -0,0 +1,82 @@ +/* + * 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.network; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.robolectric.Shadows.shadowOf; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; +import android.telephony.SubscriptionManager; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowSubscriptionManager; + +@RunWith(RobolectricTestRunner.class) +public class SubscriptionUtilRoboTest { + private static final int SUBID_1 = 1; + private static final int SUBID_2 = 2; + + private Context mContext; + private NetworkCapabilities mNetworkCapabilities; + private ShadowSubscriptionManager mShadowSubscriptionManager; + + @Mock + private ConnectivityManager mConnectivityManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + mShadowSubscriptionManager = shadowOf(mContext.getSystemService(SubscriptionManager.class)); + when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager); + } + + @Test + public void isConnectedToWifiOrDifferentSubId_hasDataOnSubId2_returnTrue() { + addNetworkTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + mShadowSubscriptionManager.setActiveDataSubscriptionId(SUBID_2); + + assertTrue(SubscriptionUtil.isConnectedToWifiOrDifferentSubId(mContext, SUBID_1)); + } + + @Test + public void isConnectedToWifiOrDifferentSubId_hasDataOnSubId1_returnFalse() { + addNetworkTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + mShadowSubscriptionManager.setActiveDataSubscriptionId(SUBID_1); + + assertFalse(SubscriptionUtil.isConnectedToWifiOrDifferentSubId(mContext, SUBID_1)); + } + + private void addNetworkTransportType(int networkType) { + mNetworkCapabilities = + new NetworkCapabilities.Builder().addTransportType(networkType).build(); + when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities); + } +} diff --git a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java index 587e7349ad8..3b9ac9dea86 100644 --- a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java +++ b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java @@ -18,9 +18,13 @@ package com.android.settings.network; import static com.android.settings.network.SubscriptionUtil.KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME; import static com.android.settings.network.SubscriptionUtil.SUB_ID; + import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -30,6 +34,8 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -61,13 +67,15 @@ public class SubscriptionUtilTest { private static final CharSequence CARRIER_2 = "carrier2"; private Context mContext; + private NetworkCapabilities mNetworkCapabilities; + @Mock private SubscriptionManager mSubMgr; @Mock private TelephonyManager mTelMgr; @Mock private Resources mResources; - + @Mock private ConnectivityManager mConnectivityManager; @Before public void setUp() { @@ -75,6 +83,7 @@ public class SubscriptionUtilTest { mContext = spy(ApplicationProvider.getApplicationContext()); when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubMgr); when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelMgr); + when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager); when(mTelMgr.getUiccSlotsInfo()).thenReturn(null); } @@ -588,4 +597,24 @@ public class SubscriptionUtilTest { assertThat(SubscriptionUtil.isValidCachedDisplayName(cacheString, originalName)).isFalse(); } + + @Test + public void isConnectedToWifiOrDifferentSubId_hasWiFi_returnTrue() { + addNetworkTransportType(NetworkCapabilities.TRANSPORT_WIFI); + + assertTrue(SubscriptionUtil.isConnectedToWifiOrDifferentSubId(mContext, SUBID_1)); + } + + @Test + public void isConnectedToWifiOrDifferentSubId_noData_and_noWiFi_returnFalse() { + addNetworkTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH); + + assertFalse(SubscriptionUtil.isConnectedToWifiOrDifferentSubId(mContext, SUBID_1)); + } + + private void addNetworkTransportType(int networkType) { + mNetworkCapabilities = + new NetworkCapabilities.Builder().addTransportType(networkType).build(); + when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities); + } } From 42b8fbb74f7bcf99cad98727a9f2157c7a855afe Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Fri, 23 Feb 2024 15:11:11 +0800 Subject: [PATCH 4/6] [Audiosharing] Impl audio sharing feature provider in Settings Add createAvailableMediaDeviceGroupController interface to provide different controller in Settings and SettingsGoogle. Bug: 324023639 Test: atest Change-Id: Ibf2ea2620c878e609eb937ff6947f5aaa0b89e7a --- res/xml/connected_devices.xml | 3 +- .../AvailableMediaDeviceGroupController.java | 169 ++++++------------ .../ConnectedDeviceDashboardFragment.java | 24 +-- .../AudioSharingFeatureProvider.java | 8 +- .../AudioSharingFeatureProviderImpl.java | 11 +- ...ailableMediaDeviceGroupControllerTest.java | 75 ++------ .../ConnectedDeviceDashboardFragmentTest.java | 27 +++ .../AudioSharingFeatureProviderImplTest.java | 9 + .../testutils/shadow/ShadowAudioManager.java | 7 +- 9 files changed, 136 insertions(+), 197 deletions(-) rename tests/robotests/{src => testutils}/com/android/settings/testutils/shadow/ShadowAudioManager.java (92%) diff --git a/res/xml/connected_devices.xml b/res/xml/connected_devices.xml index 34a57989d25..e9ec19e899b 100644 --- a/res/xml/connected_devices.xml +++ b/res/xml/connected_devices.xml @@ -41,8 +41,7 @@ + android:title="@string/connected_device_media_device_title"/> controllers = new ArrayList<>(); - if (AudioSharingUtils.isFeatureEnabled()) { - AbstractPreferenceController audioSharingController = - FeatureFactory.getFeatureFactory() - .getAudioSharingFeatureProvider() - .createAudioSharingDevicePreferenceController( - context, fragment, lifecycle); - if (audioSharingController != null) { - controllers.add(audioSharingController); - } + AbstractPreferenceController availableMediaController = + FeatureFactory.getFeatureFactory() + .getAudioSharingFeatureProvider() + .createAvailableMediaDeviceGroupController(context, fragment, lifecycle); + controllers.add(availableMediaController); + AbstractPreferenceController audioSharingController = + FeatureFactory.getFeatureFactory() + .getAudioSharingFeatureProvider() + .createAudioSharingDevicePreferenceController(context, fragment, lifecycle); + if (audioSharingController != null) { + controllers.add(audioSharingController); } return controllers; } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java index c71a368640c..9fe4d50ad6d 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java @@ -20,12 +20,12 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.lifecycle.Lifecycle; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; /** Feature provider for the audio sharing related features, */ public interface AudioSharingFeatureProvider { @@ -37,6 +37,12 @@ public interface AudioSharingFeatureProvider { @Nullable DashboardFragment fragment, @Nullable Lifecycle lifecycle); + /** Create available media device preference controller. */ + AbstractPreferenceController createAvailableMediaDeviceGroupController( + @NonNull Context context, + @Nullable DashboardFragment fragment, + @Nullable Lifecycle lifecycle); + /** * Check if the device match the audio sharing filter. * diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java index 05a6a6383af..259ed7a16b4 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java @@ -20,12 +20,13 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.lifecycle.Lifecycle; +import com.android.settings.connecteddevice.AvailableMediaDeviceGroupController; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; public class AudioSharingFeatureProviderImpl implements AudioSharingFeatureProvider { @@ -38,6 +39,14 @@ public class AudioSharingFeatureProviderImpl implements AudioSharingFeatureProvi return null; } + @Override + public AbstractPreferenceController createAvailableMediaDeviceGroupController( + @NonNull Context context, + @Nullable DashboardFragment fragment, + @Nullable Lifecycle lifecycle) { + return new AvailableMediaDeviceGroupController(context, fragment, lifecycle); + } + @Override public boolean isAudioSharingFilterMatched( @NonNull CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) { diff --git a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java index e5964d06e5c..357420af221 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java @@ -22,27 +22,23 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; -import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothLeBroadcastAssistant; import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothStatusCodes; import android.content.Context; import android.content.pm.PackageManager; import android.media.AudioManager; -import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.LifecycleOwner; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceManager; @@ -51,19 +47,16 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater; import com.android.settings.bluetooth.Utils; -import com.android.settings.flags.Flags; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; import com.android.settings.testutils.shadow.ShadowAudioManager; -import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowBluetoothUtils; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.BluetoothEventManager; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.HearingAidInfo; -import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; import org.junit.Rule; @@ -76,16 +69,12 @@ import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadow.api.Shadow; - -import java.util.concurrent.Executor; /** Tests for {@link AvailableMediaDeviceGroupController}. */ @RunWith(RobolectricTestRunner.class) @Config( shadows = { ShadowAudioManager.class, - ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class, ShadowAlertDialogCompat.class, }) @@ -105,9 +94,7 @@ public class AvailableMediaDeviceGroupControllerTest { @Mock private PackageManager mPackageManager; @Mock private BluetoothEventManager mEventManager; @Mock private LocalBluetoothManager mLocalBluetoothManager; - @Mock private LocalBluetoothProfileManager mLocalBtProfileManager; @Mock private CachedBluetoothDeviceManager mCachedDeviceManager; - @Mock private LocalBluetoothLeBroadcastAssistant mAssistant; @Mock private CachedBluetoothDevice mCachedBluetoothDevice; private PreferenceGroup mPreferenceGroup; @@ -115,13 +102,16 @@ public class AvailableMediaDeviceGroupControllerTest { private Preference mPreference; private AvailableMediaDeviceGroupController mAvailableMediaDeviceGroupController; private AudioManager mAudioManager; - private ShadowBluetoothAdapter mShadowBluetoothAdapter; + private LifecycleOwner mLifecycleOwner; + private Lifecycle mLifecycle; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); mPreference = new Preference(mContext); mPreference.setKey(PREFERENCE_KEY_1); mPreferenceGroup = spy(new PreferenceScreen(mContext, null)); @@ -130,24 +120,17 @@ public class AvailableMediaDeviceGroupControllerTest { doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); - mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); - mShadowBluetoothAdapter.setEnabled(true); - mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( - BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); - mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( - BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; mLocalBluetoothManager = Utils.getLocalBtManager(mContext); mAudioManager = mContext.getSystemService(AudioManager.class); doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager(); - when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBtProfileManager); when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); when(mCachedDeviceManager.findDevice(any(BluetoothDevice.class))) .thenReturn(mCachedBluetoothDevice); when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); mAvailableMediaDeviceGroupController = - spy(new AvailableMediaDeviceGroupController(mContext)); + spy(new AvailableMediaDeviceGroupController(mContext, null, mLifecycle)); mAvailableMediaDeviceGroupController.setBluetoothDeviceUpdater( mAvailableMediaBluetoothDeviceUpdater); mAvailableMediaDeviceGroupController.setFragmentManager( @@ -197,7 +180,7 @@ public class AvailableMediaDeviceGroupControllerTest { @Test public void testRegister() { // register the callback in onStart() - mAvailableMediaDeviceGroupController.onStart(); + mAvailableMediaDeviceGroupController.onStart(mLifecycleOwner); verify(mAvailableMediaBluetoothDeviceUpdater).registerCallback(); verify(mLocalBluetoothManager.getEventManager()) @@ -205,36 +188,15 @@ public class AvailableMediaDeviceGroupControllerTest { verify(mAvailableMediaBluetoothDeviceUpdater).refreshPreference(); } - @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - public void testRegister_audioSharingOn() { - setUpBroadcast(); - // register the callback in onStart() - mAvailableMediaDeviceGroupController.onStart(); - verify(mAssistant) - .registerServiceCallBack( - any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class)); - } - @Test public void testUnregister() { // unregister the callback in onStop() - mAvailableMediaDeviceGroupController.onStop(); + mAvailableMediaDeviceGroupController.onStop(mLifecycleOwner); verify(mAvailableMediaBluetoothDeviceUpdater).unregisterCallback(); verify(mLocalBluetoothManager.getEventManager()) .unregisterCallback(any(BluetoothCallback.class)); } - @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - public void testUnregister_audioSharingOn() { - setUpBroadcast(); - // unregister the callback in onStop() - mAvailableMediaDeviceGroupController.onStop(); - verify(mAssistant) - .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class)); - } - @Test public void testGetAvailabilityStatus_noBluetoothFeature_returnUnSupported() { doReturn(false).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); @@ -274,7 +236,7 @@ public class AvailableMediaDeviceGroupControllerTest { mAvailableMediaDeviceGroupController.mLocalBluetoothManager = null; // Shouldn't crash - mAvailableMediaDeviceGroupController.onStart(); + mAvailableMediaDeviceGroupController.onStart(mLifecycleOwner); } @Test @@ -282,7 +244,7 @@ public class AvailableMediaDeviceGroupControllerTest { mAvailableMediaDeviceGroupController.mLocalBluetoothManager = null; // Shouldn't crash - mAvailableMediaDeviceGroupController.onStop(); + mAvailableMediaDeviceGroupController.onStop(mLifecycleOwner); } @Test @@ -300,19 +262,4 @@ public class AvailableMediaDeviceGroupControllerTest { final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); assertThat(dialog.isShowing()).isTrue(); } - - private void setUpBroadcast() { - mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( - BluetoothStatusCodes.FEATURE_SUPPORTED); - mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( - BluetoothStatusCodes.FEATURE_SUPPORTED); - when(mLocalBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant); - doNothing() - .when(mAssistant) - .registerServiceCallBack( - any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class)); - doNothing() - .when(mAssistant) - .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class)); - } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java index 09f7a384244..ee4f952ca39 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java @@ -20,19 +20,25 @@ import static com.android.settings.connecteddevice.ConnectedDeviceDashboardFragm import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.PackageManager; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.SearchIndexableResource; import com.android.settings.R; +import com.android.settings.connecteddevice.fastpair.FastPairDeviceUpdater; import com.android.settings.core.BasePreferenceController; import com.android.settings.core.PreferenceControllerListHelper; +import com.android.settings.flags.Flags; import com.android.settings.slices.SlicePreferenceController; +import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowConnectivityManager; import com.android.settings.testutils.shadow.ShadowUserManager; @@ -60,6 +66,8 @@ public class ConnectedDeviceDashboardFragmentTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final String KEY_NEARBY_DEVICES = "bt_nearby_slice"; private static final String KEY_DISCOVERABLE_FOOTER = "discoverable_footer"; private static final String KEY_SAVED_DEVICE_SEE_ALL = "previously_connected_devices_see_all"; @@ -75,8 +83,11 @@ public class ConnectedDeviceDashboardFragmentTest { private static final String TEST_ACTION = "com.testapp.settings.ACTION_START"; @Mock private PackageManager mPackageManager; + @Mock private FastPairDeviceUpdater mFastPairDeviceUpdater; private Context mContext; private ConnectedDeviceDashboardFragment mFragment; + private FakeFeatureFactory mFeatureFactory; + private AvailableMediaDeviceGroupController mMediaDeviceGroupController; @Before public void setUp() { @@ -84,6 +95,22 @@ public class ConnectedDeviceDashboardFragmentTest { mContext = spy(RuntimeEnvironment.application); mFragment = new ConnectedDeviceDashboardFragment(); + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + when(mFeatureFactory + .getFastPairFeatureProvider() + .getFastPairDeviceUpdater( + any(Context.class), any(DevicePreferenceCallback.class))) + .thenReturn(mFastPairDeviceUpdater); + when(mFeatureFactory + .getAudioSharingFeatureProvider() + .createAudioSharingDevicePreferenceController(mContext, null, null)) + .thenReturn(null); + mMediaDeviceGroupController = new AvailableMediaDeviceGroupController(mContext, null, null); + when(mFeatureFactory + .getAudioSharingFeatureProvider() + .createAvailableMediaDeviceGroupController(mContext, null, null)) + .thenReturn(mMediaDeviceGroupController); doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java index 0edbc7709ba..1965bffaeba 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java @@ -22,6 +22,7 @@ import android.content.Context; import androidx.test.core.app.ApplicationProvider; +import com.android.settings.connecteddevice.AvailableMediaDeviceGroupController; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -59,6 +60,14 @@ public class AudioSharingFeatureProviderImplTest { .isNull(); } + @Test + public void createAvailableMediaDeviceGroupController_returnsNull() { + assertThat( + mFeatureProvider.createAvailableMediaDeviceGroupController( + mContext, /* fragment= */ null, /* lifecycle= */ null)) + .isInstanceOf(AvailableMediaDeviceGroupController.class); + } + @Test public void isAudioSharingFilterMatched_returnsFalse() { assertThat(mFeatureProvider.isAudioSharingFilterMatched(mCachedDevice, mLocalBtManager)) diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioManager.java b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowAudioManager.java similarity index 92% rename from tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioManager.java rename to tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowAudioManager.java index 9c066655307..b465a4159a3 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioManager.java +++ b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowAudioManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * 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. @@ -38,6 +38,7 @@ import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; import java.util.List; +/** Robolectric shadow for the AudioManager. */ @Implements(value = AudioManager.class) public class ShadowAudioManager extends org.robolectric.shadows.ShadowAudioManager { private int mRingerMode; @@ -58,11 +59,13 @@ public class ShadowAudioManager extends org.robolectric.shadows.ShadowAudioManag mRingerMode = mode; } + /** Register audio device callback. */ @Implementation public void registerAudioDeviceCallback(AudioDeviceCallback callback, Handler handler) { mDeviceCallbacks.add(callback); } + /** Unregister audio device callback. */ @Implementation public void unregisterAudioDeviceCallback(AudioDeviceCallback callback) { if (mDeviceCallbacks.contains(callback)) { @@ -79,10 +82,12 @@ public class ShadowAudioManager extends org.robolectric.shadows.ShadowAudioManag return mMusicActiveRemotely; } + /** Set output device. */ public void setOutputDevice(int deviceCodes) { mDeviceCodes = deviceCodes; } + /** Get devices for stream. */ @Implementation public int getDevicesForStream(int streamType) { switch (streamType) { From b4ab4144aa78ba82284de81983cd94ef9f27fc80 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 26 Feb 2024 18:46:58 +0800 Subject: [PATCH 5/6] Create WifiCallingRepository Move Wi-Fi calling related feature into it. Which simplifies the ImsMmTelRepository, so we can put more ImsMmTelManager related features into ImsMmTelRepository in the future cl. Bug: 325414275 Test: manual - on Mobile Settings Test: unit tests Change-Id: I391f43cfa3a79e0c44865050c357ac54346c2fb1 --- .../WifiCallingPreferenceController.kt | 9 +- .../telephony/ims/ImsMmTelRepository.kt | 26 +--- .../wificalling/WifiCallingRepository.kt | 47 ++++++++ .../WifiCallingPreferenceControllerTest.kt | 13 +- .../telephony/ims/ImsMmTelRepositoryTest.kt | 59 ++------- .../wificalling/WifiCallingRepositoryTest.kt | 114 ++++++++++++++++++ 6 files changed, 184 insertions(+), 84 deletions(-) create mode 100644 src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt create mode 100644 tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt diff --git a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt index e7b83189f86..698341cdddd 100644 --- a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt +++ b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt @@ -29,8 +29,7 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.preference.Preference import androidx.preference.PreferenceScreen import com.android.settings.R -import com.android.settings.network.telephony.ims.ImsMmTelRepository -import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl +import com.android.settings.network.telephony.wificalling.WifiCallingRepository import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -46,8 +45,8 @@ open class WifiCallingPreferenceController @JvmOverloads constructor( context: Context, key: String, private val callStateFlowFactory: (subId: Int) -> Flow = context::callStateFlow, - private val imsMmTelRepositoryFactory: (subId: Int) -> ImsMmTelRepository = { subId -> - ImsMmTelRepositoryImpl(context, subId) + private val wifiCallingRepository: (subId: Int) -> WifiCallingRepository = { subId -> + WifiCallingRepository(context, subId) }, ) : TelephonyBasePreferenceController(context, key) { @@ -123,7 +122,7 @@ open class WifiCallingPreferenceController @JvmOverloads constructor( } private fun getSummaryForWfcMode(): String { - val resId = when (imsMmTelRepositoryFactory(mSubId).getWiFiCallingMode()) { + val resId = when (wifiCallingRepository(mSubId).getWiFiCallingMode()) { ImsMmTelManager.WIFI_MODE_WIFI_ONLY -> com.android.internal.R.string.wfc_mode_wifi_only_summary diff --git a/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt index 3408eb741d5..1d288d4296b 100644 --- a/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt +++ b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -17,9 +17,6 @@ package com.android.settings.network.telephony.ims import android.content.Context -import android.telephony.CarrierConfigManager -import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL -import android.telephony.TelephonyManager import android.telephony.ims.ImsManager import android.telephony.ims.ImsMmTelManager import android.telephony.ims.ImsMmTelManager.WiFiCallingMode @@ -27,7 +24,7 @@ import android.util.Log interface ImsMmTelRepository { @WiFiCallingMode - fun getWiFiCallingMode(): Int + fun getWiFiCallingMode(useRoamingMode: Boolean): Int } class ImsMmTelRepositoryImpl( @@ -36,31 +33,18 @@ class ImsMmTelRepositoryImpl( private val imsMmTelManager: ImsMmTelManager = ImsManager(context).getImsMmTelManager(subId), ) : ImsMmTelRepository { - private val telephonyManager = context.getSystemService(TelephonyManager::class.java)!! - .createForSubscriptionId(subId) - - private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!! - @WiFiCallingMode - override fun getWiFiCallingMode(): Int = try { + override fun getWiFiCallingMode(useRoamingMode: Boolean): Int = try { when { !imsMmTelManager.isVoWiFiSettingEnabled -> ImsMmTelManager.WIFI_MODE_UNKNOWN - - telephonyManager.isNetworkRoaming && !useWfcHomeModeForRoaming() -> - imsMmTelManager.getVoWiFiRoamingModeSetting() - + useRoamingMode -> imsMmTelManager.getVoWiFiRoamingModeSetting() else -> imsMmTelManager.getVoWiFiModeSetting() } } catch (e: IllegalArgumentException) { - Log.w(TAG, "getWiFiCallingMode failed subId=$subId", e) + Log.w(TAG, "[$subId] getWiFiCallingMode failed useRoamingMode=$useRoamingMode", e) ImsMmTelManager.WIFI_MODE_UNKNOWN } - private fun useWfcHomeModeForRoaming(): Boolean = - carrierConfigManager - .getConfigForSubId(subId, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL) - .getBoolean(KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL) - private companion object { private const val TAG = "ImsMmTelRepository" } diff --git a/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt b/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt new file mode 100644 index 00000000000..3d841d5c704 --- /dev/null +++ b/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt @@ -0,0 +1,47 @@ +/* + * 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.network.telephony.wificalling + +import android.content.Context +import android.telephony.CarrierConfigManager +import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL +import android.telephony.TelephonyManager +import android.telephony.ims.ImsMmTelManager.WiFiCallingMode +import com.android.settings.network.telephony.ims.ImsMmTelRepository +import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl + +class WifiCallingRepository( + private val context: Context, + private val subId: Int, + private val imsMmTelRepository : ImsMmTelRepository = ImsMmTelRepositoryImpl(context, subId) +) { + private val telephonyManager = context.getSystemService(TelephonyManager::class.java)!! + .createForSubscriptionId(subId) + + private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!! + + @WiFiCallingMode + fun getWiFiCallingMode(): Int { + val useRoamingMode = telephonyManager.isNetworkRoaming && !useWfcHomeModeForRoaming() + return imsMmTelRepository.getWiFiCallingMode(useRoamingMode) + } + + private fun useWfcHomeModeForRoaming(): Boolean = + carrierConfigManager + .getConfigForSubId(subId, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL) + .getBoolean(KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL) +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt index fc53049ee2a..f947f815552 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt @@ -29,7 +29,7 @@ import androidx.preference.Preference import androidx.preference.PreferenceManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settings.network.telephony.ims.ImsMmTelRepository +import com.android.settings.network.telephony.wificalling.WifiCallingRepository import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flowOf @@ -60,9 +60,8 @@ class WifiCallingPreferenceControllerTest { private var callState = TelephonyManager.CALL_STATE_IDLE - private object FakeImsMmTelRepository : ImsMmTelRepository { - var wiFiMode = ImsMmTelManager.WIFI_MODE_UNKNOWN - override fun getWiFiCallingMode() = wiFiMode + private val mockWifiCallingRepository = mock { + on { getWiFiCallingMode() } doReturn ImsMmTelManager.WIFI_MODE_UNKNOWN } private val callingPreferenceCategoryController = @@ -72,7 +71,7 @@ class WifiCallingPreferenceControllerTest { context = context, key = TEST_KEY, callStateFlowFactory = { flowOf(callState) }, - imsMmTelRepositoryFactory = { FakeImsMmTelRepository }, + wifiCallingRepository = { mockWifiCallingRepository }, ).init(subId = SUB_ID, callingPreferenceCategoryController) @Before @@ -86,7 +85,9 @@ class WifiCallingPreferenceControllerTest { mockTelecomManager.stub { on { getSimCallManagerForSubscription(SUB_ID) } doReturn null } - FakeImsMmTelRepository.wiFiMode = ImsMmTelManager.WIFI_MODE_WIFI_ONLY + mockWifiCallingRepository.stub { + on { getWiFiCallingMode() } doReturn ImsMmTelManager.WIFI_MODE_WIFI_ONLY + } controller.onViewCreated(TestLifecycleOwner()) delay(100) diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt index d5142fae163..106a82f1751 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt @@ -17,11 +17,7 @@ package com.android.settings.network.telephony.ims import android.content.Context -import android.telephony.CarrierConfigManager -import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL -import android.telephony.TelephonyManager import android.telephony.ims.ImsMmTelManager -import androidx.core.os.persistableBundleOf import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat @@ -30,21 +26,11 @@ import org.junit.runner.RunWith import org.mockito.kotlin.doReturn import org.mockito.kotlin.doThrow import org.mockito.kotlin.mock -import org.mockito.kotlin.spy import org.mockito.kotlin.stub @RunWith(AndroidJUnit4::class) class ImsMmTelRepositoryTest { - private val mockTelephonyManager = mock { - on { createForSubscriptionId(SUB_ID) } doReturn mock - } - - private val mockCarrierConfigManager = mock() - - private val context: Context = spy(ApplicationProvider.getApplicationContext()) { - on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager - on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager - } + private val context: Context = ApplicationProvider.getApplicationContext() private val mockImsMmTelManager = mock { on { isVoWiFiSettingEnabled } doReturn true @@ -60,42 +46,21 @@ class ImsMmTelRepositoryTest { on { isVoWiFiSettingEnabled } doReturn false } - val wiFiCallingMode = repository.getWiFiCallingMode() + val wiFiCallingMode = repository.getWiFiCallingMode(false) assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_UNKNOWN) } @Test - fun getWiFiCallingMode_roamingAndNotUseWfcHomeModeForRoaming_returnRoamingSetting() { - mockTelephonyManager.stub { - on { isNetworkRoaming } doReturn true - } - mockUseWfcHomeModeForRoaming(false) - - val wiFiCallingMode = repository.getWiFiCallingMode() + fun getWiFiCallingMode_useRoamingMode_returnRoamingSetting() { + val wiFiCallingMode = repository.getWiFiCallingMode(true) assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiRoamingModeSetting()) } @Test - fun getWiFiCallingMode_roamingAndUseWfcHomeModeForRoaming_returnHomeSetting() { - mockTelephonyManager.stub { - on { isNetworkRoaming } doReturn true - } - mockUseWfcHomeModeForRoaming(true) - - val wiFiCallingMode = repository.getWiFiCallingMode() - - assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiModeSetting()) - } - - @Test - fun getWiFiCallingMode_notRoaming_returnHomeSetting() { - mockTelephonyManager.stub { - on { isNetworkRoaming } doReturn false - } - - val wiFiCallingMode = repository.getWiFiCallingMode() + fun getWiFiCallingMode_notSseRoamingMode_returnHomeSetting() { + val wiFiCallingMode = repository.getWiFiCallingMode(false) assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiModeSetting()) } @@ -106,21 +71,11 @@ class ImsMmTelRepositoryTest { on { isVoWiFiSettingEnabled } doThrow IllegalArgumentException() } - val wiFiCallingMode = repository.getWiFiCallingMode() + val wiFiCallingMode = repository.getWiFiCallingMode(false) assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_UNKNOWN) } - private fun mockUseWfcHomeModeForRoaming(config: Boolean) { - mockCarrierConfigManager.stub { - on { - getConfigForSubId(SUB_ID, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL) - } doReturn persistableBundleOf( - KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL to config, - ) - } - } - private companion object { const val SUB_ID = 1 } diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt new file mode 100644 index 00000000000..1f3acc29afb --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt @@ -0,0 +1,114 @@ +/* + * 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.network.telephony.wificalling + +import android.content.Context +import android.telephony.CarrierConfigManager +import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL +import android.telephony.TelephonyManager +import android.telephony.ims.ImsMmTelManager +import androidx.core.os.persistableBundleOf +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.network.telephony.ims.ImsMmTelRepository +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub + +@RunWith(AndroidJUnit4::class) +class WifiCallingRepositoryTest { + + private val mockTelephonyManager = mock { + on { createForSubscriptionId(SUB_ID) } doReturn mock + } + + private val mockCarrierConfigManager = mock() + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager + } + + private val mockImsMmTelRepository = mock { + on { getWiFiCallingMode(any()) } doReturn ImsMmTelManager.WIFI_MODE_UNKNOWN + } + + private val repository = WifiCallingRepository(context, SUB_ID, mockImsMmTelRepository) + + @Test + fun getWiFiCallingMode_roamingAndNotUseWfcHomeModeForRoaming_returnRoamingSetting() { + mockTelephonyManager.stub { + on { isNetworkRoaming } doReturn true + } + mockUseWfcHomeModeForRoaming(false) + mockImsMmTelRepository.stub { + on { getWiFiCallingMode(true) } doReturn ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED + } + + val wiFiCallingMode = repository.getWiFiCallingMode() + + assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED) + } + + @Test + fun getWiFiCallingMode_roamingAndUseWfcHomeModeForRoaming_returnHomeSetting() { + mockTelephonyManager.stub { + on { isNetworkRoaming } doReturn true + } + mockUseWfcHomeModeForRoaming(true) + mockImsMmTelRepository.stub { + on { getWiFiCallingMode(false) } doReturn ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED + } + + val wiFiCallingMode = repository.getWiFiCallingMode() + + assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED) + } + + @Test + fun getWiFiCallingMode_notRoaming_returnHomeSetting() { + mockTelephonyManager.stub { + on { isNetworkRoaming } doReturn false + } + mockImsMmTelRepository.stub { + on { getWiFiCallingMode(false) } doReturn ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED + } + + val wiFiCallingMode = repository.getWiFiCallingMode() + + assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED) + } + + private fun mockUseWfcHomeModeForRoaming(config: Boolean) { + mockCarrierConfigManager.stub { + on { + getConfigForSubId(SUB_ID, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL) + } doReturn persistableBundleOf( + KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL to config, + ) + } + } + + private companion object { + const val SUB_ID = 1 + } +} From 2a431fe8d9c1d2727568387ce5224bcc5b5cf06e Mon Sep 17 00:00:00 2001 From: josephpv Date: Thu, 22 Feb 2024 22:27:48 +0000 Subject: [PATCH 6/6] Change for private space intro screen string changes This includes below changes based on latest UX mocks - Changes the strings and icon in the intro screen - Updates the title in Auto advance screens Recording link : b/326391690#comment4 Screenshots: go/ss/9xbZP5Lp8j9yh6m.png go/ss/pvKMNdgu6yt2Xtr.png Bug: 326391690 Test: Manual, verify the strings are updated as in Mocks Change-Id: I714919ac1f3d45fa0007395bd17cfb340874ed5a --- res/layout/private_space_education_screen.xml | 14 +++++++------- res/values/strings.xml | 18 +++++++++--------- res/values/styles.xml | 1 + .../privatespace/AutoAdvanceSetupFragment.java | 9 ++++++--- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/res/layout/private_space_education_screen.xml b/res/layout/private_space_education_screen.xml index 0f57e1e5b6e..377c923f0c5 100644 --- a/res/layout/private_space_education_screen.xml +++ b/res/layout/private_space_education_screen.xml @@ -43,28 +43,28 @@ android:src="@drawable/private_space_illustration"/> + android:text="@string/private_space_setup_sub_header"/> + android:src="@drawable/counter_1_24dp" /> + android:text="@string/private_space_separate_account_text"/> + android:src="@drawable/counter_2_24dp" /> + android:text="@string/private_space_protected_lock_text"/> + android:src="@drawable/counter_3_24dp" /> + android:text="@string/private_space_install_apps_text"/> Set up - Set up a private space + Private space - Keep private apps in a separate space that you can hide or lock + Hide or lock private apps in a separate space. Use a dedicated Google Account for extra security. - How it works + Set up your private space - You can access your private space from the bottom of your apps list - - Apps in your private space are protected by a lock + Choose a Google Account for your space\nUsing a dedicated account helps to stop synced files, photos, and emails appearing outside your space + + Set a lock\nLock your space to stop other people opening it - Notifications from apps in your private space are hidden when it\u2019s locked + Install apps\nYour private space has its own Play Store so you can install apps easily. Apps in your private space won\'t appear in permission manager, privacy dashboard, and other settings when your private space is locked.\n\nYour private space can\'t be moved to a new device. You\'ll need to set up another private space if you want to use it on another device.\n\nAnyone that connects your device to a computer or installs harmful apps on your device may be able to access your private space. Setting up private space\u2026 Notifications from private space apps are hidden when it\u2019s locked - - Unlock private space to share photos or files + + Explore private space settings to hide private space and set up automatic locking Some apps are already installed in your private space diff --git a/res/values/styles.xml b/res/values/styles.xml index fbc6d7fc6e2..0a28b016ca9 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -966,6 +966,7 @@ 20dp 8dp 14sp + google-sans-medium