From e12156e2d805c492f01f1d218a1e840a03edbddb Mon Sep 17 00:00:00 2001 From: Adam Bookatz Date: Mon, 17 Mar 2025 14:34:08 -0700 Subject: [PATCH 1/8] AppRestrictions - use vetted component After vetting the intent, use the component we used for the vetting. Bug: 353680402 Bug: 365739560 Test: manual Flag: EXEMPT bugfix (cherry picked from commit d3e34060803c97ae05719fe9301026e5c54892c8) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:586422578de58f1da68fd9becf89a08ea2ae0058) Merged-In: Iff0d820c1261c29eb6703bf89194339cba700688 Change-Id: Iff0d820c1261c29eb6703bf89194339cba700688 --- .../users/AppRestrictionsFragment.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/users/AppRestrictionsFragment.java b/src/com/android/settings/users/AppRestrictionsFragment.java index c42e2f57b1d..c4a01797d0e 100644 --- a/src/com/android/settings/users/AppRestrictionsFragment.java +++ b/src/com/android/settings/users/AppRestrictionsFragment.java @@ -639,8 +639,11 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen } else if (restrictionsIntent != null) { preference.setRestrictions(restrictions); if (invokeIfCustom && AppRestrictionsFragment.this.isResumed()) { + // We don't necessarily trust the given intent to launch its component. + // We will first check it, and only use parts of it that were indeed checked. + final Intent vettedIntent; try { - assertSafeToStartCustomActivity(restrictionsIntent); + vettedIntent = assertSafeToStartCustomActivity(restrictionsIntent); } catch (ActivityNotFoundException | SecurityException e) { // return without startActivity Log.e(TAG, "Cannot start restrictionsIntent " + e); @@ -651,12 +654,16 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen int requestCode = generateCustomActivityRequestCode( RestrictionsResultReceiver.this.preference); AppRestrictionsFragment.this.startActivityForResult( - new Intent(restrictionsIntent), requestCode); + vettedIntent, requestCode); } } } - private void assertSafeToStartCustomActivity(Intent intent) { + /** + * Checks that it is safe to start the custom activity, and, if so, returns a copy of the + * Intent using its vetted components. + */ + private Intent assertSafeToStartCustomActivity(Intent intent) { EventLog.writeEvent(0x534e4554, "223578534", -1 /* UID */, ""); ResolveInfo resolveInfo = mPackageManager.resolveActivity( intent, PackageManager.MATCH_DEFAULT_ONLY); @@ -670,6 +677,13 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen throw new SecurityException("Application " + packageName + " is not allowed to start activity " + intent); } + + // We were able to vet the given intent this time. Make a copy using the components + // that were used to do the vetting, since that's as much as we've verified is safe. + final Intent vettedIntent = new Intent(intent); + vettedIntent.setComponent(activityInfo.getComponentName()); + vettedIntent.setPackage(activityInfo.packageName); + return vettedIntent; } } From e985e294f15db1267bd56a91952976b54f9970ae Mon Sep 17 00:00:00 2001 From: Aseem Kumar Date: Wed, 16 Apr 2025 11:22:07 -0700 Subject: [PATCH 2/8] Add ComponentName explicitly to make sure arbitary intents aren't launched from Settings. Bug: 378902342 Flag: EXEMPT security fix (cherry picked from commit 6a896b6b26d445800773e1b4649895bea17eac1f) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ade7c77634363b787e8e73591247d9dff2b1b149) Merged-In: I0e67f1258cb427c5b998e40a8a0c104af3ead042 Change-Id: I0e67f1258cb427c5b998e40a8a0c104af3ead042 --- .../settings/accounts/AccountTypePreferenceLoader.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java index 71c71346adb..8ca74c85dd8 100644 --- a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java +++ b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java @@ -265,7 +265,14 @@ public class AccountTypePreferenceLoader { try { // Allows to launch only authenticator owned activities. ApplicationInfo authenticatorAppInf = pm.getApplicationInfo(authDesc.packageName, 0); - return resolvedAppInfo.uid == authenticatorAppInf.uid; + if (resolvedAppInfo.uid == authenticatorAppInf.uid) { + // Explicitly set the component to be same as authenticator to + // prevent launching arbitrary activities. + intent.setComponent(resolvedActivityInfo.getComponentName()); + return true; + } else { + return false; + } } catch (NameNotFoundException e) { Log.e(TAG, "Intent considered unsafe due to exception.", From dc41d27f1bd9ebab400a85e31e81315092fd9477 Mon Sep 17 00:00:00 2001 From: Adam Bookatz Date: Thu, 24 Apr 2025 09:53:42 -0700 Subject: [PATCH 3/8] Do not enable the Content Protection toggle for non-admin users. Flag: EXEMPT bugfix Bug: 409318132 Test: m -j256 Settings && atest SettingsRoboTests:ContentProtectionTogglePreferenceControllerTest (cherry picked from commit ef801e1a8ec3a18ce9e0221fc7e1dfe495d0be8a) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:094c9df207d9bb430427dadc2e327b4834b57b01) Merged-In: I46609c795923d427a5b7fa10bc2e8b071fad72d6 Change-Id: I46609c795923d427a5b7fa10bc2e8b071fad72d6 --- .../ContentProtectionTogglePreferenceController.java | 2 +- ...ontentProtectionTogglePreferenceControllerTest.java | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java b/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java index 69ac6b100be..61987cdd8ad 100644 --- a/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java +++ b/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java @@ -132,7 +132,7 @@ public class ContentProtectionTogglePreferenceController extends TogglePreferenc UserManager userManager = mContext.getSystemService(UserManager.class); if (userManager != null - && userManager.isGuestUser() + && !userManager.isAdminUser() && mSwitchBar != null) { mSwitchBar.setEnabled(false); } diff --git a/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java index 6514a4e4043..5f63da19fe6 100644 --- a/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java @@ -85,7 +85,7 @@ public class ContentProtectionTogglePreferenceControllerTest { @Before public void setUp() { mShadowUserManager = ShadowUserManager.getShadow(); - mShadowUserManager.setGuestUser(false); + mShadowUserManager.setIsAdminUser(true); mController = new TestContentProtectionTogglePreferenceController(); SettingsMainSwitchPreference switchPreference = new SettingsMainSwitchPreference(mContext); when(mMockPreferenceScreen.findPreference(mController.getPreferenceKey())) @@ -277,8 +277,8 @@ public class ContentProtectionTogglePreferenceControllerTest { } @Test - public void updateState_flagEnabled_noEnforcedAdmin_guestUser_switchBarDisabled() { - mShadowUserManager.setGuestUser(true); + public void updateState_flagEnabled_noEnforcedAdmin_nonAdminUser_switchBarDisabled() { + mShadowUserManager.setIsAdminUser(false); mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); mContentProtectionPolicy = DevicePolicyManager.CONTENT_PROTECTION_ENABLED; setupForUpdateState(); @@ -289,13 +289,15 @@ public class ContentProtectionTogglePreferenceControllerTest { } @Test - public void updateState_flagEnabled_noEnforcedAdmin_nonGuestUser_switchBarEnabled() { + public void updateState_flagEnabled_noEnforcedAdmin_adminUser_switchBarEnabled() { + mShadowUserManager.setIsAdminUser(true); mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); mContentProtectionPolicy = DevicePolicyManager.CONTENT_PROTECTION_ENABLED; setupForUpdateState(); mController.updateState(mMockSwitchPreference); + // Verify that the switch bar is *not* set to disabled. verify(mMockSwitchPreference, never()).setEnabled(false); } From d50de07930ee2dc3f896d8932be3dbeb9c25b939 Mon Sep 17 00:00:00 2001 From: Joe Bolinger Date: Sat, 5 Apr 2025 02:30:30 +0000 Subject: [PATCH 4/8] Drop PendingIntent extras from external packages during enrollment. Bug: 388528350 Flag: EXEMPT bugfix Test: atest FingerprintEnrollIntroductionTest FaceEnrollIntroductionTest (cherry picked from commit 4ccdeee849d5fef78498ba33cadc525523efcbd7) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:9dd5ed1cea0449812775fb76a46f8be2e6862425) Merged-In: I61281dcf95e53100a96d6a218f3f00fd1b4ea3f9 Change-Id: I61281dcf95e53100a96d6a218f3f00fd1b4ea3f9 --- .../BiometricEnrollIntroduction.java | 20 +++++++++++++-- .../face/FaceEnrollIntroductionTest.java | 21 ++++++++++++++++ .../FingerprintEnrollIntroductionTest.java | 25 +++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java index 127ad6231a7..ddfadaf048f 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java @@ -50,6 +50,8 @@ import com.google.android.setupdesign.span.LinkSpan; import com.google.android.setupdesign.template.RequireScrollMixin; import com.google.android.setupdesign.util.DynamicColorPalette; +import java.util.List; + /** * Abstract base class for the intro onboarding activity for biometric enrollment. */ @@ -249,6 +251,19 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase !isScrollNeeded && !enrollmentCompleted ? View.VISIBLE : View.INVISIBLE); } + @Override + protected void onStart() { + super.onStart(); + + if (!getPackageName().equals(getCallingPackage())) { + for (String key : List.of(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, + MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, + MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT)) { + getIntent().removeExtra(key); + } + } + } + @Override protected void onResume() { super.onResume(); @@ -497,14 +512,15 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase getIntent().removeExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT); } - protected void removeEnrollNextBiometricIfSkipEnroll(@Nullable Intent data) { + private void removeEnrollNextBiometricIfSkipEnroll(@Nullable Intent data) { if (data != null && data.getBooleanExtra( MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, false)) { removeEnrollNextBiometric(); } } - protected void handleBiometricResultSkipOrFinished(int resultCode, @Nullable Intent data) { + + private void handleBiometricResultSkipOrFinished(int resultCode, @Nullable Intent data) { removeEnrollNextBiometricIfSkipEnroll(data); if (resultCode == RESULT_SKIP) { onEnrollmentSkipped(data); diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java index 81a72694592..984073f19b5 100644 --- a/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java @@ -32,11 +32,13 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Activity; +import android.app.PendingIntent; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -64,6 +66,7 @@ import com.android.settings.R; import com.android.settings.Settings; import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.BiometricUtils; +import com.android.settings.biometrics.MultiBiometricEnrollHelper; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.SettingsShadowResources; @@ -206,6 +209,12 @@ public class FaceEnrollIntroductionTest { testIntent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON, FaceEnrollOptions.ENROLL_REASON_SETTINGS); + testIntent.putExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, + mock(PendingIntent.class)); + testIntent.putExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT, + mock(PendingIntent.class)); + testIntent.putExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, false); + when(mFakeFeatureFactory.mFaceFeatureProvider.getPostureGuidanceIntent(any())).thenReturn( null /* Simulate no posture intent */); mContext = spy(ApplicationProvider.getApplicationContext()); @@ -690,4 +699,16 @@ public class FaceEnrollIntroductionTest { .isEqualTo(FaceEnrollOptions.ENROLL_REASON_SETTINGS); } + @Test + public void drops_pendingIntents() { + setupActivity(); + + mController.start(); + Shadows.shadowOf(Looper.getMainLooper()).idle(); + + final Intent intent = mActivity.getIntent(); + assertThat(intent.hasExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL)).isFalse(); + assertThat(intent.hasExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE)).isFalse(); + assertThat(intent.hasExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT)).isFalse(); + } } diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java index edd50a6e6f6..24fb7165fd0 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java @@ -34,6 +34,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -44,6 +45,7 @@ import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.os.Looper; import android.os.UserManager; import android.view.View; @@ -55,6 +57,7 @@ import com.android.internal.widget.VerifyCredentialResponse; import com.android.settings.R; import com.android.settings.biometrics.BiometricUtils; import com.android.settings.biometrics.GatekeeperPasswordProvider; +import com.android.settings.biometrics.MultiBiometricEnrollHelper; import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupdesign.GlifLayout; @@ -70,6 +73,7 @@ import org.mockito.stubbing.Answer; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; import org.robolectric.android.controller.ActivityController; import java.util.ArrayList; @@ -353,7 +357,19 @@ public class FingerprintEnrollIntroductionTest { false); Assert.assertEquals(View.INVISIBLE, mFingerprintEnrollIntroduction.getSecondaryFooterButton().getVisibility()); + } + @Test + public void drops_pendingIntents() { + setupFingerprintEnrollIntroWith(newExternalPendingIntent()); + + mController.start(); + Shadows.shadowOf(Looper.getMainLooper()).idle(); + + final Intent intent = mFingerprintEnrollIntroduction.getIntent(); + assertThat(intent.hasExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL)).isFalse(); + assertThat(intent.hasExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE)).isFalse(); + assertThat(intent.hasExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT)).isFalse(); } private Intent newTokenOnlyIntent() { @@ -383,6 +399,15 @@ public class FingerprintEnrollIntroductionTest { .putExtra(EXTRA_KEY_GK_PW_HANDLE, 1L); } + private Intent newExternalPendingIntent() { + return newTokenOnlyIntent() + .putExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, + mock(PendingIntent.class)) + .putExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT, + mock(PendingIntent.class)) + .putExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, false); + } + private VerifyCredentialResponse newGoodCredential(long gkPwHandle, @NonNull byte[] hat) { return new VerifyCredentialResponse.Builder() .setGatekeeperPasswordHandle(gkPwHandle) From 0f61e586873a164175543563a1f09f395595b68a Mon Sep 17 00:00:00 2001 From: Rubin Xu Date: Wed, 21 May 2025 15:34:51 +0100 Subject: [PATCH 5/8] Use correct API to get calling package name in CredentialStorage Activity.getCallingPackage() does not always return the package name of the actual calling app. getLaunchedFromPackage() should be used instead. Bug: 389681530 Test: manual Flag: EXEMPT bugfix (cherry picked from commit 70bd3efe0674bccb0d454845d86fb2402779a7bf) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:48e4b5e1af2156dc0db1546df1c14030e77e542e) Merged-In: Ibdbc45e53f4aa46fae79fa234705b3735bfda4cd Change-Id: Ibdbc45e53f4aa46fae79fa234705b3735bfda4cd --- .../settings/security/CredentialStorage.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/security/CredentialStorage.java b/src/com/android/settings/security/CredentialStorage.java index b1c65a7c3c0..5ea9b7ac21f 100644 --- a/src/com/android/settings/security/CredentialStorage.java +++ b/src/com/android/settings/security/CredentialStorage.java @@ -17,6 +17,7 @@ package com.android.settings.security; import android.app.Activity; +import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.DialogInterface; @@ -322,15 +323,25 @@ public final class CredentialStorage extends FragmentActivity { } } + private String getCallingPackageName() { + try { + return ActivityManager.getService().getLaunchedFromPackage(getActivityToken()); + } catch (RemoteException re) { + // Error talking to ActivityManager, just give up + return null; + } + } + /** * Check that the caller is either certinstaller or Settings running in a profile of this user. */ private boolean checkCallerIsCertInstallerOrSelfInProfile() { - if (TextUtils.equals("com.android.certinstaller", getCallingPackage())) { + String callingPackage = getCallingPackageName(); + if (TextUtils.equals("com.android.certinstaller", callingPackage)) { // CertInstaller is allowed to install credentials if it has the same signature as // Settings package. return getPackageManager().checkSignatures( - getCallingPackage(), getPackageName()) == PackageManager.SIGNATURE_MATCH; + callingPackage, getPackageName()) == PackageManager.SIGNATURE_MATCH; } final int launchedFromUserId; From 504a34c4d4a3b216fdac7bd1daa3077e7a6d0844 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Mon, 12 May 2025 14:50:40 -0400 Subject: [PATCH 6/8] Hide notification content in history - if the user is locked - and the user has chosen to hide sensistive content when locked Test: manual with a work profile with a different pin Bug: 378088320 Flag: EXEMPT bug fix (cherry picked from commit 9df37c3f8be2dedd2e44e52da4de45fba33c6a6e) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ac4d72060aeb282d5c1c024438ed9e0689b50031) Merged-In: Ia70454d9859fb788ffa1f48f88760f88c354cdff Change-Id: Ia70454d9859fb788ffa1f48f88760f88c354cdff --- .../history/NotificationHistoryActivity.java | 28 ++++++++++++++++--- .../history/NotificationHistoryAdapter.java | 21 +++++++++++--- .../history/NotificationSbnAdapter.java | 14 ++++++++-- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/com/android/settings/notification/history/NotificationHistoryActivity.java b/src/com/android/settings/notification/history/NotificationHistoryActivity.java index 709eb7fc9e0..c9b354adae1 100644 --- a/src/com/android/settings/notification/history/NotificationHistoryActivity.java +++ b/src/com/android/settings/notification/history/NotificationHistoryActivity.java @@ -16,6 +16,7 @@ package com.android.settings.notification.history; +import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; import static android.provider.Settings.Secure.NOTIFICATION_HISTORY_ENABLED; import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -28,9 +29,11 @@ import android.annotation.DrawableRes; import android.app.ActionBar; import android.app.ActivityManager; import android.app.INotificationManager; +import android.app.KeyguardManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.os.Bundle; @@ -58,6 +61,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; +import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.NotificationExpandButton; import com.android.settings.R; import com.android.settings.notification.NotificationBackend; @@ -68,6 +72,7 @@ import com.android.settingslib.widget.MainSwitchBar; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -98,6 +103,8 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity { private UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + private ArrayList mContentRestrictedUsers = new ArrayList<>(); + enum NotificationHistoryEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "User turned on notification history") NOTIFICATION_HISTORY_ON(504), @@ -205,14 +212,14 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity { final NotificationHistoryRecyclerView rv = viewForPackage.findViewById(R.id.notification_list); - rv.setAdapter(new NotificationHistoryAdapter(mNm, rv, + rv.setAdapter(new NotificationHistoryAdapter(NotificationHistoryActivity.this, mNm, rv, newCount -> { count.setText(StringUtil.getIcuPluralsString(this, newCount, R.string.notification_history_count)); if (newCount == 0) { viewForPackage.setVisibility(GONE); } - }, mUiEventLogger)); + }, mUiEventLogger, mContentRestrictedUsers)); ((NotificationHistoryAdapter) rv.getAdapter()).onRebuildComplete( new ArrayList<>(nhp.notifications)); @@ -249,6 +256,19 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity { mPm = getPackageManager(); mUm = getSystemService(UserManager.class); + + List users = mUm.getProfiles(getUserId()); + for (UserInfo user : users) { + if (Settings.Secure.getIntForUser(getContentResolver(), + LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, user.id) == 0) { + LockPatternUtils lpu = new LockPatternUtils(this); + KeyguardManager km = getSystemService(KeyguardManager.class); + if (lpu.isSecure(user.id) && km.isDeviceLocked(user.id)) { + mContentRestrictedUsers.add(user.id); + } + } + } + // wait for history loading and recent/snooze loading mCountdownLatch = new CountDownLatch(2); @@ -419,7 +439,7 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity { mSnoozedRv.setLayoutManager(lm); mSnoozedRv.setAdapter( new NotificationSbnAdapter(NotificationHistoryActivity.this, mPm, mUm, - true, mUiEventLogger)); + true, mUiEventLogger, mContentRestrictedUsers)); mSnoozedRv.setNestedScrollingEnabled(false); if (snoozed == null || snoozed.length == 0) { @@ -435,7 +455,7 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity { mDismissedRv.setLayoutManager(dismissLm); mDismissedRv.setAdapter( new NotificationSbnAdapter(NotificationHistoryActivity.this, mPm, mUm, - false, mUiEventLogger)); + false, mUiEventLogger, mContentRestrictedUsers)); mDismissedRv.setNestedScrollingEnabled(false); if (dismissed == null || dismissed.length == 0) { diff --git a/src/com/android/settings/notification/history/NotificationHistoryAdapter.java b/src/com/android/settings/notification/history/NotificationHistoryAdapter.java index 5368f25e76e..14fb98928de 100644 --- a/src/com/android/settings/notification/history/NotificationHistoryAdapter.java +++ b/src/com/android/settings/notification/history/NotificationHistoryAdapter.java @@ -22,6 +22,7 @@ import static android.provider.Settings.EXTRA_CONVERSATION_ID; import android.app.INotificationManager; import android.app.NotificationHistory.HistoricalNotification; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.RemoteException; @@ -53,16 +54,23 @@ public class NotificationHistoryAdapter extends private List mValues; private OnItemDeletedListener mListener; private UiEventLogger mUiEventLogger; - public NotificationHistoryAdapter(INotificationManager nm, + private ArrayList mContentRestrictedUsers = new ArrayList<>(); + Context mContext; + + public NotificationHistoryAdapter(Context context, + INotificationManager nm, NotificationHistoryRecyclerView listView, OnItemDeletedListener listener, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + ArrayList contentRestrictedUsers) { + mContext = context; mValues = new ArrayList<>(); setHasStableIds(true); listView.setOnItemSwipeDeleteListener(this); mNm = nm; mListener = listener; mUiEventLogger = uiEventLogger; + mContentRestrictedUsers = contentRestrictedUsers; } @Override @@ -81,8 +89,13 @@ public class NotificationHistoryAdapter extends public void onBindViewHolder(final @NonNull NotificationHistoryViewHolder holder, int position) { final HistoricalNotification hn = mValues.get(position); - holder.setTitle(hn.getTitle()); - holder.setSummary(hn.getText()); + if (mContentRestrictedUsers.contains(hn.getUserId())) { + holder.setSummary(mContext.getString( + com.android.internal.R.string.notification_hidden_text)); + } else { + holder.setTitle(hn.getTitle()); + holder.setSummary(hn.getText()); + } holder.setPostedTime(hn.getPostedTimeMs()); final View.OnClickListener onClick = v -> { mUiEventLogger.logWithPosition(NotificationHistoryActivity.NotificationHistoryEvent diff --git a/src/com/android/settings/notification/history/NotificationSbnAdapter.java b/src/com/android/settings/notification/history/NotificationSbnAdapter.java index 3262ec4aea9..fea60fdba47 100644 --- a/src/com/android/settings/notification/history/NotificationSbnAdapter.java +++ b/src/com/android/settings/notification/history/NotificationSbnAdapter.java @@ -78,9 +78,11 @@ public class NotificationSbnAdapter extends private List mEnabledProfiles = new ArrayList<>(); private boolean mIsSnoozed; private UiEventLogger mUiEventLogger; + private ArrayList mContentRestrictedUsers = new ArrayList<>(); public NotificationSbnAdapter(Context context, PackageManager pm, UserManager um, - boolean isSnoozed, UiEventLogger uiEventLogger) { + boolean isSnoozed, UiEventLogger uiEventLogger, + ArrayList contentRestrictedUsers) { mContext = context; mPm = pm; mUserBadgeCache = new HashMap<>(); @@ -101,6 +103,7 @@ public class NotificationSbnAdapter extends // If true, this is the panel for snoozed notifs, otherwise the one for dismissed notifs. mIsSnoozed = isSnoozed; mUiEventLogger = uiEventLogger; + mContentRestrictedUsers = contentRestrictedUsers; } @Override @@ -128,8 +131,13 @@ public class NotificationSbnAdapter extends holder.setIconBackground(loadBackground(sbn)); holder.setIcon(loadIcon(sbn)); holder.setPackageLabel(loadPackageLabel(sbn.getPackageName()).toString()); - holder.setTitle(getTitleString(sbn.getNotification())); - holder.setSummary(getTextString(mContext, sbn.getNotification())); + if (mContentRestrictedUsers.contains(sbn.getNormalizedUserId())) { + holder.setSummary(mContext.getString( + com.android.internal.R.string.notification_hidden_text)); + } else { + holder.setTitle(getTitleString(sbn.getNotification())); + holder.setSummary(getTextString(mContext, sbn.getNotification())); + } holder.setPostedTime(sbn.getPostTime()); int userId = normalizeUserId(sbn); if (!mUserBadgeCache.containsKey(userId)) { From 25ed933c4cf5acb7a15c146332f9586f3dd1abe0 Mon Sep 17 00:00:00 2001 From: Adam Bookatz Date: Wed, 11 Dec 2024 17:22:46 -0800 Subject: [PATCH 7/8] startActivityForResult with earlier new Intent We already make sure to use a copy of the Intent, but now we do so earlier. See bug. Bug: 353680402 Flag: EXEMPT bugfix Test: manual Test: atest com.android.settings.users.UserSettingsTest com.android.settings.users.UserDetailsSettingsTest (cherry picked from commit b7240e2f0c50455a1c8f3ae1fc4f27d55b86e89b) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:d8166274a030372f9b1184137fceef9d4dac83e1) Merged-In: I860e9e606de6b8d3c99fa52a63b72ba7a99ce179 Change-Id: I860e9e606de6b8d3c99fa52a63b72ba7a99ce179 --- src/com/android/settings/users/AppRestrictionsFragment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/users/AppRestrictionsFragment.java b/src/com/android/settings/users/AppRestrictionsFragment.java index c4a01797d0e..5b2a86fff42 100644 --- a/src/com/android/settings/users/AppRestrictionsFragment.java +++ b/src/com/android/settings/users/AppRestrictionsFragment.java @@ -665,8 +665,9 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen */ private Intent assertSafeToStartCustomActivity(Intent intent) { EventLog.writeEvent(0x534e4554, "223578534", -1 /* UID */, ""); + final Intent vettedIntent = new Intent(intent); ResolveInfo resolveInfo = mPackageManager.resolveActivity( - intent, PackageManager.MATCH_DEFAULT_ONLY); + vettedIntent, PackageManager.MATCH_DEFAULT_ONLY); if (resolveInfo == null) { throw new ActivityNotFoundException("No result for resolving " + intent); @@ -680,7 +681,6 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen // We were able to vet the given intent this time. Make a copy using the components // that were used to do the vetting, since that's as much as we've verified is safe. - final Intent vettedIntent = new Intent(intent); vettedIntent.setComponent(activityInfo.getComponentName()); vettedIntent.setPackage(activityInfo.packageName); return vettedIntent; From 2aa27461a40b17462e57801d790545a7aeb897be Mon Sep 17 00:00:00 2001 From: Joe Bolinger Date: Sat, 7 Jun 2025 03:02:15 +0000 Subject: [PATCH 8/8] Ignore face settings extras when called by an external package. Bug: 411418366 Flag: EXEMPT bug fix Test: manual from any user not 0 (adb shell am start -a android.settings.FACE_SETTINGS --ei android.intent.extra.USER_ID 0) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:486947205e05e83314bd76e4822af442ca82be9c) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:0baf462586f1bc99a120e005c5d273bd8cfa01e9) Merged-In: I06193e421a140a90568251fc25baa7fc81c12d78 Change-Id: I06193e421a140a90568251fc25baa7fc81c12d78 --- .../biometrics/face/FaceSettings.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java index 8270d50a3bb..ce4fdd62c72 100644 --- a/src/com/android/settings/biometrics/face/FaceSettings.java +++ b/src/com/android/settings/biometrics/face/FaceSettings.java @@ -95,8 +95,8 @@ public class FaceSettings extends DashboardFragment { private FaceManager mFaceManager; private DevicePolicyManager mDevicePolicyManager; private int mUserId; - private int mSensorId; - private long mChallenge; + private int mSensorId = -1; + private long mChallenge = 0; private byte[] mToken; private FaceSettingsAttentionPreferenceController mAttentionController; private FaceSettingsRemoveButtonPreferenceController mRemoveController; @@ -181,12 +181,19 @@ public class FaceSettings extends DashboardFragment { mUserManager = context.getSystemService(UserManager.class); mFaceManager = context.getSystemService(FaceManager.class); mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class); - mToken = getIntent().getByteArrayExtra(KEY_TOKEN); - mSensorId = getIntent().getIntExtra(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, -1); - mChallenge = getIntent().getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, 0L); - mUserId = getActivity().getIntent().getIntExtra( - Intent.EXTRA_USER_ID, UserHandle.myUserId()); + final SettingsActivity activity = (SettingsActivity) requireActivity(); + final String callingPackage = activity.getInitialCallingPackage(); + if (callingPackage == null || !callingPackage.equals(activity.getPackageName())) { + mUserId = UserHandle.myUserId(); + } else { + // only allow these extras when called internally by Settings + mToken = getIntent().getByteArrayExtra(KEY_TOKEN); + mSensorId = getIntent().getIntExtra(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, -1); + mChallenge = getIntent().getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, 0L); + mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId()); + } + mFaceFeatureProvider = FeatureFactory.getFeatureFactory().getFaceFeatureProvider(); if (mUserManager.getUserInfo(mUserId).isManagedProfile()) {