diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-05-11 18:42:05 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-05-11 18:42:05 +0000 |
commit | a267ee1bc216c7e62ec984a88aa2310762bb5519 (patch) | |
tree | abbfffbd6ed474912186f60a8b965eb7344987d0 | |
parent | c39d2cf4662903fc19f6550ec2fd468d25a19adb (diff) | |
parent | 91bfcbbd87886049778142618a655352b16cd911 (diff) | |
download | base-a267ee1bc216c7e62ec984a88aa2310762bb5519.tar.gz |
Merge cherrypicks of ['googleplex-android-review.googlesource.com/20219552', 'googleplex-android-review.googlesource.com/20946190', 'googleplex-android-review.googlesource.com/20460002', 'googleplex-android-review.googlesource.com/22390145', 'googleplex-android-review.googlesource.com/22616881', 'googleplex-android-review.googlesource.com/21327525', 'googleplex-android-review.googlesource.com/22621774', 'googleplex-android-review.googlesource.com/22898060', 'googleplex-android-review.googlesource.com/17738545', 'googleplex-android-review.googlesource.com/21253698', 'googleplex-android-review.googlesource.com/22787457', 'googleplex-android-review.googlesource.com/22509574', 'googleplex-android-review.googlesource.com/22895638'] into security-aosp-tm-release.android-security-13.0.0_r7
Change-Id: Ied11657b48451b612e638edbf54755756e2fd348
17 files changed, 406 insertions, 139 deletions
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 0ba1614ab6c5..7ed3baa48c1f 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -20,6 +20,8 @@ import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIO import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import static android.view.Display.INVALID_DISPLAY; import android.annotation.IntDef; @@ -1684,7 +1686,9 @@ public class ActivityOptions extends ComponentOptions { * @hide */ public int getPendingIntentLaunchFlags() { - return mPendingIntentLaunchFlags; + // b/243794108: Ignore all flags except the new task flag, to be reconsidered in b/254490217 + return mPendingIntentLaunchFlags & + (FLAG_ACTIVITY_NEW_TASK | FLAG_RECEIVER_FOREGROUND); } /** diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index e82073380394..b3921fe8bd79 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2853,6 +2853,17 @@ public class Notification implements Parcelable if (person != null) { visitor.accept(person.getIconUri()); } + + final RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[]) + extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + if (history != null) { + for (int i = 0; i < history.length; i++) { + RemoteInputHistoryItem item = history[i]; + if (item.getUri() != null) { + visitor.accept(item.getUri()); + } + } + } } if (isStyle(MessagingStyle.class) && extras != null) { @@ -2883,6 +2894,14 @@ public class Notification implements Parcelable } } + if (isStyle(CallStyle.class) & extras != null) { + Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON); + if (callPerson != null) { + visitor.accept(callPerson.getIconUri()); + } + visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON)); + } + if (mBubbleMetadata != null) { visitIconUri(visitor, mBubbleMetadata.getIcon()); } diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 52774e354c90..c33390398400 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -283,6 +283,12 @@ public final class ShortcutInfo implements Parcelable { */ public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; + /** + * The maximum length of Shortcut ID. IDs will be truncated at this limit. + * @hide + */ + public static final int MAX_ID_LENGTH = 1000; + /** @hide */ @IntDef(prefix = { "DISABLED_REASON_" }, value = { DISABLED_REASON_NOT_DISABLED, @@ -475,8 +481,7 @@ public final class ShortcutInfo implements Parcelable { private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); - - mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"); + mId = getSafeId(Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided")); // Note we can't do other null checks here because SM.updateShortcuts() takes partial // information. @@ -582,6 +587,14 @@ public final class ShortcutInfo implements Parcelable { return ret; } + @NonNull + private static String getSafeId(@NonNull String id) { + if (id.length() > MAX_ID_LENGTH) { + return id.substring(0, MAX_ID_LENGTH); + } + return id; + } + /** * Throws if any of the mandatory fields is not set. * @@ -2336,7 +2349,8 @@ public final class ShortcutInfo implements Parcelable { final ClassLoader cl = getClass().getClassLoader(); mUserId = source.readInt(); - mId = source.readString8(); + mId = getSafeId(Preconditions.checkStringNotEmpty(source.readString8(), + "Shortcut ID must be provided")); mPackageName = source.readString8(); mActivity = source.readParcelable(cl, android.content.ComponentName.class); mFlags = source.readInt(); diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 6a93b354f4da..45dad9861406 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -77,8 +77,7 @@ interface IPermissionManager { List<SplitPermissionInfoParcelable> getSplitPermissions(); void startOneTimePermissionSession(String packageName, int userId, long timeout, - long revokeAfterKilledDelay, int importanceToResetTimer, - int importanceToKeepSessionAlive); + long revokeAfterKilledDelay); void stopOneTimePermissionSession(String packageName, int userId); diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 6b540d72bba0..67699543131a 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1371,8 +1371,7 @@ public final class PermissionManager { @ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) { try { mPermissionManager.startOneTimePermissionSession(packageName, mContext.getUserId(), - timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer, - importanceToKeepSessionAlive); + timeoutMillis, revokeAfterKilledDelayMillis); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index c8c1fd4eba21..9801559854f9 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -75,6 +75,11 @@ public class ApkSignatureSchemeV2Verifier { private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; /** + * The maximum number of signers supported by the v2 APK signature scheme. + */ + private static final int MAX_V2_SIGNERS = 10; + + /** * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature. * * <p><b>NOTE: This method does not verify the signature.</b> @@ -182,6 +187,11 @@ public class ApkSignatureSchemeV2Verifier { } while (signers.hasRemaining()) { signerCount++; + if (signerCount > MAX_V2_SIGNERS) { + throw new SecurityException( + "APK Signature Scheme v2 only supports a maximum of " + MAX_V2_SIGNERS + + " signers"); + } try { ByteBuffer signer = getLengthPrefixedSlice(signers); X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory); diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java index 45254908c5c9..a6aca330d323 100644 --- a/core/java/android/util/jar/StrictJarVerifier.java +++ b/core/java/android/util/jar/StrictJarVerifier.java @@ -78,6 +78,11 @@ class StrictJarVerifier { "SHA1", }; + /** + * The maximum number of signers supported by the JAR signature scheme. + */ + private static final int MAX_JAR_SIGNERS = 10; + private final String jarName; private final StrictJarManifest manifest; private final HashMap<String, byte[]> metaEntries; @@ -293,10 +298,16 @@ class StrictJarVerifier { return false; } + int signerCount = 0; Iterator<String> it = metaEntries.keySet().iterator(); while (it.hasNext()) { String key = it.next(); if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) { + if (++signerCount > MAX_JAR_SIGNERS) { + throw new SecurityException( + "APK Signature Scheme v1 only supports a maximum of " + MAX_JAR_SIGNERS + + " signers"); + } verifyCertificate(key); it.remove(); } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 2879cd888d2d..4a920e3b71db 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -728,6 +728,12 @@ public class RemoteViews implements Parcelable, Filter { mActions.get(i).visitUris(visitor); } } + if (mLandscape != null) { + mLandscape.visitUris(visitor); + } + if (mPortrait != null) { + mPortrait.visitUris(visitor); + } } private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) { diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 00b3693c902b..a9d3ce51b2c3 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -24,6 +24,10 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.app.ActivityOptions; import android.app.PendingIntent; @@ -33,6 +37,8 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; import android.os.Looper; @@ -58,6 +64,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Map; import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; /** * Tests for RemoteViews. @@ -690,4 +697,61 @@ public class RemoteViewsTest { return null; } } + + @Test + public void visitUris() { + RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); + + final Uri imageUri = Uri.parse("content://media/image"); + final Icon icon1 = Icon.createWithContentUri("content://media/icon1"); + final Icon icon2 = Icon.createWithContentUri("content://media/icon2"); + final Icon icon3 = Icon.createWithContentUri("content://media/icon3"); + final Icon icon4 = Icon.createWithContentUri("content://media/icon4"); + views.setImageViewUri(R.id.image, imageUri); + views.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUri)); + verify(visitor, times(1)).accept(eq(icon1.getUri())); + verify(visitor, times(1)).accept(eq(icon2.getUri())); + verify(visitor, times(1)).accept(eq(icon3.getUri())); + verify(visitor, times(1)).accept(eq(icon4.getUri())); + } + + @Test + public void visitUris_separateOrientation() { + final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test); + final Uri imageUriL = Uri.parse("content://landscape/image"); + final Icon icon1L = Icon.createWithContentUri("content://landscape/icon1"); + final Icon icon2L = Icon.createWithContentUri("content://landscape/icon2"); + final Icon icon3L = Icon.createWithContentUri("content://landscape/icon3"); + final Icon icon4L = Icon.createWithContentUri("content://landscape/icon4"); + landscape.setImageViewUri(R.id.image, imageUriL); + landscape.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L); + + final RemoteViews portrait = new RemoteViews(mPackage, 33); + final Uri imageUriP = Uri.parse("content://portrait/image"); + final Icon icon1P = Icon.createWithContentUri("content://portrait/icon1"); + final Icon icon2P = Icon.createWithContentUri("content://portrait/icon2"); + final Icon icon3P = Icon.createWithContentUri("content://portrait/icon3"); + final Icon icon4P = Icon.createWithContentUri("content://portrait/icon4"); + portrait.setImageViewUri(R.id.image, imageUriP); + portrait.setTextViewCompoundDrawables(R.id.text, icon1P, icon2P, icon3P, icon4P); + + RemoteViews views = new RemoteViews(landscape, portrait); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriL)); + verify(visitor, times(1)).accept(eq(icon1L.getUri())); + verify(visitor, times(1)).accept(eq(icon2L.getUri())); + verify(visitor, times(1)).accept(eq(icon3L.getUri())); + verify(visitor, times(1)).accept(eq(icon4L.getUri())); + verify(visitor, times(1)).accept(eq(imageUriP)); + verify(visitor, times(1)).accept(eq(icon1P.getUri())); + verify(visitor, times(1)).accept(eq(icon2P.getUri())); + verify(visitor, times(1)).accept(eq(icon3P.getUri())); + verify(visitor, times(1)).accept(eq(icon4P.getUri())); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 2b9553d3eda2..0309d212e03f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -469,7 +469,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard case SimPuk: // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); - if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled( + if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled( KeyguardUpdateMonitor.getCurrentUser())) { finish = true; eventSubtype = BOUNCER_DISMISS_SIM; diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml index f971a0916837..a85b8e4ff553 100644 --- a/packages/VpnDialogs/res/values/strings.xml +++ b/packages/VpnDialogs/res/values/strings.xml @@ -100,4 +100,32 @@ without any consequences. [CHAR LIMIT=20] --> <string name="dismiss">Dismiss</string> + <!-- Malicious VPN apps may provide very long labels or cunning HTML to trick the system dialogs + into displaying what they want. The system will attempt to sanitize the label, and if the + label is deemed dangerous, then this string is used instead. The first argument is the + first 30 characters of the label, and the second argument is the package name of the app. + Example : Normally a VPN app may be called "My VPN app" in which case the dialog will read + "My VPN app wants to set up a VPN connection...". If the label is very long, then, this + will be used to show "VerylongVPNlabel… (com.my.vpn.app) wants to set up a VPN + connection...". For this case, the code will refer to sanitized_vpn_label_with_ellipsis. + --> + <string name="sanitized_vpn_label_with_ellipsis"> + <xliff:g id="sanitized_vpn_label_with_ellipsis" example="My VPN app">%1$s</xliff:g>… ( + <xliff:g id="sanitized_vpn_label_with_ellipsis" example="com.my.vpn.app">%2$s</xliff:g>) + </string> + + <!-- Malicious VPN apps may provide very long labels or cunning HTML to trick the system dialogs + into displaying what they want. The system will attempt to sanitize the label, and if the + label is deemed dangerous, then this string is used instead. The first argument is the + label, and the second argument is the package name of the app. + Example : Normally a VPN app may be called "My VPN app" in which case the dialog will read + "My VPN app wants to set up a VPN connection...". If the VPN label contains HTML tag but + the length is not very long, the dialog will show "VpnLabelWith<br>HtmlTag + (com.my.vpn.app) wants to set up a VPN connection...". For this case, the code will refer + to sanitized_vpn_label. + --> + <string name="sanitized_vpn_label"> + <xliff:g id="sanitized_vpn_label" example="My VPN app">%1$s</xliff:g> ( + <xliff:g id="sanitized_vpn_label" example="com.my.vpn.app">%2$s</xliff:g>) + </string> </resources> diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java index fb2367843fc1..2b3202e0a982 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java @@ -40,12 +40,18 @@ public class ConfirmDialog extends AlertActivity implements DialogInterface.OnClickListener, ImageGetter { private static final String TAG = "VpnConfirm"; + // Usually the label represents the app name, 150 code points might be enough to display the app + // name, and 150 code points won't cover the warning message from VpnDialog. + static final int MAX_VPN_LABEL_LENGTH = 150; + @VpnManager.VpnType private final int mVpnType; private String mPackage; private VpnManager mVm; + private View mView; + public ConfirmDialog() { this(VpnManager.TYPE_VPN_SERVICE); } @@ -54,6 +60,42 @@ public class ConfirmDialog extends AlertActivity mVpnType = vpnType; } + /** + * This function will use the string resource to combine the VPN label and the package name. + * + * If the VPN label violates the length restriction, the first 30 code points of VPN label and + * the package name will be returned. Or return the VPN label and the package name directly if + * the VPN label doesn't violate the length restriction. + * + * The result will be something like, + * - ThisIsAVeryLongVpnAppNameWhich... (com.vpn.app) + * if the VPN label violates the length restriction. + * or + * - VpnLabelWith<br>HtmlTag (com.vpn.app) + * if the VPN label doesn't violate the length restriction. + * + */ + private String getSimplifiedLabel(String vpnLabel, String packageName) { + if (vpnLabel.codePointCount(0, vpnLabel.length()) > 30) { + return getString(R.string.sanitized_vpn_label_with_ellipsis, + vpnLabel.substring(0, vpnLabel.offsetByCodePoints(0, 30)), + packageName); + } + + return getString(R.string.sanitized_vpn_label, vpnLabel, packageName); + } + + protected String getSanitizedVpnLabel(String vpnLabel, String packageName) { + final String sanitizedVpnLabel = Html.escapeHtml(vpnLabel); + final boolean exceedMaxVpnLabelLength = sanitizedVpnLabel.codePointCount(0, + sanitizedVpnLabel.length()) > MAX_VPN_LABEL_LENGTH; + if (exceedMaxVpnLabelLength || !vpnLabel.equals(sanitizedVpnLabel)) { + return getSimplifiedLabel(sanitizedVpnLabel, packageName); + } + + return sanitizedVpnLabel; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -75,15 +117,16 @@ public class ConfirmDialog extends AlertActivity finish(); return; } - View view = View.inflate(this, R.layout.confirm, null); - ((TextView) view.findViewById(R.id.warning)).setText( - Html.fromHtml(getString(R.string.warning, getVpnLabel()), - this, null /* tagHandler */)); + mView = View.inflate(this, R.layout.confirm, null); + ((TextView) mView.findViewById(R.id.warning)).setText( + Html.fromHtml(getString(R.string.warning, getSanitizedVpnLabel( + getVpnLabel().toString(), mPackage)), + this /* imageGetter */, null /* tagHandler */)); mAlertParams.mTitle = getText(R.string.prompt); mAlertParams.mPositiveButtonText = getText(android.R.string.ok); mAlertParams.mPositiveButtonListener = this; mAlertParams.mNegativeButtonText = getText(android.R.string.cancel); - mAlertParams.mView = view; + mAlertParams.mView = mView; setupAlert(); getWindow().setCloseOnTouchOutside(false); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 5d73de103e90..1a7d8e968edf 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2093,9 +2093,25 @@ final class InstallPackageHelper { // The caller explicitly specified INSTALL_ALL_USERS flag. // Thus, updating the settings to install the app for all users. for (int currentUserId : allUsers) { - ps.setInstalled(true, currentUserId); - ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, + // If the app is already installed for the currentUser, + // keep it as installed as we might be updating the app at this place. + // If not currently installed, check if the currentUser is restricted by + // DISALLOW_INSTALL_APPS or DISALLOW_DEBUGGING_FEATURES device policy. + // Install / update the app if the user isn't restricted. Skip otherwise. + final boolean installedForCurrentUser = ArrayUtils.contains( + installedForUsers, currentUserId); + final boolean restrictedByPolicy = + mPm.isUserRestricted(currentUserId, + UserManager.DISALLOW_INSTALL_APPS) + || mPm.isUserRestricted(currentUserId, + UserManager.DISALLOW_DEBUGGING_FEATURES); + if (installedForCurrentUser || !restrictedByPolicy) { + ps.setInstalled(true, currentUserId); + ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, currentUserId, installerPackageName); + } else { + ps.setInstalled(false, currentUserId); + } } } diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java index 881f8707fdd8..d28048ce74c7 100644 --- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java +++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java @@ -16,23 +16,26 @@ package com.android.server.pm.permission; -import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; - import android.annotation.NonNull; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AlarmManager; +import android.app.IActivityManager; +import android.app.IUidObserver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.Handler; +import android.os.RemoteException; import android.permission.PermissionControllerManager; import android.provider.DeviceConfig; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; /** * Class that handles one-time permissions for a user @@ -47,7 +50,8 @@ public class OneTimePermissionUserManager { "one_time_permissions_killed_delay_millis"; private final @NonNull Context mContext; - private final @NonNull ActivityManager mActivityManager; + private final @NonNull IActivityManager mIActivityManager; + private final @NonNull ActivityManagerInternal mActivityManagerInternal; private final @NonNull AlarmManager mAlarmManager; private final @NonNull PermissionControllerManager mPermissionControllerManager; @@ -77,49 +81,15 @@ public class OneTimePermissionUserManager { OneTimePermissionUserManager(@NonNull Context context) { mContext = context; - mActivityManager = context.getSystemService(ActivityManager.class); + mIActivityManager = ActivityManager.getService(); + mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mAlarmManager = context.getSystemService(AlarmManager.class); mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class); mHandler = context.getMainThreadHandler(); } - /** - * Starts a one-time permission session for a given package. A one-time permission session is - * ended if app becomes inactive. Inactivity is defined as the package's uid importance level - * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid - * importance level goes <= importanceToResetTimer then the timer is reset and doesn't start - * until going > importanceToResetTimer. - * <p> - * When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive - * then the session is extended until either the importance goes above - * importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which - * will continue the session and reset the timer. - * </p> - * <p> - * Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}. - * </p> - * <p> - * Once the session ends PermissionControllerService#onNotifyOneTimePermissionSessionTimeout - * is invoked. - * </p> - * <p> - * Note that if there is currently an active session for a package a new one isn't created and - * the existing one isn't changed. - * </p> - * @param packageName The package to start a one-time permission session for - * @param timeoutMillis Number of milliseconds for an app to be in an inactive state - * @param revokeAfterKilledDelayMillis Number of milliseconds to wait after the process dies - * before ending the session. Set to -1 to use default value - * for the device. - * @param importanceToResetTimer The least important level to uid must be to reset the timer - * @param importanceToKeepSessionAlive The least important level the uid must be to keep the - * session alive - * - * @hide - */ void startPackageOneTimeSession(@NonNull String packageName, long timeoutMillis, - long revokeAfterKilledDelayMillis, int importanceToResetTimer, - int importanceToKeepSessionAlive) { + long revokeAfterKilledDelayMillis) { int uid; try { uid = mContext.getPackageManager().getPackageUid(packageName, 0); @@ -131,13 +101,11 @@ public class OneTimePermissionUserManager { synchronized (mLock) { PackageInactivityListener listener = mListeners.get(uid); if (listener != null) { - listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis, - importanceToResetTimer, importanceToKeepSessionAlive); + listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis); return; } listener = new PackageInactivityListener(uid, packageName, timeoutMillis, - revokeAfterKilledDelayMillis, importanceToResetTimer, - importanceToKeepSessionAlive); + revokeAfterKilledDelayMillis); mListeners.put(uid, listener); } } @@ -182,34 +150,58 @@ public class OneTimePermissionUserManager { private static final long TIMER_INACTIVE = -1; + private static final int STATE_GONE = 0; + private static final int STATE_TIMER = 1; + private static final int STATE_ACTIVE = 2; + private final int mUid; private final @NonNull String mPackageName; private long mTimeout; private long mRevokeAfterKilledDelay; - private int mImportanceToResetTimer; - private int mImportanceToKeepSessionAlive; private boolean mIsAlarmSet; private boolean mIsFinished; private long mTimerStart = TIMER_INACTIVE; - private final ActivityManager.OnUidImportanceListener mStartTimerListener; - private final ActivityManager.OnUidImportanceListener mSessionKillableListener; - private final ActivityManager.OnUidImportanceListener mGoneListener; - private final Object mInnerLock = new Object(); private final Object mToken = new Object(); + private final IUidObserver.Stub mObserver = new IUidObserver.Stub() { + @Override + public void onUidGone(int uid, boolean disabled) { + if (uid == mUid) { + PackageInactivityListener.this.updateUidState(STATE_GONE); + } + } - private PackageInactivityListener(int uid, @NonNull String packageName, long timeout, - long revokeAfterkilledDelay, int importanceToResetTimer, - int importanceToKeepSessionAlive) { + @Override + public void onUidStateChanged(int uid, int procState, long procStateSeq, + int capability) { + if (uid == mUid) { + if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE + && procState != ActivityManager.PROCESS_STATE_NONEXISTENT) { + PackageInactivityListener.this.updateUidState(STATE_TIMER); + } else { + PackageInactivityListener.this.updateUidState(STATE_ACTIVE); + } + } + } + + public void onUidActive(int uid) { + } + public void onUidIdle(int uid, boolean disabled) { + } + public void onUidProcAdjChanged(int uid) { + } + public void onUidCachedChanged(int uid, boolean cached) { + } + }; + private PackageInactivityListener(int uid, @NonNull String packageName, long timeout, + long revokeAfterkilledDelay) { Log.i(LOG_TAG, "Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout - + " killedDelay=" + revokeAfterkilledDelay - + " importanceToResetTimer=" + importanceToResetTimer - + " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive); + + " killedDelay=" + revokeAfterkilledDelay); mUid = uid; mPackageName = packageName; @@ -219,27 +211,24 @@ public class OneTimePermissionUserManager { DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS) : revokeAfterkilledDelay; - mImportanceToResetTimer = importanceToResetTimer; - mImportanceToKeepSessionAlive = importanceToKeepSessionAlive; - - mStartTimerListener = - (changingUid, importance) -> onImportanceChanged(changingUid, importance); - mSessionKillableListener = - (changingUid, importance) -> onImportanceChanged(changingUid, importance); - mGoneListener = - (changingUid, importance) -> onImportanceChanged(changingUid, importance); - - mActivityManager.addOnUidImportanceListener(mStartTimerListener, - importanceToResetTimer); - mActivityManager.addOnUidImportanceListener(mSessionKillableListener, - importanceToKeepSessionAlive); - mActivityManager.addOnUidImportanceListener(mGoneListener, IMPORTANCE_CACHED); - - onImportanceChanged(mUid, mActivityManager.getPackageImportance(packageName)); + + try { + mIActivityManager.registerUidObserver(mObserver, + ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_PROCSTATE, + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, + null); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Couldn't check uid proc state", e); + // Can't register uid observer, just revoke immediately + synchronized (mInnerLock) { + onPackageInactiveLocked(); + } + } + + updateUidState(); } - public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis, - int importanceToResetTimer, int importanceToKeepSessionAlive) { + public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis) { synchronized (mInnerLock) { mTimeout = Math.min(mTimeout, timeoutMillis); mRevokeAfterKilledDelay = Math.min(mRevokeAfterKilledDelay, @@ -248,63 +237,74 @@ public class OneTimePermissionUserManager { DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS) : revokeAfterKilledDelayMillis); - mImportanceToResetTimer = Math.min(importanceToResetTimer, mImportanceToResetTimer); - mImportanceToKeepSessionAlive = Math.min(importanceToKeepSessionAlive, - mImportanceToKeepSessionAlive); Log.v(LOG_TAG, "Updated params for " + mPackageName + ". timeout=" + mTimeout - + " killedDelay=" + mRevokeAfterKilledDelay - + " importanceToResetTimer=" + mImportanceToResetTimer - + " importanceToKeepSessionAlive=" + mImportanceToKeepSessionAlive); - onImportanceChanged(mUid, mActivityManager.getPackageImportance(mPackageName)); + + " killedDelay=" + mRevokeAfterKilledDelay); + updateUidState(); } } - private void onImportanceChanged(int uid, int importance) { - if (uid != mUid) { - return; + private int getCurrentState() { + return getStateFromProcState(mActivityManagerInternal.getUidProcessState(mUid)); + } + + private int getStateFromProcState(int procState) { + if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) { + return STATE_GONE; + } else { + if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { + return STATE_TIMER; + } else { + return STATE_ACTIVE; + } } + } - Log.v(LOG_TAG, "Importance changed for " + mPackageName + " (" + mUid + ")." - + " importance=" + importance); + private void updateUidState() { + updateUidState(getCurrentState()); + } + + private void updateUidState(int state) { + Log.v(LOG_TAG, "Updating state for " + mPackageName + " (" + mUid + ")." + + " state=" + state); synchronized (mInnerLock) { // Remove any pending inactivity callback mHandler.removeCallbacksAndMessages(mToken); - if (importance > IMPORTANCE_CACHED) { + if (state == STATE_GONE) { if (mRevokeAfterKilledDelay == 0) { onPackageInactiveLocked(); return; } // Delay revocation in case app is restarting mHandler.postDelayed(() -> { - int imp = mActivityManager.getUidImportance(mUid); - if (imp > IMPORTANCE_CACHED) { - onPackageInactiveLocked(); - } else { - if (DEBUG) { - Log.d(LOG_TAG, "No longer gone after delayed revocation. " - + "Rechecking for " + mPackageName + " (" + mUid + ")."); + int currentState; + synchronized (mInnerLock) { + currentState = getCurrentState(); + if (currentState == STATE_GONE) { + onPackageInactiveLocked(); + return; } - onImportanceChanged(mUid, imp); } + if (DEBUG) { + Log.d(LOG_TAG, "No longer gone after delayed revocation. " + + "Rechecking for " + mPackageName + " (" + mUid + + ")."); + } + updateUidState(currentState); }, mToken, mRevokeAfterKilledDelay); return; - } - if (importance > mImportanceToResetTimer) { + } else if (state == STATE_TIMER) { if (mTimerStart == TIMER_INACTIVE) { if (DEBUG) { Log.d(LOG_TAG, "Start the timer for " + mPackageName + " (" + mUid + ")."); } mTimerStart = System.currentTimeMillis(); + setAlarmLocked(); } - } else { + } else if (state == STATE_ACTIVE) { mTimerStart = TIMER_INACTIVE; - } - if (importance > mImportanceToKeepSessionAlive) { - setAlarmLocked(); - } else { cancelAlarmLocked(); } } @@ -318,19 +318,9 @@ public class OneTimePermissionUserManager { mIsFinished = true; cancelAlarmLocked(); try { - mActivityManager.removeOnUidImportanceListener(mStartTimerListener); - } catch (IllegalArgumentException e) { - Log.e(LOG_TAG, "Could not remove start timer listener", e); - } - try { - mActivityManager.removeOnUidImportanceListener(mSessionKillableListener); - } catch (IllegalArgumentException e) { - Log.e(LOG_TAG, "Could not remove session killable listener", e); - } - try { - mActivityManager.removeOnUidImportanceListener(mGoneListener); - } catch (IllegalArgumentException e) { - Log.e(LOG_TAG, "Could not remove gone listener", e); + mIActivityManager.unregisterUidObserver(mObserver); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Unable to unregister uid observer.", e); } } } @@ -394,9 +384,11 @@ public class OneTimePermissionUserManager { mPermissionControllerManager.notifyOneTimePermissionSessionTimeout( mPackageName); }); - mActivityManager.removeOnUidImportanceListener(mStartTimerListener); - mActivityManager.removeOnUidImportanceListener(mSessionKillableListener); - mActivityManager.removeOnUidImportanceListener(mGoneListener); + try { + mIActivityManager.unregisterUidObserver(mObserver); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Unable to unregister uid observer.", e); + } synchronized (mLock) { mListeners.remove(mUid); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 2c81b21d37bc..1d6a74b2e917 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -386,8 +386,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void startOneTimePermissionSession(String packageName, @UserIdInt int userId, - long timeoutMillis, long revokeAfterKilledDelayMillis, int importanceToResetTimer, - int importanceToKeepSessionAlive) { + long timeoutMillis, long revokeAfterKilledDelayMillis) { mContext.enforceCallingOrSelfPermission( Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS, "Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS @@ -397,8 +396,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { final long token = Binder.clearCallingIdentity(); try { getOneTimePermissionUserManager(userId).startPackageOneTimeSession(packageName, - timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer, - importanceToKeepSessionAlive); + timeoutMillis, revokeAfterKilledDelayMillis); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index c78678431dac..86d4655e9d3a 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -228,6 +228,15 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { }); } + public void testShortcutIdTruncated() { + ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), + "s".repeat(Short.MAX_VALUE)).build(); + + assertTrue( + "id must be truncated to MAX_ID_LENGTH", + si.getId().length() <= ShortcutInfo.MAX_ID_LENGTH); + } + public void testShortcutInfoParcel() { setCaller(CALLING_PACKAGE_1, USER_10); ShortcutInfo si = parceled(new ShortcutInfo.Builder(mClientContext) diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index c72bd4e91b8d..cc0bc24fc660 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -123,6 +123,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Person; import android.app.RemoteInput; +import android.app.RemoteInputHistoryItem; import android.app.StatsManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; @@ -5164,10 +5165,36 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testVisitUris() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); final Uri backgroundImage = Uri.parse("content://com.example/background"); + final Icon personIcon1 = Icon.createWithContentUri("content://media/person1"); + final Icon personIcon2 = Icon.createWithContentUri("content://media/person2"); + final Icon personIcon3 = Icon.createWithContentUri("content://media/person3"); + final Person person1 = new Person.Builder() + .setName("Messaging Person") + .setIcon(personIcon1) + .build(); + final Person person2 = new Person.Builder() + .setName("People List Person 1") + .setIcon(personIcon2) + .build(); + final Person person3 = new Person.Builder() + .setName("People List Person 2") + .setIcon(personIcon3) + .build(); + final Uri historyUri1 = Uri.parse("content://com.example/history1"); + final Uri historyUri2 = Uri.parse("content://com.example/history2"); + final RemoteInputHistoryItem historyItem1 = new RemoteInputHistoryItem(null, historyUri1, + "a"); + final RemoteInputHistoryItem historyItem2 = new RemoteInputHistoryItem(null, historyUri2, + "b"); Bundle extras = new Bundle(); extras.putParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents); extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, backgroundImage.toString()); + extras.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person1); + extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, + new ArrayList<>(Arrays.asList(person2, person3))); + extras.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, + new RemoteInputHistoryItem[]{historyItem1, historyItem2}); Notification n = new Notification.Builder(mContext, "a") .setContentTitle("notification with uris") @@ -5179,6 +5206,34 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { n.visitUris(visitor); verify(visitor, times(1)).accept(eq(audioContents)); verify(visitor, times(1)).accept(eq(backgroundImage)); + verify(visitor, times(1)).accept(eq(personIcon1.getUri())); + verify(visitor, times(1)).accept(eq(personIcon2.getUri())); + verify(visitor, times(1)).accept(eq(personIcon3.getUri())); + verify(visitor, times(1)).accept(eq(historyUri1)); + verify(visitor, times(1)).accept(eq(historyUri2)); + } + + @Test + public void testVisitUris_callStyle() { + Icon personIcon = Icon.createWithContentUri("content://media/person"); + Icon verificationIcon = Icon.createWithContentUri("content://media/verification"); + Person callingPerson = new Person.Builder().setName("Someone") + .setIcon(personIcon) + .build(); + PendingIntent hangUpIntent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); + Notification n = new Notification.Builder(mContext, "a") + .setStyle(Notification.CallStyle.forOngoingCall(callingPerson, hangUpIntent) + .setVerificationIcon(verificationIcon)) + .setContentTitle("Calling...") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor, times(1)).accept(eq(personIcon.getUri())); + verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); } @Test |