diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0f39c6c2c47..31af5990f3c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -5114,6 +5114,8 @@ + diff --git a/res/values/strings.xml b/res/values/strings.xml index 33f186445ba..90cc39c9fad 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6948,6 +6948,8 @@ Not set up - Work profile + Owner + Admin You (%s) @@ -13182,4 +13184,7 @@ Converted to eSIM. Remove and discard. + + + Sync across devices diff --git a/res/xml/configure_notification_settings.xml b/res/xml/configure_notification_settings.xml index d9fccc4a04d..7fcf85e17f7 100644 --- a/res/xml/configure_notification_settings.xml +++ b/res/xml/configure_notification_settings.xml @@ -108,6 +108,11 @@ + + diff --git a/src/com/android/settings/accessibility/OWNERS b/src/com/android/settings/accessibility/OWNERS index 3bd156bdd9f..633e9c74aec 100644 --- a/src/com/android/settings/accessibility/OWNERS +++ b/src/com/android/settings/accessibility/OWNERS @@ -1,3 +1,6 @@ +# The Android Accessibility team should approve all changes to Settings > Accessibility content. +set noparent + # Default reviewers for this and subdirectories. chunkulin@google.com danielnorman@google.com @@ -8,5 +11,9 @@ thomasli@google.com # Legacy owner(s). menghanli@google.com #{LAST_RESORT_SUGGESTION} +# Core Settings owner for emergency changes. +cipson@google.com #{LAST_RESORT_SUGGESTION} + +# Partner-team files per-file HapticFeedbackIntensityPreferenceController.java = michaelwr@google.com per-file *Vibration* = michaelwr@google.com diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 17e7422fb34..39b13133dd4 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -818,13 +818,6 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { CharSequence announcement = getString( R.string.security_settings_sfps_enroll_progress_a11y_message, percent); announceEnrollmentProgress(announcement); - if (mIllustrationLottie != null) { - mIllustrationLottie.setContentDescription( - getString( - R.string.security_settings_sfps_animation_a11y_label, - percent) - ); - } } updateTitleAndDescription(); animateFlash(); diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java index 5e395b24d90..564e1384779 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java @@ -199,10 +199,8 @@ public class BluetoothDetailsHearingAidsPresetsController extends @Override public void onPresetSelectionFailed(@NonNull BluetoothDevice device, int reason) { if (device.equals(mCachedDevice.getDevice())) { - if (DEBUG) { - Log.d(TAG, "onPresetSelectionFailed, device: " + device.getAddress() - + ", reason: " + reason); - } + Log.w(TAG, "onPresetSelectionFailed, device: " + device.getAddress() + + ", reason: " + reason); mContext.getMainExecutor().execute(() -> { refresh(); showErrorToast(); @@ -213,10 +211,8 @@ public class BluetoothDetailsHearingAidsPresetsController extends @Override public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) { if (hapGroupId == mHapClientProfile.getHapGroup(mCachedDevice.getDevice())) { - if (DEBUG) { - Log.d(TAG, "onPresetSelectionForGroupFailed, group: " + hapGroupId - + ", reason: " + reason); - } + Log.w(TAG, "onPresetSelectionForGroupFailed, group: " + hapGroupId + + ", reason: " + reason); // Try to set the preset independently if group operation failed if (mPreference != null) { selectPresetIndependently(Integer.parseInt(mPreference.getValue())); @@ -242,11 +238,8 @@ public class BluetoothDetailsHearingAidsPresetsController extends @Override public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int reason) { if (device.equals(mCachedDevice.getDevice())) { - if (DEBUG) { - Log.d(TAG, - "onSetPresetNameFailed, device: " + device.getAddress() - + ", reason: " + reason); - } + Log.w(TAG, "onSetPresetNameFailed, device: " + device.getAddress() + + ", reason: " + reason); mContext.getMainExecutor().execute(() -> { refresh(); showErrorToast(); @@ -257,10 +250,8 @@ public class BluetoothDetailsHearingAidsPresetsController extends @Override public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) { if (hapGroupId == mHapClientProfile.getHapGroup(mCachedDevice.getDevice())) { - if (DEBUG) { - Log.d(TAG, "onSetPresetNameForGroupFailed, group: " + hapGroupId - + ", reason: " + reason); - } + Log.w(TAG, "onSetPresetNameForGroupFailed, group: " + hapGroupId + + ", reason: " + reason); mContext.getMainExecutor().execute(() -> { refresh(); showErrorToast(); @@ -288,9 +279,6 @@ public class BluetoothDetailsHearingAidsPresetsController extends for (int i = 0; i < infoList.size(); i++) { presetNames[i] = infoList.get(i).getName(); presetIndexes[i] = Integer.toString(infoList.get(i).getIndex()); - if (DEBUG) { - Log.d(TAG, "loadAllPresetInfo, preset " + presetIndexes[i] + ": " + presetNames[i]); - } } mPreference.setEntries(presetNames); mPreference.setEntryValues(presetIndexes); diff --git a/src/com/android/settings/development/OemUnlockPreferenceController.java b/src/com/android/settings/development/OemUnlockPreferenceController.java index 9076f9a976b..2542a11ca2d 100644 --- a/src/com/android/settings/development/OemUnlockPreferenceController.java +++ b/src/com/android/settings/development/OemUnlockPreferenceController.java @@ -21,6 +21,7 @@ import static com.android.settings.development.DevelopmentOptionsActivityRequest import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.SystemProperties; import android.os.UserHandle; @@ -176,6 +177,13 @@ public class OemUnlockPreferenceController extends DeveloperOptionsPreferenceCon /** Returns {@code true} if the device is SIM-locked. Otherwise, returns {@code false}. */ private boolean isSimLockedDevice() { + if (!mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TELEPHONY_CARRIERLOCK)) { + Log.w(TAG, + "getAllowedCarriers is unsupported without " + + "PackageManager#FEATURE_TELEPHONY_CARRIERLOCK"); + return false; + } int phoneCount = mTelephonyManager.getPhoneCount(); for (int i = 0; i < phoneCount; i++) { if (mTelephonyManager.getAllowedCarriers(i).size() > 0) { diff --git a/src/com/android/settings/network/telephony/NetworkOperatorPreference.kt b/src/com/android/settings/network/telephony/NetworkOperatorPreference.kt index c696ef13315..7f42400fe85 100644 --- a/src/com/android/settings/network/telephony/NetworkOperatorPreference.kt +++ b/src/com/android/settings/network/telephony/NetworkOperatorPreference.kt @@ -19,6 +19,12 @@ package com.android.settings.network.telephony import android.content.Context import android.telephony.AccessNetworkConstants.AccessNetworkType import android.telephony.CellIdentity +import android.telephony.CellIdentityCdma +import android.telephony.CellIdentityGsm +import android.telephony.CellIdentityLte +import android.telephony.CellIdentityNr +import android.telephony.CellIdentityTdscdma +import android.telephony.CellIdentityWcdma import android.telephony.CellInfo import android.telephony.CellInfoCdma import android.telephony.CellInfoGsm @@ -120,17 +126,17 @@ open class NetworkOperatorPreference( getAccessNetworkTypeFromCellInfo(), ) - private fun getIconIdForCell(): Int = when (cellInfo) { - is CellInfoGsm -> R.drawable.signal_strength_g - is CellInfoCdma -> R.drawable.signal_strength_1x - is CellInfoWcdma, is CellInfoTdscdma -> R.drawable.signal_strength_3g + private fun getIconIdForCell(): Int = when (cellId) { + is CellIdentityGsm -> R.drawable.signal_strength_g + is CellIdentityCdma -> R.drawable.signal_strength_1x + is CellIdentityWcdma, is CellIdentityTdscdma -> R.drawable.signal_strength_3g - is CellInfoLte -> { + is CellIdentityLte -> { if (show4GForLTE) R.drawable.ic_signal_strength_4g else R.drawable.signal_strength_lte } - is CellInfoNr -> R.drawable.signal_strength_5g + is CellIdentityNr -> R.drawable.signal_strength_5g else -> MobileNetworkUtils.NO_CELL_DATA_TYPE_ICON } diff --git a/src/com/android/settings/notification/modes/ZenMode.java b/src/com/android/settings/notification/modes/ZenMode.java index 2e615008ff5..66d68c5c7cd 100644 --- a/src/com/android/settings/notification/modes/ZenMode.java +++ b/src/com/android/settings/notification/modes/ZenMode.java @@ -16,10 +16,12 @@ package com.android.settings.notification.modes; +import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; import static java.util.Objects.requireNonNull; +import android.annotation.SuppressLint; import android.app.AutomaticZenRule; import android.app.NotificationManager; import android.content.Context; @@ -50,11 +52,25 @@ class ZenMode { private static final String TAG = "ZenMode"; + /** + * Additional value for the {@code @ZenPolicy.ChannelType} enumeration that indicates that all + * channels can bypass DND when this policy is active. + * + *

