diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5ffbbf84d01..8730b421739 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -629,6 +629,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 @@ -10864,6 +10887,12 @@ Audio + + Documents + + + Other + Apps @@ -12159,7 +12188,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 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/MainClear.java b/src/com/android/settings/MainClear.java index 87b11f747a2..0639037a6b1 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/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/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 c38ebfe0069..c4b001432ec 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -1365,6 +1365,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/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/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/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/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/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/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/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/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/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/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/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java index 4aa751bdbb2..ce9a5667dfc 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( 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/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); } } 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( 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 = 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) + } +} 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) { 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); + } + } +}