From b5d57eaa582dc11ec7bd146eaa06e8546f5a6356 Mon Sep 17 00:00:00 2001 From: George Chang Date: Fri, 15 Mar 2024 04:28:17 +0000 Subject: [PATCH 01/13] Revert "settings(dev): Remove NFC stack logging control" This reverts commit a5bd1cf34fb450af15a5e70117206d500f8169b9. Bug: 329776725 Test: manual turn on/off debug logging. Merged-In: I3c6026e230c7d35f04d9771442fadbf040a84b94 Change-Id: Ica004bb5f22636777287acfe1226b0f6a650d4f7 --- .../DevelopmentSettingsDashboardFragment.java | 1 + .../NfcStackDebugLogPreferenceController.java | 82 +++++++++++++ ...StackDebugLogPreferenceControllerTest.java | 112 ++++++++++++++++++ 3 files changed, 195 insertions(+) create mode 100644 src/com/android/settings/development/NfcStackDebugLogPreferenceController.java create mode 100644 tests/unit/src/com/android/settings/development/NfcStackDebugLogPreferenceControllerTest.java diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index cfa4a589cd0..6b38b28adc9 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -686,6 +686,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controllers.add(new BluetoothA2dpHwOffloadPreferenceController(context, fragment)); controllers.add(new BluetoothLeAudioHwOffloadPreferenceController(context, fragment)); controllers.add(new BluetoothMaxConnectedAudioDevicesPreferenceController(context)); + controllers.add(new NfcStackDebugLogPreferenceController(context)); controllers.add(new NfcSnoopLogPreferenceController(context, fragment)); controllers.add(new NfcVerboseVendorLogPreferenceController(context, fragment)); controllers.add(new ShowTapsPreferenceController(context)); diff --git a/src/com/android/settings/development/NfcStackDebugLogPreferenceController.java b/src/com/android/settings/development/NfcStackDebugLogPreferenceController.java new file mode 100644 index 00000000000..4464923b958 --- /dev/null +++ b/src/com/android/settings/development/NfcStackDebugLogPreferenceController.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2021 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.development; + +import android.content.Context; +import android.os.SystemProperties; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.TwoStatePreference; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.development.DeveloperOptionsPreferenceController; + +public class NfcStackDebugLogPreferenceController extends + DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener, + PreferenceControllerMixin { + + private static final String NFC_STACK_DEBUGLOG_ENABLED_KEY = + "nfc_stack_debuglog_enabled"; + @VisibleForTesting + static final String NFC_STACK_DEBUGLOG_ENABLED_PROPERTY = + "persist.nfc.debug_enabled"; + + public NfcStackDebugLogPreferenceController(Context context) { + super(context); + } + + @Override + public String getPreferenceKey() { + return NFC_STACK_DEBUGLOG_ENABLED_KEY; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean isEnabled = (Boolean) newValue; + try { + SystemProperties.set(NFC_STACK_DEBUGLOG_ENABLED_PROPERTY, + isEnabled ? "true" : "false"); + } catch (RuntimeException e) { + Log.e(TAG, "Fail to set nfc system property: " + e.getMessage()); + } + return true; + } + + @Override + public void updateState(Preference preference) { + try { + final boolean isEnabled = SystemProperties.getBoolean( + NFC_STACK_DEBUGLOG_ENABLED_PROPERTY, false /* default */); + ((TwoStatePreference) mPreference).setChecked(isEnabled); + } catch (RuntimeException e) { + Log.e(TAG, "Fail to get nfc system property: " + e.getMessage()); + } + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + super.onDeveloperOptionsSwitchDisabled(); + try { + SystemProperties.set(NFC_STACK_DEBUGLOG_ENABLED_PROPERTY, "false"); + ((TwoStatePreference) mPreference).setChecked(false); + } catch (RuntimeException e) { + Log.e(TAG, "Fail to set nfc system property: " + e.getMessage()); + } + } +} diff --git a/tests/unit/src/com/android/settings/development/NfcStackDebugLogPreferenceControllerTest.java b/tests/unit/src/com/android/settings/development/NfcStackDebugLogPreferenceControllerTest.java new file mode 100644 index 00000000000..914d01d9d23 --- /dev/null +++ b/tests/unit/src/com/android/settings/development/NfcStackDebugLogPreferenceControllerTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2021 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.development; + +import static com.android.settings.development.NfcStackDebugLogPreferenceController + .NFC_STACK_DEBUGLOG_ENABLED_PROPERTY; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.Looper; +import android.os.SystemProperties; + +import androidx.preference.SwitchPreference; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class NfcStackDebugLogPreferenceControllerTest { + + private Context mContext; + private NfcStackDebugLogPreferenceController mController; + private SwitchPreference mPreference; + + @Before + public void setUp() { + mContext = ApplicationProvider.getApplicationContext(); + mController = new NfcStackDebugLogPreferenceController(mContext); + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + final PreferenceManager preferenceManager = new PreferenceManager(mContext); + final PreferenceScreen screen = preferenceManager.createPreferenceScreen(mContext); + mPreference = new SwitchPreference(mContext); + mPreference.setKey(mController.getPreferenceKey()); + screen.addPreference(mPreference); + mController.displayPreference(screen); + } + + @Test + public void onPreferenceChanged_settingDisabled_shouldTurnOffNfcStackDebugLog() { + mController.onPreferenceChange(mPreference, false /* new value */); + + final boolean mode = SystemProperties.getBoolean( + NFC_STACK_DEBUGLOG_ENABLED_PROPERTY, false /* default */); + + assertThat(mode).isFalse(); + } + + @Test + public void onPreferenceChanged_settingEnabled_shouldTurnOnNfcStackDebugLog() { + mController.onPreferenceChange(mPreference, true /* new value */); + + final boolean mode = SystemProperties.getBoolean( + NFC_STACK_DEBUGLOG_ENABLED_PROPERTY, false /* default */); + + assertThat(mode).isTrue(); + } + + @Test + public void updateState_settingEnabled_preferenceShouldBeChecked() { + SystemProperties.set(NFC_STACK_DEBUGLOG_ENABLED_PROPERTY, + Boolean.toString(true)); + + mController.updateState(mPreference); + assertThat(mPreference.isChecked()).isTrue(); + } + + @Test + public void updateState_settingDisabled_preferenceShouldNotBeChecked() { + SystemProperties.set(NFC_STACK_DEBUGLOG_ENABLED_PROPERTY, + Boolean.toString(false)); + + mController.updateState(mPreference); + assertThat(mPreference.isChecked()).isFalse(); + } + + @Test + public void onDeveloperOptionsDisabled_shouldDisablePreference() { + mController.onDeveloperOptionsSwitchDisabled(); + final boolean mode = SystemProperties.getBoolean( + NFC_STACK_DEBUGLOG_ENABLED_PROPERTY, + false /* default */); + + mController.updateState(mPreference); + + assertThat(mode).isFalse(); + assertThat(mPreference.isChecked()).isFalse(); + } +} From e5028048a0aa52f923d2f08e203c11024513842a Mon Sep 17 00:00:00 2001 From: Manish Singh Date: Tue, 12 Mar 2024 12:54:50 +0000 Subject: [PATCH 02/13] Update Trusted credentials page for private profile screen recording - https://drive.google.com/file/d/1XuDEkgOGSIvHlk1q5r7-EX13BGld0OWh/view?usp=sharing&resourcekey=0-FKRl_ddBn2HAkxfnV2dfKg Bug: 307896268 Test: manual Change-Id: Ic4b438282686704deef2a051b5c302b7fae49edc --- .../settings/TrustedCredentialsFragment.java | 74 +++++++++++++++---- src/com/android/settings/Utils.java | 10 +++ 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/src/com/android/settings/TrustedCredentialsFragment.java b/src/com/android/settings/TrustedCredentialsFragment.java index a150850f922..fc8fea3adbd 100644 --- a/src/com/android/settings/TrustedCredentialsFragment.java +++ b/src/com/android/settings/TrustedCredentialsFragment.java @@ -17,6 +17,7 @@ package com.android.settings; import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.PRIVATE_CATEGORY_HEADER; import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER; import static android.widget.LinearLayout.LayoutParams.MATCH_PARENT; import static android.widget.LinearLayout.LayoutParams.WRAP_CONTENT; @@ -108,18 +109,37 @@ public class TrustedCredentialsFragment extends ObservableFragment mKeyChainConnectionByProfileId = new SparseArray<>(); private ViewGroup mFragmentView; - private final BroadcastReceiver mWorkProfileChangedReceiver = new BroadcastReceiver() { + private final BroadcastReceiver mProfileChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) - || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) - || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { + if (isBroadcastValidForAction(intent)) { mGroupAdapter.load(); } } }; + private boolean isBroadcastValidForAction(Intent intent) { + String action = intent.getAction(); + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) { + UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class); + if (userHandle == null) { + Log.w(TAG, "received action " + action + " with missing user extra"); + return false; + } + + UserInfo userInfo = mUserManager.getUserInfo(userHandle.getIdentifier()); + return (Intent.ACTION_PROFILE_AVAILABLE.equals(action) + || Intent.ACTION_PROFILE_UNAVAILABLE.equals(action) + || Intent.ACTION_PROFILE_ACCESSIBLE.equals(action)) + && (userInfo.isManagedProfile() || userInfo.isPrivateProfile()); + } + return (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) + || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) + || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -142,10 +162,18 @@ public class TrustedCredentialsFragment extends ObservableFragment } IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED); - activity.registerReceiver(mWorkProfileChangedReceiver, filter); + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) { + filter.addAction(Intent.ACTION_PROFILE_AVAILABLE); + filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE); + filter.addAction(Intent.ACTION_PROFILE_ACCESSIBLE); + } else { + filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); + filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); + filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED); + } + activity.registerReceiver(mProfileChangedReceiver, filter); } @Override @@ -177,7 +205,16 @@ public class TrustedCredentialsFragment extends ObservableFragment private void createChildView( LayoutInflater inflater, ViewGroup parent, Bundle childState, int i) { - boolean isWork = mGroupAdapter.getUserInfoByGroup(i).isManagedProfile(); + UserInfo userInfo = mGroupAdapter.getUserInfoByGroup(i); + if (Utils.shouldHideUser(userInfo.getUserHandle(), mUserManager)) { + return; + } + boolean isProfile = userInfo.isManagedProfile(); + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) { + isProfile |= userInfo.isPrivateProfile(); + } ChildAdapter adapter = mGroupAdapter.createChildAdapter(i); LinearLayout containerView = (LinearLayout) inflater.inflate( @@ -186,9 +223,9 @@ public class TrustedCredentialsFragment extends ObservableFragment int profilesSize = mGroupAdapter.getGroupCount(); adapter.showHeader(profilesSize > 1); - adapter.showDivider(isWork); - adapter.setExpandIfAvailable(profilesSize <= 2 || !isWork, childState); - if (isWork) { + adapter.showDivider(isProfile); + adapter.setExpandIfAvailable(profilesSize <= 2 || !isProfile, childState); + if (isProfile) { parent.addView(containerView); } else { parent.addView(containerView, 0); @@ -203,7 +240,7 @@ public class TrustedCredentialsFragment extends ObservableFragment @Override public void onDestroy() { - getActivity().unregisterReceiver(mWorkProfileChangedReceiver); + getActivity().unregisterReceiver(mProfileChangedReceiver); for (AdapterData.AliasLoader aliasLoader : mAliasLoaders) { aliasLoader.cancel(true); } @@ -331,9 +368,16 @@ public class TrustedCredentialsFragment extends ObservableFragment } TextView title = convertView.findViewById(android.R.id.title); - if (getUserInfoByGroup(groupPosition).isManagedProfile()) { + UserInfo userInfo = getUserInfoByGroup(groupPosition); + if (userInfo.isManagedProfile()) { title.setText(mDevicePolicyManager.getResources().getString(WORK_CATEGORY_HEADER, () -> getString(com.android.settingslib.R.string.category_work))); + } else if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace() + && userInfo.isPrivateProfile()) { + title.setText(mDevicePolicyManager.getResources().getString(PRIVATE_CATEGORY_HEADER, + () -> getString(com.android.settingslib.R.string.category_private))); } else { title.setText(mDevicePolicyManager.getResources().getString( PERSONAL_CATEGORY_HEADER, diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 6e36ee36733..e68d9f487e4 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -1364,6 +1364,16 @@ public final class Utils extends com.android.settingslib.Utils { } } + /** + * Returns true if the user should be hidden in Settings when it's in quiet mode. + */ + public static boolean shouldHideUser( + @NonNull UserHandle userHandle, @NonNull UserManager userManager) { + UserProperties userProperties = userManager.getUserProperties(userHandle); + return userProperties.getShowInQuietMode() == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN + && userManager.isQuietModeEnabled(userHandle); + } + private static FaceManager.RemovalCallback faceManagerRemovalCallback(int userId) { return new FaceManager.RemovalCallback() { @Override From d7ff6fe9666ae227ecd8cad64acb0e9808ffc09c Mon Sep 17 00:00:00 2001 From: Manish Singh Date: Fri, 8 Mar 2024 17:04:23 +0000 Subject: [PATCH 03/13] Support private profile in spinner adapters Screenshot - https://screenshot.googleplex.com/35czwQHeevX75pj Video - https://drive.google.com/file/d/1LkYPJ3i8llArnQBE3ieLPYMctEOw3xdp/view?usp=sharing&resourcekey=0-j-3-VV4OyXJKBPXwAqJvKg Bug: 328565911 Bug: 313610609 Bug: 302082696 Test: manual Test: atest UserAdapterTest Test: atest StylusDevicesControllerTest Change-Id: If8395eba5cc73809ab4abc95bc13067451c38b8f --- src/com/android/settings/Utils.java | 10 ++ .../stylus/StylusDevicesController.java | 25 ++--- .../profileselector/ProfileSelectDialog.java | 13 +-- .../profileselector/UserAdapter.java | 17 +++- .../stylus/StylusDevicesControllerTest.java | 95 +++++++++++++++++-- .../profileselector/UserAdapterTest.java | 69 ++++++++++++++ 6 files changed, 197 insertions(+), 32 deletions(-) diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 6e36ee36733..e68d9f487e4 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -1364,6 +1364,16 @@ public final class Utils extends com.android.settingslib.Utils { } } + /** + * Returns true if the user should be hidden in Settings when it's in quiet mode. + */ + public static boolean shouldHideUser( + @NonNull UserHandle userHandle, @NonNull UserManager userManager) { + UserProperties userProperties = userManager.getUserProperties(userHandle); + return userProperties.getShowInQuietMode() == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN + && userManager.isQuietModeEnabled(userHandle); + } + private static FaceManager.RemovalCallback faceManagerRemovalCallback(int userId) { return new FaceManager.RemovalCallback() { @Override diff --git a/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java b/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java index cd23103c5e4..7fbaf3c18f1 100644 --- a/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java +++ b/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java @@ -214,7 +214,7 @@ public class StylusDevicesController extends AbstractPreferenceController implem Intent intent = new Intent(Intent.ACTION_MANAGE_DEFAULT_APP).setPackage( packageName).putExtra(Intent.EXTRA_ROLE_NAME, RoleManager.ROLE_NOTES); - List users = getUserAndManagedProfiles(); + List users = getUserProfiles(); if (users.size() <= 1) { mContext.startActivity(intent); } else { @@ -311,22 +311,23 @@ public class StylusDevicesController extends AbstractPreferenceController implem return inputMethod != null && inputMethod.supportsStylusHandwriting(); } - private List getUserAndManagedProfiles() { + private List getUserProfiles() { UserManager um = mContext.getSystemService(UserManager.class); - final List userManagedProfiles = new ArrayList<>(); - // Add the current user, then add all the associated managed profiles. final UserHandle currentUser = Process.myUserHandle(); - userManagedProfiles.add(currentUser); + final List userProfiles = new ArrayList<>(); + userProfiles.add(currentUser); - final List userInfos = um.getUsers(); - for (UserInfo info : userInfos) { - int userId = info.id; - if (um.isManagedProfile(userId) - && um.getProfileParent(userId).id == currentUser.getIdentifier()) { - userManagedProfiles.add(UserHandle.of(userId)); + final List userInfos = um.getProfiles(currentUser.getIdentifier()); + for (UserInfo userInfo : userInfos) { + if (userInfo.isManagedProfile() + || (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace() + && userInfo.isPrivateProfile())) { + userProfiles.add(userInfo.getUserHandle()); } } - return userManagedProfiles; + return userProfiles; } private UserHandle getDefaultNoteTaskProfile() { diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java index 4df1fdd3bb0..b276a39dffd 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java @@ -25,7 +25,6 @@ import android.content.DialogInterface.OnDismissListener; import android.content.DialogInterface.OnShowListener; import android.content.Intent; import android.content.pm.UserInfo; -import android.content.pm.UserProperties; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; @@ -42,6 +41,7 @@ import com.android.internal.widget.DialogTitle; import com.android.internal.widget.LinearLayoutManager; import com.android.internal.widget.RecyclerView; import com.android.settings.R; +import com.android.settings.Utils; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.drawer.Tile; @@ -186,7 +186,7 @@ public class ProfileSelectDialog extends DialogFragment implements UserAdapter.O UserInfo userInfo = userManager.getUserInfo(userHandles.get(i).getIdentifier()); if (userInfo == null || userInfo.isCloneProfile() - || shouldHideUserInQuietMode(userHandles.get(i), userManager)) { + || Utils.shouldHideUser(userHandles.get(i), userManager)) { if (DEBUG) { Log.d(TAG, "Delete the user: " + userHandles.get(i).getIdentifier()); } @@ -219,7 +219,7 @@ public class ProfileSelectDialog extends DialogFragment implements UserAdapter.O UserInfo userInfo = userManager.getUserInfo(userHandle.getIdentifier()); if (userInfo == null || userInfo.isCloneProfile() - || shouldHideUserInQuietMode(userHandle, userManager)) { + || Utils.shouldHideUser(userHandle, userManager)) { if (DEBUG) { Log.d(TAG, "Delete the user: " + userHandle.getIdentifier()); } @@ -228,11 +228,4 @@ public class ProfileSelectDialog extends DialogFragment implements UserAdapter.O } } } - - private static boolean shouldHideUserInQuietMode( - UserHandle userHandle, UserManager userManager) { - UserProperties userProperties = userManager.getUserProperties(userHandle); - return userProperties.getShowInQuietMode() == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN - && userManager.isQuietModeEnabled(userHandle); - } } diff --git a/src/com/android/settings/dashboard/profileselector/UserAdapter.java b/src/com/android/settings/dashboard/profileselector/UserAdapter.java index 40d1a93e5d7..0fefa2f246e 100644 --- a/src/com/android/settings/dashboard/profileselector/UserAdapter.java +++ b/src/com/android/settings/dashboard/profileselector/UserAdapter.java @@ -64,7 +64,11 @@ public class UserAdapter extends BaseAdapter { UserInfo userInfo = um.getUserInfo(mUserHandle.getIdentifier()); int tintColor = Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.materialColorPrimary); - if (userInfo.isManagedProfile()) { + if (userInfo.isManagedProfile() + || (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace() + && userInfo.isPrivateProfile())) { mIcon = context.getPackageManager().getUserBadgeForDensityNoBackground( userHandle, /* density= */ 0); mIcon.setTint(tintColor); @@ -88,6 +92,7 @@ public class UserAdapter extends BaseAdapter { () -> context.getString(com.android.settingslib.R.string.category_work)); } else if (android.os.Flags.allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace() && mUserManager.getUserInfo(userId).isPrivateProfile()) { return resources.getString(PRIVATE_CATEGORY_HEADER, () -> context.getString(com.android.settingslib.R.string.category_private)); @@ -209,6 +214,7 @@ public class UserAdapter extends BaseAdapter { private static UserAdapter createUserAdapter( UserManager userManager, Context context, List userProfiles) { + updateUserHandlesIfNeeded(userManager, userProfiles); ArrayList userDetails = new ArrayList<>(userProfiles.size()); for (UserHandle userProfile : userProfiles) { userDetails.add(new UserDetails(userProfile, userManager, context)); @@ -216,6 +222,15 @@ public class UserAdapter extends BaseAdapter { return new UserAdapter(context, userDetails); } + private static void updateUserHandlesIfNeeded( + UserManager userManager, List userProfiles) { + for (int i = userProfiles.size() - 1; i >= 0; --i) { + if (com.android.settings.Utils.shouldHideUser(userProfiles.get(i), userManager)) { + userProfiles.remove(i); + } + } + } + static class ViewHolder extends RecyclerView.ViewHolder { private final ImageView mIconView; private final TextView mTitleView; diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java index 6c96f852ddc..c1cbf17f2c3 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java @@ -41,9 +41,12 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.content.pm.UserProperties; +import android.graphics.drawable.Drawable; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.provider.Settings.Secure; import android.view.InputDevice; @@ -64,6 +67,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -77,8 +81,12 @@ import java.util.List; @RunWith(RobolectricTestRunner.class) public class StylusDevicesControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private static final String NOTES_PACKAGE_NAME = "notes.package"; private static final CharSequence NOTES_APP_LABEL = "App Label"; + private static final int WORK_USER_ID = 1; + private static final int PRIVATE_USER_ID = 2; private Context mContext; private StylusDevicesController mController; @@ -95,6 +103,12 @@ public class StylusDevicesControllerTest { @Mock private UserManager mUserManager; @Mock + UserInfo mPersonalUserInfo; + @Mock + UserInfo mWorkUserInfo; + @Mock + UserInfo mPrivateUserInfo; + @Mock private RoleManager mRm; @Mock private Lifecycle mLifecycle; @@ -102,6 +116,8 @@ public class StylusDevicesControllerTest { private CachedBluetoothDevice mCachedBluetoothDevice; @Mock private BluetoothDevice mBluetoothDevice; + @Mock + private Drawable mIcon; @Before public void setUp() throws Exception { @@ -134,6 +150,7 @@ public class StylusDevicesControllerTest { when(mPm.getApplicationInfo(eq(NOTES_PACKAGE_NAME), any(PackageManager.ApplicationInfoFlags.class))).thenReturn(new ApplicationInfo()); when(mPm.getApplicationLabel(any(ApplicationInfo.class))).thenReturn(NOTES_APP_LABEL); + when(mPm.getUserBadgeForDensityNoBackground(any(), anyInt())).thenReturn(mIcon); when(mUserManager.getUsers()).thenReturn(Arrays.asList(new UserInfo(0, "default", 0))); when(mUserManager.isManagedProfile(anyInt())).thenReturn(false); @@ -371,14 +388,26 @@ public class StylusDevicesControllerTest { final String permissionPackageName = "permissions.package"; final UserHandle currentUser = Process.myUserHandle(); List userInfos = Arrays.asList( - new UserInfo(currentUser.getIdentifier(), "current", 0), - new UserInfo(1, "profile", UserInfo.FLAG_PROFILE) + mPersonalUserInfo, + mWorkUserInfo ); - when(mUserManager.getUsers()).thenReturn(userInfos); - when(mUserManager.isManagedProfile(1)).thenReturn(true); - when(mUserManager.getUserInfo(currentUser.getIdentifier())).thenReturn(userInfos.get(0)); - when(mUserManager.getUserInfo(1)).thenReturn(userInfos.get(1)); - when(mUserManager.getProfileParent(1)).thenReturn(userInfos.get(0)); + UserProperties personalUserProperties = + new UserProperties.Builder() + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_DEFAULT) + .build(); + UserProperties workUserProperties = + new UserProperties.Builder() + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_PAUSED) + .build(); + when(mWorkUserInfo.isManagedProfile()).thenReturn(true); + when(mWorkUserInfo.getUserHandle()).thenReturn(UserHandle.of(WORK_USER_ID)); + when(mUserManager.getProfiles(currentUser.getIdentifier())).thenReturn(userInfos); + when(mUserManager.getUserInfo(currentUser.getIdentifier())).thenReturn(mPersonalUserInfo); + when(mUserManager.getUserInfo(WORK_USER_ID)).thenReturn(mWorkUserInfo); + when(mUserManager.getProfileParent(WORK_USER_ID)).thenReturn(mPersonalUserInfo); + when(mUserManager.getUserProperties(currentUser)).thenReturn(personalUserProperties); + when(mUserManager.getUserProperties(UserHandle.of(WORK_USER_ID))) + .thenReturn(workUserProperties); when(mPm.getPermissionControllerPackageName()).thenReturn(permissionPackageName); showScreen(mController); @@ -389,7 +418,55 @@ public class StylusDevicesControllerTest { } @Test - public void defaultNotesPreferenceClick_noManagedProfile_sendsManageDefaultRoleIntent() { + public void defaultNotesPreferenceClick_multiUsers_showsProfileSelectorDialog() { + mSetFlagsRule.enableFlags( + android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES, + android.multiuser.Flags.FLAG_HANDLE_INTERLEAVED_SETTINGS_FOR_PRIVATE_SPACE); + mContext.setTheme(androidx.appcompat.R.style.Theme_AppCompat); + final String permissionPackageName = "permissions.package"; + final UserHandle currentUser = Process.myUserHandle(); + List userInfos = Arrays.asList( + mPersonalUserInfo, + mPrivateUserInfo, + mWorkUserInfo + ); + UserProperties personalUserProperties = + new UserProperties.Builder() + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_DEFAULT) + .build(); + UserProperties workUserProperties = + new UserProperties.Builder() + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_PAUSED) + .build(); + UserProperties privateUserProperties = + new UserProperties.Builder() + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) + .build(); + when(mWorkUserInfo.isManagedProfile()).thenReturn(true); + when(mWorkUserInfo.getUserHandle()).thenReturn(UserHandle.of(WORK_USER_ID)); + when(mPrivateUserInfo.isPrivateProfile()).thenReturn(true); + when(mPrivateUserInfo.getUserHandle()).thenReturn(UserHandle.of(PRIVATE_USER_ID)); + when(mUserManager.getProfiles(currentUser.getIdentifier())).thenReturn(userInfos); + when(mUserManager.getUserInfo(currentUser.getIdentifier())).thenReturn(mPersonalUserInfo); + when(mUserManager.getUserInfo(WORK_USER_ID)).thenReturn(mWorkUserInfo); + when(mUserManager.getUserInfo(PRIVATE_USER_ID)).thenReturn(mPrivateUserInfo); + when(mUserManager.getUserProperties(currentUser)).thenReturn(personalUserProperties); + when(mUserManager.getUserProperties(UserHandle.of(PRIVATE_USER_ID))) + .thenReturn(privateUserProperties); + when(mUserManager.getUserProperties(UserHandle.of(WORK_USER_ID))) + .thenReturn(workUserProperties); + when(mPm.getPermissionControllerPackageName()).thenReturn(permissionPackageName); + + showScreen(mController); + Preference defaultNotesPref = mPreferenceContainer.getPreference(0); + mController.onPreferenceClick(defaultNotesPref); + + assertTrue(mController.mDialog.isShowing()); + } + + @Test + public void defaultNotesPreferenceClick_noProfiles_sendsManageDefaultRoleIntent() { final ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); mContext.setTheme(androidx.appcompat.R.style.Theme_AppCompat); final String permissionPackageName = "permissions.package"; @@ -398,7 +475,7 @@ public class StylusDevicesControllerTest { new UserInfo(currentUser.getIdentifier(), "current", 0), new UserInfo(1, "other", UserInfo.FLAG_FULL) ); - when(mUserManager.getUsers()).thenReturn(userInfos); + when(mUserManager.getProfiles(currentUser.getIdentifier())).thenReturn(userInfos); when(mUserManager.isManagedProfile(1)).thenReturn(false); when(mUserManager.getUserInfo(currentUser.getIdentifier())).thenReturn(userInfos.get(0)); when(mUserManager.getUserInfo(1)).thenReturn(userInfos.get(1)); diff --git a/tests/robotests/src/com/android/settings/dashboard/profileselector/UserAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/profileselector/UserAdapterTest.java index 2fb5e03fa7c..b6ac410ece9 100644 --- a/tests/robotests/src/com/android/settings/dashboard/profileselector/UserAdapterTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/profileselector/UserAdapterTest.java @@ -24,8 +24,10 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.UserInfo; +import android.content.pm.UserProperties; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.flag.junit.SetFlagsRule; import android.widget.FrameLayout; import android.widget.TextView; @@ -52,9 +54,12 @@ import java.util.ArrayList; public class UserAdapterTest { @Rule public MockitoRule mRule = MockitoJUnit.rule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private final int mPersonalUserId = UserHandle.myUserId(); private static final int WORK_USER_ID = 1; + private static final int PRIVATE_USER_ID = 2; @Mock private UserManager mUserManager; @@ -64,6 +69,8 @@ public class UserAdapterTest { @Mock private UserInfo mWorkUserInfo; + @Mock + private UserInfo mPrivateUserInfo; @Mock private UserAdapter.OnClickListener mOnClickListener; @@ -71,11 +78,31 @@ public class UserAdapterTest { @Spy private Context mContext = ApplicationProvider.getApplicationContext(); + private UserProperties mPersonalUserProperties = + new UserProperties.Builder() + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_DEFAULT) + .build(); + private UserProperties mWorkUserProperties = + new UserProperties.Builder() + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_PAUSED) + .build(); + private UserProperties mPrivateUserProperties = + new UserProperties.Builder() + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) + .build(); + @Before public void setUp() { when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); when(mUserManager.getUserInfo(mPersonalUserId)).thenReturn(mPersonalUserInfo); when(mUserManager.getUserInfo(WORK_USER_ID)).thenReturn(mWorkUserInfo); + when(mUserManager.getUserInfo(PRIVATE_USER_ID)).thenReturn(mPrivateUserInfo); + when(mUserManager.getUserProperties(UserHandle.of(mPersonalUserId))) + .thenReturn(mPersonalUserProperties); + when(mUserManager.getUserProperties(UserHandle.of(WORK_USER_ID))) + .thenReturn(mWorkUserProperties); + when(mUserManager.getUserProperties(UserHandle.of(PRIVATE_USER_ID))) + .thenReturn(mPrivateUserProperties); } @Test @@ -102,6 +129,48 @@ public class UserAdapterTest { assertThat(userSpinnerAdapter.getUserHandle(1).getIdentifier()).isEqualTo(WORK_USER_ID); } + @Test + public void createUserSpinnerAdapter_withWorkAndPrivateProfiles_shouldSucceed() { + mSetFlagsRule.enableFlags( + android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES, + android.multiuser.Flags.FLAG_HANDLE_INTERLEAVED_SETTINGS_FOR_PRIVATE_SPACE); + when(mUserManager.getUserProfiles()).thenReturn( + Lists.newArrayList( + UserHandle.of(mPersonalUserId), + UserHandle.of(WORK_USER_ID), + UserHandle.of(PRIVATE_USER_ID))); + + UserAdapter userSpinnerAdapter = + UserAdapter.createUserSpinnerAdapter(mUserManager, mContext); + + assertThat(userSpinnerAdapter.getCount()).isEqualTo(3); + assertThat(userSpinnerAdapter.getUserHandle(0).getIdentifier()).isEqualTo(mPersonalUserId); + assertThat(userSpinnerAdapter.getUserHandle(1).getIdentifier()).isEqualTo(WORK_USER_ID); + assertThat(userSpinnerAdapter.getUserHandle(2).getIdentifier()).isEqualTo(PRIVATE_USER_ID); + } + + @Test + public void createUserSpinnerAdapter_withWorkAndQuietPrivateProfile_shouldShowTwoProfiles() { + mSetFlagsRule.enableFlags( + android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES, + android.multiuser.Flags.FLAG_HANDLE_INTERLEAVED_SETTINGS_FOR_PRIVATE_SPACE); + when(mUserManager.getUserProfiles()).thenReturn( + Lists.newArrayList( + UserHandle.of(mPersonalUserId), + UserHandle.of(WORK_USER_ID), + UserHandle.of(PRIVATE_USER_ID))); + when(mUserManager.isQuietModeEnabled(UserHandle.of(PRIVATE_USER_ID))).thenReturn(true); + + UserAdapter userSpinnerAdapter = + UserAdapter.createUserSpinnerAdapter(mUserManager, mContext); + + assertThat(userSpinnerAdapter.getCount()).isEqualTo(2); + assertThat(userSpinnerAdapter.getUserHandle(0).getIdentifier()).isEqualTo(mPersonalUserId); + assertThat(userSpinnerAdapter.getUserHandle(1).getIdentifier()).isEqualTo(WORK_USER_ID); + } + @Test public void createUserRecycleViewAdapter_canBindViewHolderCorrectly() { ArrayList userHandles = From c5e07bb662e8a4f5972b1a119e0464e4c4212ef7 Mon Sep 17 00:00:00 2001 From: Manish Singh Date: Tue, 12 Mar 2024 16:36:25 +0000 Subject: [PATCH 04/13] Don't show private accounts when locked screen recoriding - https://drive.google.com/file/d/1_QzXJgISWhiYFGOGQkqDw6ZMxlVmojH_/view?usp=drive_link&resourcekey=0-64S56eFqFukR2ez4yKQlpQ Bug: 324574475 Test: manual Change-Id: I08eafbd66ee81d2ea539993d2d96c28b89a6dad0 --- src/com/android/settings/MainClear.java | 11 +++++++++++ src/com/android/settings/Utils.java | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/com/android/settings/MainClear.java b/src/com/android/settings/MainClear.java index 7b96d42b912..acf25d5b078 100644 --- a/src/com/android/settings/MainClear.java +++ b/src/com/android/settings/MainClear.java @@ -17,6 +17,7 @@ package com.android.settings; import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.PRIVATE_CATEGORY_HEADER; import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; @@ -505,6 +506,9 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis final UserInfo userInfo = profiles.get(profileIndex); final int profileId = userInfo.id; final UserHandle userHandle = new UserHandle(profileId); + if (Utils.shouldHideUser(userHandle, um)) { + continue; + } Account[] accounts = mgr.getAccountsAsUser(profileId); final int accountLength = accounts.length; if (accountLength == 0) { @@ -529,6 +533,13 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis titleText.setText(devicePolicyManager.getResources().getString( WORK_CATEGORY_HEADER, () -> getString( com.android.settingslib.R.string.category_work))); + } else if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace() + && userInfo.isPrivateProfile()) { + titleText.setText(devicePolicyManager.getResources().getString( + PRIVATE_CATEGORY_HEADER, () -> getString( + com.android.settingslib.R.string.category_private))); } else { titleText.setText(devicePolicyManager.getResources().getString( PERSONAL_CATEGORY_HEADER, () -> getString( diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 6e36ee36733..e68d9f487e4 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -1364,6 +1364,16 @@ public final class Utils extends com.android.settingslib.Utils { } } + /** + * Returns true if the user should be hidden in Settings when it's in quiet mode. + */ + public static boolean shouldHideUser( + @NonNull UserHandle userHandle, @NonNull UserManager userManager) { + UserProperties userProperties = userManager.getUserProperties(userHandle); + return userProperties.getShowInQuietMode() == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN + && userManager.isQuietModeEnabled(userHandle); + } + private static FaceManager.RemovalCallback faceManagerRemovalCallback(int userId) { return new FaceManager.RemovalCallback() { @Override From 40debb2540acce66284195fc63cc3c1cbf52f0be Mon Sep 17 00:00:00 2001 From: Richard MacGregor Date: Fri, 15 Mar 2024 15:11:53 -0700 Subject: [PATCH 05/13] Update screenshare protection strings Update developer options strings for screen share protections Bug: 323015826 Test: manual Flag: ACONFIG com.android.server.notification.sensitive_notification_app_protection TRUNK_FOOD Change-Id: Ib597c0bc7df518337647e537c948ace60af79046 --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 224425b3ad5..ba99a1fca16 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -12135,7 +12135,7 @@ Disable screen share protections - Disables system applied app and notifications protections during screen sharing + Turn off system protections for sensitive app content for upcoming screen share sessions Media From e1bff83335d5778e3659d86a3c57ccc6265e2e21 Mon Sep 17 00:00:00 2001 From: Ze Li Date: Mon, 18 Mar 2024 19:21:47 +0800 Subject: [PATCH 06/13] [ConnectedDevicePage] Add setup to fix testing fail. Test: manual test Bug: 322712259 Change-Id: I770e56524ce28511b9d3f773b439d6ea419b1509 --- .../PreviouslyConnectedDevicePreferenceControllerTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/robotests/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceControllerTest.java index f1cea6d76ec..23649f31255 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceControllerTest.java @@ -53,7 +53,6 @@ import com.android.settings.widget.SingleTargetGearPreference; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -133,6 +132,7 @@ public class PreviouslyConnectedDevicePreferenceControllerTest { doReturn(mPackageManager).when(mContext).getPackageManager(); when(mContext.getSystemService(BluetoothManager.class)).thenReturn(mBluetoothManager); when(mBluetoothManager.getAdapter()).thenReturn(mBluetoothAdapter); + when(mBluetoothAdapter.isEnabled()).thenReturn(true); mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); when(mCachedDevice1.getDevice()).thenReturn(mBluetoothDevice1); @@ -223,7 +223,6 @@ public class PreviouslyConnectedDevicePreferenceControllerTest { AVAILABLE); } - @Ignore("b/322712259") @Test public void onDeviceAdded_addDevicePreference_displayIt() { final BluetoothDevicePreference preference1 = new BluetoothDevicePreference( @@ -234,7 +233,6 @@ public class PreviouslyConnectedDevicePreferenceControllerTest { assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2); } - @Ignore("b/322712259") @Test public void onDeviceAdded_addDockDevicePreference_displayIt() { final SingleTargetGearPreference dockPreference = new SingleTargetGearPreference( @@ -245,7 +243,6 @@ public class PreviouslyConnectedDevicePreferenceControllerTest { assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2); } - @Ignore("b/322712259") @Test public void onDeviceAdded_addFourDevicePreference_onlyDisplayThree() { final BluetoothDevicePreference preference1 = new BluetoothDevicePreference( From 124040e17e20402f1511bec628b084dc924d5704 Mon Sep 17 00:00:00 2001 From: alukin Date: Wed, 13 Mar 2024 14:30:24 +0000 Subject: [PATCH 07/13] Add strings for Documents&Other split Adding strings that are used for Documents&Other category split in Settings > Storage Bug: 328505030 Test: atest StorageItemPreferenceControllerTest Test: atest StorageCacheHelperTest Change-Id: I8b8d0cc96d85273da878655295f3d0919f46a46c --- res/values/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/res/values/strings.xml b/res/values/strings.xml index 46b7e868321..e1a8abc00c5 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10762,6 +10762,12 @@ Audio + + Documents + + + Other + Apps From 5599de4fb5fa2a39fcd15e3c74d7653bda6601fd Mon Sep 17 00:00:00 2001 From: yomna Date: Tue, 23 Jan 2024 22:49:11 +0000 Subject: [PATCH 08/13] Add new Cellular Network Security page Test: m & atest CellularSecurityNotificationsDividerControllerTest CellularSecurityNotificationsPreferenceControllerTest CellularSecurityEncryptionDividerControllerTest CellularSecurityPreferenceControllerTest Bug: b/318428717 Change-Id: I4a6ec5f47beb36bd455e04c2e6c4cea0ba65110f --- AndroidManifest.xml | 11 + res/values/strings.xml | 23 ++ res/xml/cellular_security.xml | 42 +++ res/xml/more_security_privacy_settings.xml | 6 + res/xml/network_provider_internet.xml | 7 + src/com/android/settings/Settings.java | 1 + .../core/gateway/SettingsGateway.java | 2 + .../CellularSecurityPreferenceController.java | 161 ++++++++++++ ...arSecurityEncryptionDividerController.java | 92 +++++++ ...ecurityNotificationsDividerController.java | 115 ++++++++ ...rityNotificationsPreferenceController.java | 196 ++++++++++++++ .../CellularSecuritySettingsFragment.java | 59 +++++ ...lularSecurityPreferenceControllerTest.java | 179 +++++++++++++ ...curityEncryptionDividerControllerTest.java | 84 ++++++ ...ityNotificationsDividerControllerTest.java | 143 ++++++++++ ...NotificationsPreferenceControllerTest.java | 247 ++++++++++++++++++ 16 files changed, 1368 insertions(+) create mode 100644 res/xml/cellular_security.xml create mode 100644 src/com/android/settings/network/CellularSecurityPreferenceController.java create mode 100644 src/com/android/settings/network/telephony/CellularSecurityEncryptionDividerController.java create mode 100644 src/com/android/settings/network/telephony/CellularSecurityNotificationsDividerController.java create mode 100644 src/com/android/settings/network/telephony/CellularSecurityNotificationsPreferenceController.java create mode 100644 src/com/android/settings/network/telephony/CellularSecuritySettingsFragment.java create mode 100644 tests/unit/src/com/android/settings/network/CellularSecurityPreferenceControllerTest.java create mode 100644 tests/unit/src/com/android/settings/network/telephony/CellularSecurityEncryptionDividerControllerTest.java create mode 100644 tests/unit/src/com/android/settings/network/telephony/CellularSecurityNotificationsDividerControllerTest.java create mode 100644 tests/unit/src/com/android/settings/network/telephony/CellularSecurityNotificationsPreferenceControllerTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index aed51f3e0eb..bc239904406 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -628,6 +628,17 @@ android:value="true" /> + + + + + + + + Use adaptive connectivity + + + Cellular network security + + Network type, encryption, notification controls + + + Cellular network security + + Notifications + + Security notifications + + Receive notifications in case the cellular network you are connected to is insecure due to lack of encryption, or if the cellular network records your unique decive or SIM identifiers (IMEI & IMSI) + + + + Encryption + + Network generations + + You can configure each installed SIM card to only connect to networks that support 3G, 4G, and 5G. The SIM will not connect to older, insecure 2G networks. This setting may limit your connectivity in case the only available network is 2G. In case of an emergency, 2G may be used. + Credential storage diff --git a/res/xml/cellular_security.xml b/res/xml/cellular_security.xml new file mode 100644 index 00000000000..e5fee150783 --- /dev/null +++ b/res/xml/cellular_security.xml @@ -0,0 +1,42 @@ + + + + + + + + + + diff --git a/res/xml/more_security_privacy_settings.xml b/res/xml/more_security_privacy_settings.xml index 92c3fa7a9e5..3e11db206b5 100644 --- a/res/xml/more_security_privacy_settings.xml +++ b/res/xml/more_security_privacy_settings.xml @@ -118,6 +118,12 @@ android:summary="@string/content_capture_summary" settings:controller="com.android.settings.privacy.EnableContentCaptureWithServiceSettingsPreferenceController"/> + diff --git a/res/xml/network_provider_internet.xml b/res/xml/network_provider_internet.xml index 04f248ed745..2a08aae892d 100644 --- a/res/xml/network_provider_internet.xml +++ b/res/xml/network_provider_internet.xml @@ -109,4 +109,11 @@ android:summary="@string/summary_placeholder" android:order="25" settings:controller="com.android.settings.network.AdaptiveConnectivityPreferenceController"/> + + diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 2f6f04af6ac..4c2ee67fd30 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -348,6 +348,7 @@ public class Settings extends SettingsActivity { /* empty */ } + public static class CellularSecuritySettingsActivity extends SettingsActivity { /* empty */ } public static class SatelliteSettingActivity extends SettingsActivity { /* empty */ } public static class ApnSettingsActivity extends SettingsActivity { /* empty */ } public static class WifiCallingSettingsActivity extends SettingsActivity { /* empty */ } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index e08e8560c3c..8b3dbf432b8 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -144,6 +144,7 @@ import com.android.settings.network.NetworkDashboardFragment; import com.android.settings.network.NetworkProviderSettings; import com.android.settings.network.apn.ApnEditor; import com.android.settings.network.apn.ApnSettings; +import com.android.settings.network.telephony.CellularSecuritySettingsFragment; import com.android.settings.network.telephony.MobileNetworkSettings; import com.android.settings.network.telephony.NetworkSelectSettings; import com.android.settings.network.telephony.SatelliteSetting; @@ -388,6 +389,7 @@ public class SettingsGateway { ScreenTimeoutSettings.class.getName(), ResetNetwork.class.getName(), VibrationIntensitySettingsFragment.class.getName(), + CellularSecuritySettingsFragment.class.getName(), }; public static final String[] SETTINGS_FOR_RESTRICTED = { diff --git a/src/com/android/settings/network/CellularSecurityPreferenceController.java b/src/com/android/settings/network/CellularSecurityPreferenceController.java new file mode 100644 index 00000000000..237e7040fca --- /dev/null +++ b/src/com/android/settings/network/CellularSecurityPreferenceController.java @@ -0,0 +1,161 @@ +/* + * 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 android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.safetycenter.SafetyCenterManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.internal.telephony.flags.Flags; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.network.telephony.CellularSecuritySettingsFragment; + +/** + * {@link BasePreferenceController} for accessing Cellular Security settings from Network & + * Internet Settings menu. + */ +public class CellularSecurityPreferenceController extends BasePreferenceController { + + private static final String LOG_TAG = "CellularSecurityPreferenceController"; + + private @Nullable TelephonyManager mTelephonyManager; + + /** + * Class constructor of "Cellular Security" preference. + * + * @param context of settings + * @param prefKey assigned within UI entry of XML file + */ + public CellularSecurityPreferenceController(@NonNull Context context, @NonNull String prefKey) { + super(context, prefKey); + mTelephonyManager = context.getSystemService(TelephonyManager.class); + } + + @Override + public void displayPreference(@NonNull PreferenceScreen screen) { + super.displayPreference(screen); + } + + @Override + public int getAvailabilityStatus() { + if (!Flags.enableIdentifierDisclosureTransparencyUnsolEvents() + || !Flags.enableModemCipherTransparencyUnsolEvents() + || !Flags.enableIdentifierDisclosureTransparency() + || !Flags.enableModemCipherTransparency()) { + return UNSUPPORTED_ON_DEVICE; + } + if (mTelephonyManager == null) { + Log.w(LOG_TAG, "Telephony manager not yet initialized"); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); + } + + boolean isNullCipherDisablementAvailable = false; + boolean areCellSecNotificationsAvailable = false; + try { + mTelephonyManager.isNullCipherAndIntegrityPreferenceEnabled(); + isNullCipherDisablementAvailable = true; // true because it doesn't throw an exception, + // we don't want the value of + // isNullCipherAndIntegrityEnabled() + } catch (UnsupportedOperationException e) { + Log.i(LOG_TAG, "Null cipher enablement is unsupported, hiding divider: " + + e.getMessage()); + } catch (Exception e) { + Log.e(LOG_TAG, + "Failed isNullCipherAndIntegrityEnabled. Setting availability to " + + "CONDITIONALLY_UNAVAILABLE. Exception: " + + e.getMessage()); + } + + try { + // Must call both APIs, as we can't use the combined toggle if both aren't available + areNotificationsEnabled(); + areCellSecNotificationsAvailable = true; // true because it doesn't throw an exception + // and we don't want the value of + // areNotificationsEnabled() + } catch (UnsupportedOperationException e) { + Log.i(LOG_TAG, "Cellular security notifications are unsupported, hiding divider: " + + e.getMessage()); + } + + if (isNullCipherDisablementAvailable || areCellSecNotificationsAvailable) { + return AVAILABLE; + } else { + return UNSUPPORTED_ON_DEVICE; + } + } + + @Override + public boolean handlePreferenceTreeClick(@NonNull Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return super.handlePreferenceTreeClick(preference); + } + boolean isSafetyCenterSupported = isSafetyCenterSupported(); + if (isSafetyCenterSupported) { + Intent safetyCenterIntent = new Intent(Intent.ACTION_SAFETY_CENTER); + safetyCenterIntent.putExtra(SafetyCenterManager.EXTRA_SAFETY_SOURCES_GROUP_ID, + "AndroidCellularNetworkSecuritySources"); + mContext.startActivity(safetyCenterIntent); + } else { + final Bundle bundle = new Bundle(); + bundle.putString(CellularSecuritySettingsFragment.KEY_CELLULAR_SECURITY_PREFERENCE, ""); + + new SubSettingLauncher(mContext) + .setDestination(CellularSecuritySettingsFragment.class.getName()) + .setArguments(bundle) + .setSourceMetricsCategory(SettingsEnums.CELLULAR_SECURITY_SETTINGS) + .launch(); + } + return true; + } + + @VisibleForTesting + protected boolean isSafetyCenterSupported() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + return false; + } + SafetyCenterManager safetyCenterManager = mContext.getSystemService( + SafetyCenterManager.class); + if (safetyCenterManager == null) { + return false; + } + return safetyCenterManager.isSafetyCenterEnabled(); + } + + @VisibleForTesting + protected boolean areNotificationsEnabled() { + if (mTelephonyManager == null) { + Log.w(LOG_TAG, "Telephony manager not yet initialized"); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); + } + + return mTelephonyManager.isNullCipherNotificationsEnabled() + && mTelephonyManager.isCellularIdentifierDisclosureNotificationsEnabled(); + } +} diff --git a/src/com/android/settings/network/telephony/CellularSecurityEncryptionDividerController.java b/src/com/android/settings/network/telephony/CellularSecurityEncryptionDividerController.java new file mode 100644 index 00000000000..6d31c72c02f --- /dev/null +++ b/src/com/android/settings/network/telephony/CellularSecurityEncryptionDividerController.java @@ -0,0 +1,92 @@ +/* + * 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.telephony.TelephonyManager; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.preference.PreferenceScreen; + +import com.android.internal.telephony.flags.FeatureFlags; +import com.android.internal.telephony.flags.FeatureFlagsImpl; +import com.android.settings.core.BasePreferenceController; + +/** + * {@link BasePreferenceController} for visibility of Encryption divider on Cellular Security + * settings page. + */ +public class CellularSecurityEncryptionDividerController extends + BasePreferenceController { + + private static final String LOG_TAG = "CellularSecurityEncryptionDividerController"; + + private TelephonyManager mTelephonyManager; + + protected final FeatureFlags mFeatureFlags = new FeatureFlagsImpl(); + + /** + * Class constructor of "Cellular Security" preference. + * + * @param context of settings + * @param prefKey assigned within UI entry of XML file + */ + public CellularSecurityEncryptionDividerController( + @NonNull Context context, @NonNull String prefKey) { + super(context, prefKey); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); + } + + /** + * Initialization. + */ + public CellularSecurityEncryptionDividerController init() { + return this; + } + + @Override + public void displayPreference(@NonNull PreferenceScreen screen) { + super.displayPreference(screen); + } + + @Override + public int getAvailabilityStatus() { + if (mTelephonyManager == null) { + Log.w(LOG_TAG, + "Telephony manager not yet initialized. Marking availability as " + + "CONDITIONALLY_UNAVAILABLE"); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); + return CONDITIONALLY_UNAVAILABLE; + } + + try { + mTelephonyManager.isNullCipherAndIntegrityPreferenceEnabled(); + } catch (UnsupportedOperationException e) { + Log.i(LOG_TAG, "Null cipher enablement is unsupported, hiding divider: " + + e.getMessage()); + return UNSUPPORTED_ON_DEVICE; + } catch (Exception e) { + Log.e(LOG_TAG, + "Failed isNullCipherAndIntegrityEnabled. Setting availability to " + + "CONDITIONALLY_UNAVAILABLE. Exception: " + + e.getMessage()); + return CONDITIONALLY_UNAVAILABLE; + } + return AVAILABLE; + } +} diff --git a/src/com/android/settings/network/telephony/CellularSecurityNotificationsDividerController.java b/src/com/android/settings/network/telephony/CellularSecurityNotificationsDividerController.java new file mode 100644 index 00000000000..bbe954cf0ef --- /dev/null +++ b/src/com/android/settings/network/telephony/CellularSecurityNotificationsDividerController.java @@ -0,0 +1,115 @@ +/* + * 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.os.Build; +import android.safetycenter.SafetyCenterManager; +import android.telephony.TelephonyManager; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceScreen; + +import com.android.internal.telephony.flags.Flags; +import com.android.settings.core.BasePreferenceController; + +/** + * {@link BasePreferenceController} for visibility of Notifications divider on Cellular Security + * settings page. + */ +public class CellularSecurityNotificationsDividerController extends + BasePreferenceController { + + private static final String LOG_TAG = "CellularSecurityNotificationsDividerController"; + + private TelephonyManager mTelephonyManager; + @VisibleForTesting + protected SafetyCenterManager mSafetyCenterManager; + + /** + * Class constructor of "Cellular Security" preference. + * + * @param context of settings + * @param prefKey assigned within UI entry of XML file + */ + public CellularSecurityNotificationsDividerController( + @NonNull Context context, @NonNull String prefKey) { + super(context, prefKey); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); + mSafetyCenterManager = mContext.getSystemService(SafetyCenterManager.class); + } + + /** + * Initialization. + */ + public CellularSecurityNotificationsDividerController init() { + return this; + } + + @Override + public void displayPreference(@NonNull PreferenceScreen screen) { + super.displayPreference(screen); + } + + @Override + public int getAvailabilityStatus() { + if (!Flags.enableIdentifierDisclosureTransparencyUnsolEvents() + || !Flags.enableModemCipherTransparencyUnsolEvents() + || !Flags.enableIdentifierDisclosureTransparency() + || !Flags.enableModemCipherTransparency()) { + return UNSUPPORTED_ON_DEVICE; + } + if (!isSafetyCenterSupported()) { + return UNSUPPORTED_ON_DEVICE; + } + if (mTelephonyManager == null) { + Log.w(LOG_TAG, "Telephony manager not yet initialized"); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); + } + // Checking for hardware support, i.e. IRadio AIDL version must be >= 2.2 + try { + // Must call both APIs, as we can't use the combined toggle if both aren't available + areNotificationsEnabled(); + } catch (UnsupportedOperationException e) { + Log.i(LOG_TAG, "Cellular security notifications are unsupported, hiding divider: " + + e.getMessage()); + return UNSUPPORTED_ON_DEVICE; + } + + return AVAILABLE; + } + + @VisibleForTesting + protected boolean areNotificationsEnabled() { + return mTelephonyManager.isNullCipherNotificationsEnabled() + && mTelephonyManager.isCellularIdentifierDisclosureNotificationsEnabled(); + } + + protected boolean isSafetyCenterSupported() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + return false; + } + mSafetyCenterManager = mContext.getSystemService( + SafetyCenterManager.class); + if (mSafetyCenterManager == null) { + return false; + } + return mSafetyCenterManager.isSafetyCenterEnabled(); + } +} diff --git a/src/com/android/settings/network/telephony/CellularSecurityNotificationsPreferenceController.java b/src/com/android/settings/network/telephony/CellularSecurityNotificationsPreferenceController.java new file mode 100644 index 00000000000..520e7c5aa2d --- /dev/null +++ b/src/com/android/settings/network/telephony/CellularSecurityNotificationsPreferenceController.java @@ -0,0 +1,196 @@ +/* + * 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.os.Build; +import android.safetycenter.SafetyCenterManager; +import android.telephony.TelephonyManager; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceScreen; + +import com.android.internal.telephony.flags.Flags; + +/** + * {@link TelephonyTogglePreferenceController} for accessing Cellular Security settings through + * Safety Center. + */ +public class CellularSecurityNotificationsPreferenceController extends + TelephonyTogglePreferenceController { + + private static final String LOG_TAG = "CellularSecurityNotificationsPreferenceController"; + + private TelephonyManager mTelephonyManager; + @VisibleForTesting + protected SafetyCenterManager mSafetyCenterManager; + + /** + * Class constructor of "Cellular Security" preference. + * + * @param context of settings + * @param prefKey assigned within UI entry of XML file + */ + public CellularSecurityNotificationsPreferenceController( + @NonNull Context context, @NonNull String prefKey) { + super(context, prefKey); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); + mSafetyCenterManager = mContext.getSystemService(SafetyCenterManager.class); + } + + /** + * Initialization based on a given subscription id. + * + * @param subId is the subscription id + * @return this instance after initialization + */ + public CellularSecurityNotificationsPreferenceController init(@NonNull int subId) { + mTelephonyManager = mContext.getSystemService(TelephonyManager.class) + .createForSubscriptionId(subId); + return this; + } + + @Override + public void displayPreference(@NonNull PreferenceScreen screen) { + super.displayPreference(screen); + } + + @Override + public int getAvailabilityStatus(int subId) { + if (!isSafetyCenterSupported()) { + return UNSUPPORTED_ON_DEVICE; + } + + if (!areFlagsEnabled()) { + return UNSUPPORTED_ON_DEVICE; + } + if (mTelephonyManager == null) { + Log.w(LOG_TAG, "Telephony manager not yet initialized"); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); + } + + // Checking for hardware support, i.e. IRadio AIDL version must be >= 2.2 + try { + areNotificationsEnabled(); + } catch (UnsupportedOperationException e) { + Log.i(LOG_TAG, "Cellular security notifications are unsupported: " + e.getMessage()); + return UNSUPPORTED_ON_DEVICE; + } + + return AVAILABLE; + } + + /** + * Return {@code true} if cellular security notifications are on + * + *