This value shouldn't be used on "real" ZenPolicy objects sent to or returned from + * {@link android.app.NotificationManager}; it's a way of representing rules with interruption + * filter = {@link NotificationManager#INTERRUPTION_FILTER_ALL} in the UI. + */ + public static final int CHANNEL_POLICY_ALL = -1; + static final String MANUAL_DND_MODE_ID = "manual_dnd"; + @SuppressLint("WrongConstant") private static final ZenPolicy POLICY_INTERRUPTION_FILTER_ALL = - // TODO: b/331267485 - Support "allow all channels"! - new ZenPolicy.Builder().allowAllSounds().showAllVisualEffects().build(); + new ZenPolicy.Builder() + .allowChannels(CHANNEL_POLICY_ALL) + .allowAllSounds() + .showAllVisualEffects() + .build(); // Must match com.android.server.notification.ZenModeHelper#applyCustomPolicy. private static final ZenPolicy POLICY_INTERRUPTION_FILTER_ALARMS = @@ -75,9 +91,11 @@ class ZenMode { private final String mId; private final AutomaticZenRule mRule; - private boolean mIsActive; + private final boolean mIsActive; private final boolean mIsManualDnd; +// private ZenPolicy mPreviousPolicy; + ZenMode(String id, AutomaticZenRule rule, boolean isActive) { this(id, rule, isActive, false); } @@ -172,14 +190,48 @@ class ZenMode { } } - public void setZenPolicy(@NonNull ZenPolicy policy) { - // TODO: b/331267485 - A policy with apps=ALL should be mapped to INTERRUPTION_FILTER_ALL. - if (mRule.getInterruptionFilter() != INTERRUPTION_FILTER_PRIORITY) { - ZenPolicy currentPolicy = getPolicy(); - if (!currentPolicy.equals(policy)) { - // If policy is customized from any of the "special" ones, make the rule PRIORITY. - mRule.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY); + /** + * Updates the {@link ZenPolicy} of the associated {@link AutomaticZenRule} based on the + * supplied policy. In some cases this involves conversions, so that the following call + * to {@link #getPolicy} might return a different policy from the one supplied here. + */ + @SuppressLint("WrongConstant") + public void setPolicy(@NonNull ZenPolicy policy) { + ZenPolicy currentPolicy = getPolicy(); + if (currentPolicy.equals(policy)) { + return; + } + + // A policy with CHANNEL_POLICY_ALL is only a UI representation of the + // INTERRUPTION_FILTER_ALL filter. Thus, switching to or away to this value only updates + // the filter, discarding the rest of the supplied policy. + if (policy.getAllowedChannels() == CHANNEL_POLICY_ALL + && currentPolicy.getAllowedChannels() != CHANNEL_POLICY_ALL) { + if (mIsManualDnd) { + throw new IllegalArgumentException("Manual DND cannot have CHANNEL_POLICY_ALL"); } + mRule.setInterruptionFilter(INTERRUPTION_FILTER_ALL); + // Preserve the existing policy, e.g. if the user goes PRIORITY -> ALL -> PRIORITY that + // shouldn't discard all other policy customizations. The existing policy will be a + // synthetic one if the rule originally had filter NONE or ALARMS_ONLY and that's fine. + if (mRule.getZenPolicy() == null) { + mRule.setZenPolicy(currentPolicy); + } + return; + } else if (policy.getAllowedChannels() != CHANNEL_POLICY_ALL + && currentPolicy.getAllowedChannels() == CHANNEL_POLICY_ALL) { + mRule.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY); + // Go back to whatever policy the rule had before, unless the rule never had one, in + // which case we use the supplied policy (which we know has a valid allowedChannels). + if (mRule.getZenPolicy() == null) { + mRule.setZenPolicy(policy); + } + return; + } + + // If policy is customized from any of the "special" ones, make the rule PRIORITY. + if (mRule.getInterruptionFilter() != INTERRUPTION_FILTER_PRIORITY) { + mRule.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY); } mRule.setZenPolicy(policy); } @@ -206,7 +258,7 @@ class ZenMode { @Override public int hashCode() { - return Objects.hash(mId, mRule); + return Objects.hash(mId, mRule, mIsActive); } @Override diff --git a/src/com/android/settings/notification/modes/ZenModesRuleSettingsBase.java b/src/com/android/settings/notification/modes/ZenModesRuleSettingsBase.java new file mode 100644 index 00000000000..3ddf70440a5 --- /dev/null +++ b/src/com/android/settings/notification/modes/ZenModesRuleSettingsBase.java @@ -0,0 +1,89 @@ +/* + * 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.notification.modes; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + +import com.android.settings.R; + +/** + * Base class for Settings pages used to configure individual modes. + */ +abstract class ZenModesRuleSettingsBase extends ZenModesSettingsBase { + static final String TAG = "ZenModesRuleSettings"; + static final String MODE_ID = "MODE_ID"; + + @Nullable + protected ZenMode mZenMode; + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + + // TODO: b/322373473 - Update if modes page ends up using a different method of passing id + Bundle bundle = getArguments(); + if (bundle != null && bundle.containsKey(MODE_ID)) { + String id = bundle.getString(MODE_ID); + if (!reloadMode(id)) { + Log.d(TAG, "Mode id " + id + " not found"); + toastAndFinish(); + } + } else { + Log.d(TAG, "Mode id required to set mode config settings"); + toastAndFinish(); + } + } + + /** + * Refresh stored ZenMode data. + * @param id the mode ID + * @return whether we successfully got mode data from the backend. + */ + private boolean reloadMode(String id) { + mZenMode = mBackend.getMode(id); + return mZenMode != null; + } + + /** + * Refresh ZenMode data any time the system's zen mode state changes (either the zen mode value + * itself, or the config). + */ + @Override + protected void updateZenModeState() { + if (mZenMode == null) { + // This shouldn't happen, but guard against it in case + toastAndFinish(); + return; + } + String id = mZenMode.getId(); + if (!reloadMode(id)) { + Log.d(TAG, "Mode id=" + id + " not found"); + toastAndFinish(); + } + } + + private void toastAndFinish() { + Toast.makeText(mContext, R.string.zen_mode_rule_not_found_text, Toast.LENGTH_SHORT) + .show(); + this.finish(); + } +} diff --git a/src/com/android/settings/notification/modes/ZenModesSettingsBase.java b/src/com/android/settings/notification/modes/ZenModesSettingsBase.java new file mode 100644 index 00000000000..3d97a99d29e --- /dev/null +++ b/src/com/android/settings/notification/modes/ZenModesSettingsBase.java @@ -0,0 +1,113 @@ +/* + * 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.notification.modes; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.UserManager; +import android.provider.Settings.Global; +import android.util.Log; + +import com.android.settings.dashboard.RestrictedDashboardFragment; + +/** + * Base class for all Settings pages controlling Modes behavior. + */ +abstract class ZenModesSettingsBase extends RestrictedDashboardFragment { + protected static final String TAG = "ZenModesSettings"; + protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final Handler mHandler = new Handler(); + private final SettingsObserver mSettingsObserver = new SettingsObserver(); + + protected Context mContext; + + protected ZenModesBackend mBackend; + + // Individual pages must implement this method based on what they should do when + // the device's zen mode state changes. + protected abstract void updateZenModeState(); + + ZenModesSettingsBase() { + super(UserManager.DISALLOW_ADJUST_VOLUME); + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mContext = context; + mBackend = ZenModesBackend.getInstance(context); + } + + @Override + public void onStart() { + super.onStart(); + updateZenModeState(); + mSettingsObserver.register(); + if (isUiRestricted()) { + if (isUiRestrictedByOnlyAdmin()) { + getPreferenceScreen().removeAll(); + return; + } else { + finish(); + } + } + } + + @Override + public void onStop() { + super.onStop(); + mSettingsObserver.unregister(); + } + + private final class SettingsObserver extends ContentObserver { + private static final Uri ZEN_MODE_URI = Global.getUriFor(Global.ZEN_MODE); + private static final Uri ZEN_MODE_CONFIG_ETAG_URI = Global.getUriFor( + Global.ZEN_MODE_CONFIG_ETAG); + + private SettingsObserver() { + super(mHandler); + } + + public void register() { + getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this); + getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_ETAG_URI, false, this); + } + + public void unregister() { + getContentResolver().unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange, @Nullable Uri uri) { + super.onChange(selfChange, uri); + // Shouldn't have any other URIs trigger this method, but check just in case. + if (ZEN_MODE_URI.equals(uri) || ZEN_MODE_CONFIG_ETAG_URI.equals(uri)) { + updateZenModeState(); + } + } + } +} diff --git a/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureCallback.java b/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureCallback.java new file mode 100644 index 00000000000..6dfe183a4b3 --- /dev/null +++ b/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureCallback.java @@ -0,0 +1,38 @@ +/* + * 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.notification.syncacrossdevices; + +import androidx.annotation.Nullable; +import androidx.preference.Preference; + +/** Callback to add or remove {@link Preference} in Sync Across Devices feature. */ +public interface SyncAcrossDevicesFeatureCallback { + + /** + * Called when a sync across devices feature is added + * + * @param preference present the feature + */ + void onFeatureAdded(@Nullable Preference preference); + + /** + * Called when a sync across devices feature is removed + * + * @param preference present the feature + */ + void onFeatureRemoved(@Nullable Preference preference); +} diff --git a/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureProvider.java b/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureProvider.java new file mode 100644 index 00000000000..d575d591922 --- /dev/null +++ b/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureProvider.java @@ -0,0 +1,32 @@ +/* + * 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.notification.syncacrossdevices; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** Feature provider for the Sync Across Devices. */ +public interface SyncAcrossDevicesFeatureProvider { + + /** Returns the SyncAcrossDevicesFeatureUpdater of the Sync Across Devices feature */ + @Nullable + SyncAcrossDevicesFeatureUpdater getSyncAcrossDevicesFeatureUpdater( + @NonNull Context context, + @NonNull SyncAcrossDevicesFeatureCallback featurePreferenceCallback); +} diff --git a/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureProviderImpl.java b/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureProviderImpl.java new file mode 100644 index 00000000000..090bf637806 --- /dev/null +++ b/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureProviderImpl.java @@ -0,0 +1,34 @@ +/* + * 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.notification.syncacrossdevices; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** Default implementation for {@link SyncAcrossDevicesFeatureProvider} */ +public class SyncAcrossDevicesFeatureProviderImpl implements SyncAcrossDevicesFeatureProvider { + + @Override + @Nullable + public SyncAcrossDevicesFeatureUpdater getSyncAcrossDevicesFeatureUpdater( + @NonNull Context context, + @NonNull SyncAcrossDevicesFeatureCallback featurePreferenceCallback) { + return new SyncAcrossDevicesFeatureUpdater() {}; + } +} diff --git a/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureUpdater.java b/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureUpdater.java new file mode 100644 index 00000000000..f9407b0d545 --- /dev/null +++ b/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureUpdater.java @@ -0,0 +1,34 @@ +/* + * 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.notification.syncacrossdevices; + +import android.content.Context; + +import androidx.annotation.Nullable; + +/** + * Updates the sync across devices feature state. It notifies the upper level whether to add/remove + * the preference through {@link SyncAcrossDevicesFeatureCallback} + */ +public interface SyncAcrossDevicesFeatureUpdater { + + /** Forces to update the list of the Sync Across Devices feature. */ + default void forceUpdate() {} + + /** Sets the context to generate the {@link Preference}, so it could get the correct theme. */ + default void setPreferenceContext(@Nullable Context preferenceContext) {} +} diff --git a/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesPreferenceController.java b/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesPreferenceController.java new file mode 100644 index 00000000000..ccea67184ab --- /dev/null +++ b/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesPreferenceController.java @@ -0,0 +1,100 @@ +/* + * 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.notification.syncacrossdevices; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.overlay.FeatureFactory; + +public class SyncAcrossDevicesPreferenceController extends BasePreferenceController + implements PreferenceControllerMixin, SyncAcrossDevicesFeatureCallback { + + private static final String TAG = "SyncXDevicesPrefCtr"; + + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private PreferenceGroup mPreferenceGroup; + private SyncAcrossDevicesFeatureUpdater mSyncAcrossDevicesFeatureUpdater; + + public SyncAcrossDevicesPreferenceController(@NonNull Context context, @NonNull String key) { + super(context, key); + SyncAcrossDevicesFeatureProvider syncAcrossDevicesFeatureProvider = + FeatureFactory.getFeatureFactory().getSyncAcrossDevicesFeatureProvider(); + mSyncAcrossDevicesFeatureUpdater = + syncAcrossDevicesFeatureProvider.getSyncAcrossDevicesFeatureUpdater(context, this); + } + + @Override + public void displayPreference(@NonNull PreferenceScreen screen) { + super.displayPreference(screen); + mPreferenceGroup = screen.findPreference(getPreferenceKey()); + mPreferenceGroup.setVisible(false); + if (isAvailable()) { + final Context context = screen.getContext(); + mSyncAcrossDevicesFeatureUpdater.setPreferenceContext(context); + mSyncAcrossDevicesFeatureUpdater.forceUpdate(); + } + } + + @Override + public int getAvailabilityStatus() { + return mSyncAcrossDevicesFeatureUpdater != null ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public void onFeatureAdded(@Nullable Preference preference) { + if (preference == null) { + if (DEBUG) { + Log.d(TAG, "onFeatureAdded receives null preference. Ignore."); + } + return; + } + mPreferenceGroup.addPreference(preference); + updatePreferenceVisibility(); + } + + @Override + public void onFeatureRemoved(@Nullable Preference preference) { + if (preference == null) { + if (DEBUG) { + Log.d(TAG, "onFeatureRemoved receives null preference. Ignore."); + } + return; + } + mPreferenceGroup.removePreference(preference); + updatePreferenceVisibility(); + } + + private void updatePreferenceVisibility() { + mPreferenceGroup.setVisible(mPreferenceGroup.getPreferenceCount() > 0); + } + + @VisibleForTesting + public void setPreferenceGroup(@NonNull PreferenceGroup preferenceGroup) { + mPreferenceGroup = preferenceGroup; + } +} diff --git a/src/com/android/settings/overlay/FeatureFactory.kt b/src/com/android/settings/overlay/FeatureFactory.kt index 53ad8ba642e..ef63f194ca2 100644 --- a/src/com/android/settings/overlay/FeatureFactory.kt +++ b/src/com/android/settings/overlay/FeatureFactory.kt @@ -38,6 +38,7 @@ import com.android.settings.fuelgauge.PowerUsageFeatureProvider import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider import com.android.settings.inputmethod.KeyboardSettingsFeatureProvider import com.android.settings.localepicker.LocaleFeatureProvider +import com.android.settings.notification.syncacrossdevices.SyncAcrossDevicesFeatureProvider import com.android.settings.onboarding.OnboardingFeatureProvider import com.android.settings.overlay.FeatureFactory.Companion.setFactory import com.android.settings.panel.PanelFeatureProvider @@ -188,6 +189,11 @@ abstract class FeatureFactory { */ abstract val audioSharingFeatureProvider: AudioSharingFeatureProvider + /** + * Gets implementation for sync across devices related feature. + */ + abstract val syncAcrossDevicesFeatureProvider: SyncAcrossDevicesFeatureProvider + companion object { private var _factory: FeatureFactory? = null diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.kt b/src/com/android/settings/overlay/FeatureFactoryImpl.kt index 1770209cc86..c74260c4070 100644 --- a/src/com/android/settings/overlay/FeatureFactoryImpl.kt +++ b/src/com/android/settings/overlay/FeatureFactoryImpl.kt @@ -55,6 +55,8 @@ import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvid import com.android.settings.inputmethod.KeyboardSettingsFeatureProvider import com.android.settings.inputmethod.KeyboardSettingsFeatureProviderImpl import com.android.settings.localepicker.LocaleFeatureProviderImpl +import com.android.settings.notification.syncacrossdevices.SyncAcrossDevicesFeatureProvider +import com.android.settings.notification.syncacrossdevices.SyncAcrossDevicesFeatureProviderImpl import com.android.settings.panel.PanelFeatureProviderImpl import com.android.settings.search.SearchFeatureProvider import com.android.settings.search.SearchFeatureProviderImpl @@ -197,4 +199,8 @@ open class FeatureFactoryImpl : FeatureFactory() { override val audioSharingFeatureProvider: AudioSharingFeatureProvider by lazy { AudioSharingFeatureProviderImpl() } + + override val syncAcrossDevicesFeatureProvider: SyncAcrossDevicesFeatureProvider by lazy { + SyncAcrossDevicesFeatureProviderImpl() + } } diff --git a/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java b/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java index 4e904d32bf9..ed70030c314 100644 --- a/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java +++ b/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java @@ -58,7 +58,8 @@ public class PrivateSpaceDashboardFragment extends DashboardFragment { public void onStart() { super.onStart(); if (PrivateSpaceMaintainer.getInstance(getContext()).isPrivateSpaceLocked()) { - finish(); + // To make sure the task is removed if it is the last activity in that stack. + getActivity().finishAndRemoveTask(); } } diff --git a/src/com/android/settings/spa/network/SimOnboardingLabelSim.kt b/src/com/android/settings/spa/network/SimOnboardingLabelSim.kt index 2b40a91f96a..03cd743a817 100644 --- a/src/com/android/settings/spa/network/SimOnboardingLabelSim.kt +++ b/src/com/android/settings/spa/network/SimOnboardingLabelSim.kt @@ -111,7 +111,7 @@ private fun LabelSimPreference( placeholder = {Text(text = originalSimCarrierName)}, modifier = Modifier.fillMaxWidth() ) { - titleSimName = it + titleSimName = if (it.isEmpty()) originalSimCarrierName else it } }, ) diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index 83dc1274c06..8dbb91cf506 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -368,9 +368,6 @@ public class UserSettings extends SettingsPreferenceFragment mMePreference = new UserPreference(getPrefContext(), null /* attrs */, myUserId); mMePreference.setKey(KEY_USER_ME); mMePreference.setOnPreferenceClickListener(this); - if (isCurrentUserAdmin()) { - mMePreference.setSummary(R.string.user_admin); - } mGuestCategory = findPreference(KEY_GUEST_CATEGORY); @@ -1241,13 +1238,15 @@ public class UserSettings extends SettingsPreferenceFragment pref.setEnabled(canOpenUserDetails); pref.setSelectable(true); pref.setKey("id=" + user.id); - if (user.isAdmin()) { - pref.setSummary(R.string.user_admin); - } } if (pref == null) { continue; } + if (user.isMain()) { + pref.setSummary(R.string.user_owner); + } else if (user.isAdmin()) { + pref.setSummary(R.string.user_admin); + } if (user.id != UserHandle.myUserId() && !user.isGuest() && !user.isInitialized()) { // sometimes after creating a guest the initialized flag isn't immediately set // and we don't want to show "Not set up" summary for them diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeTest.java index 761f10758c8..05286212e12 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeTest.java @@ -61,7 +61,7 @@ public class ZenModeTest { } @Test - public void getZenPolicy_interruptionFilterPriority_returnsZenPolicy() { + public void getPolicy_interruptionFilterPriority_returnsZenPolicy() { ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY) .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) .setZenPolicy(ZEN_POLICY) @@ -71,18 +71,19 @@ public class ZenModeTest { } @Test - public void getZenPolicy_interruptionFilterAll_returnsPolicyAllowingAll() { + public void getPolicy_interruptionFilterAll_returnsPolicyAllowingAll() { ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY) .setInterruptionFilter(INTERRUPTION_FILTER_ALL) .setZenPolicy(ZEN_POLICY) // should be ignored .build(), false); assertThat(zenMode.getPolicy()).isEqualTo( - new ZenPolicy.Builder().allowAllSounds().showAllVisualEffects().build()); + new ZenPolicy.Builder().allowChannels(ZenMode.CHANNEL_POLICY_ALL) + .allowAllSounds().showAllVisualEffects().build()); } @Test - public void getZenPolicy_interruptionFilterAlarms_returnsPolicyAllowingAlarms() { + public void getPolicy_interruptionFilterAlarms_returnsPolicyAllowingAlarms() { ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY) .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) .setZenPolicy(ZEN_POLICY) // should be ignored @@ -98,7 +99,7 @@ public class ZenModeTest { } @Test - public void getZenPolicy_interruptionFilterNone_returnsPolicyAllowingNothing() { + public void getPolicy_interruptionFilterNone_returnsPolicyAllowingNothing() { ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY) .setInterruptionFilter(INTERRUPTION_FILTER_NONE) .setZenPolicy(ZEN_POLICY) // should be ignored @@ -111,4 +112,83 @@ public class ZenModeTest { .allowPriorityChannels(false) .build()); } + + @Test + public void setPolicy_setsInterruptionFilterPriority() { + ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) + .build(), false); + + zenMode.setPolicy(ZEN_POLICY); + + assertThat(zenMode.getRule().getInterruptionFilter()).isEqualTo( + INTERRUPTION_FILTER_PRIORITY); + assertThat(zenMode.getPolicy()).isEqualTo(ZEN_POLICY); + assertThat(zenMode.getRule().getZenPolicy()).isEqualTo(ZEN_POLICY); + } + + @Test + public void setPolicy_withAllChannelsAllowed_setsInterruptionFilterAll() { + ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) + .setZenPolicy(ZEN_POLICY) + .build(), false); + + zenMode.setPolicy( + new ZenPolicy.Builder().allowChannels(ZenMode.CHANNEL_POLICY_ALL).build()); + + assertThat(zenMode.getRule().getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL); + assertThat(zenMode.getPolicy()).isEqualTo( + new ZenPolicy.Builder().allowChannels(ZenMode.CHANNEL_POLICY_ALL) + .allowAllSounds().showAllVisualEffects().build()); + } + + @Test + public void setPolicy_priorityToAllChannelsAndBack_restoresOldPolicy() { + ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy(ZEN_POLICY) + .build(), false); + + zenMode.setPolicy( + new ZenPolicy.Builder().allowChannels(ZenMode.CHANNEL_POLICY_ALL).build()); + assertThat(zenMode.getRule().getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL); + assertThat(zenMode.getPolicy()).isEqualTo( + new ZenPolicy.Builder().allowChannels(ZenMode.CHANNEL_POLICY_ALL) + .allowAllSounds().showAllVisualEffects().build()); + + zenMode.setPolicy( + new ZenPolicy.Builder().allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY).build()); + + assertThat(zenMode.getRule().getInterruptionFilter()).isEqualTo( + INTERRUPTION_FILTER_PRIORITY); + assertThat(zenMode.getPolicy()).isEqualTo(ZEN_POLICY); + assertThat(zenMode.getRule().getZenPolicy()).isEqualTo(ZEN_POLICY); + } + + @Test + public void setPolicy_alarmsOnlyToAllChannelsAndBack_restoresPolicySimilarToAlarmsOnly() { + ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY) + .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) + .build(), false); + + zenMode.setPolicy( + new ZenPolicy.Builder().allowChannels(ZenMode.CHANNEL_POLICY_ALL).build()); + assertThat(zenMode.getRule().getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL); + assertThat(zenMode.getPolicy()).isEqualTo( + new ZenPolicy.Builder().allowChannels(ZenMode.CHANNEL_POLICY_ALL) + .allowAllSounds().showAllVisualEffects().build()); + + zenMode.setPolicy( + new ZenPolicy.Builder().allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY).build()); + + // We don't go back to ALARMS, but the policy must be the one the user was seeing before. + ZenPolicy alarmsOnlyLikePolicy = new ZenPolicy.Builder().disallowAllSounds() + .allowAlarms(true).allowMedia(true).allowPriorityChannels(false) + .build(); + assertThat(zenMode.getRule().getInterruptionFilter()).isEqualTo( + INTERRUPTION_FILTER_PRIORITY); + assertThat(zenMode.getPolicy()).isEqualTo(alarmsOnlyLikePolicy); + assertThat(zenMode.getRule().getZenPolicy()).isEqualTo(alarmsOnlyLikePolicy); + } } diff --git a/tests/robotests/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesPreferenceControllerTest.java new file mode 100644 index 00000000000..7bfde1b4240 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesPreferenceControllerTest.java @@ -0,0 +1,133 @@ +/* + * 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.notification.syncacrossdevices; + +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.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; + +import com.android.settings.testutils.FakeFeatureFactory; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class SyncAcrossDevicesPreferenceControllerTest { + + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Mock private SyncAcrossDevicesFeatureUpdater mSyncAcrossDevicesFeatureUpdater; + @Mock private PreferenceManager mPreferenceManager; + + private static final String PREFERENCE_KEY = "preference_key"; + + private Context mContext; + private SyncAcrossDevicesPreferenceController mSyncAcrossDevicesPreferenceController; + private PreferenceGroup mPreferenceGroup; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + SyncAcrossDevicesFeatureProvider provider = + FakeFeatureFactory.setupForTest().getSyncAcrossDevicesFeatureProvider(); + doReturn(mSyncAcrossDevicesFeatureUpdater) + .when(provider) + .getSyncAcrossDevicesFeatureUpdater(any(), any()); + + mSyncAcrossDevicesPreferenceController = + new SyncAcrossDevicesPreferenceController(mContext, PREFERENCE_KEY); + + mPreferenceGroup = spy(new PreferenceCategory(mContext)); + doReturn(mPreferenceManager).when(mPreferenceGroup).getPreferenceManager(); + mPreferenceGroup.setVisible(false); + + mPreferenceGroup.setKey(mSyncAcrossDevicesPreferenceController.getPreferenceKey()); + mSyncAcrossDevicesPreferenceController.setPreferenceGroup(mPreferenceGroup); + } + + @Test + public void testGetAvailabilityStatus_noFeatureUpdater_returnUnSupported() { + SyncAcrossDevicesFeatureProvider provider = + FakeFeatureFactory.setupForTest().getSyncAcrossDevicesFeatureProvider(); + doReturn(null).when(provider).getSyncAcrossDevicesFeatureUpdater(any(), any()); + + SyncAcrossDevicesPreferenceController syncAcrossDevicesPreferenceController = + new SyncAcrossDevicesPreferenceController(mContext, PREFERENCE_KEY); + + assertThat(syncAcrossDevicesPreferenceController.getAvailabilityStatus()) + .isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + @Test + public void testGetAvailabilityStatus_withFeatureUpdater_returnSupported() { + assertThat(mSyncAcrossDevicesPreferenceController.getAvailabilityStatus()) + .isEqualTo(AVAILABLE); + } + + @Test + public void testUpdatePreferenceVisibility_addFeaturePreference_shouldShowPreference() { + Preference preference = new Preference(mContext); + + mSyncAcrossDevicesPreferenceController.onFeatureAdded(preference); + + assertThat(mPreferenceGroup.isVisible()).isTrue(); + } + + @Test + public void testUpdatePreferenceVisibility_removeFeaturePreference_shouldHidePreference() { + Preference preference = new Preference(mContext); + + mSyncAcrossDevicesPreferenceController.onFeatureAdded(preference); + mSyncAcrossDevicesPreferenceController.onFeatureRemoved(preference); + + assertThat(mPreferenceGroup.isVisible()).isFalse(); + } + + @Test + public void testDisplayPreference_availabilityStatusIsAvailable_shouldForceUpdated() { + PreferenceManager preferenceManager = new PreferenceManager(mContext); + PreferenceScreen preferenceScreen = preferenceManager.createPreferenceScreen(mContext); + preferenceScreen.addPreference(mPreferenceGroup); + + mSyncAcrossDevicesPreferenceController.displayPreference(preferenceScreen); + + verify(mSyncAcrossDevicesFeatureUpdater, times(1)).setPreferenceContext(any()); + verify(mSyncAcrossDevicesFeatureUpdater, times(1)).forceUpdate(); + } +} diff --git a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java index 38683d01ea6..71f8e5834fe 100644 --- a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java +++ b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java @@ -40,6 +40,7 @@ import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider; import com.android.settings.inputmethod.KeyboardSettingsFeatureProvider; import com.android.settings.localepicker.LocaleFeatureProvider; +import com.android.settings.notification.syncacrossdevices.SyncAcrossDevicesFeatureProvider; import com.android.settings.onboarding.OnboardingFeatureProvider; import com.android.settings.overlay.DockUpdaterFeatureProvider; import com.android.settings.overlay.FeatureFactory; @@ -103,6 +104,7 @@ public class FakeFeatureFactory extends FeatureFactory { public PrivateSpaceLoginFeatureProvider mPrivateSpaceLoginFeatureProvider; public DisplayFeatureProvider mDisplayFeatureProvider; public AudioSharingFeatureProvider mAudioSharingFeatureProvider; + public SyncAcrossDevicesFeatureProvider mSyncAcrossDevicesFeatureProvider; /** * Call this in {@code @Before} method of the test class to use fake factory. @@ -150,9 +152,10 @@ public class FakeFeatureFactory extends FeatureFactory { mStylusFeatureProvider = mock(StylusFeatureProvider.class); mOnboardingFeatureProvider = mock(OnboardingFeatureProvider.class); mFastPairFeatureProvider = mock(FastPairFeatureProvider.class); - mPrivateSpaceLoginFeatureProvider = mock(PrivateSpaceLoginFeatureProvider.class); + mPrivateSpaceLoginFeatureProvider = mock(PrivateSpaceLoginFeatureProvider.class); mDisplayFeatureProvider = mock(DisplayFeatureProvider.class); mAudioSharingFeatureProvider = mock(AudioSharingFeatureProvider.class); + mSyncAcrossDevicesFeatureProvider = mock(SyncAcrossDevicesFeatureProvider.class); } @Override @@ -340,5 +343,10 @@ public class FakeFeatureFactory extends FeatureFactory { public AudioSharingFeatureProvider getAudioSharingFeatureProvider() { return mAudioSharingFeatureProvider; } + + @Override + public SyncAcrossDevicesFeatureProvider getSyncAcrossDevicesFeatureProvider() { + return mSyncAcrossDevicesFeatureProvider; + } } diff --git a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt index 606db8e1c87..e1dcda211a3 100644 --- a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt +++ b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt @@ -39,6 +39,7 @@ import com.android.settings.fuelgauge.PowerUsageFeatureProvider import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider import com.android.settings.inputmethod.KeyboardSettingsFeatureProvider import com.android.settings.localepicker.LocaleFeatureProvider +import com.android.settings.notification.syncacrossdevices.SyncAcrossDevicesFeatureProvider import com.android.settings.overlay.DockUpdaterFeatureProvider import com.android.settings.overlay.FeatureFactory import com.android.settings.overlay.SurveyFeatureProvider @@ -152,4 +153,6 @@ class FakeFeatureFactory : FeatureFactory() { get() = TODO("Not yet implemented") override val audioSharingFeatureProvider: AudioSharingFeatureProvider get() = TODO("Not yet implemented") + override val syncAcrossDevicesFeatureProvider: SyncAcrossDevicesFeatureProvider + get() = TODO("Not yet implemented") } diff --git a/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java index 71d419ee77b..71899fbb730 100644 --- a/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java +++ b/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java @@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -45,10 +46,12 @@ import android.safetycenter.SafetySourceStatus; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.internal.widget.LockPatternUtils; import com.android.settings.Settings; import com.android.settings.biometrics.face.FaceEnrollIntroductionInternal; import com.android.settings.biometrics.fingerprint.FingerprintSettings; import com.android.settings.testutils.ActiveUnlockTestUtils; +import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.ResourcesUtils; import com.android.settingslib.utils.StringUtil; @@ -78,6 +81,7 @@ public class BiometricsSafetySourceTest { @Mock private DevicePolicyManager mDevicePolicyManager; @Mock private FingerprintManager mFingerprintManager; @Mock private FaceManager mFaceManager; + @Mock private LockPatternUtils mLockPatternUtils; @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper; @Before @@ -94,6 +98,10 @@ public class BiometricsSafetySourceTest { when(mApplicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE)) .thenReturn(mDevicePolicyManager); when(mApplicationContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager); + FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest(); + when(featureFactory.securityFeatureProvider.getLockPatternUtils(mApplicationContext)) + .thenReturn(mLockPatternUtils); + doReturn(true).when(mLockPatternUtils).isSecure(anyInt()); SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper; } diff --git a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java index 29758de5555..cc129fd23a8 100644 --- a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java +++ b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java @@ -40,6 +40,7 @@ import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider; import com.android.settings.inputmethod.KeyboardSettingsFeatureProvider; import com.android.settings.localepicker.LocaleFeatureProvider; +import com.android.settings.notification.syncacrossdevices.SyncAcrossDevicesFeatureProvider; import com.android.settings.onboarding.OnboardingFeatureProvider; import com.android.settings.overlay.DockUpdaterFeatureProvider; import com.android.settings.overlay.FeatureFactory; @@ -102,6 +103,7 @@ public class FakeFeatureFactory extends FeatureFactory { public PrivateSpaceLoginFeatureProvider mPrivateSpaceLoginFeatureProvider; public DisplayFeatureProvider mDisplayFeatureProvider; public AudioSharingFeatureProvider mAudioSharingFeatureProvider; + public SyncAcrossDevicesFeatureProvider mSyncAcrossDevicesFeatureProvider; /** Call this in {@code @Before} method of the test class to use fake factory. */ public static FakeFeatureFactory setupForTest() { @@ -154,6 +156,7 @@ public class FakeFeatureFactory extends FeatureFactory { mPrivateSpaceLoginFeatureProvider = mock(PrivateSpaceLoginFeatureProvider.class); mDisplayFeatureProvider = mock(DisplayFeatureProvider.class); mAudioSharingFeatureProvider = mock(AudioSharingFeatureProvider.class); + mSyncAcrossDevicesFeatureProvider = mock(SyncAcrossDevicesFeatureProvider.class); } @Override @@ -341,4 +344,9 @@ public class FakeFeatureFactory extends FeatureFactory { public AudioSharingFeatureProvider getAudioSharingFeatureProvider() { return mAudioSharingFeatureProvider; } + + @Override + public SyncAcrossDevicesFeatureProvider getSyncAcrossDevicesFeatureProvider() { + return mSyncAcrossDevicesFeatureProvider; + } }