From d5fd2b8dde2e7914a785aa9b91b818000cc2c55c Mon Sep 17 00:00:00 2001 From: Angela Wang Date: Fri, 12 Apr 2024 08:21:33 +0000 Subject: [PATCH 01/13] Update logs for BluetoothDetailsHearingAidsPresetsController 1. Change logs in error callback from debug to warning level 2. Remove redundant logs Bug: 334000375 Test: simple change, no tests needed Change-Id: I0fa0d1a693d43b8f774dd87b72839370840f65a8 --- ...thDetailsHearingAidsPresetsController.java | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) 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); From 0c61a9662cb3b5adb0d293bb7d28a34d97d876ca Mon Sep 17 00:00:00 2001 From: Jason Chang Date: Tue, 16 Apr 2024 09:59:55 +0000 Subject: [PATCH 02/13] Fix Talkback announcement for SFPS enrollment issues Remove the redundant "enrolled %" string when talkback announcement. Bug: 324488176 Flag: NONE Test: manually do SFPS enrolling then hearing the sounds to check. Change-Id: I00d1f885bca2609cd954817943a1d4f5625a95b3 --- .../biometrics/fingerprint/FingerprintEnrollEnrolling.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index bbeaf2afb85..23324809e49 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -812,13 +812,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(); From 5c32deeb1686268c592922e88a532a56e2e96485 Mon Sep 17 00:00:00 2001 From: Daniel Norman Date: Mon, 22 Apr 2024 16:52:56 +0000 Subject: [PATCH 03/13] Tightens Accessibility Settings owners. Per discussion with leads, we'd like to ensure that Settings > Accessibility changes are seen and approved by the Android Accessibility team. This change prevents default Settings owners from accidentally approving changes without looping in a11y, while ensuring Settings lead cipson has approval for emergency changes. Change-Id: I914067a4d2616c212309b70fb29679f9acfa7643 --- src/com/android/settings/accessibility/OWNERS | 7 +++++++ 1 file changed, 7 insertions(+) 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 From dae4c04b74fa1054949eb6c348a9641a535cdff6 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Tue, 23 Apr 2024 17:22:24 +0800 Subject: [PATCH 04/13] Get icon id from CellIdentity instead of CellInfo Currently NetworkSelectSettings.forceUpdateConnectedPreferenceCategory() only pass CellIdentity, but not the CellInfo. Get icon id from CellIdentity to fix. Fix: 336238346 Test: visual - on NetworkSelectSettings Change-Id: Ia30a45eef9d55815c8a45629c86cf83a6a480868 --- .../telephony/NetworkOperatorPreference.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) 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 } From 4fffb3ad64c329cc6181b72c65fddd57719ba124 Mon Sep 17 00:00:00 2001 From: Elliot Sisteron Date: Mon, 22 Apr 2024 23:18:19 +0000 Subject: [PATCH 05/13] Fix failure in BiometricsSafetySourceTest Bug: 335886401 Change-Id: I07ce2a1f3b4938348c93208a35b1407960617917 Test: atest BiometricsSafetySourceTest --- .../settings/safetycenter/BiometricsSafetySourceTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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; } From f58acef107d008d77ac94ab736f2f45e1e182149 Mon Sep 17 00:00:00 2001 From: josephpv Date: Mon, 22 Apr 2024 21:49:41 +0000 Subject: [PATCH 06/13] Clear settings task on deleting private space When private space is deleted from private space settings launched from the All apps settings cog the settings sub activity is not cleared and visibile in recents. With this change it is taken care to remove and clear the sub settings task hosting the Private space settings page when private space does not exist and to make sure the activity is removed if it is the last activity in that stack. Bug: 333538964 Test: Manual Change-Id: I22ba8ca8d2947c6604280cf398cd47434038d542 --- .../settings/privatespace/PrivateSpaceDashboardFragment.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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(); } } From 59cb0e1b3086f759b55911fb32f815d3258bb25e Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Mon, 22 Apr 2024 08:35:43 +0000 Subject: [PATCH 07/13] Fix Settings Crash TelephonyManager modify getAllowedCarriers api, cause Setting crash. Add try catch to avoid the issue. Bug: 335751239 Change-Id: I5777c92f8eaf44413f60002e746e9969b3bd9f0d Test: manual test --- .../development/OemUnlockPreferenceController.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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) { From 9e32702dedf1e2f1fb82daf8080273e78e79d7be Mon Sep 17 00:00:00 2001 From: Tetiana Meronyk Date: Tue, 23 Apr 2024 17:32:59 +0000 Subject: [PATCH 08/13] Add separate summary "Owner" for main user on the device Bug: 298008926 Test: visual Change-Id: I78ece410ab939a4727e44e371ae43779e96950e0 --- res/values/strings.xml | 2 ++ src/com/android/settings/users/UserSettings.java | 11 +++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 3e500b97a4c..b99c86edd25 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6942,6 +6942,8 @@ Not set up - Work profile + Owner + Admin You (%s) 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 From 5f391359e33975ea6f52f241ac81dd7900799df9 Mon Sep 17 00:00:00 2001 From: paulzhchen Date: Tue, 23 Apr 2024 11:52:09 +0000 Subject: [PATCH 09/13] Reland add FeatureProvider for SyncAcrossDevices Settings integration. Prior CL ag/26930404 was reverted because an unused class was trimmed in AOSP, causing test cases to throw NoClassDefFoundError exceptions. This CL combines ag/26930404 and ag/26995936 to address the failure. Bug: 330498032 Test: atest FakeFeatureFactory, atest SyncAcrossDevicesPreferenceControllerTest Change-Id: I0a7ebccdebcad20e06d7542d7c4a0005885f393b --- res/values/strings.xml | 3 + res/xml/configure_notification_settings.xml | 5 + .../SyncAcrossDevicesFeatureCallback.java | 38 +++++ .../SyncAcrossDevicesFeatureProvider.java | 32 +++++ .../SyncAcrossDevicesFeatureProviderImpl.java | 34 +++++ .../SyncAcrossDevicesFeatureUpdater.java | 34 +++++ ...SyncAcrossDevicesPreferenceController.java | 100 +++++++++++++ .../settings/overlay/FeatureFactory.kt | 6 + .../settings/overlay/FeatureFactoryImpl.kt | 6 + ...AcrossDevicesPreferenceControllerTest.java | 133 ++++++++++++++++++ .../testutils/FakeFeatureFactory.java | 10 +- .../settings/testutils/FakeFeatureFactory.kt | 3 + .../testutils/FakeFeatureFactory.java | 8 ++ 13 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureCallback.java create mode 100644 src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureProvider.java create mode 100644 src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureProviderImpl.java create mode 100644 src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesFeatureUpdater.java create mode 100644 src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/notification/syncacrossdevices/SyncAcrossDevicesPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 33f186445ba..710ee5d7cf7 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -13182,4 +13182,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/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/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/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; + } } From 49e3afc551b717560011fb858c8add7d8bec2c46 Mon Sep 17 00:00:00 2001 From: tomhsu Date: Wed, 24 Apr 2024 03:30:27 +0000 Subject: [PATCH 10/13] Prevent blank sim label showing on screen. fix: 333642116 Test: Manual test, see b/333642116#14 Change-Id: I776135d8ba609c4ea1fac47cfb765e00f8525497 --- src/com/android/settings/spa/network/SimOnboardingLabelSim.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 } }, ) From 3819299a08e1971af90f209047321e8d569f38e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Tue, 23 Apr 2024 12:11:10 +0200 Subject: [PATCH 11/13] Add support for rules with filter=ALL in ZenModesBackend This is represented as an extra, special value for the AllowChannels field in ZenPolicy, with special treatment. Bug: 327419222 Bug: 331267485 Test: atest ZenModeTest Flag: android.app.modes_ui Change-Id: I44440e40e729d295150b36beda824ddbfa1f170d --- .../settings/notification/modes/ZenMode.java | 74 ++++++++++++--- .../notification/modes/ZenModeTest.java | 90 +++++++++++++++++-- 2 files changed, 148 insertions(+), 16 deletions(-) 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/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); + } } From 34c82e17ec132fd7c7f63ad299d2a5a6809781d2 Mon Sep 17 00:00:00 2001 From: Yuri Lin Date: Tue, 16 Apr 2024 16:32:08 -0400 Subject: [PATCH 12/13] Base classes for general modes settings and rule-specific settings pages. Flag: android.app.modes_ui Bug: 335259054 Test: manual through toy extensions of the base classes Change-Id: I3b950c5556299aaf89fd730c91bf87adfe2f0784 --- .../modes/ZenModesRuleSettingsBase.java | 89 ++++++++++++++ .../modes/ZenModesSettingsBase.java | 113 ++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 src/com/android/settings/notification/modes/ZenModesRuleSettingsBase.java create mode 100644 src/com/android/settings/notification/modes/ZenModesSettingsBase.java 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(); + } + } + } +} From bcd2d58f8880fb9ffd50ed01d62fbcb98f60d720 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Wed, 24 Apr 2024 16:38:53 +0800 Subject: [PATCH 13/13] Highlight the Security menu entry while deeplinking to PS settings Bug: 334792208 Test: visual Change-Id: I6e55a3746e8b9999e4b6e7b32fa47729a061a7e2 --- AndroidManifest.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0f39c6c2c47..31af5990f3c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -5114,6 +5114,8 @@ +