NOTE: This method returns the active state of the preference controller and is not + * the parameter passed into {@link #setChecked(boolean)}, which is instead the requested future + * state. + */ + @Override + public boolean isChecked() { + if (!areFlagsEnabled()) { + return false; + } + + try { + // Note: the default behavior for this toggle is disabled (as the underlying + // TelephonyManager APIs are disabled by default) + return areNotificationsEnabled(); + } catch (Exception e) { + Log.e(LOG_TAG, + "Failed isNullCipherNotificationsEnabled and " + + "isCellularIdentifierDisclosureNotificationsEnabled." + + "Defaulting toggle to checked = true. Exception: " + + e.getMessage()); + return false; + } + } + + /** + * Called when a user preference changes on the toggle. We pass this info on to the Telephony + * Framework so that the modem can be updated with the user's preference. + * + *

See {@link com.android.settings.core.TogglePreferenceController#setChecked(boolean)} for + * details. + * + * @param isChecked The toggle value that we're being requested to enforce. A value of {@code + * true} denotes that both (1) null cipher/integrity notifications, and + * (2) IMSI disclosure notifications will be enabled by the modem after this + * function completes, if they are not already. + */ + @Override + public boolean setChecked(boolean isChecked) { + if (isChecked) { + Log.i(LOG_TAG, "Enabling cellular security notifications."); + } else { + Log.i(LOG_TAG, "Disabling cellular security notifications."); + } + + // Check flag status + if (!areFlagsEnabled()) { + return false; + } + + try { + setNotifications(isChecked); + } catch (Exception e) { + Log.e(LOG_TAG, + "Failed setCellularIdentifierDisclosureNotificationEnabled or " + + " setNullCipherNotificationsEnabled. Setting not updated. Exception: " + + e.getMessage()); + // Reset to defaults so we don't end up in an inconsistent state + setNotifications(!isChecked); + return false; + } + return true; + } + + private void setNotifications(boolean isChecked) { + mTelephonyManager.setEnableCellularIdentifierDisclosureNotifications(isChecked); + mTelephonyManager.setNullCipherNotificationsEnabled(isChecked); + } + + private boolean areNotificationsEnabled() { + return mTelephonyManager.isNullCipherNotificationsEnabled() + && mTelephonyManager.isCellularIdentifierDisclosureNotificationsEnabled(); + } + + private boolean areFlagsEnabled() { + if (!Flags.enableIdentifierDisclosureTransparencyUnsolEvents() + || !Flags.enableModemCipherTransparencyUnsolEvents() + || !Flags.enableIdentifierDisclosureTransparency() + || !Flags.enableModemCipherTransparency()) { + return false; + } + return true; + } + + protected boolean isSafetyCenterSupported() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + return false; + } + mSafetyCenterManager = mContext.getSystemService( + SafetyCenterManager.class); + if (mSafetyCenterManager == null) { + return false; + } + return mSafetyCenterManager.isSafetyCenterEnabled(); + } +} diff --git a/src/com/android/settings/network/telephony/CellularSecuritySettingsFragment.java b/src/com/android/settings/network/telephony/CellularSecuritySettingsFragment.java new file mode 100644 index 00000000000..3e37352867d --- /dev/null +++ b/src/com/android/settings/network/telephony/CellularSecuritySettingsFragment.java @@ -0,0 +1,59 @@ +/* + * 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.settings.SettingsEnums; +import android.os.Bundle; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +/** + * Cellular Security features (insecure network notifications, network security controls, etc) + */ +@SearchIndexable +public class CellularSecuritySettingsFragment extends DashboardFragment { + + private static final String TAG = "CellularSecuritySettingsFragment"; + + public static final String KEY_CELLULAR_SECURITY_PREFERENCE = "cellular_security"; + + @Override + public int getMetricsCategory() { + return SettingsEnums.CELLULAR_SECURITY_SETTINGS; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.cellular_security; + } + + @Override + public void onCreatePreferences(Bundle bundle, String rootKey) { + super.onCreatePreferences(bundle, rootKey); + setPreferencesFromResource(R.xml.cellular_security, rootKey); + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.cellular_security); +} diff --git a/tests/unit/src/com/android/settings/network/CellularSecurityPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/CellularSecurityPreferenceControllerTest.java new file mode 100644 index 00000000000..59e10c36ec7 --- /dev/null +++ b/tests/unit/src/com/android/settings/network/CellularSecurityPreferenceControllerTest.java @@ -0,0 +1,179 @@ +/* + * 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 com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.platform.test.flag.junit.SetFlagsRule; +import android.safetycenter.SafetyCenterManager; +import android.telephony.TelephonyManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.telephony.flags.Flags; + +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public final class CellularSecurityPreferenceControllerTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Mock + private TelephonyManager mTelephonyManager; + private Preference mPreference; + private PreferenceScreen mPreferenceScreen; + + private static final String PREF_KEY = "cellular_security_pref_controller_test"; + private Context mContext; + private CellularSecurityPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + // Tests must be skipped if these conditions aren't met as they cannot be mocked + Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU); + SafetyCenterManager mSafetyCenterManager = InstrumentationRegistry.getInstrumentation() + .getContext().getSystemService(SafetyCenterManager.class); + Assume.assumeTrue(mSafetyCenterManager != null); + Assume.assumeTrue(mSafetyCenterManager.isSafetyCenterEnabled()); + + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + + doNothing().when(mContext).startActivity(any(Intent.class)); + + mController = new CellularSecurityPreferenceController(mContext, PREF_KEY); + + mPreference = spy(new Preference(mContext)); + mPreference.setKey(PREF_KEY); + mPreferenceScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext); + mPreferenceScreen.addPreference(mPreference); + } + + @Test + public void getAvailabilityStatus_hardwareSupported_shouldReturnTrue() { + // Enable telephony API flags for testing + enableFlags(true); + + // Hardware support is enabled + doReturn(true).when(mTelephonyManager).isNullCipherNotificationsEnabled(); + doReturn(true).when(mTelephonyManager) + .isCellularIdentifierDisclosureNotificationsEnabled(); + doReturn(true).when(mTelephonyManager).isNullCipherAndIntegrityPreferenceEnabled(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + + // Disable null cipher toggle API, should still be available + doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager) + .isNullCipherAndIntegrityPreferenceEnabled(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + + // Enable null cipher toggle API, disable notifications API, should still be available + doReturn(true).when(mTelephonyManager).isNullCipherAndIntegrityPreferenceEnabled(); + doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager) + .isNullCipherNotificationsEnabled(); + doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager) + .isCellularIdentifierDisclosureNotificationsEnabled(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailabilityStatus_noHardwareSupport_shouldReturnFalse() { + // Enable telephony API flags for testing + enableFlags(true); + + // Hardware support is disabled + doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager) + .isNullCipherNotificationsEnabled(); + doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager) + .isCellularIdentifierDisclosureNotificationsEnabled(); + doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager) + .isNullCipherAndIntegrityPreferenceEnabled(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + @Test + public void getAvailabilityStatus_flagsDisabled_shouldReturnFalse() { + // Both flags disabled + enableFlags(false); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + + // One flag is disabled + mSetFlagsRule.disableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + @Test + public void handlePreferenceTreeClick_safetyCenterSupported_shouldRedirectToSafetyCenter() { + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + + boolean prefHandled = mController.handlePreferenceTreeClick(mPreference); + + assertThat(prefHandled).isTrue(); + verify(mContext).startActivity(intentCaptor.capture()); + assertThat(intentCaptor.getValue().getAction()).isEqualTo(Intent.ACTION_SAFETY_CENTER); + } + + private void enableFlags(boolean enabled) { + if (enabled) { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS); + mSetFlagsRule.enableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS); + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY); + mSetFlagsRule.enableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY); + } else { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS); + mSetFlagsRule.disableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS); + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY); + mSetFlagsRule.disableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY); + } + } +} diff --git a/tests/unit/src/com/android/settings/network/telephony/CellularSecurityEncryptionDividerControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/CellularSecurityEncryptionDividerControllerTest.java new file mode 100644 index 00000000000..f542209925f --- /dev/null +++ b/tests/unit/src/com/android/settings/network/telephony/CellularSecurityEncryptionDividerControllerTest.java @@ -0,0 +1,84 @@ +/* + * 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 static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.telephony.TelephonyManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class CellularSecurityEncryptionDividerControllerTest { + @Mock + private TelephonyManager mTelephonyManager; + @Mock + private Preference mPreference; + private PreferenceScreen mPreferenceScreen; + + private static final String PREF_KEY = "cellular_security_encryption_divider_test"; + private Context mContext; + private CellularSecurityEncryptionDividerController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + + mController = new CellularSecurityEncryptionDividerController(mContext, PREF_KEY); + + mPreference = spy(new Preference(mContext)); + mPreference.setKey(PREF_KEY); + mPreferenceScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext); + mPreferenceScreen.addPreference(mPreference); + } + + @Test + public void getAvailabilityStatus_hardwareSupported_shouldReturnAvailable() { + doReturn(true).when(mTelephonyManager).isNullCipherAndIntegrityPreferenceEnabled(); + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailabilityStatus_noHardwareSupport_shouldReturnUnsupported() { + // Hardware support is disabled + doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager) + .isNullCipherAndIntegrityPreferenceEnabled(); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } +} diff --git a/tests/unit/src/com/android/settings/network/telephony/CellularSecurityNotificationsDividerControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/CellularSecurityNotificationsDividerControllerTest.java new file mode 100644 index 00000000000..4e2351f1c7d --- /dev/null +++ b/tests/unit/src/com/android/settings/network/telephony/CellularSecurityNotificationsDividerControllerTest.java @@ -0,0 +1,143 @@ +/* + * 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 static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Build; +import android.platform.test.flag.junit.SetFlagsRule; +import android.safetycenter.SafetyCenterManager; +import android.telephony.TelephonyManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.telephony.flags.Flags; + +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class CellularSecurityNotificationsDividerControllerTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Mock + private TelephonyManager mTelephonyManager; + private Preference mPreference; + private PreferenceScreen mPreferenceScreen; + + private static final String PREF_KEY = "cellular_security_notifications_divider_test"; + private Context mContext; + private CellularSecurityNotificationsDividerController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + // Tests must be skipped if these conditions aren't met as they cannot be mocked + Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU); + SafetyCenterManager mSafetyCenterManager = InstrumentationRegistry.getInstrumentation() + .getContext().getSystemService(SafetyCenterManager.class); + Assume.assumeTrue(mSafetyCenterManager != null); + Assume.assumeTrue(mSafetyCenterManager.isSafetyCenterEnabled()); + + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + + mController = new CellularSecurityNotificationsDividerController(mContext, PREF_KEY); + + mPreference = spy(new Preference(mContext)); + mPreference.setKey(PREF_KEY); + mPreferenceScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext); + mPreferenceScreen.addPreference(mPreference); + } + + @Test + public void getAvailabilityStatus_hardwareSupported_shouldReturnTrue() { + // Enable telephony API flags for testing + enableFlags(true); + + // Hardware support is enabled + doReturn(true).when(mTelephonyManager).isNullCipherNotificationsEnabled(); + doReturn(true).when(mTelephonyManager) + .isCellularIdentifierDisclosureNotificationsEnabled(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailabilityStatus_noHardwareSupport_shouldReturnFalse() { + // Enable telephony API flags for testing + enableFlags(true); + + // Hardware support is disabled + doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager) + .isNullCipherNotificationsEnabled(); + doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager) + .isCellularIdentifierDisclosureNotificationsEnabled(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + @Test + public void getAvailabilityStatus_flagsDisabled_shouldReturnFalse() { + // Both flags disabled + enableFlags(false); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + + // One flag is disabled + mSetFlagsRule.disableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + private void enableFlags(boolean enabled) { + if (enabled) { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS); + mSetFlagsRule.enableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS); + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY); + mSetFlagsRule.enableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY); + } else { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS); + mSetFlagsRule.disableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS); + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY); + mSetFlagsRule.disableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY); + } + } +} diff --git a/tests/unit/src/com/android/settings/network/telephony/CellularSecurityNotificationsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/CellularSecurityNotificationsPreferenceControllerTest.java new file mode 100644 index 00000000000..8a72bd5fae3 --- /dev/null +++ b/tests/unit/src/com/android/settings/network/telephony/CellularSecurityNotificationsPreferenceControllerTest.java @@ -0,0 +1,247 @@ +/* + * 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 static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Build; +import android.platform.test.flag.junit.SetFlagsRule; +import android.safetycenter.SafetyCenterManager; +import android.telephony.TelephonyManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.telephony.flags.Flags; + +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class CellularSecurityNotificationsPreferenceControllerTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Mock + private TelephonyManager mTelephonyManager; + private Preference mPreference; + private PreferenceScreen mPreferenceScreen; + + private static final String PREF_KEY = "cellular_security_notifications_pref_controller_test"; + private Context mContext; + private CellularSecurityNotificationsPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + // Tests must be skipped if these conditions aren't met as they cannot be mocked + Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU); + SafetyCenterManager mSafetyCenterManager = InstrumentationRegistry.getInstrumentation() + .getContext().getSystemService(SafetyCenterManager.class); + Assume.assumeTrue(mSafetyCenterManager != null); + Assume.assumeTrue(mSafetyCenterManager.isSafetyCenterEnabled()); + + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + + mController = new CellularSecurityNotificationsPreferenceController(mContext, PREF_KEY); + + mPreference = spy(new Preference(mContext)); + mPreference.setKey(PREF_KEY); + mPreferenceScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext); + mPreferenceScreen.addPreference(mPreference); + } + + @Test + public void getAvailabilityStatus_hardwareSupported_shouldReturnTrue() { + // All flags enabled + enableFlags(true); + + // Hardware support is enabled + doReturn(true).when(mTelephonyManager).isNullCipherNotificationsEnabled(); + doReturn(true).when(mTelephonyManager) + .isCellularIdentifierDisclosureNotificationsEnabled(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailabilityStatus_noHardwareSupport_shouldReturnFalse() { + // All flags enabled + enableFlags(true); + + // Hardware support is disabled + doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager) + .isNullCipherNotificationsEnabled(); + doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager) + .isCellularIdentifierDisclosureNotificationsEnabled(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + @Test + public void getAvailabilityStatus_flagsDisabled_shouldReturnFalse() { + // All flags disabled + enableFlags(false); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + + // One flag is disabled + enableFlags(true); + mSetFlagsRule.disableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + @Test + public void setChecked_flagsDisabled_shouldReturnFalse() { + // Flags disabled + enableFlags(false); + + // Hardware support is enabled + doNothing().when(mTelephonyManager).setNullCipherNotificationsEnabled(true); + doNothing().when(mTelephonyManager) + .setEnableCellularIdentifierDisclosureNotifications(true); + assertThat(mController.setChecked(false)).isFalse(); + assertThat(mController.setChecked(true)).isFalse(); + + // Enable 1 flag, make sure it still fails + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS); + assertThat(mController.setChecked(false)).isFalse(); + assertThat(mController.setChecked(true)).isFalse(); + } + + @Test + public void setChecked_hardwareDisabled_shouldReturnFalse() { + // Flags disabled + enableFlags(false); + + // Hardware support is enabled + doNothing().when(mTelephonyManager).setNullCipherNotificationsEnabled(true); + doNothing().when(mTelephonyManager) + .setEnableCellularIdentifierDisclosureNotifications(true); + assertThat(mController.setChecked(true)).isFalse(); + + // Hardware support is enabled, called with false + doNothing().when(mTelephonyManager).setNullCipherNotificationsEnabled(false); + doNothing().when(mTelephonyManager) + .setEnableCellularIdentifierDisclosureNotifications(false); + assertThat(mController.setChecked(false)).isFalse(); + } + + @Test + public void setChecked_shouldReturnTrue() { + enableFlags(true); + + // Hardware support is enabled, enabling the feature + doNothing().when(mTelephonyManager).setNullCipherNotificationsEnabled(true); + doNothing().when(mTelephonyManager) + .setEnableCellularIdentifierDisclosureNotifications(true); + assertThat(mController.setChecked(true)).isTrue(); + + // Hardware support is enabled, disabling the feature + doNothing().when(mTelephonyManager).setNullCipherNotificationsEnabled(false); + doNothing().when(mTelephonyManager) + .setEnableCellularIdentifierDisclosureNotifications(false); + assertThat(mController.setChecked(false)).isTrue(); + } + + @Test + public void isChecked_shouldReturnTrue() { + // Hardware support is enabled + doReturn(true).when(mTelephonyManager).isNullCipherNotificationsEnabled(); + doReturn(true).when(mTelephonyManager) + .isCellularIdentifierDisclosureNotificationsEnabled(); + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void isChecked_flagsDisabled_shouldReturnFalse() { + enableFlags(false); + + // Hardware support is enabled + doReturn(true).when(mTelephonyManager).isNullCipherNotificationsEnabled(); + doReturn(true).when(mTelephonyManager) + .isCellularIdentifierDisclosureNotificationsEnabled(); + + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void isChecked_hardwareUnsupported_shouldReturnFalse() { + enableFlags(true); + + // Hardware support is disabled + doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager) + .isNullCipherNotificationsEnabled(); + doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager) + .isCellularIdentifierDisclosureNotificationsEnabled(); + + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void isChecked_notificationsDisabled_shouldReturnFalse() { + enableFlags(true); + + // Hardware support is enabled, but APIs are disabled + doReturn(false).when(mTelephonyManager).isNullCipherNotificationsEnabled(); + doReturn(false).when(mTelephonyManager) + .isCellularIdentifierDisclosureNotificationsEnabled(); + assertThat(mController.isChecked()).isFalse(); + + // Enable 1 API, should still return false + doReturn(true).when(mTelephonyManager).isNullCipherNotificationsEnabled(); + assertThat(mController.isChecked()).isFalse(); + } + + private void enableFlags(boolean enabled) { + if (enabled) { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS); + mSetFlagsRule.enableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS); + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY); + mSetFlagsRule.enableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY); + } else { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS); + mSetFlagsRule.disableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS); + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY); + mSetFlagsRule.disableFlags( + Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY); + } + } +} From 118abc5568357444deba392c1bb1d61ee0a185c8 Mon Sep 17 00:00:00 2001 From: Vincent Wang Date: Wed, 6 Mar 2024 09:11:34 +0000 Subject: [PATCH 09/13] Show calibtation result in FingerprintSettings & EnrollEnrolling if results are available Bug: b/326155807 Test: Enroll multiple fingerprints and check if calibration isn't triggered. Change-Id: I006db64f001fb70d2bb294a15a2d3efc77e2da25 Merged-In: I006db64f001fb70d2bb294a15a2d3efc77e2da25 --- .../FingerprintEnrollEnrolling.java | 21 ++++++++----------- .../FingerprintEnrollFindSensor.java | 4 ++-- .../FingerprintEnrollIntroduction.java | 2 +- .../fingerprint/FingerprintSettings.java | 16 ++++++++++++++ .../SetupFingerprintEnrollFindSensor.java | 2 +- .../SetupFingerprintEnrollIntroduction.java | 2 +- .../fingerprint/UdfpsEnrollCalibrator.kt | 7 ++++--- 7 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 90b93461b9c..bbeaf2afb85 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -71,7 +71,6 @@ import com.android.settings.biometrics.BiometricsEnrollEnrolling; import com.android.settings.biometrics.BiometricsSplitScreenDialog; import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; -import com.android.settings.flags.Flags; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.display.DisplayDensityUtils; @@ -252,12 +251,6 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { setContentView(layout); setDescriptionText(R.string.security_settings_udfps_enroll_start_message); - - if (Flags.udfpsEnrollCalibration()) { - mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider() - .getUdfpsEnrollCalibrator(getApplicationContext(), savedInstanceState, - getIntent()); - } } else if (mCanAssumeSfps) { mSfpsEnrollmentFeature = FeatureFactory.getFeatureFactory() .getFingerprintFeatureProvider().getSfpsEnrollmentFeature(); @@ -342,6 +335,15 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { final Configuration config = getApplicationContext().getResources().getConfiguration(); maybeHideSfpsText(config); + + if (!mIsSetupWizard) { + mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider() + .getUdfpsEnrollCalibrator(getApplicationContext(), null, getIntent()); + if (mCalibrator != null) { + mCalibrator.onWaitingPage(getLifecycle(), + getSupportFragmentManager(), null); + } + } } private void setHelpAnimation() { @@ -371,11 +373,6 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { super.onSaveInstanceState(outState); outState.putBoolean(KEY_STATE_CANCELED, mIsCanceled); outState.putInt(KEY_STATE_PREVIOUS_ROTATION, mPreviousRotation); - if (Flags.udfpsEnrollCalibration()) { - if (mCalibrator != null) { - mCalibrator.onSaveInstanceState(outState); - } - } } private void restoreSavedState(Bundle savedInstanceState) { diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java index 6b7538ae220..aeb0dac97c4 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java @@ -167,7 +167,7 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements .getUdfpsEnrollCalibrator(getApplicationContext(), savedInstanceState, getIntent()); if (mCalibrator != null) { - mCalibrator.onFindSensorPage( + mCalibrator.onWaitingPage( getLifecycle(), getSupportFragmentManager(), this::enableUdfpsLottieAndNextButton @@ -296,7 +296,7 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements getIntent().getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1)); if (Flags.udfpsEnrollCalibration()) { if (mCalibrator != null) { - ret.putExtras(mCalibrator.getExtrasForNextIntent(true)); + ret.putExtras(mCalibrator.getExtrasForNextIntent()); } } return ret; diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java index f92cfbf6696..59e4035497c 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java @@ -384,7 +384,7 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { } if (Flags.udfpsEnrollCalibration()) { if (mCalibrator != null) { - intent.putExtras(mCalibrator.getExtrasForNextIntent(false)); + intent.putExtras(mCalibrator.getExtrasForNextIntent()); } } intent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON, diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java index 2aacbe4bd88..9dac46dd1d1 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -88,6 +88,7 @@ import com.android.settingslib.transition.SettingsTransitionHelper; import com.android.settingslib.widget.FooterPreference; import com.android.settingslib.widget.TwoTargetPreference; +import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupdesign.util.DeviceHelper; import java.util.ArrayList; @@ -111,6 +112,9 @@ public class FingerprintSettings extends SubSettings { private static final int RESULT_SKIP = BiometricEnrollBase.RESULT_SKIP; private static final int RESULT_TIMEOUT = BiometricEnrollBase.RESULT_TIMEOUT; + @Nullable + private UdfpsEnrollCalibrator mCalibrator; + @Override public Intent getIntent() { Intent modIntent = new Intent(super.getIntent()); @@ -131,6 +135,13 @@ public class FingerprintSettings extends SubSettings { setTitle(msg); } + @Override + public void onResume() { + super.onResume(); + mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider() + .getUdfpsEnrollCalibrator(getApplicationContext(), null, null); + } + /** * @param context * @return true if the Fingerprint hardware is detected. @@ -800,6 +811,11 @@ public class FingerprintSettings extends SubSettings { } intent.putExtra(Intent.EXTRA_USER_ID, mUserId); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); + if (((FingerprintSettings) getActivity()).mCalibrator != null) { + intent.putExtras( + (((FingerprintSettings) getActivity()).mCalibrator) + .getExtrasForNextIntent()); + } startActivityForResult(intent, ADD_FINGERPRINT_REQUEST); } else if (pref instanceof FingerprintPreference) { FingerprintPreference fpref = (FingerprintPreference) pref; diff --git a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollFindSensor.java index 6590530cecf..3037c2a61eb 100644 --- a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollFindSensor.java +++ b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollFindSensor.java @@ -51,7 +51,7 @@ public class SetupFingerprintEnrollFindSensor extends FingerprintEnrollFindSenso SetupWizardUtils.copySetupExtras(getIntent(), intent); if (Flags.udfpsEnrollCalibration()) { if (mCalibrator != null) { - intent.putExtras(mCalibrator.getExtrasForNextIntent(true)); + intent.putExtras(mCalibrator.getExtrasForNextIntent()); } } return intent; diff --git a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java index 0ee9ad380b5..c263b2e4e2d 100644 --- a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java @@ -49,7 +49,7 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu SetupWizardUtils.copySetupExtras(getIntent(), intent); if (Flags.udfpsEnrollCalibration()) { if (mCalibrator != null) { - intent.putExtras(mCalibrator.getExtrasForNextIntent(false)); + intent.putExtras(mCalibrator.getExtrasForNextIntent()); } } return intent; diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt index c54c6b5eba5..22a69baacb0 100644 --- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt +++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt @@ -6,13 +6,14 @@ import androidx.lifecycle.Lifecycle interface UdfpsEnrollCalibrator { - fun getExtrasForNextIntent(isEnrolling: Boolean): Bundle + fun getExtrasForNextIntent(): Bundle fun onSaveInstanceState(outState: Bundle) - fun onFindSensorPage( + fun onWaitingPage( lifecycle: Lifecycle, fragmentManager: FragmentManager, - enableEnrollingRunnable: Runnable + enableEnrollingRunnable: Runnable? ) + } \ No newline at end of file From a3507d535fb2eea7ebe4b5c85daf771ee4a84357 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Tue, 19 Mar 2024 13:56:32 +0800 Subject: [PATCH 10/13] Fix APN network type not updated The issue is caused by SettingsDropdownCheckBox's interface is updated, but ApnNetworkTypeCheckBox not updated. Update ApnNetworkTypeCheckBox to fix. Bug: 329547692 Test: manual - update network type Test: unit test Change-Id: I4a5a1ad7d9d97960799f5ba0d89b5178ce410b6e --- .../network/apn/ApnNetworkTypeCheckBox.kt | 9 +-- .../settings/network/apn/ApnNetworkTypes.kt | 33 ++++------- .../network/apn/ApnNetworkTypesTest.kt | 58 +++++++++++++++++++ 3 files changed, 71 insertions(+), 29 deletions(-) create mode 100644 tests/spa_unit/src/com/android/settings/network/apn/ApnNetworkTypesTest.kt diff --git a/src/com/android/settings/network/apn/ApnNetworkTypeCheckBox.kt b/src/com/android/settings/network/apn/ApnNetworkTypeCheckBox.kt index bc85f5582b7..a42031b388a 100644 --- a/src/com/android/settings/network/apn/ApnNetworkTypeCheckBox.kt +++ b/src/com/android/settings/network/apn/ApnNetworkTypeCheckBox.kt @@ -24,18 +24,13 @@ import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckBox @Composable fun ApnNetworkTypeCheckBox(apnData: ApnData, onNetworkTypeChanged: (Long) -> Unit) { - val options = remember { ApnNetworkTypes.getNetworkTypeOptions() } - val selectedStateMap = remember { - ApnNetworkTypes.networkTypeToSelectedStateMap(options, apnData.networkType) - } + val options = remember { ApnNetworkTypes.getNetworkTypeOptions(apnData.networkType) } SettingsDropdownCheckBox( label = stringResource(R.string.network_type), options = options, emptyText = stringResource(R.string.network_type_unspecified), enabled = apnData.networkTypeEnabled, ) { - onNetworkTypeChanged( - ApnNetworkTypes.selectedStateMapToNetworkType(options, selectedStateMap) - ) + onNetworkTypeChanged(ApnNetworkTypes.optionsToNetworkType(options)) } } diff --git a/src/com/android/settings/network/apn/ApnNetworkTypes.kt b/src/com/android/settings/network/apn/ApnNetworkTypes.kt index e7a93b3cb79..f14b0b34029 100644 --- a/src/com/android/settings/network/apn/ApnNetworkTypes.kt +++ b/src/com/android/settings/network/apn/ApnNetworkTypes.kt @@ -17,8 +17,7 @@ package com.android.settings.network.apn import android.telephony.TelephonyManager -import androidx.compose.runtime.mutableStateMapOf -import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.runtime.mutableStateOf import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckOption object ApnNetworkTypes { @@ -40,38 +39,28 @@ object ApnNetworkTypes { TelephonyManager.NETWORK_TYPE_NR, ) - fun getNetworkTypeOptions(): List = - Types.map { SettingsDropdownCheckOption(TelephonyManager.getNetworkTypeName(it)) } - /** * Gets the selected Network type Selected Options according to network type. * @param networkType Initialized network type bitmask, often multiple network type options may * be included. */ - fun networkTypeToSelectedStateMap( - options: List, - networkType: Long, - ): SnapshotStateMap { - val stateMap = mutableStateMapOf() - Types.forEachIndexed { index, type -> - if (networkType and TelephonyManager.getBitMaskForNetworkType(type) != 0L) { - stateMap[options[index]] = true - } + fun getNetworkTypeOptions(networkType: Long): List = + Types.map { type -> + val selected = networkType and TelephonyManager.getBitMaskForNetworkType(type) != 0L + SettingsDropdownCheckOption( + text = TelephonyManager.getNetworkTypeName(type), + selected = mutableStateOf(selected), + ) } - return stateMap - } /** * Gets the network type according to the selected Network type Selected Options. - * @param stateMap the selected Network type Selected Options. + * @param options the selected Network type Selected Options. */ - fun selectedStateMapToNetworkType( - options: List, - stateMap: SnapshotStateMap, - ): Long { + fun optionsToNetworkType(options: List): Long { var networkType = 0L options.forEachIndexed { index, option -> - if (stateMap[option] == true) { + if (option.selected.value) { networkType = networkType or TelephonyManager.getBitMaskForNetworkType(Types[index]) } } diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnNetworkTypesTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnNetworkTypesTest.kt new file mode 100644 index 00000000000..f8aed591327 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnNetworkTypesTest.kt @@ -0,0 +1,58 @@ +/* + * 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.apn + +import android.telephony.TelephonyManager.NETWORK_TYPE_BITMASK_CDMA +import android.telephony.TelephonyManager.NETWORK_TYPE_BITMASK_EDGE +import android.telephony.TelephonyManager.NETWORK_TYPE_BITMASK_HSPAP +import android.telephony.TelephonyManager.NETWORK_TYPE_BITMASK_HSUPA +import androidx.compose.runtime.mutableStateOf +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckOption +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ApnNetworkTypesTest { + + @Test + fun getNetworkTypeOptions() { + val networkTypeOptions = + ApnNetworkTypes.getNetworkTypeOptions( + NETWORK_TYPE_BITMASK_EDGE xor NETWORK_TYPE_BITMASK_CDMA + ) + + assertThat(networkTypeOptions.single { it.text == "EDGE" }.selected.value).isTrue() + assertThat(networkTypeOptions.single { it.text == "CDMA" }.selected.value).isTrue() + assertThat(networkTypeOptions.single { it.text == "GPRS" }.selected.value).isFalse() + } + + @Test + fun optionsToNetworkType() { + val options = listOf( + SettingsDropdownCheckOption(text = "", selected = mutableStateOf(false)), + SettingsDropdownCheckOption(text = "", selected = mutableStateOf(true)), + SettingsDropdownCheckOption(text = "", selected = mutableStateOf(false)), + SettingsDropdownCheckOption(text = "", selected = mutableStateOf(true)), + ) + + val networkType = ApnNetworkTypes.optionsToNetworkType(options) + + assertThat(networkType).isEqualTo(NETWORK_TYPE_BITMASK_HSPAP xor NETWORK_TYPE_BITMASK_HSUPA) + } +} From bfd8a517be2298d2c4a83fc159ee23d99094a30b Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 18 Mar 2024 17:47:51 +0800 Subject: [PATCH 11/13] Refactor AutomaticDataSwitchingPreference Split into AutomaticDataSwitchingPreference.kt, and use isMobileDataPolicyEnabledFlow. Bug: 329061940 Test: manual - Set Automatic data switching Test: unit test Change-Id: I878ed70328307c0a5dba6dfb461ff5a85efbcf88 --- .../settings/network/SimOnboardingService.kt | 19 ++--- .../network/telephony/TelephonyRepository.kt | 46 ++++++++++- .../AutomaticDataSwitchingPreference.kt | 58 ++++++++++++++ .../network/NetworkCellularGroupProvider.kt | 80 ++++--------------- .../spa/network/SimOnboardingPrimarySim.kt | 14 ++-- .../telephony/TelephonyRepositoryTest.kt | 43 ++++++++++ 6 files changed, 173 insertions(+), 87 deletions(-) create mode 100644 src/com/android/settings/spa/network/AutomaticDataSwitchingPreference.kt diff --git a/src/com/android/settings/network/SimOnboardingService.kt b/src/com/android/settings/network/SimOnboardingService.kt index 2ec1ad3ae43..f99a2b982d4 100644 --- a/src/com/android/settings/network/SimOnboardingService.kt +++ b/src/com/android/settings/network/SimOnboardingService.kt @@ -24,6 +24,7 @@ import android.telephony.UiccCardInfo import android.telephony.UiccSlotInfo import android.util.Log import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType +import com.android.settings.network.telephony.TelephonyRepository import com.android.settings.sim.SimActivationNotifier import com.android.settings.spa.network.setAutomaticData import com.android.settings.spa.network.setDefaultData @@ -31,6 +32,7 @@ import com.android.settings.spa.network.setDefaultSms import com.android.settings.spa.network.setDefaultVoice import com.android.settingslib.utils.ThreadUtils import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.withContext class SimOnboardingService { @@ -46,7 +48,7 @@ class SimOnboardingService { var targetPrimarySimCalls: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID var targetPrimarySimTexts: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID var targetPrimarySimMobileData: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID - var targetPrimarySimAutoDataSwitch: Boolean = false + val targetPrimarySimAutoDataSwitch = MutableStateFlow(false) var targetNonDds: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID get() { if(targetPrimarySimMobileData == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { @@ -349,19 +351,10 @@ class SimOnboardingService { null, targetPrimarySimMobileData ) - - var nonDds = targetNonDds - Log.d( - TAG, - "setAutomaticData: targetNonDds: $nonDds," + - " targetPrimarySimAutoDataSwitch: $targetPrimarySimAutoDataSwitch" + TelephonyRepository(context).setAutomaticData( + targetNonDds, + targetPrimarySimAutoDataSwitch.value ) - if (nonDds != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - val telephonyManagerForNonDds: TelephonyManager? = - context.getSystemService(TelephonyManager::class.java) - ?.createForSubscriptionId(nonDds) - setAutomaticData(telephonyManagerForNonDds, targetPrimarySimAutoDataSwitch) - } } // no next action, send finish callback(CallbackType.CALLBACK_FINISH) diff --git a/src/com/android/settings/network/telephony/TelephonyRepository.kt b/src/com/android/settings/network/telephony/TelephonyRepository.kt index 678aaac0ee4..18af621564e 100644 --- a/src/com/android/settings/network/telephony/TelephonyRepository.kt +++ b/src/com/android/settings/network/telephony/TelephonyRepository.kt @@ -17,8 +17,10 @@ package com.android.settings.network.telephony import android.content.Context +import android.telephony.SubscriptionManager import android.telephony.TelephonyCallback import android.telephony.TelephonyManager +import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.ProducerScope @@ -26,15 +28,51 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map + +class TelephonyRepository( + private val context: Context, + private val subscriptionsChangedFlow: Flow = context.subscriptionsChangedFlow(), +) { + fun isMobileDataPolicyEnabledFlow( + subId: Int, + @TelephonyManager.MobileDataPolicy policy: Int, + ): Flow { + if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false) + + val telephonyManager = context.telephonyManager(subId) + + return subscriptionsChangedFlow.map { + telephonyManager.isMobileDataPolicyEnabled(policy) + .also { Log.d(TAG, "[$subId] isMobileDataPolicyEnabled($policy): $it") } + }.conflate().flowOn(Dispatchers.Default) + } + + fun setMobileDataPolicyEnabled( + subId: Int, + @TelephonyManager.MobileDataPolicy policy: Int, + enabled: Boolean, + ) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) return + + val telephonyManager = context.telephonyManager(subId) + Log.d(TAG, "[$subId] setMobileDataPolicyEnabled($policy): $enabled") + telephonyManager.setMobileDataPolicyEnabled(policy, enabled) + } + + private companion object { + private const val TAG = "TelephonyRepository" + } +} /** Creates an instance of a cold Flow for Telephony callback of given [subId]. */ fun Context.telephonyCallbackFlow( subId: Int, block: ProducerScope.() -> TelephonyCallback, ): Flow = callbackFlow { - val telephonyManager = getSystemService(TelephonyManager::class.java)!! - .createForSubscriptionId(subId) + val telephonyManager = telephonyManager(subId) val callback = block() @@ -42,3 +80,7 @@ fun Context.telephonyCallbackFlow( awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } }.conflate().flowOn(Dispatchers.Default) + +fun Context.telephonyManager(subId: Int): TelephonyManager = + getSystemService(TelephonyManager::class.java)!! + .createForSubscriptionId(subId) diff --git a/src/com/android/settings/spa/network/AutomaticDataSwitchingPreference.kt b/src/com/android/settings/spa/network/AutomaticDataSwitchingPreference.kt new file mode 100644 index 00000000000..824a93532b2 --- /dev/null +++ b/src/com/android/settings/spa/network/AutomaticDataSwitchingPreference.kt @@ -0,0 +1,58 @@ +/* + * 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.telephony.TelephonyManager +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.res.stringResource +import com.android.settings.R +import com.android.settings.network.telephony.TelephonyRepository +import com.android.settingslib.spa.widget.preference.SwitchPreference +import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@Composable +fun AutomaticDataSwitchingPreference( + isAutoDataEnabled: () -> Boolean?, + setAutoDataEnabled: (newEnabled: Boolean) -> Unit, +) { + val autoDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg) + val coroutineScope = rememberCoroutineScope() + SwitchPreference( + object : SwitchPreferenceModel { + override val title = stringResource(id = R.string.primary_sim_automatic_data_title) + override val summary = { autoDataSummary } + override val checked = { isAutoDataEnabled() } + override val onCheckedChange: (Boolean) -> Unit = { newEnabled -> + coroutineScope.launch(Dispatchers.Default) { + setAutoDataEnabled(newEnabled) + } + } + } + ) +} + +fun TelephonyRepository.setAutomaticData(subId: Int, newEnabled: Boolean) { + setMobileDataPolicyEnabled( + subId = subId, + policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, + enabled = newEnabled, + ) + //TODO: setup backup calling +} diff --git a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt index 5a2a3947621..bc5a4b7fb88 100644 --- a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt +++ b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt @@ -30,7 +30,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableIntState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable @@ -44,6 +43,7 @@ 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.network.telephony.TelephonyRepository import com.android.settings.spa.network.PrimarySimRepository.PrimarySimInfo import com.android.settings.wifi.WifiPickerTrackerHelper import com.android.settingslib.spa.framework.common.SettingsEntryBuilder @@ -53,8 +53,6 @@ import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel -import com.android.settingslib.spa.widget.preference.SwitchPreference -import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spa.widget.scaffold.RegularScaffold import com.android.settingslib.spa.widget.ui.Category import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow @@ -193,7 +191,6 @@ fun PrimarySimImpl( callsSelectedId: MutableIntState, textsSelectedId: MutableIntState, mobileDataSelectedId: MutableIntState, - nonDds: MutableIntState, subscriptionManager: SubscriptionManager? = LocalContext.current.getSystemService(SubscriptionManager::class.java), coroutineScope: CoroutineScope = rememberCoroutineScope(), @@ -223,23 +220,9 @@ fun PrimarySimImpl( ) } }, - actionSetAutoDataSwitch: (Boolean) -> Unit = { newState -> - coroutineScope.launch { - val telephonyManagerForNonDds: TelephonyManager? = - context.getSystemService(TelephonyManager::class.java) - ?.createForSubscriptionId(nonDds.intValue) - Log.d(NetworkCellularGroupProvider.name, "NonDds:${nonDds.intValue} setAutomaticData") - setAutomaticData(telephonyManagerForNonDds, newState) - } - }, + isAutoDataEnabled: () -> Boolean?, + setAutoDataEnabled: (newEnabled: Boolean) -> Unit, ) { - 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), primarySimInfo.callsAndSmsList, @@ -262,31 +245,7 @@ fun PrimarySimImpl( 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}" - ) - } - } - automaticDataChecked.value - } - override val onCheckedChange: ((Boolean) -> Unit)? = { - automaticDataChecked.value = it - actionSetAutoDataSwitch(it) - } - } - ) + AutomaticDataSwitchingPreference(isAutoDataEnabled, setAutoDataEnabled) } @Composable @@ -308,12 +267,21 @@ fun PrimarySimSectionImpl( }.collectAsStateWithLifecycle(initialValue = null).value ?: return Category(title = stringResource(id = R.string.primary_sim_title)) { + val isAutoDataEnabled by remember(nonDds.intValue) { + TelephonyRepository(context).isMobileDataPolicyEnabledFlow( + subId = nonDds.intValue, + policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH + ) + }.collectAsStateWithLifecycle(initialValue = null) PrimarySimImpl( primarySimInfo, callsSelectedId, textsSelectedId, mobileDataSelectedId, - nonDds + isAutoDataEnabled = { isAutoDataEnabled }, + setAutoDataEnabled = { newEnabled -> + TelephonyRepository(context).setAutomaticData(nonDds.intValue, newEnabled) + }, ) } } @@ -381,23 +349,3 @@ suspend fun setDefaultData( wifiPickerTrackerHelper.setCarrierNetworkEnabled(true) } } - -suspend fun getAutomaticData(telephonyManagerForNonDds: TelephonyManager?): Boolean = - withContext(Dispatchers.Default) { - telephonyManagerForNonDds != null - && telephonyManagerForNonDds.isMobileDataPolicyEnabled( - TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH) - } - -suspend fun setAutomaticData(telephonyManager: TelephonyManager?, newState: Boolean): Unit = - withContext(Dispatchers.Default) { - Log.d( - NetworkCellularGroupProvider.name, - "setAutomaticData: MOBILE_DATA_POLICY_AUTO_DATA_SWITCH as $newState" - ) - telephonyManager?.setMobileDataPolicyEnabled( - TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, - newState - ) - //TODO: setup backup calling - } \ No newline at end of file diff --git a/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt b/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt index a8c0575e3e8..4fad3325b11 100644 --- a/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt +++ b/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt @@ -23,6 +23,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.SignalCellularAlt import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableIntState +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable @@ -75,9 +76,6 @@ fun SimOnboardingPrimarySimImpl( val mobileDataSelectedId = rememberSaveable { mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID) } - val nonDdsRemember = rememberSaveable { - mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID) - } Column(Modifier.padding(SettingsDimension.itemPadding)) { SettingsBody(stringResource(id = R.string.sim_onboarding_primary_sim_msg)) @@ -94,12 +92,14 @@ fun SimOnboardingPrimarySimImpl( callsSelectedId.intValue = onboardingService.targetPrimarySimCalls textsSelectedId.intValue = onboardingService.targetPrimarySimTexts mobileDataSelectedId.intValue = onboardingService.targetPrimarySimMobileData + val isAutoDataEnabled by + onboardingService.targetPrimarySimAutoDataSwitch + .collectAsStateWithLifecycle(initialValue = null) PrimarySimImpl( primarySimInfo = primarySimInfo, callsSelectedId = callsSelectedId, textsSelectedId = textsSelectedId, mobileDataSelectedId = mobileDataSelectedId, - nonDds = nonDdsRemember, actionSetCalls = { callsSelectedId.intValue = it onboardingService.targetPrimarySimCalls = it}, @@ -109,8 +109,10 @@ fun SimOnboardingPrimarySimImpl( actionSetMobileData = { mobileDataSelectedId.intValue = it onboardingService.targetPrimarySimMobileData = it}, - actionSetAutoDataSwitch = { - onboardingService.targetPrimarySimAutoDataSwitch = it}, + isAutoDataEnabled = { isAutoDataEnabled }, + setAutoDataEnabled = { newEnabled -> + onboardingService.targetPrimarySimAutoDataSwitch.value = newEnabled + }, ) } } diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt index b7e1dcc37b5..ce27ed4c4f8 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt @@ -17,12 +17,14 @@ package com.android.settings.network.telephony import android.content.Context +import android.telephony.SubscriptionManager import android.telephony.TelephonyCallback import android.telephony.TelephonyManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith @@ -31,6 +33,7 @@ import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.spy +import org.mockito.kotlin.stub import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) @@ -48,6 +51,46 @@ class TelephonyRepositoryTest { on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager } + private val repository = TelephonyRepository(context, flowOf(Unit)) + + @Test + fun isMobileDataPolicyEnabledFlow_invalidSub_returnFalse() = runBlocking { + val flow = repository.isMobileDataPolicyEnabledFlow( + subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, + ) + + assertThat(flow.firstWithTimeoutOrNull()).isFalse() + } + + @Test + fun isMobileDataPolicyEnabledFlow_validSub_returnPolicyState() = runBlocking { + mockTelephonyManager.stub { + on { + isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH) + } doReturn true + } + + val flow = repository.isMobileDataPolicyEnabledFlow( + subId = SUB_ID, + policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, + ) + + assertThat(flow.firstWithTimeoutOrNull()).isTrue() + } + + @Test + fun setMobileDataPolicyEnabled() = runBlocking { + repository.setMobileDataPolicyEnabled( + subId = SUB_ID, + policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, + enabled = true + ) + + verify(mockTelephonyManager) + .setMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true) + } + @Test fun telephonyCallbackFlow_callbackRegistered() = runBlocking { val flow = context.telephonyCallbackFlow(SUB_ID) { From b2d6c06c5c79bd09ba5491bb338e04129b6d7f33 Mon Sep 17 00:00:00 2001 From: Haijie Hong Date: Wed, 13 Mar 2024 14:15:01 +0800 Subject: [PATCH 12/13] Add event for Spatial Audio toggle in Settings Bug: 322279959 Test: atest BluetoothDetailsSpatialAudioControllerTest Change-Id: Id0a3e4c0bcdce80c25326c83db7913a51462ef58 --- ...luetoothDetailsSpatialAudioController.java | 24 +++ ...oothDetailsSpatialAudioControllerTest.java | 140 ++++++++++++++---- 2 files changed, 136 insertions(+), 28 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java index e5fb365e199..30e86fe9b2f 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java @@ -18,6 +18,7 @@ package com.android.settings.bluetooth; import static android.media.Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; +import android.app.settings.SettingsEnums; import android.content.Context; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; @@ -60,6 +61,7 @@ public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsCont AudioDeviceAttributes mAudioDevice = null; AtomicBoolean mHasHeadTracker = new AtomicBoolean(false); + AtomicBoolean mInitialRefresh = new AtomicBoolean(true); public BluetoothDetailsSpatialAudioController( Context context, @@ -81,6 +83,10 @@ public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsCont TwoStatePreference switchPreference = (TwoStatePreference) preference; String key = switchPreference.getKey(); if (TextUtils.equals(key, KEY_SPATIAL_AUDIO)) { + mMetricsFeatureProvider.action( + mContext, + SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_SPATIAL_AUDIO_TOGGLE_CLICKED, + switchPreference.isChecked()); updateSpatializerEnabled(switchPreference.isChecked()); ThreadUtils.postOnBackgroundThread( () -> { @@ -91,6 +97,10 @@ public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsCont }); return true; } else if (TextUtils.equals(key, KEY_HEAD_TRACKING)) { + mMetricsFeatureProvider.action( + mContext, + SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_HEAD_TRACKING_TOGGLE_CLICKED, + switchPreference.isChecked()); updateSpatializerHeadTracking(switchPreference.isChecked()); return true; } else { @@ -186,6 +196,20 @@ public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsCont if (isHeadTrackingAvailable) { headTrackingPref.setChecked(mSpatializer.isHeadTrackerEnabled(mAudioDevice)); } + + if (mInitialRefresh.compareAndSet(true, false)) { + // Only triggered when shown for the first time + mMetricsFeatureProvider.action( + mContext, + SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_SPATIAL_AUDIO_TRIGGERED, + spatialAudioPref.isChecked()); + if (mHasHeadTracker.get()) { + mMetricsFeatureProvider.action( + mContext, + SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_HEAD_TRACKING_TRIGGERED, + headTrackingPref.isChecked()); + } + } } @VisibleForTesting diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java index 2cc55a70098..d9a917b0cdd 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothDevice; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; @@ -34,8 +35,11 @@ import android.media.Spatializer; import androidx.preference.PreferenceCategory; import androidx.preference.TwoStatePreference; +import com.android.settings.testutils.FakeFeatureFactory; import com.android.settingslib.core.lifecycle.Lifecycle; +import com.google.common.collect.ImmutableList; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -55,36 +59,37 @@ public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetails private static final String KEY_SPATIAL_AUDIO = "spatial_audio"; private static final String KEY_HEAD_TRACKING = "head_tracking"; - @Mock - private AudioManager mAudioManager; - @Mock - private Spatializer mSpatializer; - @Mock - private Lifecycle mSpatialAudioLifecycle; - @Mock - private PreferenceCategory mProfilesContainer; - @Mock - private BluetoothDevice mBluetoothDevice; + @Mock private AudioManager mAudioManager; + @Mock private Spatializer mSpatializer; + @Mock private Lifecycle mSpatialAudioLifecycle; + @Mock private PreferenceCategory mProfilesContainer; + @Mock private BluetoothDevice mBluetoothDevice; private AudioDeviceAttributes mAvailableDevice; private BluetoothDetailsSpatialAudioController mController; private TwoStatePreference mSpatialAudioPref; private TwoStatePreference mHeadTrackingPref; + private FakeFeatureFactory mFeatureFactory; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager); when(mAudioManager.getSpatializer()).thenReturn(mSpatializer); when(mCachedDevice.getAddress()).thenReturn(MAC_ADDRESS); when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice); when(mBluetoothDevice.getAnonymizedAddress()).thenReturn(MAC_ADDRESS); + when(mFeatureFactory.getBluetoothFeatureProvider().getSpatializer(mContext)) + .thenReturn(mSpatializer); - mController = new BluetoothDetailsSpatialAudioController(mContext, mFragment, - mCachedDevice, mSpatialAudioLifecycle); + mController = + new BluetoothDetailsSpatialAudioController( + mContext, mFragment, mCachedDevice, mSpatialAudioLifecycle); mController.mProfilesContainer = mProfilesContainer; mSpatialAudioPref = mController.createSpatialAudioPreference(mContext); @@ -93,10 +98,11 @@ public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetails when(mProfilesContainer.findPreference(KEY_SPATIAL_AUDIO)).thenReturn(mSpatialAudioPref); when(mProfilesContainer.findPreference(KEY_HEAD_TRACKING)).thenReturn(mHeadTrackingPref); - mAvailableDevice = new AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, - MAC_ADDRESS); + mAvailableDevice = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + MAC_ADDRESS); } @Test @@ -107,8 +113,8 @@ public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetails @Test public void isAvailable_forSpatializerWithLevelNotNone_returnsTrue() { - when(mSpatializer.getImmersiveAudioLevel()).thenReturn( - SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL); + when(mSpatializer.getImmersiveAudioLevel()) + .thenReturn(SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL); assertThat(mController.isAvailable()).isTrue(); } @@ -151,29 +157,77 @@ public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetails } @Test - public void - refresh_spatialAudioOnAndHeadTrackingIsNotAvailable_hidesHeadTrackingPreference() { - List compatibleAudioDevices = new ArrayList<>(); + public void refresh_spatialAudioOnHeadTrackingOff_recordMetrics() { mController.setAvailableDevice(mAvailableDevice); - compatibleAudioDevices.add(mController.mAudioDevice); - when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices); - when(mSpatializer.hasHeadTracker(mController.mAudioDevice)).thenReturn(false); + when(mSpatializer.isAvailableForDevice(mAvailableDevice)).thenReturn(true); + when(mSpatializer.getCompatibleAudioDevices()) + .thenReturn(ImmutableList.of(mAvailableDevice)); + when(mSpatializer.hasHeadTracker(mAvailableDevice)).thenReturn(true); + when(mSpatializer.isHeadTrackerEnabled(mController.mAudioDevice)).thenReturn(false); mController.refresh(); ShadowLooper.idleMainLooper(); - verify(mProfilesContainer).removePreference(mHeadTrackingPref); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_SPATIAL_AUDIO_TRIGGERED, + true); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_HEAD_TRACKING_TRIGGERED, + false); + } + + @Test + public void refresh_spatialAudioOff_recordMetrics() { + mController.setAvailableDevice(mAvailableDevice); + when(mSpatializer.isAvailableForDevice(mAvailableDevice)).thenReturn(true); + when(mSpatializer.getCompatibleAudioDevices()).thenReturn(ImmutableList.of()); + when(mSpatializer.hasHeadTracker(mAvailableDevice)).thenReturn(true); + when(mSpatializer.isHeadTrackerEnabled(mController.mAudioDevice)).thenReturn(false); + + mController.refresh(); + ShadowLooper.idleMainLooper(); + + verify(mFeatureFactory.metricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_SPATIAL_AUDIO_TRIGGERED, + false); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_HEAD_TRACKING_TRIGGERED, + false); + } + + @Test + public void refresh_spatialAudioOnAndHeadTrackingIsNotAvailable_hidesHeadTrackingPreference() { + mController.setAvailableDevice(mAvailableDevice); + when(mSpatializer.isAvailableForDevice(mAvailableDevice)).thenReturn(true); + when(mSpatializer.getCompatibleAudioDevices()) + .thenReturn(ImmutableList.of(mAvailableDevice)); + when(mSpatializer.hasHeadTracker(mAvailableDevice)).thenReturn(false); + + mController.refresh(); + ShadowLooper.idleMainLooper(); + + assertThat(mHeadTrackingPref.isVisible()).isFalse(); } @Test public void refresh_spatialAudioOff_hidesHeadTrackingPreference() { - List compatibleAudioDevices = new ArrayList<>(); - when(mSpatializer.getCompatibleAudioDevices()).thenReturn(compatibleAudioDevices); + mController.setAvailableDevice(mAvailableDevice); + when(mSpatializer.isAvailableForDevice(mAvailableDevice)).thenReturn(true); + when(mSpatializer.getCompatibleAudioDevices()).thenReturn(ImmutableList.of()); + when(mSpatializer.hasHeadTracker(mAvailableDevice)).thenReturn(true); mController.refresh(); ShadowLooper.idleMainLooper(); - verify(mProfilesContainer).removePreference(mHeadTrackingPref); + assertThat(mHeadTrackingPref.isVisible()).isFalse(); } @Test @@ -190,6 +244,11 @@ public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetails ShadowLooper.idleMainLooper(); assertThat(mHeadTrackingPref.isChecked()).isTrue(); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_HEAD_TRACKING_TRIGGERED, + true); } @Test @@ -206,6 +265,11 @@ public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetails ShadowLooper.idleMainLooper(); assertThat(mHeadTrackingPref.isChecked()).isFalse(); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_HEAD_TRACKING_TRIGGERED, + false); } @Test @@ -214,6 +278,11 @@ public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetails mSpatialAudioPref.setChecked(true); mController.onPreferenceClick(mSpatialAudioPref); verify(mSpatializer).addCompatibleAudioDevice(mController.mAudioDevice); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_SPATIAL_AUDIO_TOGGLE_CLICKED, + true); } @Test @@ -222,6 +291,11 @@ public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetails mSpatialAudioPref.setChecked(false); mController.onPreferenceClick(mSpatialAudioPref); verify(mSpatializer).removeCompatibleAudioDevice(mController.mAudioDevice); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_SPATIAL_AUDIO_TOGGLE_CLICKED, + false); } @Test @@ -230,6 +304,11 @@ public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetails mHeadTrackingPref.setChecked(true); mController.onPreferenceClick(mHeadTrackingPref); verify(mSpatializer).setHeadTrackerEnabled(true, mController.mAudioDevice); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_HEAD_TRACKING_TOGGLE_CLICKED, + true); } @Test @@ -238,5 +317,10 @@ public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetails mHeadTrackingPref.setChecked(false); mController.onPreferenceClick(mHeadTrackingPref); verify(mSpatializer).setHeadTrackerEnabled(false, mController.mAudioDevice); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_HEAD_TRACKING_TOGGLE_CLICKED, + false); } } From f9bc323633be6cf6ff10b71fc3b57b96e5a02042 Mon Sep 17 00:00:00 2001 From: josephpv Date: Wed, 21 Feb 2024 19:57:09 +0000 Subject: [PATCH 13/13] To skip face enrollment for PS unlock setup based on intent extra For private space lock setup as part of both PS setup and separate lock form private space settings we need to show only traditional unlock factors and Fingerprint but not show Face enrolment even on devices where Face unlock is supported by hardware. Once LSKF is enrolled it should be followed by Fingerprint enrollment flow and after that Face enrollment should not be shown and exit lock setup flow. Currently for separate profile lock setup ACTION_SET_NEW_PASSWORD intent is used in private space setup. With this intent the options of LSKF+fingerprint+Face is shown in devices supporting both fingerprint and face hardware. After the LSKF ennrollment BiometricEnrollActivity is started which continues with fingerprint and Face enrollment. With this change we are passing an extra along with the intent to enroll fingerprint only. Based on the intent extra value if set even if hardware support exists the lock enrollment for the profile will support only LSKF and fingerprint enrollment but not start Face enrollment. User will still have the option to enroll Face from the dedicated settings entrypoint in private space settings. Recording link : b/323839067#comment4 Bug: 323839067 Test: Manual, verified option for face enrollment is shown or not shown based on the intent extra. When extra is not passed the behaviour will be default. Change-Id: Idf92084052e02df9ca89f288c618796750e563e6 --- .../settings/biometrics/BiometricEnrollActivity.java | 7 ++++++- .../android/settings/password/ChooseLockGeneric.java | 12 ++++++++++-- .../settings/password/ChooseLockSettingsHelper.java | 2 ++ .../settings/password/SetNewPasswordActivity.java | 3 +++ .../settings/password/SetNewPasswordController.java | 6 +++++- .../PrivateProfileContextHelperActivity.java | 2 ++ 6 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/biometrics/BiometricEnrollActivity.java b/src/com/android/settings/biometrics/BiometricEnrollActivity.java index 73e1af1bc8a..c9616f1802c 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollActivity.java +++ b/src/com/android/settings/biometrics/BiometricEnrollActivity.java @@ -87,6 +87,10 @@ public class BiometricEnrollActivity extends InstrumentedActivity { // this only applies to fingerprint. public static final String EXTRA_SKIP_INTRO = "skip_intro"; + // Intent extra. If true, support fingerprint enrollment only and skip other biometric + // enrollment methods like face unlock. + public static final String EXTRA_FINGERPRINT_ENROLLMENT_ONLY = "fingerprint_enrollment_only"; + // Intent extra. If true, parental consent will be requested before user enrollment. public static final String EXTRA_REQUIRE_PARENTAL_CONSENT = "require_consent"; @@ -194,7 +198,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity { final PackageManager pm = getApplicationContext().getPackageManager(); mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT); - mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE); + mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE) + && !(intent.getBooleanExtra(EXTRA_FINGERPRINT_ENROLLMENT_ONLY, false)); // Default behavior is to enroll BIOMETRIC_WEAK or above. See ACTION_BIOMETRIC_ENROLL. final int authenticators = getIntent().getIntExtra( diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java index 178c3872c21..c693ec31de4 100644 --- a/src/com/android/settings/password/ChooseLockGeneric.java +++ b/src/com/android/settings/password/ChooseLockGeneric.java @@ -33,6 +33,7 @@ import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_C import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHOOSE_LOCK_SCREEN_DESCRIPTION; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHOOSE_LOCK_SCREEN_TITLE; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_DEVICE_PASSWORD_REQUIREMENT_ONLY; +import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FINGERPRINT_ENROLLMENT_ONLY; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_IS_CALLING_APP_ADMIN; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW; @@ -63,6 +64,7 @@ import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; @@ -167,7 +169,7 @@ public class ChooseLockGeneric extends SettingsActivity { private boolean mWaitingForActivityResult = false; private LockscreenCredential mUserPassword; private FingerprintManager mFingerprintManager; - private FaceManager mFaceManager; + @Nullable private FaceManager mFaceManager; private int mUserId; private boolean mIsManagedProfile; private ManagedLockPasswordProvider mManagedPasswordProvider; @@ -206,6 +208,7 @@ public class ChooseLockGeneric extends SettingsActivity { private int mExtraLockScreenTitleResId; private int mExtraLockScreenDescriptionResId; private boolean mWaitingForBiometricEnrollment = false; + private boolean mEnrollFingerPrintOnly = false; @Override public int getMetricsCategory() { @@ -225,8 +228,10 @@ public class ChooseLockGeneric extends SettingsActivity { } final Intent intent = activity.getIntent(); String chooseLockAction = intent.getAction(); + mEnrollFingerPrintOnly = + intent.getBooleanExtra(EXTRA_KEY_FINGERPRINT_ENROLLMENT_ONLY, false); mFingerprintManager = Utils.getFingerprintManagerOrNull(activity); - mFaceManager = Utils.getFaceManagerOrNull(activity); + mFaceManager = !mEnrollFingerPrintOnly ? Utils.getFaceManagerOrNull(activity) : null; mDpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); mLockPatternUtils = new LockPatternUtils(activity); mIsSetNewPassword = ACTION_SET_NEW_PARENT_PROFILE_PASSWORD.equals(chooseLockAction) @@ -530,6 +535,9 @@ public class ChooseLockGeneric extends SettingsActivity { final Intent intent = new Intent(context, BiometricEnrollActivity.InternalActivity.class); intent.putExtra(BiometricEnrollActivity.EXTRA_SKIP_INTRO, true); + if (mEnrollFingerPrintOnly) { + intent.putExtra(BiometricEnrollActivity.EXTRA_FINGERPRINT_ENROLLMENT_ONLY, true); + } return intent; } diff --git a/src/com/android/settings/password/ChooseLockSettingsHelper.java b/src/com/android/settings/password/ChooseLockSettingsHelper.java index e74b7767faa..91875cc88d6 100644 --- a/src/com/android/settings/password/ChooseLockSettingsHelper.java +++ b/src/com/android/settings/password/ChooseLockSettingsHelper.java @@ -64,6 +64,8 @@ public final class ChooseLockSettingsHelper { public static final String EXTRA_KEY_FOR_FACE = "for_face"; // For the paths where multiple biometric sensors exist public static final String EXTRA_KEY_FOR_BIOMETRICS = "for_biometrics"; + // To support fingerprint enrollment only and skip other biometric enrollments like face. + public static final String EXTRA_KEY_FINGERPRINT_ENROLLMENT_ONLY = "for_fingerprint_only"; // For the paths where setup biometrics in suw flow public static final String EXTRA_KEY_IS_SUW = "is_suw"; public static final String EXTRA_KEY_FOREGROUND_ONLY = "foreground_only"; diff --git a/src/com/android/settings/password/SetNewPasswordActivity.java b/src/com/android/settings/password/SetNewPasswordActivity.java index 9614d280009..0ba52eab132 100644 --- a/src/com/android/settings/password/SetNewPasswordActivity.java +++ b/src/com/android/settings/password/SetNewPasswordActivity.java @@ -27,6 +27,7 @@ import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_C import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHOOSE_LOCK_SCREEN_DESCRIPTION; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHOOSE_LOCK_SCREEN_TITLE; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_DEVICE_PASSWORD_REQUIREMENT_ONLY; +import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FINGERPRINT_ENROLLMENT_ONLY; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_IS_CALLING_APP_ADMIN; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY; @@ -132,6 +133,8 @@ public class SetNewPasswordActivity extends Activity implements SetNewPasswordCo getIntent().getIntExtra(EXTRA_KEY_CHOOSE_LOCK_SCREEN_TITLE, -1)); intent.putExtra(EXTRA_KEY_CHOOSE_LOCK_SCREEN_DESCRIPTION, getIntent().getIntExtra(EXTRA_KEY_CHOOSE_LOCK_SCREEN_DESCRIPTION, -1)); + intent.putExtra(EXTRA_KEY_FINGERPRINT_ENROLLMENT_ONLY, + getIntent().getBooleanExtra(EXTRA_KEY_FINGERPRINT_ENROLLMENT_ONLY, false)); if (mCallerAppName != null) { intent.putExtra(EXTRA_KEY_CALLER_APP_NAME, mCallerAppName); } diff --git a/src/com/android/settings/password/SetNewPasswordController.java b/src/com/android/settings/password/SetNewPasswordController.java index aa8fe5168bc..96c0b8edf37 100644 --- a/src/com/android/settings/password/SetNewPasswordController.java +++ b/src/com/android/settings/password/SetNewPasswordController.java @@ -21,6 +21,7 @@ import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT; import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FINGERPRINT_ENROLLMENT_ONLY; import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; @@ -79,7 +80,10 @@ final class SetNewPasswordController { } // Create a wrapper of FingerprintManager for testing, see IFingerPrintManager for details. final FingerprintManager fingerprintManager = Utils.getFingerprintManagerOrNull(context); - final FaceManager faceManager = Utils.getFaceManagerOrNull(context); + final FaceManager faceManager = + !intent.getBooleanExtra(EXTRA_KEY_FINGERPRINT_ENROLLMENT_ONLY, false) + ? Utils.getFaceManagerOrNull(context) + : null; return new SetNewPasswordController(userId, context.getPackageManager(), fingerprintManager, faceManager, diff --git a/src/com/android/settings/privatespace/PrivateProfileContextHelperActivity.java b/src/com/android/settings/privatespace/PrivateProfileContextHelperActivity.java index f2a50dc1e34..416b2dd7105 100644 --- a/src/com/android/settings/privatespace/PrivateProfileContextHelperActivity.java +++ b/src/com/android/settings/privatespace/PrivateProfileContextHelperActivity.java @@ -22,6 +22,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHOOSE_LOCK_SCREEN_DESCRIPTION; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHOOSE_LOCK_SCREEN_TITLE; +import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FINGERPRINT_ENROLLMENT_ONLY; import static com.android.settings.privatespace.PrivateSpaceSetupActivity.ACCOUNT_LOGIN_ACTION; import static com.android.settings.privatespace.PrivateSpaceSetupActivity.EXTRA_ACTION_TYPE; import static com.android.settings.privatespace.PrivateSpaceSetupActivity.SET_LOCK_ACTION; @@ -85,6 +86,7 @@ public class PrivateProfileContextHelperActivity extends FragmentActivity { private void createPrivateSpaceLock() { final Intent intent = new Intent(ACTION_SET_NEW_PASSWORD); intent.putExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_LOW); + intent.putExtra(EXTRA_KEY_FINGERPRINT_ENROLLMENT_ONLY, true); intent.putExtra( EXTRA_KEY_CHOOSE_LOCK_SCREEN_TITLE, R.string.private_space_lock_setup_title); intent.putExtra(