): 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);
+ }
+ }
+}