From 02b6139c3c13ee6cd9ce4e279c74d57bfaae1ddf Mon Sep 17 00:00:00 2001 From: Becca Hughes Date: Thu, 28 Mar 2024 21:56:28 +0000 Subject: [PATCH 01/10] Update title of settings page to reflect updated mocks https://screenshot.googleplex.com/BUumUJgm8j5Uzxt Change-Id: I1b8783a34fa7a09f6d112aa477f62cdb785fa9de Test: ondevice Bug: 329705806 --- res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index db68a847033..23c2c6cad68 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -11084,8 +11084,8 @@ ]]> - - Passwords, passkeys & autofill + + Passwords, passkeys & accounts From 13c6b078680bbfa2bbad1fd48ce9b36cd2357254 Mon Sep 17 00:00:00 2001 From: Becca Hughes Date: Tue, 2 Apr 2024 17:53:30 +0000 Subject: [PATCH 02/10] Pass the package name from the intent to the picker fragment When an app uses the legacy autofill intent to launch the settings activity we should pass the package name of the launching app to the picker. If the app is then picked by the user we will return result_ok to the app and if it is not picked we will return result_cancel. Change-Id: I99dbfad2c69ed57ff891c189f726ecc408d9d188 Test: ondevice manual Bug: 324197609 --- .../credentials/CredentialsPickerActivity.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java b/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java index 479a184752f..44dbac0afb7 100644 --- a/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java +++ b/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java @@ -54,7 +54,12 @@ public class CredentialsPickerActivity extends SettingsActivity { @Override protected void onCreate(Bundle savedInstanceState) { - injectFragmentIntoIntent(this, getIntent()); + final String packageName = getCallingPackage(); + final Intent intent = getIntent(); + + intent.putExtra(DefaultCombinedPicker.EXTRA_PACKAGE_NAME, packageName); + injectFragmentIntoIntent(this, intent); + super.onCreate(savedInstanceState); } From 46e5fd8c2807134ada38ee0e04ffff790616fa2c Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 3 Apr 2024 14:27:42 +0000 Subject: [PATCH 03/10] Touchpad: add final strings for tap dragging The title string was approved by UX as it is, so mark it as ready to translate, and add a summary line. Screenshot: go/b-324058706-strings-screenshot Bug: 324058706 Bug: 321978150 Change-Id: Iaa757be16b58b23fba8a6841621ad8000b51c250 Test: Open touchpad settings, check new summary line appears --- res/values/strings.xml | 6 ++++-- res/xml/trackpad_settings.xml | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 0f62fc66c39..ceb3cb8016c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4419,8 +4419,10 @@ Tap to click - - Tap dragging + + Tap dragging + + Tap and drag your finger on the touchpad to move objects Touchpad gestures diff --git a/res/xml/trackpad_settings.xml b/res/xml/trackpad_settings.xml index 6601036073f..fcd43a575c9 100644 --- a/res/xml/trackpad_settings.xml +++ b/res/xml/trackpad_settings.xml @@ -48,10 +48,10 @@ android:order="30" settings:keywords="@string/keywords_trackpad_bottom_right_tap"/> - From 0fee5fa0b0fb4f243e5ec9ee5b5265e4c21be278 Mon Sep 17 00:00:00 2001 From: Joseph Vincent Date: Wed, 3 Apr 2024 08:29:09 +0000 Subject: [PATCH 04/10] Revert^2 "Add waiting screen before PS setup completion" This reverts commit d2c91b587ed2c16a9f60c82d6ce6804e6715df82. Reason for revert: Landing the original cl as the test passes locally with changes and failed test looks to be flaky and not related to the change. Failure is not seen in the ABTD test run results with this CL b/331935387#comment10 Change-Id: I744e11a04b70e265c5dcfe5aa8db7237561f3a08 --- res/layout/private_space_pre_finish_delay.xml | 29 ++++ .../privatespace_main_context_nav.xml | 13 +- res/values/strings.xml | 2 + .../AutoAdvanceSetupFragment.java | 16 ++- .../PrivateSpaceSetLockFragment.java | 2 +- .../PrivateSpaceSetupActivity.java | 2 +- .../SetupPreFinishDelayFragment.java | 136 ++++++++++++++++++ .../privatespace/SetupSuccessFragment.java | 2 - 8 files changed, 188 insertions(+), 14 deletions(-) create mode 100644 res/layout/private_space_pre_finish_delay.xml create mode 100644 src/com/android/settings/privatespace/SetupPreFinishDelayFragment.java diff --git a/res/layout/private_space_pre_finish_delay.xml b/res/layout/private_space_pre_finish_delay.xml new file mode 100644 index 00000000000..3b620bf256a --- /dev/null +++ b/res/layout/private_space_pre_finish_delay.xml @@ -0,0 +1,29 @@ + + + + + \ No newline at end of file diff --git a/res/navigation/privatespace_main_context_nav.xml b/res/navigation/privatespace_main_context_nav.xml index 80cd3997c4b..b027d87e556 100644 --- a/res/navigation/privatespace_main_context_nav.xml +++ b/res/navigation/privatespace_main_context_nav.xml @@ -46,6 +46,13 @@ android:id="@+id/action_retry_profile_creation" app:destination="@id/ps_auto_advance_fragment"/> + + + @@ -64,7 +71,7 @@ android:label="fragment_ps_lock"> + app:destination="@id/ps_pre_finish_delay_fragment"/> - + diff --git a/res/values/strings.xml b/res/values/strings.xml index 0f62fc66c39..f147c5e9cd9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1352,6 +1352,8 @@ Use screen lock Choose new lock + + Just a sec\u2026 All set! diff --git a/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java b/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java index 7d551eeaf89..14627ecbf25 100644 --- a/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java +++ b/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java @@ -60,7 +60,7 @@ public class AutoAdvanceSetupFragment extends InstrumentedFragment { private static final int ANIMATION_DURATION_MILLIS = 500; private static final int HEADER_TEXT_MAX_LINES = 4; private GlifLayout mRootView; - private Handler mHandler; + private static final Handler sHandler = new Handler(Looper.getMainLooper()); private int mScreenTitleIndex; private static final List> HEADER_ILLUSTRATION_PAIRS = ImmutableList.of( @@ -78,7 +78,7 @@ public class AutoAdvanceSetupFragment extends InstrumentedFragment { if (getActivity() != null) { if (++mScreenTitleIndex < HEADER_ILLUSTRATION_PAIRS.size()) { startFadeOutAnimation(); - mHandler.postDelayed(mUpdateScreenResources, DELAY_BETWEEN_SCREENS); + sHandler.postDelayed(mUpdateScreenResources, DELAY_BETWEEN_SCREENS); } else if (PrivateSpaceMaintainer.getInstance(getActivity()) .doesPrivateSpaceExist()) { mMetricsFeatureProvider.action( @@ -133,8 +133,6 @@ public class AutoAdvanceSetupFragment extends InstrumentedFragment { mRootView.getHeaderTextView().setBreakStrategy(BREAK_STRATEGY_SIMPLE); mRootView.getHeaderTextView().setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE); updateHeaderAndIllustration(); - mHandler = new Handler(Looper.getMainLooper()); - mHandler.postDelayed(mUpdateScreenResources, DELAY_BETWEEN_SCREENS); OnBackPressedCallback callback = new OnBackPressedCallback(true /* enabled by default */) { @Override @@ -155,12 +153,16 @@ public class AutoAdvanceSetupFragment extends InstrumentedFragment { @Override public void onDestroy() { - if (mHandler != null) { - mHandler.removeCallbacks(mUpdateScreenResources); - } + sHandler.removeCallbacks(mUpdateScreenResources); super.onDestroy(); } + @Override + public void onResume() { + sHandler.postDelayed(mUpdateScreenResources, DELAY_BETWEEN_SCREENS); + super.onResume(); + } + @Override public int getMetricsCategory() { return SettingsEnums.PRIVATE_SPACE_SETUP_SPACE_CREATION; diff --git a/src/com/android/settings/privatespace/PrivateSpaceSetLockFragment.java b/src/com/android/settings/privatespace/PrivateSpaceSetLockFragment.java index 399c2c82fbb..11f4bcb74e3 100644 --- a/src/com/android/settings/privatespace/PrivateSpaceSetLockFragment.java +++ b/src/com/android/settings/privatespace/PrivateSpaceSetLockFragment.java @@ -97,7 +97,7 @@ public class PrivateSpaceSetLockFragment extends InstrumentedFragment { getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_USE_SCREEN_LOCK); // Simply Use default screen lock. No need to handle NavHostFragment.findNavController(PrivateSpaceSetLockFragment.this) - .navigate(R.id.action_lock_success_fragment); + .navigate(R.id.action_pre_finish_delay_fragment); }; } diff --git a/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java b/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java index ec7132adf0e..4cbcac74646 100644 --- a/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java +++ b/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java @@ -60,7 +60,7 @@ public class PrivateSpaceSetupActivity extends FragmentActivity { @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (requestCode == SET_LOCK_ACTION && resultCode == RESULT_OK) { - mNavHostFragment.getNavController().navigate(R.id.action_success_fragment); + mNavHostFragment.getNavController().navigate(R.id.action_pre_finish_delay_fragment); } else if (requestCode == ACCOUNT_LOGIN_ACTION) { if (resultCode == RESULT_OK) { mMetricsFeatureProvider.action( diff --git a/src/com/android/settings/privatespace/SetupPreFinishDelayFragment.java b/src/com/android/settings/privatespace/SetupPreFinishDelayFragment.java new file mode 100644 index 00000000000..aee8512ec9b --- /dev/null +++ b/src/com/android/settings/privatespace/SetupPreFinishDelayFragment.java @@ -0,0 +1,136 @@ +/* + * 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.privatespace; + +import static android.content.Intent.ACTION_PROFILE_INACCESSIBLE; +import static android.content.Intent.ACTION_PROFILE_UNAVAILABLE; + +import android.app.settings.SettingsEnums; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.navigation.fragment.NavHostFragment; + +import com.android.settings.R; +import com.android.settings.core.InstrumentedFragment; + +import com.google.android.setupdesign.GlifLayout; + +public class SetupPreFinishDelayFragment extends InstrumentedFragment { + private static final String TAG = "SetupPreFinishDelayFrag"; + private static final Handler sHandler = new Handler(Looper.getMainLooper()); + private static final int MAX_DELAY_BEFORE_SETUP_FINISH = 5000; + private boolean mActionProfileUnavailable; + private boolean mActionProfileInaccessible; + + protected final BroadcastReceiver mBroadcastReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent == null) { + return; + } + String action = intent.getAction(); + Log.i(TAG, "Received broadcast: " + action); + if (ACTION_PROFILE_UNAVAILABLE.equals(action)) { + mActionProfileUnavailable = true; + } else if (ACTION_PROFILE_INACCESSIBLE.equals(action)) { + mActionProfileInaccessible = true; + } + if (mActionProfileUnavailable && mActionProfileInaccessible) { + showSetupSuccessScreen(); + } + } + }; + + private Runnable mRunnable = + () -> { + showSetupSuccessScreen(); + }; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures()) { + super.onCreate(savedInstanceState); + } + } + + @NonNull + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + GlifLayout rootView = + (GlifLayout) + inflater.inflate(R.layout.private_space_pre_finish_delay, container, false); + OnBackPressedCallback callback = + new OnBackPressedCallback(true /* enabled by default */) { + @Override + public void handleOnBackPressed() { + // Handle the back button event. We intentionally don't want to allow back + // button to work in this screen during the setup flow. + } + }; + requireActivity().getOnBackPressedDispatcher().addCallback(this, callback); + if (savedInstanceState == null) { + // TODO(b/307729746): Add a test to verify PS is locked after setup completion. + PrivateSpaceMaintainer.getInstance(getActivity()).lockPrivateSpace(); + } + return rootView; + } + + @Override + public void onPause() { + super.onPause(); + getActivity().unregisterReceiver(mBroadcastReceiver); + } + + @Override + public void onResume() { + super.onResume(); + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(ACTION_PROFILE_UNAVAILABLE); + intentFilter.addAction(ACTION_PROFILE_INACCESSIBLE); + getActivity().registerReceiver(mBroadcastReceiver, intentFilter); + sHandler.postDelayed(mRunnable, MAX_DELAY_BEFORE_SETUP_FINISH); + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.PRIVATE_SPACE_SETUP_PRE_FINISH; + } + + private void showSetupSuccessScreen() { + sHandler.removeCallbacks(mRunnable); + NavHostFragment.findNavController(SetupPreFinishDelayFragment.this) + .navigate(R.id.action_success_fragment); + } +} diff --git a/src/com/android/settings/privatespace/SetupSuccessFragment.java b/src/com/android/settings/privatespace/SetupSuccessFragment.java index c0a2cd4d87a..90be48ea9a4 100644 --- a/src/com/android/settings/privatespace/SetupSuccessFragment.java +++ b/src/com/android/settings/privatespace/SetupSuccessFragment.java @@ -87,8 +87,6 @@ public class SetupSuccessFragment extends InstrumentedFragment { if (activity != null) { mMetricsFeatureProvider.action( getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_DONE); - //TODO(b/307729746): Add a test to verify PS is locked after setup completion. - PrivateSpaceMaintainer.getInstance(activity).lockPrivateSpace(); Intent allAppsIntent = new Intent(Intent.ACTION_ALL_APPS); ResolveInfo resolveInfo = activity.getPackageManager() From 1791ce216b93ad781104a9eba3f54f9ef4da0bd0 Mon Sep 17 00:00:00 2001 From: josephpv Date: Wed, 13 Mar 2024 14:53:31 +0000 Subject: [PATCH 05/10] Support face unlock for PS if class2 biometric and above With this change it is checked if face unlock is supported as a class2 or class3 biometrics on the device. If face is convenience biometrics then face unlock for private space controller is not added. Bug: 329044103 Test: atest UtilsTest and verified Face unlock is not added if face is convenience Change-Id: I6e1a6557774be1173ad3ee7ff7b14d51f9fe1716 --- src/com/android/settings/Utils.java | 19 +++++ .../PrivateSpaceFacePreferenceController.java | 7 +- ...eSpaceFingerprintPreferenceController.java | 7 +- .../onelock/UseOneLockSettingsFragment.java | 5 +- .../src/com/android/settings/UtilsTest.java | 70 +++++++++++++++++++ 5 files changed, 100 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index d44d727390c..e067c730d60 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -54,8 +54,10 @@ import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.VectorDrawable; +import android.hardware.biometrics.SensorProperties; import android.hardware.face.Face; import android.hardware.face.FaceManager; +import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.net.ConnectivityManager; @@ -927,6 +929,23 @@ public final class Utils extends com.android.settingslib.Utils { return hasFingerprintHardware(context) && hasFaceHardware(context); } + /** + * Return true if face is supported as Class 2 biometrics and above on the device, false + * otherwise. + */ + public static boolean isFaceNotConvenienceBiometric(@NonNull Context context) { + FaceManager faceManager = getFaceManagerOrNull(context); + if (faceManager != null) { + final List faceProperties = + faceManager.getSensorPropertiesInternal(); + if (!faceProperties.isEmpty()) { + final FaceSensorPropertiesInternal props = faceProperties.get(0); + return props.sensorStrength != SensorProperties.STRENGTH_CONVENIENCE; + } + } + return false; + } + /** * Launches an intent which may optionally have a user id defined. * @param fragment Fragment to use to launch the activity. diff --git a/src/com/android/settings/privatespace/onelock/PrivateSpaceFacePreferenceController.java b/src/com/android/settings/privatespace/onelock/PrivateSpaceFacePreferenceController.java index 583a093dbc2..6ccaacb4b4d 100644 --- a/src/com/android/settings/privatespace/onelock/PrivateSpaceFacePreferenceController.java +++ b/src/com/android/settings/privatespace/onelock/PrivateSpaceFacePreferenceController.java @@ -64,8 +64,8 @@ public class PrivateSpaceFacePreferenceController extends BiometricFaceStatusPre @Override public int getAvailabilityStatus() { return android.os.Flags.allowPrivateProfile() - && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace() - && android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace() + && android.multiuser.Flags.enablePrivateSpaceFeatures() ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } @@ -87,7 +87,8 @@ public class PrivateSpaceFacePreferenceController extends BiometricFaceStatusPre public void displayPreference(@NonNull PreferenceScreen screen) { super.displayPreference(screen); Preference preference = screen.findPreference(getPreferenceKey()); - if (!Utils.isMultipleBiometricsSupported(mContext)) { + if (!Utils.isMultipleBiometricsSupported(mContext) + && Utils.isFaceNotConvenienceBiometric(mContext)) { preference.setTitle(R.string.private_space_face_title); } } diff --git a/src/com/android/settings/privatespace/onelock/PrivateSpaceFingerprintPreferenceController.java b/src/com/android/settings/privatespace/onelock/PrivateSpaceFingerprintPreferenceController.java index f88c9facccc..dd6518af67c 100644 --- a/src/com/android/settings/privatespace/onelock/PrivateSpaceFingerprintPreferenceController.java +++ b/src/com/android/settings/privatespace/onelock/PrivateSpaceFingerprintPreferenceController.java @@ -66,8 +66,8 @@ public class PrivateSpaceFingerprintPreferenceController @Override public int getAvailabilityStatus() { return android.os.Flags.allowPrivateProfile() - && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace() - && android.multiuser.Flags.enablePrivateSpaceFeatures() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace() + && android.multiuser.Flags.enablePrivateSpaceFeatures() ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } @@ -89,7 +89,8 @@ public class PrivateSpaceFingerprintPreferenceController public void displayPreference(@NonNull PreferenceScreen screen) { super.displayPreference(screen); Preference preference = screen.findPreference(getPreferenceKey()); - if (!Utils.isMultipleBiometricsSupported(mContext)) { + if (!Utils.isMultipleBiometricsSupported(mContext) + || !Utils.isFaceNotConvenienceBiometric(mContext)) { preference.setTitle(R.string.private_space_fingerprint_title); } } diff --git a/src/com/android/settings/privatespace/onelock/UseOneLockSettingsFragment.java b/src/com/android/settings/privatespace/onelock/UseOneLockSettingsFragment.java index ce017e31152..bf5748e6e2b 100644 --- a/src/com/android/settings/privatespace/onelock/UseOneLockSettingsFragment.java +++ b/src/com/android/settings/privatespace/onelock/UseOneLockSettingsFragment.java @@ -73,13 +73,14 @@ public class UseOneLockSettingsFragment extends DashboardFragment { final List controllers = new ArrayList<>(); controllers.add(new UseOneLockControllerSwitch(context, this)); controllers.add(new PrivateSpaceLockController(context, this)); - if (Utils.isMultipleBiometricsSupported(context)) { + boolean isFaceAuthAllowed = Utils.isFaceNotConvenienceBiometric(context); + if (Utils.isMultipleBiometricsSupported(context) && isFaceAuthAllowed) { controllers.add(new FaceFingerprintUnlockController(context, getSettingsLifecycle())); } else if (Utils.hasFingerprintHardware(context)) { controllers.add( new PrivateSpaceFingerprintPreferenceController( context, "private_space_biometrics", getSettingsLifecycle())); - } else if (Utils.hasFaceHardware(context)) { + } else if (Utils.hasFaceHardware(context) && isFaceAuthAllowed) { controllers.add( new PrivateSpaceFacePreferenceController( context, "private_space_biometrics", getSettingsLifecycle())); diff --git a/tests/robotests/src/com/android/settings/UtilsTest.java b/tests/robotests/src/com/android/settings/UtilsTest.java index 0c555da2075..0c57b014506 100644 --- a/tests/robotests/src/com/android/settings/UtilsTest.java +++ b/tests/robotests/src/com/android/settings/UtilsTest.java @@ -16,6 +16,10 @@ package com.android.settings; +import static android.hardware.biometrics.SensorProperties.STRENGTH_CONVENIENCE; +import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; +import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertNull; @@ -43,6 +47,9 @@ import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.VectorDrawable; +import android.hardware.face.FaceManager; +import android.hardware.face.FaceSensorProperties; +import android.hardware.face.FaceSensorPropertiesInternal; import android.net.ConnectivityManager; import android.net.LinkAddress; import android.net.LinkProperties; @@ -433,6 +440,69 @@ public class UtilsTest { assertNull(confirmCredentialString); } + @Test + public void isFaceNotConvenienceBiometric_faceStrengthStrong_shouldReturnTrue() { + FaceManager mockFaceManager = mock(FaceManager.class); + when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mockFaceManager); + doReturn(true).when(mPackageManager).hasSystemFeature(anyString()); + List props = List.of(new FaceSensorPropertiesInternal( + 0 /* id */, + STRENGTH_STRONG, + 1 /* maxTemplatesAllowed */, + new ArrayList<>() /* componentInfo */, + FaceSensorProperties.TYPE_UNKNOWN, + true /* supportsFaceDetection */, + true /* supportsSelfIllumination */, + false /* resetLockoutRequiresChallenge */)); + doReturn(props).when(mockFaceManager).getSensorPropertiesInternal(); + + assertThat(Utils.isFaceNotConvenienceBiometric(mContext)).isTrue(); + } + + @Test + public void isFaceNotConvenienceBiometric_faceStrengthWeak_shouldReturnTrue() { + FaceManager mockFaceManager = mock(FaceManager.class); + when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mockFaceManager); + doReturn(true).when(mPackageManager).hasSystemFeature(anyString()); + List props = List.of(new FaceSensorPropertiesInternal( + 0 /* id */, + STRENGTH_WEAK, + 1 /* maxTemplatesAllowed */, + new ArrayList<>() /* componentInfo */, + FaceSensorProperties.TYPE_UNKNOWN, + true /* supportsFaceDetection */, + true /* supportsSelfIllumination */, + false /* resetLockoutRequiresChallenge */)); + doReturn(props).when(mockFaceManager).getSensorPropertiesInternal(); + + assertThat(Utils.isFaceNotConvenienceBiometric(mContext)).isTrue(); + } + + @Test + public void isFaceNotConvenienceBiometric_faceStrengthConvenience_shouldReturnFalse() { + FaceManager mockFaceManager = mock(FaceManager.class); + when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mockFaceManager); + doReturn(true).when(mPackageManager).hasSystemFeature(anyString()); + List props = List.of(new FaceSensorPropertiesInternal( + 0 /* id */, + STRENGTH_CONVENIENCE, + 1 /* maxTemplatesAllowed */, + new ArrayList<>() /* componentInfo */, + FaceSensorProperties.TYPE_UNKNOWN, + true /* supportsFaceDetection */, + true /* supportsSelfIllumination */, + false /* resetLockoutRequiresChallenge */)); + doReturn(props).when(mockFaceManager).getSensorPropertiesInternal(); + + assertThat(Utils.isFaceNotConvenienceBiometric(mContext)).isFalse(); + } + + @Test + public void isFaceNotConvenienceBiometric_faceManagerNull_shouldReturnFalse() { + when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(null); + assertThat(Utils.isFaceNotConvenienceBiometric(mContext)).isFalse(); + } + private void setUpForConfirmCredentialString(boolean isEffectiveUserManagedProfile) { when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager); when(mMockUserManager.getCredentialOwnerProfile(USER_ID)).thenReturn(USER_ID); From 4e32375f3d87a76499d085b2b3d22202b05af187 Mon Sep 17 00:00:00 2001 From: josephpv Date: Tue, 2 Apr 2024 12:38:19 +0000 Subject: [PATCH 06/10] Use canAddPrivateProfile() to enable PS delete from Reset This change adds canAddPrivateProfile() check to enable/disable Private space delete controller in Reset options. Matches the availability to delete private space from reset options with the conditions used to show the private space entry point. In Settings Reset options preference to delete private space will be shown only when private space creation is allowed on the device or if private space already exists on the device. Bug: 330396315 Test: ResetOptionsDeletePrivateSpaceControllerTest Change-Id: I63232556f7927aeb07b73e8732bbb8b1d2423456 --- .../privatespace/PrivateSpaceMaintainer.java | 8 +++ .../PrivateSpaceSafetySource.java | 4 +- ...etOptionsDeletePrivateSpaceController.java | 8 ++- ...tionsDeletePrivateSpaceControllerTest.java | 16 ++++- .../PrivateSpaceMaintainerTest.java | 61 +++++++++++++++++++ 5 files changed, 90 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java index a0c4cbecaed..6a9ea9dcbca 100644 --- a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java +++ b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java @@ -287,6 +287,14 @@ public class PrivateSpaceMaintainer { } } + /** + * Returns true if private profile can be added to the device or if private space already + * exists, false otherwise. + */ + public boolean isPrivateSpaceEntryPointEnabled() { + return mUserManager.canAddPrivateProfile() || doesPrivateSpaceExist(); + } + /** Returns true if private space exists and is running, otherwise returns false */ @VisibleForTesting synchronized boolean isPrivateProfileRunning() { diff --git a/src/com/android/settings/privatespace/PrivateSpaceSafetySource.java b/src/com/android/settings/privatespace/PrivateSpaceSafetySource.java index 3272f125756..9a018539b0e 100644 --- a/src/com/android/settings/privatespace/PrivateSpaceSafetySource.java +++ b/src/com/android/settings/privatespace/PrivateSpaceSafetySource.java @@ -52,9 +52,7 @@ public final class PrivateSpaceSafetySource { // Do not add the entry point when // -Private Profile is not present and // -Private Profile cannot be added. - if (!privateSpaceMaintainer.doesPrivateSpaceExist() - && userManager != null - && !userManager.canAddPrivateProfile()) { + if (!privateSpaceMaintainer.isPrivateSpaceEntryPointEnabled()) { Log.i(TAG, "Private Space not allowed for user " + context.getUser()); return; } diff --git a/src/com/android/settings/privatespace/delete/ResetOptionsDeletePrivateSpaceController.java b/src/com/android/settings/privatespace/delete/ResetOptionsDeletePrivateSpaceController.java index 274bb2b184b..6e1f0df96d5 100644 --- a/src/com/android/settings/privatespace/delete/ResetOptionsDeletePrivateSpaceController.java +++ b/src/com/android/settings/privatespace/delete/ResetOptionsDeletePrivateSpaceController.java @@ -59,10 +59,9 @@ public class ResetOptionsDeletePrivateSpaceController extends BasePreferenceCont @Override public int getAvailabilityStatus() { - // TODO(b/330396315) : use canAddPrivateProfile() to check if private space is supported - // on the device return android.multiuser.Flags.enablePrivateSpaceFeatures() && android.multiuser.Flags.deletePrivateSpaceFromReset() + && isPrivateSpaceEntryPointEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } @@ -107,6 +106,11 @@ public class ResetOptionsDeletePrivateSpaceController extends BasePreferenceCont return mHostFragment.getFragmentManager(); } + @VisibleForTesting + boolean isPrivateSpaceEntryPointEnabled() { + return PrivateSpaceMaintainer.getInstance(mContext).isPrivateSpaceEntryPointEnabled(); + } + /* Dialog shown when deleting private space from Reset Options. */ public static class DeletePrivateSpaceDialogFragment extends InstrumentedDialogFragment { private static final String TAG = "PrivateSpaceResetFrag"; diff --git a/tests/robotests/src/com/android/settings/privatespace/delete/ResetOptionsDeletePrivateSpaceControllerTest.java b/tests/robotests/src/com/android/settings/privatespace/delete/ResetOptionsDeletePrivateSpaceControllerTest.java index ebff07a44d3..a17859ad9ae 100644 --- a/tests/robotests/src/com/android/settings/privatespace/delete/ResetOptionsDeletePrivateSpaceControllerTest.java +++ b/tests/robotests/src/com/android/settings/privatespace/delete/ResetOptionsDeletePrivateSpaceControllerTest.java @@ -103,11 +103,23 @@ public class ResetOptionsDeletePrivateSpaceControllerTest { } @Test - public void getAvailabilityStatus_flagsEnabled_returnsAvailable() { + public void getAvailabilityStatus_flagsEnabledCanAddProfile_returnsAvailable() { mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_DELETE_PRIVATE_SPACE_FROM_RESET); + ResetOptionsDeletePrivateSpaceController spyController = spy(mController); + doReturn(true).when(spyController).isPrivateSpaceEntryPointEnabled(); - assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + assertThat(spyController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailabilityStatus_flagsEnabledCannotAddProfile_returnsUnsupported() { + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); + mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_DELETE_PRIVATE_SPACE_FROM_RESET); + ResetOptionsDeletePrivateSpaceController spyController = spy(mController); + doReturn(false).when(spyController).isPrivateSpaceEntryPointEnabled(); + + assertThat(spyController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); } @Test diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java index 50f67d3c55b..98797d5cfcd 100644 --- a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java +++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java @@ -25,12 +25,18 @@ import static com.android.settings.privatespace.PrivateSpaceMaintainer.PRIVATE_S import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import android.app.ActivityManager; import android.app.IActivityManager; import android.content.ContentResolver; import android.content.Context; import android.os.Flags; import android.os.RemoteException; +import android.os.UserManager; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; @@ -300,6 +306,61 @@ public class PrivateSpaceMaintainerTest { .isEqualTo(privateSpaceAutLockValue); } + @Test + public void isPrivateSpaceEntryPointEnabled_psExistCanAddProfileTrue_returnsTrue() { + mSetFlagsRule.enableFlags( + Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); + assumeTrue(mContext.getSystemService(UserManager.class).canAddPrivateProfile()); + PrivateSpaceMaintainer privateSpaceMaintainer = + PrivateSpaceMaintainer.getInstance(mContext); + privateSpaceMaintainer.createPrivateSpace(); + assertThat(privateSpaceMaintainer.doesPrivateSpaceExist()).isTrue(); + + assertThat(privateSpaceMaintainer.isPrivateSpaceEntryPointEnabled()).isTrue(); + } + + @Test + public void isPrivateSpaceEntryPointEnabled_psNotExistsCanAddProfileTrue_returnsTrue() { + mSetFlagsRule.enableFlags( + Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); + assumeTrue(mContext.getSystemService(UserManager.class).canAddPrivateProfile()); + PrivateSpaceMaintainer privateSpaceMaintainer = + PrivateSpaceMaintainer.getInstance(mContext); + privateSpaceMaintainer.deletePrivateSpace(); + assertThat(privateSpaceMaintainer.doesPrivateSpaceExist()).isFalse(); + + assertThat(privateSpaceMaintainer.isPrivateSpaceEntryPointEnabled()).isTrue(); + } + + @Test + public void isPrivateSpaceEntryPointEnabled_psExistsCanAddProfileFalse_returnsTrue() { + mSetFlagsRule.enableFlags( + Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); + assumeFalse(mContext.getSystemService(UserManager.class).canAddPrivateProfile()); + PrivateSpaceMaintainer privateSpaceMaintainer = + spy(PrivateSpaceMaintainer.getInstance(mContext)); + when(privateSpaceMaintainer.doesPrivateSpaceExist()).thenReturn(true); + + assertThat(privateSpaceMaintainer.isPrivateSpaceEntryPointEnabled()).isTrue(); + } + + @Test + public void isPrivateSpaceEntryPointEnabled_psNotExistsCanAddProfileFalse_returnsFalse() { + mSetFlagsRule.enableFlags( + Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); + assumeFalse(mContext.getSystemService(UserManager.class).canAddPrivateProfile()); + PrivateSpaceMaintainer privateSpaceMaintainer = + PrivateSpaceMaintainer.getInstance(mContext); + privateSpaceMaintainer.deletePrivateSpace(); + assertThat(privateSpaceMaintainer.doesPrivateSpaceExist()).isFalse(); + + assertThat(privateSpaceMaintainer.isPrivateSpaceEntryPointEnabled()).isFalse(); + } + private int getSecureUserSetupComplete() { PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext); From 1108e206f4e4f1613a0e8b31ffa247afee7f2671 Mon Sep 17 00:00:00 2001 From: josephpv Date: Wed, 3 Apr 2024 22:23:05 +0000 Subject: [PATCH 07/10] Fix for test failure in PrivateSpaceMaintainerTest Bug: 332488382 Test: atest PrivateSpaceMaintainerTest Change-Id: Id6a18305f03f45c580ada482a352d1d7ad3dd788 --- .../PrivateSpaceMaintainerTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java index 50f67d3c55b..b206c7d36e6 100644 --- a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java +++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java @@ -72,6 +72,8 @@ public class PrivateSpaceMaintainerTest { /** Tests that {@link PrivateSpaceMaintainer#deletePrivateSpace()} deletes PS when PS exists. */ @Test public void deletePrivateSpace_psExists_deletesPS() { + mSetFlagsRule.enableFlags( + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext); privateSpaceMaintainer.createPrivateSpace(); @@ -88,6 +90,8 @@ public class PrivateSpaceMaintainerTest { */ @Test public void deletePrivateSpace_psDoesNotExist_returnsNoPSError() { + mSetFlagsRule.enableFlags( + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext); ErrorDeletingPrivateSpace errorDeletingPrivateSpace = @@ -100,6 +104,8 @@ public class PrivateSpaceMaintainerTest { /** Tests that {@link PrivateSpaceMaintainer#createPrivateSpace()} when PS exists creates PS. */ @Test public void createPrivateSpace_psDoesNotExist_createsPS() { + mSetFlagsRule.enableFlags( + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext); privateSpaceMaintainer.deletePrivateSpace(); @@ -113,6 +119,8 @@ public class PrivateSpaceMaintainerTest { */ @Test public void createPrivateSpace_psExists_returnsFalse() { + mSetFlagsRule.enableFlags( + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext); privateSpaceMaintainer.deletePrivateSpace(); @@ -127,6 +135,8 @@ public class PrivateSpaceMaintainerTest { */ @Test public void createPrivateSpace_psDoesNotExist_resetsHidePSSettings() { + mSetFlagsRule.enableFlags( + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext); Settings.Secure.putInt( @@ -165,6 +175,8 @@ public class PrivateSpaceMaintainerTest { */ @Test public void createPrivateSpace_psExists_doesNotResetHidePSSettings() { + mSetFlagsRule.enableFlags( + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext); privateSpaceMaintainer.createPrivateSpace(); @@ -184,6 +196,8 @@ public class PrivateSpaceMaintainerTest { */ @Test public void lockPrivateSpace_psExistsAndPrivateProfileRunning_locksCreatedPrivateSpace() { + mSetFlagsRule.enableFlags( + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext); privateSpaceMaintainer.createPrivateSpace(); @@ -200,6 +214,8 @@ public class PrivateSpaceMaintainerTest { */ @Test public void lockPrivateSpace_psExistsAndPrivateProfileNotRunning_returnsFalse() { + mSetFlagsRule.enableFlags( + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext); privateSpaceMaintainer.createPrivateSpace(); @@ -220,6 +236,8 @@ public class PrivateSpaceMaintainerTest { */ @Test public void lockPrivateSpace_psDoesNotExist_returnsFalse() { + mSetFlagsRule.enableFlags( + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext); assertThat(privateSpaceMaintainer.doesPrivateSpaceExist()).isFalse(); @@ -232,6 +250,8 @@ public class PrivateSpaceMaintainerTest { */ @Test public void createPrivateSpace_psDoesNotExist_setsUserSetupComplete() { + mSetFlagsRule.enableFlags( + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext); privateSpaceMaintainer.createPrivateSpace(); @@ -244,6 +264,8 @@ public class PrivateSpaceMaintainerTest { */ @Test public void createPrivateSpace_pSExists_doesNotChangeUserSetupSetting() { + mSetFlagsRule.enableFlags( + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext); privateSpaceMaintainer.createPrivateSpace(); From 7851c33bbda1b6ee8915fb431e9202d754bd7d98 Mon Sep 17 00:00:00 2001 From: Olivier Nshimiye Date: Thu, 28 Mar 2024 11:06:03 +0000 Subject: [PATCH 08/10] Remove 'Hide Sensitive Notifications' item from PS settings page Remove the hide sensitive notifications toggle, but keep private space sensitive notifications always hidden on lockscreen Bug: 329657118 Test: atest PrivateSpaceMaintainerTest Test: Manual - verified that PS sensitive notifications are always hidden on lockscreen. Change-Id: Id4229c11a2c5a3a4d3fedaf710c51f25af64bf86 --- res/values/strings.xml | 6 - res/xml/private_space_settings.xml | 11 -- ...SpaceSensitiveNotificationsController.java | 106 ----------- .../privatespace/PrivateSpaceMaintainer.java | 4 +- ...eSensitiveNotificationsControllerTest.java | 164 ------------------ .../PrivateSpaceMaintainerTest.java | 5 +- 6 files changed, 6 insertions(+), 290 deletions(-) delete mode 100644 src/com/android/settings/privatespace/HidePrivateSpaceSensitiveNotificationsController.java delete mode 100644 tests/unit/src/com/android/settings/privatespace/HidePrivateSpaceSensitiveNotificationsControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 3268b2f06dd..9fbce465110 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1380,12 +1380,6 @@ Set a password for your private space Set a pattern for your private space - - Apps and notifications - - Sensitive notifications on lock screen - - Show sensitive content when private space is unlocked Create a Google Account to help keep your data private diff --git a/res/xml/private_space_settings.xml b/res/xml/private_space_settings.xml index f9795990903..b1233b9d797 100644 --- a/res/xml/private_space_settings.xml +++ b/res/xml/private_space_settings.xml @@ -59,17 +59,6 @@ - - - - - - diff --git a/src/com/android/settings/privatespace/HidePrivateSpaceSensitiveNotificationsController.java b/src/com/android/settings/privatespace/HidePrivateSpaceSensitiveNotificationsController.java deleted file mode 100644 index 6cb54a1cd89..00000000000 --- a/src/com/android/settings/privatespace/HidePrivateSpaceSensitiveNotificationsController.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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.privatespace; - -import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; - -import android.content.Context; -import android.os.UserHandle; -import android.provider.Settings; - -import androidx.annotation.NonNull; - -import com.android.settings.core.TogglePreferenceController; - -import java.util.Objects; - -/** - * A controller object for sensitive notifications in Private Space settings page. - */ -public class HidePrivateSpaceSensitiveNotificationsController extends TogglePreferenceController { - private final PrivateSpaceMaintainer mPrivateSpaceMaintainer; - private final UserHandle mPrivateProfileId; - public static final int ENABLED = 1; - public static final int DISABLED = 0; - private static final int DEVICE_SENSITIVE_NOTIFICATIONS_DEFAULT = ENABLED; - private static final int DEVICE_LOCK_SCREEN_NOTIFICATIONS_DEFAULT = ENABLED; - private static final int PRIVATE_SPACE_SENSITIVE_NOTIFICATIONS_DEFAULT = DISABLED; - - public HidePrivateSpaceSensitiveNotificationsController(@NonNull Context context, - @NonNull String preferenceKey) { - super(context, preferenceKey); - mPrivateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(context); - mPrivateProfileId = Objects.requireNonNull( - mPrivateSpaceMaintainer.getPrivateProfileHandle()); - } - - @Override - public int getAvailabilityStatus() { - if (!android.os.Flags.allowPrivateProfile() - || !android.multiuser.Flags.enablePsSensitiveNotificationsToggle() - || !android.multiuser.Flags.enablePrivateSpaceFeatures() - || !mPrivateSpaceMaintainer.doesPrivateSpaceExist()) { - return UNSUPPORTED_ON_DEVICE; - } - if (!getLockscreenNotificationsEnabled(mContext) - || !getLockscreenSensitiveNotificationsEnabledOnDevice(mContext)) { - return DISABLED_DEPENDENT_SETTING; - } - return AVAILABLE; - } - - @Override - public boolean isChecked() { - return Settings.Secure.getIntForUser(mContext.getContentResolver(), - LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, - PRIVATE_SPACE_SENSITIVE_NOTIFICATIONS_DEFAULT, mPrivateProfileId.getIdentifier()) - != DISABLED; - } - - @Override - public boolean setChecked(boolean isChecked) { - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, - isChecked ? ENABLED : DISABLED, mPrivateProfileId.getIdentifier()); - return true; - } - - @Override - public int getSliceHighlightMenuRes() { - return 0; - } - - /** - * If notifications are disabled on the device, the toggle for private space sensitive - * notifications should be unavailable. - */ - private static boolean getLockscreenNotificationsEnabled(Context context) { - return Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, - DEVICE_LOCK_SCREEN_NOTIFICATIONS_DEFAULT) != DISABLED; - } - - /** - * If sensitive notifications are hidden on the device, they should be hidden for private space - * also. - */ - private static boolean getLockscreenSensitiveNotificationsEnabledOnDevice(Context context) { - return Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, - DEVICE_SENSITIVE_NOTIFICATIONS_DEFAULT) != DISABLED; - } -} diff --git a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java index 6a9ea9dcbca..300cbdbb2fb 100644 --- a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java +++ b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java @@ -65,6 +65,8 @@ public class PrivateSpaceMaintainer { @Settings.Secure.PrivateSpaceAutoLockOption public static final int PRIVATE_SPACE_AUTO_LOCK_DEFAULT_VAL = PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART; + /** Default value for the hide private space sensitive notifications on lockscreen. */ + public static final int HIDE_PRIVATE_SPACE_SENSITIVE_NOTIFICATIONS_DISABLED_VAL = 0; public enum ErrorDeletingPrivateSpace { DELETE_PS_ERROR_NONE, @@ -316,7 +318,7 @@ public class PrivateSpaceMaintainer { private void setPrivateSpaceSensitiveNotificationsDefaultValue() { Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, - HidePrivateSpaceSensitiveNotificationsController.DISABLED, + HIDE_PRIVATE_SPACE_SENSITIVE_NOTIFICATIONS_DISABLED_VAL, mUserHandle.getIdentifier()); } diff --git a/tests/unit/src/com/android/settings/privatespace/HidePrivateSpaceSensitiveNotificationsControllerTest.java b/tests/unit/src/com/android/settings/privatespace/HidePrivateSpaceSensitiveNotificationsControllerTest.java deleted file mode 100644 index 88503a525b6..00000000000 --- a/tests/unit/src/com/android/settings/privatespace/HidePrivateSpaceSensitiveNotificationsControllerTest.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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.privatespace; - -import static com.android.settings.core.BasePreferenceController.AVAILABLE; -import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING; -import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; - -import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assume.assumeTrue; -import static org.mockito.Mockito.spy; - -import android.content.ContentResolver; -import android.content.Context; -import android.platform.test.flag.junit.SetFlagsRule; -import android.provider.Settings; - -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; - -/** - * Tests for HidePrivateSpaceSensitiveNotificationsController. - * Run as {@code atest SettingsUnitTests:HidePrivateSpaceSensitiveNotificationsControllerTest} - */ -@RunWith(AndroidJUnit4.class) -public class HidePrivateSpaceSensitiveNotificationsControllerTest { - @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); - - private Context mContext; - private HidePrivateSpaceSensitiveNotificationsController - mHidePrivateSpaceSensitiveNotificationsController; - @Mock - private ContentResolver mContentResolver; - private int mOriginalDeviceSensitiveNotifValue; - private int mOriginalDeviceNotifValue; - private int mOriginalPsSensitiveNotifValue; - private int mPrivateProfileId; - - @Before - public void setUp() { - mContext = spy(ApplicationProvider.getApplicationContext()); - mContentResolver = mContext.getContentResolver(); - assumeTrue(PrivateSpaceMaintainer.getInstance(mContext).doesPrivateSpaceExist()); - - mSetFlagsRule.enableFlags( - android.multiuser.Flags.FLAG_ENABLE_PS_SENSITIVE_NOTIFICATIONS_TOGGLE); - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); - - mPrivateProfileId = PrivateSpaceMaintainer.getInstance( - mContext).getPrivateProfileHandle().getIdentifier(); - - mOriginalDeviceSensitiveNotifValue = Settings.Secure.getInt(mContentResolver, - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); - mOriginalDeviceNotifValue = Settings.Secure.getInt(mContentResolver, - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); - mOriginalPsSensitiveNotifValue = Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, mPrivateProfileId); - - final String preferenceKey = "private_space_sensitive_notifications"; - mHidePrivateSpaceSensitiveNotificationsController = - new HidePrivateSpaceSensitiveNotificationsController(mContext, preferenceKey); - } - - @After - public void tearDown() { - Settings.Secure.putInt(mContentResolver, - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, - mOriginalDeviceSensitiveNotifValue - ); - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, mOriginalDeviceNotifValue); - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, - mOriginalPsSensitiveNotifValue, mPrivateProfileId); - } - - /** - * Tests that the controller is unavailable if lockscreen sensitive notifications are disabled - * on the device. - */ - @Test - public void getAvailabilityStatus_lockScreenPrivateNotificationsOff() { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0); - assertThat(mHidePrivateSpaceSensitiveNotificationsController.getAvailabilityStatus()) - .isEqualTo(DISABLED_DEPENDENT_SETTING); - } - - /** - * Tests that the controller is unavailable if lockscreen notifications are disabled on the - * device. - */ - @Test - public void getAvailabilityStatus_lockScreenNotificationsOff() { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); - assertThat(mHidePrivateSpaceSensitiveNotificationsController.getAvailabilityStatus()) - .isEqualTo(DISABLED_DEPENDENT_SETTING); - } - - /** - * Tests that the controller is available if lockscreen notifications and lockscreen private - * notifications are enabled on the device. - */ - @Test - public void getAvailabilityStatus_returnAvailable() { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); - assertThat(mHidePrivateSpaceSensitiveNotificationsController.getAvailabilityStatus()) - .isEqualTo(AVAILABLE); - } - - - /** - * Tests that toggle is not available if the flag for this feature and MVP flag are disabled. - */ - @Test - public void getAvailabilityStatus_flagDisabled() { - mSetFlagsRule.disableFlags( - android.multiuser.Flags.FLAG_ENABLE_PS_SENSITIVE_NOTIFICATIONS_TOGGLE); - mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); - assertThat(mHidePrivateSpaceSensitiveNotificationsController.getAvailabilityStatus()) - .isEqualTo(UNSUPPORTED_ON_DEVICE); - } - - @Test - public void testSetChecked() { - assertThat(mHidePrivateSpaceSensitiveNotificationsController.setChecked(true)).isTrue(); - assertThat(mHidePrivateSpaceSensitiveNotificationsController.isChecked()).isEqualTo(true); - assertThat(mHidePrivateSpaceSensitiveNotificationsController.setChecked(false)).isTrue(); - assertThat(mHidePrivateSpaceSensitiveNotificationsController.isChecked()).isEqualTo(false); - } -} diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java index 98797d5cfcd..e0f855acbbe 100644 --- a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java +++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java @@ -21,6 +21,7 @@ import static android.provider.Settings.Secure.PRIVATE_SPACE_AUTO_LOCK; import static com.android.settings.privatespace.PrivateSpaceMaintainer.HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL; import static com.android.settings.privatespace.PrivateSpaceMaintainer.HIDE_PRIVATE_SPACE_ENTRY_POINT_ENABLED_VAL; +import static com.android.settings.privatespace.PrivateSpaceMaintainer.HIDE_PRIVATE_SPACE_SENSITIVE_NOTIFICATIONS_DISABLED_VAL; import static com.android.settings.privatespace.PrivateSpaceMaintainer.PRIVATE_SPACE_AUTO_LOCK_DEFAULT_VAL; import static com.google.common.truth.Truth.assertThat; @@ -162,7 +163,7 @@ public class PrivateSpaceMaintainerTest { privateSpaceMaintainer.createPrivateSpace(); assertThat(privateSpaceMaintainer.doesPrivateSpaceExist()).isTrue(); assertThat(getPsSensitiveNotificationsValue(privateSpaceMaintainer)) - .isEqualTo(HidePrivateSpaceSensitiveNotificationsController.DISABLED); + .isEqualTo(HIDE_PRIVATE_SPACE_SENSITIVE_NOTIFICATIONS_DISABLED_VAL); } /** @@ -374,7 +375,7 @@ public class PrivateSpaceMaintainerTest { private int getPsSensitiveNotificationsValue(PrivateSpaceMaintainer privateSpaceMaintainer) { return Settings.Secure.getIntForUser(mContentResolver, LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, - HidePrivateSpaceSensitiveNotificationsController.ENABLED, + /* enabled */ 1, privateSpaceMaintainer.getPrivateProfileHandle().getIdentifier()); } } From fecb6062ba66ab796804071dae5cf9596231dfd3 Mon Sep 17 00:00:00 2001 From: josephpv Date: Thu, 4 Apr 2024 10:43:01 +0000 Subject: [PATCH 09/10] Remove "skip lock screen" from private profile face setting This change removes toggle "skip lock screen" from private profile face unlock settings page. screenshot : go/ss/BgucXsP9FRk23SM.png Bug: 320702155 Test: Manual Change-Id: Ibcd83edb1a93a4cd4b33c672f32e3b90328060ea --- src/com/android/settings/biometrics/face/FaceSettings.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java index 197aca03e0b..3398c203c65 100644 --- a/src/com/android/settings/biometrics/face/FaceSettings.java +++ b/src/com/android/settings/biometrics/face/FaceSettings.java @@ -201,8 +201,9 @@ public class FaceSettings extends DashboardFragment { } mRemoveController.setUserId(mUserId); - // Don't show keyguard controller for work profile settings. - if (mUserManager.isManagedProfile(mUserId)) { + // Don't show keyguard controller for work and private profile settings. + if (mUserManager.isManagedProfile(mUserId) + || mUserManager.getUserInfo(mUserId).isPrivateProfile()) { removePreference(FaceSettingsKeyguardPreferenceController.KEY); removePreference(mLockscreenController.getPreferenceKey()); } From f4329ada0026b9048591caf0dc8cff373621e2ff Mon Sep 17 00:00:00 2001 From: alukin Date: Tue, 12 Mar 2024 16:30:24 +0000 Subject: [PATCH 10/10] Split System category Splitting System category in Settings > Storage into "Android 14" and "Temporary system files". Android 14 is calculated as StorageStatsManager#getTotalBytes - (size of /data partition). Basically it's the size of all partitions except for the userdata. It also includes the storage rounding which we do to display a nice round storage size (128GB, 256GB, etc). Temporary System Files serves as "Everything else" category that represents all the used storage that didn't fall under any other category. It's the same as how currently "System" category works. Bug: 309801699 Test: atest StorageItemPreferenceControllerTest Test: atest StorageCacheHelperTest Change-Id: Ifae5819bf1d7a1a675df1fa67bd6dcdfa074b584 --- res/drawable/ic_android_vd_theme_24.xml | 26 +++++++++++++ res/drawable/ic_database_vd_theme_24.xml | 25 ++++++++++++ res/xml/storage_category_fragment.xml | 28 +++++++++---- res/xml/storage_dashboard_fragment.xml | 28 +++++++++---- .../storage/StorageAsyncLoader.java | 14 +++++++ .../storage/StorageCacheHelper.java | 4 ++ .../StorageItemPreferenceController.java | 39 +++++++++++++++++-- .../deviceinfo/storage/StorageUtils.java | 33 ++++++++++++++-- .../storage/StorageCacheHelperTest.java | 3 ++ .../StorageItemPreferenceControllerTest.java | 26 ++++++++++++- 10 files changed, 201 insertions(+), 25 deletions(-) create mode 100644 res/drawable/ic_android_vd_theme_24.xml create mode 100644 res/drawable/ic_database_vd_theme_24.xml diff --git a/res/drawable/ic_android_vd_theme_24.xml b/res/drawable/ic_android_vd_theme_24.xml new file mode 100644 index 00000000000..65812090007 --- /dev/null +++ b/res/drawable/ic_android_vd_theme_24.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/res/drawable/ic_database_vd_theme_24.xml b/res/drawable/ic_database_vd_theme_24.xml new file mode 100644 index 00000000000..713a65e9c57 --- /dev/null +++ b/res/drawable/ic_database_vd_theme_24.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/xml/storage_category_fragment.xml b/res/xml/storage_category_fragment.xml index 2c9588938a2..58bd89138df 100644 --- a/res/xml/storage_category_fragment.xml +++ b/res/xml/storage_category_fragment.xml @@ -61,19 +61,31 @@ android:title="@string/storage_documents_and_other" android:icon="@drawable/ic_folder_vd_theme_24" android:order="106"/> - - + android:order="107"/> + + + + + + android:order="204" /> diff --git a/res/xml/storage_dashboard_fragment.xml b/res/xml/storage_dashboard_fragment.xml index 7c0f3a6d195..fd866ad8604 100644 --- a/res/xml/storage_dashboard_fragment.xml +++ b/res/xml/storage_dashboard_fragment.xml @@ -80,19 +80,31 @@ android:title="@string/storage_documents_and_other" android:icon="@drawable/ic_folder_vd_theme_24" android:order="106"/> - - + android:order="107"/> + + + + + + android:order="204" /> diff --git a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java index 54935ecf4de..4906cf2d8d8 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java +++ b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java @@ -30,8 +30,10 @@ import android.content.pm.UserInfo; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; +import android.os.Environment; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; import android.provider.MediaStore; import android.provider.MediaStore.Files.FileColumns; import android.provider.MediaStore.MediaColumns; @@ -94,6 +96,7 @@ public class StorageAsyncLoader media /* queryArgs */); result.audioSize = getFilesSize(info.id, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, media /* queryArgs */); + result.systemSize = getSystemSize(); final Bundle documentsAndOtherQueryArgs = new Bundle(); documentsAndOtherQueryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, @@ -140,6 +143,16 @@ public class StorageAsyncLoader } } + private long getSystemSize() { + try { + return mStatsManager.getTotalBytes(StorageManager.UUID_DEFAULT) + - Environment.getDataDirectory().getTotalSpace(); + } catch (IOException e) { + Log.e(TAG, "Exception in calculating System category size", e); + return 0; + } + } + private StorageResult getAppsAndGamesSize(int userId) { Log.d(TAG, "Loading apps"); final List applicationInfos = @@ -225,6 +238,7 @@ public class StorageAsyncLoader public long videosSize; public long documentsAndOtherSize; public long trashSize; + public long systemSize; public long cacheSize; public long duplicateCodeSize; diff --git a/src/com/android/settings/deviceinfo/storage/StorageCacheHelper.java b/src/com/android/settings/deviceinfo/storage/StorageCacheHelper.java index e6da454f2f1..50690cb0791 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageCacheHelper.java +++ b/src/com/android/settings/deviceinfo/storage/StorageCacheHelper.java @@ -35,6 +35,7 @@ public class StorageCacheHelper { private static final String DOCUMENTS_AND_OTHER_SIZE_KEY = "documents_and_other_size_key"; private static final String TRASH_SIZE_KEY = "trash_size_key"; private static final String SYSTEM_SIZE_KEY = "system_size_key"; + private static final String TEMPORARY_FILES_SIZE_KEY = "temporary_files_size_key"; private static final String USED_SIZE_KEY = "used_size_key"; private final SharedPreferences mSharedPreferences; @@ -66,6 +67,7 @@ public class StorageCacheHelper { .putLong(DOCUMENTS_AND_OTHER_SIZE_KEY, data.documentsAndOtherSize) .putLong(TRASH_SIZE_KEY, data.trashSize) .putLong(SYSTEM_SIZE_KEY, data.systemSize) + .putLong(TEMPORARY_FILES_SIZE_KEY, data.temporaryFilesSize) .apply(); } @@ -109,6 +111,7 @@ public class StorageCacheHelper { result.documentsAndOtherSize = mSharedPreferences.getLong(DOCUMENTS_AND_OTHER_SIZE_KEY, 0); result.trashSize = mSharedPreferences.getLong(TRASH_SIZE_KEY, 0); result.systemSize = mSharedPreferences.getLong(SYSTEM_SIZE_KEY, 0); + result.temporaryFilesSize = mSharedPreferences.getLong(TEMPORARY_FILES_SIZE_KEY, 0); return result; } @@ -126,5 +129,6 @@ public class StorageCacheHelper { public long documentsAndOtherSize; public long trashSize; public long systemSize; + public long temporaryFilesSize; } } diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java index fd424178f0c..62422ca1ea5 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java +++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java @@ -27,6 +27,7 @@ import android.content.pm.UserInfo; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; @@ -40,6 +41,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import com.android.settings.R; @@ -52,6 +54,7 @@ import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.profileselector.ProfileSelectFragment; import com.android.settings.deviceinfo.StorageItemPreference; import com.android.settings.deviceinfo.storage.StorageUtils.SystemInfoFragment; +import com.android.settings.deviceinfo.storage.StorageUtils.TemporaryFilesInfoFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -74,6 +77,7 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle private static final String TAG = "StorageItemPreference"; private static final String SYSTEM_FRAGMENT_TAG = "SystemInfo"; + private static final String TEMPORARY_FILES_FRAGMENT_TAG = "TemporaryFilesInfo"; @VisibleForTesting static final String PUBLIC_STORAGE_KEY = "pref_public_storage"; @@ -92,6 +96,10 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle @VisibleForTesting static final String SYSTEM_KEY = "pref_system"; @VisibleForTesting + static final String TEMPORARY_FILES_KEY = "temporary_files"; + @VisibleForTesting + static final String CATEGORY_SPLITTER = "storage_category_splitter"; + @VisibleForTesting static final String TRASH_KEY = "pref_trash"; @VisibleForTesting @@ -133,9 +141,13 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle @VisibleForTesting @Nullable StorageItemPreference mDocumentsAndOtherPreference; @VisibleForTesting + @Nullable StorageItemPreference mTrashPreference; + @VisibleForTesting @Nullable StorageItemPreference mSystemPreference; @VisibleForTesting - @Nullable StorageItemPreference mTrashPreference; + @Nullable StorageItemPreference mTemporaryFilesPreference; + @VisibleForTesting + @Nullable PreferenceCategory mCategorySplitterPreferenceCategory; private final int mProfileType; @@ -220,6 +232,13 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle dialog.setTargetFragment(mFragment, 0); dialog.show(mFragment.getFragmentManager(), SYSTEM_FRAGMENT_TAG); return true; + case TEMPORARY_FILES_KEY: + final TemporaryFilesInfoFragment temporaryFilesDialog = + new TemporaryFilesInfoFragment(); + temporaryFilesDialog.setTargetFragment(mFragment, 0); + temporaryFilesDialog.show(mFragment.getFragmentManager(), + TEMPORARY_FILES_FRAGMENT_TAG); + return true; case TRASH_KEY: launchTrashIntent(); return true; @@ -285,6 +304,8 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle mAppsPreference.setVisible(visible); mGamesPreference.setVisible(visible); mSystemPreference.setVisible(visible); + mTemporaryFilesPreference.setVisible(visible); + mCategorySplitterPreferenceCategory.setVisible(visible); mTrashPreference.setVisible(visible); // If we don't have a shared volume for our internal storage (or the shared volume isn't @@ -315,7 +336,6 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle mPrivateStorageItemPreferences.add(mAppsPreference); mPrivateStorageItemPreferences.add(mGamesPreference); mPrivateStorageItemPreferences.add(mDocumentsAndOtherPreference); - mPrivateStorageItemPreferences.add(mSystemPreference); mPrivateStorageItemPreferences.add(mTrashPreference); } mScreen.removePreference(mImagesPreference); @@ -324,7 +344,6 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle mScreen.removePreference(mAppsPreference); mScreen.removePreference(mGamesPreference); mScreen.removePreference(mDocumentsAndOtherPreference); - mScreen.removePreference(mSystemPreference); mScreen.removePreference(mTrashPreference); // Sort display order by size. @@ -361,6 +380,7 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle tintPreference(mGamesPreference); tintPreference(mDocumentsAndOtherPreference); tintPreference(mSystemPreference); + tintPreference(mTemporaryFilesPreference); tintPreference(mTrashPreference); } @@ -389,7 +409,9 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle mAppsPreference = screen.findPreference(APPS_KEY); mGamesPreference = screen.findPreference(GAMES_KEY); mDocumentsAndOtherPreference = screen.findPreference(DOCUMENTS_AND_OTHER_KEY); + mCategorySplitterPreferenceCategory = screen.findPreference(CATEGORY_SPLITTER); mSystemPreference = screen.findPreference(SYSTEM_KEY); + mTemporaryFilesPreference = screen.findPreference(TEMPORARY_FILES_KEY); mTrashPreference = screen.findPreference(TRASH_KEY); } @@ -417,6 +439,12 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle mTrashPreference.setStorageSize(storageCache.trashSize, mTotalSize, animate); if (mSystemPreference != null) { mSystemPreference.setStorageSize(storageCache.systemSize, mTotalSize, animate); + mSystemPreference.setTitle(mContext.getString(R.string.storage_os_name, + Build.VERSION.RELEASE)); + } + if (mTemporaryFilesPreference != null) { + mTemporaryFilesPreference.setStorageSize(storageCache.temporaryFilesSize, mTotalSize, + animate); } // Cache the size info if (result != null) { @@ -445,6 +473,7 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle storageCache.gamesSize = data.gamesSize; storageCache.documentsAndOtherSize = data.documentsAndOtherSize; storageCache.trashSize = data.trashSize; + storageCache.systemSize = data.systemSize; // Everything else that hasn't already been attributed is tracked as // belonging to system. long attributedSize = 0; @@ -460,7 +489,9 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle + otherData.allAppsExceptGamesSize; attributedSize -= otherData.duplicateCodeSize; } - storageCache.systemSize = Math.max(DataUnit.GIBIBYTES.toBytes(1), + // System size is equal for each user and should be added only once + attributedSize += data.systemSize; + storageCache.temporaryFilesSize = Math.max(DataUnit.GIBIBYTES.toBytes(1), mUsedBytes - attributedSize); return storageCache; } diff --git a/src/com/android/settings/deviceinfo/storage/StorageUtils.java b/src/com/android/settings/deviceinfo/storage/StorageUtils.java index 4b6a2c40fd8..5c4a4b40f2a 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageUtils.java +++ b/src/com/android/settings/deviceinfo/storage/StorageUtils.java @@ -23,7 +23,6 @@ import android.content.Intent; import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.os.storage.DiskInfo; import android.os.storage.StorageManager; @@ -34,6 +33,7 @@ import android.text.format.Formatter; import android.util.Log; import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; @@ -206,7 +206,7 @@ public class StorageUtils { /** Shows information about system storage. */ public static class SystemInfoFragment extends InstrumentedDialogFragment { /** Shows the fragment. */ - public static void show(Fragment parent) { + public static void show(@NonNull Fragment parent) { if (!parent.isAdded()) return; final SystemInfoFragment dialog = new SystemInfoFragment(); @@ -222,8 +222,33 @@ public class StorageUtils { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return new AlertDialog.Builder(getActivity()) - .setMessage(getContext().getString(R.string.storage_detail_dialog_system, - Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY)) + .setMessage(getContext().getString(R.string.storage_os_detail_dialog_system)) + .setPositiveButton(android.R.string.ok, null) + .create(); + } + } + + /** Shows information about temporary system files. */ + public static class TemporaryFilesInfoFragment extends InstrumentedDialogFragment { + /** Shows the fragment. */ + public static void show(@NonNull Fragment parent) { + if (!parent.isAdded()) return; + + final TemporaryFilesInfoFragment dialog = new TemporaryFilesInfoFragment(); + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getFragmentManager(), "temporaryFilesInfo"); + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.DIALOG_TEMPORARY_FILES_INFO; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setMessage(getContext().getString( + R.string.storage_other_files_detail_dialog_system)) .setPositiveButton(android.R.string.ok, null) .create(); } diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageCacheHelperTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageCacheHelperTest.java index 9e20e78c03a..a26ea1dfca2 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageCacheHelperTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageCacheHelperTest.java @@ -41,6 +41,7 @@ public class StorageCacheHelperTest { private static final long FAKE_TOTAL_SIZE = 256000L; private static final long FAKE_TOTAL_USED_SIZE = 50000L; private static final long FAKE_USED_SIZE = 6500L; + private static final long FAKE_TEMPORARY_FILES_SIZE = 2500L; private Context mContext; private StorageCacheHelper mHelper; @@ -70,6 +71,7 @@ public class StorageCacheHelperTest { StorageCacheHelper.StorageCache storageCache = mHelper.retrieveCachedSize(); assertThat(storageCache.imagesSize).isEqualTo(FAKE_IMAGES_SIZE); + assertThat(storageCache.temporaryFilesSize).isEqualTo(FAKE_TEMPORARY_FILES_SIZE); assertThat(storageCache.totalSize).isEqualTo(0); } @@ -100,6 +102,7 @@ public class StorageCacheHelperTest { result.gamesSize = FAKE_GAMES_SIZE; result.videosSize = FAKE_VIDEOS_SIZE; result.allAppsExceptGamesSize = FAKE_APPS_SIZE; + result.temporaryFilesSize = FAKE_TEMPORARY_FILES_SIZE; return result; } } diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java index b61f5ab4ef4..2590f52340f 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java @@ -45,6 +45,7 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; +import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import com.android.settings.R; @@ -126,7 +127,10 @@ public class StorageItemPreferenceControllerTest { final StorageItemPreference documentsAndOther = spy(new StorageItemPreference(mContext)); documentsAndOther.setIcon(R.drawable.ic_folder_vd_theme_24); final StorageItemPreference system = spy(new StorageItemPreference(mContext)); - system.setIcon(com.android.settingslib.R.drawable.ic_system_update); + system.setIcon(R.drawable.ic_android_vd_theme_24); + final StorageItemPreference temporaryFiles = spy(new StorageItemPreference(mContext)); + temporaryFiles.setIcon(R.drawable.ic_database_vd_theme_24); + final PreferenceCategory categorySplitter = spy(new PreferenceCategory(mContext)); final StorageItemPreference trash = spy(new StorageItemPreference(mContext)); trash.setIcon(R.drawable.ic_trash_can); @@ -147,6 +151,10 @@ public class StorageItemPreferenceControllerTest { .thenReturn(documentsAndOther); when(screen.findPreference(eq(StorageItemPreferenceController.SYSTEM_KEY))) .thenReturn(system); + when(screen.findPreference(eq(StorageItemPreferenceController.TEMPORARY_FILES_KEY))) + .thenReturn(temporaryFiles); + when(screen.findPreference(eq(StorageItemPreferenceController.CATEGORY_SPLITTER))) + .thenReturn(categorySplitter); when(screen.findPreference(eq(StorageItemPreferenceController.TRASH_KEY))) .thenReturn(trash); @@ -219,6 +227,7 @@ public class StorageItemPreferenceControllerTest { assertThat(mController.mGamesPreference.isVisible()).isFalse(); assertThat(mController.mDocumentsAndOtherPreference.isVisible()).isFalse(); assertThat(mController.mSystemPreference.isVisible()).isFalse(); + assertThat(mController.mTemporaryFilesPreference.isVisible()).isFalse(); assertThat(mController.mTrashPreference.isVisible()).isFalse(); } @@ -329,6 +338,16 @@ public class StorageItemPreferenceControllerTest { .add(nullable(StorageUtils.SystemInfoFragment.class), nullable(String.class)); } + @Test + public void testClickTemporaryFiles() { + mPreference.setKey(StorageItemPreferenceController.TEMPORARY_FILES_KEY); + assertThat(mController.handlePreferenceTreeClick(mPreference)).isTrue(); + + verify(mFragment.getFragmentManager().beginTransaction()) + .add(nullable(StorageUtils.TemporaryFilesInfoFragment.class), + nullable(String.class)); + } + @Test @Config(shadows = ShadowUserManager.class) public void testMeasurementCompletedUpdatesPreferences() { @@ -343,6 +362,7 @@ public class StorageItemPreferenceControllerTest { result.documentsAndOtherSize = MEGABYTE_IN_BYTES * 50; result.trashSize = KILOBYTE_IN_BYTES * 100; result.allAppsExceptGamesSize = MEGABYTE_IN_BYTES * 90; + result.systemSize = MEGABYTE_IN_BYTES * 60; final SparseArray results = new SparseArray<>(); results.put(0, result); @@ -356,6 +376,8 @@ public class StorageItemPreferenceControllerTest { assertThat(mController.mDocumentsAndOtherPreference.getSummary().toString()) .isEqualTo("50 MB"); assertThat(mController.mTrashPreference.getSummary().toString()).isEqualTo("100 kB"); + assertThat(mController.mSystemPreference.getSummary().toString()) + .isEqualTo("60 MB"); } @Test @@ -373,6 +395,7 @@ public class StorageItemPreferenceControllerTest { verify(mController.mDocumentsAndOtherPreference, times(2)) .setIcon(nullable(Drawable.class)); verify(mController.mSystemPreference, times(2)).setIcon(nullable(Drawable.class)); + verify(mController.mTemporaryFilesPreference, times(2)).setIcon(nullable(Drawable.class)); verify(mController.mTrashPreference, times(2)).setIcon(nullable(Drawable.class)); } @@ -437,6 +460,7 @@ public class StorageItemPreferenceControllerTest { assertThat(mController.mGamesPreference.isVisible()).isFalse(); assertThat(mController.mDocumentsAndOtherPreference.isVisible()).isFalse(); assertThat(mController.mSystemPreference.isVisible()).isFalse(); + assertThat(mController.mTemporaryFilesPreference.isVisible()).isFalse(); assertThat(mController.mTrashPreference.isVisible()).isFalse(); }