diff options
46 files changed, 940 insertions, 196 deletions
diff --git a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java index 3b14be7327f7..24727c5f2448 100644 --- a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java +++ b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java @@ -107,7 +107,7 @@ public class DumpCommand extends Command { DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); int rotation = display.getRotation(); Point size = new Point(); - display.getSize(size); + display.getRealSize(size); AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x, size.y); } diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java index ab198b319e27..488292d68620 100644 --- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java +++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java @@ -139,7 +139,7 @@ public class AccessibilityNodeInfoDumper { serializer.attribute("", "id", Integer.toString(displayId)); int rotation = display.getRotation(); Point size = new Point(); - display.getSize(size); + display.getRealSize(size); for (int i = 0, n = windows.size(); i < n; ++i) { dumpWindowRec(windows.get(i), serializer, i, size.x, size.y, rotation); } diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java index b1b432bf79ab..a31deb084575 100644 --- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java +++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java @@ -767,7 +767,7 @@ public class UiDevice { if(root != null) { Display display = getAutomatorBridge().getDefaultDisplay(); Point size = new Point(); - display.getSize(size); + display.getRealSize(size); AccessibilityNodeInfoDumper.dumpWindowToFile(root, new File(new File(Environment.getDataDirectory(), "local/tmp"), fileName), display.getRotation(), size.x, size.y); diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index ac00a042b79e..e52b9c177dc1 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -1982,6 +1982,10 @@ public abstract class AccessibilityService extends Service { IAccessibilityServiceConnection connection = AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); if (mInfo != null && connection != null) { + if (!mInfo.isWithinParcelableSize()) { + throw new IllegalStateException( + "Cannot update service info: size is larger than safe parcelable limits."); + } try { connection.setServiceInfo(mInfo); mInfo = null; diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index a41fa6431d4d..f853dff01e8a 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -39,6 +39,7 @@ import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.hardware.fingerprint.FingerprintManager; import android.os.Build; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; @@ -1028,6 +1029,15 @@ public class AccessibilityServiceInfo implements Parcelable { return 0; } + /** @hide */ + public final boolean isWithinParcelableSize() { + final Parcel parcel = Parcel.obtain(); + writeToParcel(parcel, 0); + final boolean result = parcel.dataSize() <= IBinder.MAX_IPC_SIZE; + parcel.recycle(); + return result; + } + public void writeToParcel(Parcel parcel, int flagz) { parcel.writeInt(eventTypes); parcel.writeStringArray(packageNames); diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java index 8cdc6a71caf8..adfe171f1a8d 100644 --- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java +++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java @@ -407,7 +407,7 @@ public class ChooseTypeAndAccountActivity extends Activity mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage, mCallingUid); intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK); - startActivityForResult(intent, REQUEST_ADD_ACCOUNT); + startActivityForResult(new Intent(intent), REQUEST_ADD_ACCOUNT); return; } } catch (OperationCanceledException e) { diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 4aedfeefb72b..c9723bd4fce3 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -61,7 +61,7 @@ import java.util.ArrayList; * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle) * Context.startActivity(Intent, Bundle)} and related methods. */ -public class ActivityOptions { +public class ActivityOptions extends ComponentOptions { private static final String TAG = "ActivityOptions"; /** @@ -963,13 +963,12 @@ public class ActivityOptions { } private ActivityOptions() { + super(); } /** @hide */ public ActivityOptions(Bundle opts) { - // If the remote side sent us bad parcelables, they won't get the - // results they want, which is their loss. - opts.setDefusable(true); + super(opts); mPackageName = opts.getString(KEY_PACKAGE_NAME); try { @@ -1575,8 +1574,9 @@ public class ActivityOptions { * object; you must not modify it, but can supply it to the startActivity * methods that take an options Bundle. */ + @Override public Bundle toBundle() { - Bundle b = new Bundle(); + Bundle b = super.toBundle(); if (mPackageName != null) { b.putString(KEY_PACKAGE_NAME, mPackageName); } diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 161e2ad06ec0..8947fb44b07b 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -28,7 +28,7 @@ import android.os.Bundle; * {@hide} */ @SystemApi -public class BroadcastOptions { +public class BroadcastOptions extends ComponentOptions { private long mTemporaryAppWhitelistDuration; private int mMinManifestReceiverApiLevel = 0; private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT; @@ -72,10 +72,12 @@ public class BroadcastOptions { } private BroadcastOptions() { + super(); } /** @hide */ public BroadcastOptions(Bundle opts) { + super(opts); mTemporaryAppWhitelistDuration = opts.getLong(KEY_TEMPORARY_APP_WHITELIST_DURATION); mMinManifestReceiverApiLevel = opts.getInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, 0); mMaxManifestReceiverApiLevel = opts.getInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, @@ -174,6 +176,24 @@ public class BroadcastOptions { } /** + * Set PendingIntent activity is allowed to be started in the background if the caller + * can start background activities. + * @hide + */ + public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) { + super.setPendingIntentBackgroundActivityLaunchAllowed(allowed); + } + + /** + * Get PendingIntent activity is allowed to be started in the background if the caller + * can start background activities. + * @hide + */ + public boolean isPendingIntentBackgroundActivityLaunchAllowed() { + return super.isPendingIntentBackgroundActivityLaunchAllowed(); + } + + /** * Returns the created options as a Bundle, which can be passed to * {@link android.content.Context#sendBroadcast(android.content.Intent) * Context.sendBroadcast(Intent)} and related methods. @@ -181,8 +201,9 @@ public class BroadcastOptions { * object; you must not modify it, but can supply it to the sendBroadcast * methods that take an options Bundle. */ + @Override public Bundle toBundle() { - Bundle b = new Bundle(); + Bundle b = super.toBundle(); if (mTemporaryAppWhitelistDuration > 0) { b.putLong(KEY_TEMPORARY_APP_WHITELIST_DURATION, mTemporaryAppWhitelistDuration); } diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java new file mode 100644 index 000000000000..34ee9138a364 --- /dev/null +++ b/core/java/android/app/ComponentOptions.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 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 android.app; + +import android.os.Bundle; + +/** + * @hide + */ +public class ComponentOptions { + + /** + * Default value for KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED. + * @hide + **/ + public static final boolean PENDING_INTENT_BAL_ALLOWED_DEFAULT = true; + + /** + * PendingIntent caller allows activity start even if PendingIntent creator is in background. + * This only works if the PendingIntent caller is allowed to start background activities, + * for example if it's in the foreground, or has BAL permission. + * @hide + */ + public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED = + "android.pendingIntent.backgroundActivityAllowed"; + + private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT; + + ComponentOptions() { + } + + ComponentOptions(Bundle opts) { + // If the remote side sent us bad parcelables, they won't get the + // results they want, which is their loss. + opts.setDefusable(true); + setPendingIntentBackgroundActivityLaunchAllowed( + opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, + PENDING_INTENT_BAL_ALLOWED_DEFAULT)); + } + + /** + * Set PendingIntent activity is allowed to be started in the background if the caller + * can start background activities. + * + * @hide + */ + public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) { + mPendingIntentBalAllowed = allowed; + } + + /** + * Get PendingIntent activity is allowed to be started in the background if the caller + * can start background activities. + * + * @hide + */ + public boolean isPendingIntentBackgroundActivityLaunchAllowed() { + return mPendingIntentBalAllowed; + } + + /** + * @hide + */ + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, + mPendingIntentBalAllowed); + return bundle; + } +} diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index ededd0d2ea30..24d59a0826c8 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -10750,7 +10750,7 @@ public class Intent implements Parcelable, Cloneable { private void toUriInner(StringBuilder uri, String scheme, String defAction, String defPackage, int flags) { if (scheme != null) { - uri.append("scheme=").append(scheme).append(';'); + uri.append("scheme=").append(Uri.encode(scheme)).append(';'); } if (mAction != null && !mAction.equals(defAction)) { uri.append("action=").append(Uri.encode(mAction)).append(';'); diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java index f40dc298d560..b34b72254743 100644 --- a/core/java/android/content/IntentSender.java +++ b/core/java/android/content/IntentSender.java @@ -16,7 +16,9 @@ package android.content; +import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.compat.annotation.UnsupportedAppUsage; import android.os.Bundle; import android.os.Handler; @@ -154,7 +156,7 @@ public class IntentSender implements Parcelable { */ public void sendIntent(Context context, int code, Intent intent, OnFinished onFinished, Handler handler) throws SendIntentException { - sendIntent(context, code, intent, onFinished, handler, null); + sendIntent(context, code, intent, onFinished, handler, null, null /* options */); } /** @@ -186,6 +188,42 @@ public class IntentSender implements Parcelable { public void sendIntent(Context context, int code, Intent intent, OnFinished onFinished, Handler handler, String requiredPermission) throws SendIntentException { + sendIntent(context, code, intent, onFinished, handler, requiredPermission, + null /* options */); + } + + /** + * Perform the operation associated with this IntentSender, allowing the + * caller to specify information about the Intent to use and be notified + * when the send has completed. + * + * @param context The Context of the caller. This may be null if + * <var>intent</var> is also null. + * @param code Result code to supply back to the IntentSender's target. + * @param intent Additional Intent data. See {@link Intent#fillIn + * Intent.fillIn()} for information on how this is applied to the + * original Intent. Use null to not modify the original Intent. + * @param onFinished The object to call back on when the send has + * completed, or null for no callback. + * @param handler Handler identifying the thread on which the callback + * should happen. If null, the callback will happen from the thread + * pool of the process. + * @param requiredPermission Name of permission that a recipient of the PendingIntent + * is required to hold. This is only valid for broadcast intents, and + * corresponds to the permission argument in + * {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}. + * If null, no permission is required. + * @param options Additional options the caller would like to provide to modify the sending + * behavior. May be built from an {@link ActivityOptions} to apply to an activity start. + * + * @throws SendIntentException Throws CanceledIntentException if the IntentSender + * is no longer allowing more intents to be sent through it. + * @hide + */ + public void sendIntent(Context context, int code, Intent intent, + OnFinished onFinished, Handler handler, String requiredPermission, + @Nullable Bundle options) + throws SendIntentException { try { String resolvedType = intent != null ? intent.resolveTypeIfNeeded(context.getContentResolver()) @@ -195,7 +233,7 @@ public class IntentSender implements Parcelable { onFinished != null ? new FinishedDispatcher(this, onFinished, handler) : null, - requiredPermission, null); + requiredPermission, options); if (res < 0) { throw new SendIntentException(); } diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java index 65ce1e7ef079..9cd568fe2aaf 100644 --- a/core/java/android/content/pm/PackageItemInfo.java +++ b/core/java/android/content/pm/PackageItemInfo.java @@ -207,7 +207,9 @@ public class PackageItemInfo { return loadSafeLabel(pm, DEFAULT_MAX_LABEL_SIZE_PX, SAFE_STRING_FLAG_TRIM | SAFE_STRING_FLAG_FIRST_LINE); } else { - return loadUnsafeLabel(pm); + // Trims the label string to the MAX_SAFE_LABEL_LENGTH. This is to prevent that the + // system is overwhelmed by an enormous string returned by the application. + return TextUtils.trimToSize(loadUnsafeLabel(pm), MAX_SAFE_LABEL_LENGTH); } } diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java index 53a5785f7c76..23f4c6301ec1 100644 --- a/core/java/android/hardware/usb/UsbDeviceConnection.java +++ b/core/java/android/hardware/usb/UsbDeviceConnection.java @@ -108,6 +108,34 @@ public class UsbDeviceConnection { } /** + * This is meant to be called by UsbRequest's queue() in order to synchronize on + * UsbDeviceConnection's mLock to prevent the connection being closed while queueing. + */ + /* package */ boolean queueRequest(UsbRequest request, ByteBuffer buffer, int length) { + synchronized (mLock) { + if (!isOpen()) { + return false; + } + + return request.queueIfConnectionOpen(buffer, length); + } + } + + /** + * This is meant to be called by UsbRequest's queue() in order to synchronize on + * UsbDeviceConnection's mLock to prevent the connection being closed while queueing. + */ + /* package */ boolean queueRequest(UsbRequest request, @Nullable ByteBuffer buffer) { + synchronized (mLock) { + if (!isOpen()) { + return false; + } + + return request.queueIfConnectionOpen(buffer); + } + } + + /** * Releases all system resources related to the device. * Once the object is closed it cannot be used again. * The client must call {@link UsbManager#openDevice} again diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java index 473df712e3f9..c5573214048c 100644 --- a/core/java/android/hardware/usb/UsbRequest.java +++ b/core/java/android/hardware/usb/UsbRequest.java @@ -113,11 +113,13 @@ public class UsbRequest { * Releases all resources related to this request. */ public void close() { - if (mNativeContext != 0) { - mEndpoint = null; - mConnection = null; - native_close(); - mCloseGuard.close(); + synchronized (mLock) { + if (mNativeContext != 0) { + mEndpoint = null; + mConnection = null; + native_close(); + mCloseGuard.close(); + } } } @@ -191,10 +193,32 @@ public class UsbRequest { */ @Deprecated public boolean queue(ByteBuffer buffer, int length) { + UsbDeviceConnection connection = mConnection; + if (connection == null) { + // The expected exception by CTS Verifier - USB Device test + throw new NullPointerException("invalid connection"); + } + + // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent + // the connection being closed while queueing. + return connection.queueRequest(this, buffer, length); + } + + /** + * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over + * there, to prevent the connection being closed while queueing. + */ + /* package */ boolean queueIfConnectionOpen(ByteBuffer buffer, int length) { + UsbDeviceConnection connection = mConnection; + if (connection == null || !connection.isOpen()) { + // The expected exception by CTS Verifier - USB Device test + throw new NullPointerException("invalid connection"); + } + boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); boolean result; - if (mConnection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P + if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P && length > MAX_USBFS_BUFFER_SIZE) { length = MAX_USBFS_BUFFER_SIZE; } @@ -243,6 +267,28 @@ public class UsbRequest { * @return true if the queueing operation succeeded */ public boolean queue(@Nullable ByteBuffer buffer) { + UsbDeviceConnection connection = mConnection; + if (connection == null) { + // The expected exception by CTS Verifier - USB Device test + throw new IllegalStateException("invalid connection"); + } + + // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent + // the connection being closed while queueing. + return connection.queueRequest(this, buffer); + } + + /** + * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over + * there, to prevent the connection being closed while queueing. + */ + /* package */ boolean queueIfConnectionOpen(@Nullable ByteBuffer buffer) { + UsbDeviceConnection connection = mConnection; + if (connection == null || !connection.isOpen()) { + // The expected exception by CTS Verifier - USB Device test + throw new IllegalStateException("invalid connection"); + } + // Request need to be initialized Preconditions.checkState(mNativeContext != 0, "request is not initialized"); @@ -260,7 +306,7 @@ public class UsbRequest { mIsUsingNewQueue = true; wasQueued = native_queue(null, 0, 0); } else { - if (mConnection.getContext().getApplicationInfo().targetSdkVersion + if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P) { // Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE, @@ -363,11 +409,12 @@ public class UsbRequest { * @return true if cancelling succeeded */ public boolean cancel() { - if (mConnection == null) { + UsbDeviceConnection connection = mConnection; + if (connection == null) { return false; } - return mConnection.cancelRequest(this); + return connection.cancelRequest(this); } /** @@ -382,7 +429,8 @@ public class UsbRequest { * @return true if cancelling succeeded. */ /* package */ boolean cancelIfOpen() { - if (mNativeContext == 0 || (mConnection != null && !mConnection.isOpen())) { + UsbDeviceConnection connection = mConnection; + if (mNativeContext == 0 || (connection != null && !connection.isOpen())) { Log.w(TAG, "Detected attempt to cancel a request on a connection which isn't open"); return false; diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 1cb4fe8cf4e7..84fc4f78edc4 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -1194,13 +1194,16 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } static Uri readFrom(Parcel parcel) { - return new HierarchicalUri( - parcel.readString8(), - Part.readFrom(parcel), - PathPart.readFrom(parcel), - Part.readFrom(parcel), - Part.readFrom(parcel) - ); + final String scheme = parcel.readString8(); + final Part authority = Part.readFrom(parcel); + // In RFC3986 the path should be determined based on whether there is a scheme or + // authority present (https://www.rfc-editor.org/rfc/rfc3986.html#section-3.3). + final boolean hasSchemeOrAuthority = + (scheme != null && scheme.length() > 0) || !authority.isEmpty(); + final PathPart path = PathPart.readFrom(hasSchemeOrAuthority, parcel); + final Part query = Part.readFrom(parcel); + final Part fragment = Part.readFrom(parcel); + return new HierarchicalUri(scheme, authority, path, query, fragment); } public int describeContents() { @@ -2259,6 +2262,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } } + static PathPart readFrom(boolean hasSchemeOrAuthority, Parcel parcel) { + final PathPart path = readFrom(parcel); + return hasSchemeOrAuthority ? makeAbsolute(path) : path; + } + /** * Creates a path from the encoded string. * diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java index 6adba63f42ce..485d43066b0f 100644 --- a/core/java/android/os/WorkSource.java +++ b/core/java/android/os/WorkSource.java @@ -129,7 +129,7 @@ public class WorkSource implements Parcelable { mNames = in.createStringArray(); int numChains = in.readInt(); - if (numChains > 0) { + if (numChains >= 0) { mChains = new ArrayList<>(numChains); in.readParcelableList(mChains, WorkChain.class.getClassLoader()); } else { diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 24bbe749aaef..5ef6f16c1a2d 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1233,9 +1233,6 @@ public class ResolverActivity extends Activity implements } if (target != null) { - if (intent != null && isLaunchingTargetInOtherProfile()) { - prepareIntentForCrossProfileLaunch(intent); - } safelyStartActivity(target); // Rely on the ActivityManager to pop up a dialog regarding app suspension @@ -1248,15 +1245,6 @@ public class ResolverActivity extends Activity implements return true; } - private void prepareIntentForCrossProfileLaunch(Intent intent) { - intent.fixUris(UserHandle.myUserId()); - } - - private boolean isLaunchingTargetInOtherProfile() { - return mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier() - != UserHandle.myUserId(); - } - @VisibleForTesting public void safelyStartActivity(TargetInfo cti) { // We're dispatching intents that might be coming from legacy apps, so diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java index fe0e7d012262..cbbfbdd88a6c 100644 --- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java +++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java @@ -178,6 +178,7 @@ public class DisplayResolveInfo implements TargetInfo { if (ENABLE_CHOOSER_DELEGATE) { return activity.startAsCallerImpl(mResolvedIntent, options, false, userId); } else { + TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, userId); activity.startActivityAsCaller(mResolvedIntent, options, null, false, userId); return true; } @@ -185,6 +186,7 @@ public class DisplayResolveInfo implements TargetInfo { @Override public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { + TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier()); activity.startActivityAsUser(mResolvedIntent, options, user); return false; } diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java index 900e18d468bb..9d057b34363e 100644 --- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java +++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java @@ -230,6 +230,7 @@ public final class SelectableTargetInfo implements ChooserTargetInfo { } intent.setComponent(mChooserTarget.getComponentName()); intent.putExtras(mChooserTarget.getIntentExtras()); + TargetInfo.prepareIntentForCrossProfileLaunch(intent, userId); // Important: we will ignore the target security checks in ActivityManager // if and only if the ChooserTarget's target package is the same package diff --git a/core/java/com/android/internal/app/chooser/TargetInfo.java b/core/java/com/android/internal/app/chooser/TargetInfo.java index f56ab17cb059..7bb7ddc65c6d 100644 --- a/core/java/com/android/internal/app/chooser/TargetInfo.java +++ b/core/java/com/android/internal/app/chooser/TargetInfo.java @@ -130,4 +130,15 @@ public interface TargetInfo { * @return true if this target should be pinned to the front by the request of the user */ boolean isPinned(); + + /** + * Fix the URIs in {@code intent} if cross-profile sharing is required. This should be called + * before launching the intent as another user. + */ + static void prepareIntentForCrossProfileLaunch(Intent intent, int targetUserId) { + final int currentUserId = UserHandle.myUserId(); + if (targetUserId != currentUserId) { + intent.fixUris(currentUserId); + } + } } diff --git a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl index 8e454db4cb04..a8003a1169e9 100644 --- a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl +++ b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl @@ -16,7 +16,7 @@ package com.android.internal.policy; interface IKeyguardStateCallback { - void onShowingStateChanged(boolean showing); + void onShowingStateChanged(boolean showing, int userId); void onSimSecureStateChanged(boolean simSecure); void onInputRestrictedStateChanged(boolean inputRestricted); void onTrustedChanged(boolean trusted); diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index f20220c4ab9b..670aefd21d36 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -48,6 +48,7 @@ public class UriTest extends TestCase { public void testParcelling() { parcelAndUnparcel(Uri.parse("foo:bob%20lee")); parcelAndUnparcel(Uri.fromParts("foo", "bob lee", "fragment")); + parcelAndUnparcel(Uri.fromParts("https", "www.google.com", null)); parcelAndUnparcel(new Uri.Builder() .scheme("http") .authority("crazybob.org") @@ -873,9 +874,62 @@ public class UriTest extends TestCase { Throwable targetException = expected.getTargetException(); // Check that the exception was thrown for the correct reason. assertEquals("Unknown representation: 0", targetException.getMessage()); + } finally { + parcel.recycle(); } } + private Uri buildUriFromRawParcel(boolean argumentsEncoded, + String scheme, + String authority, + String path, + String query, + String fragment) { + // Representation value (from AbstractPart.REPRESENTATION_{ENCODED,DECODED}). + final int representation = argumentsEncoded ? 1 : 2; + Parcel parcel = Parcel.obtain(); + try { + parcel.writeInt(3); // hierarchical + parcel.writeString8(scheme); + parcel.writeInt(representation); + parcel.writeString8(authority); + parcel.writeInt(representation); + parcel.writeString8(path); + parcel.writeInt(representation); + parcel.writeString8(query); + parcel.writeInt(representation); + parcel.writeString8(fragment); + parcel.setDataPosition(0); + return Uri.CREATOR.createFromParcel(parcel); + } finally { + parcel.recycle(); + } + } + + public void testUnparcelMalformedPath() { + // Regression tests for b/171966843. + + // Test cases with arguments encoded (covering testing `scheme` * `authority` options). + Uri uri0 = buildUriFromRawParcel(true, "https", "google.com", "@evil.com", null, null); + assertEquals("https://google.com/@evil.com", uri0.toString()); + Uri uri1 = buildUriFromRawParcel(true, null, "google.com", "@evil.com", "name=spark", "x"); + assertEquals("//google.com/@evil.com?name=spark#x", uri1.toString()); + Uri uri2 = buildUriFromRawParcel(true, "http:", null, "@evil.com", null, null); + assertEquals("http::/@evil.com", uri2.toString()); + Uri uri3 = buildUriFromRawParcel(true, null, null, "@evil.com", null, null); + assertEquals("@evil.com", uri3.toString()); + + // Test cases with arguments not encoded (covering testing `scheme` * `authority` options). + Uri uriA = buildUriFromRawParcel(false, "https", "google.com", "@evil.com", null, null); + assertEquals("https://google.com/%40evil.com", uriA.toString()); + Uri uriB = buildUriFromRawParcel(false, null, "google.com", "@evil.com", null, null); + assertEquals("//google.com/%40evil.com", uriB.toString()); + Uri uriC = buildUriFromRawParcel(false, "http:", null, "@evil.com", null, null); + assertEquals("http::/%40evil.com", uriC.toString()); + Uri uriD = buildUriFromRawParcel(false, null, null, "@evil.com", "name=spark", "y"); + assertEquals("%40evil.com?name%3Dspark#y", uriD.toString()); + } + public void testToSafeString() { checkToSafeString("tel:xxxxxx", "tel:Google"); checkToSafeString("tel:xxxxxxxxxx", "tel:1234567890"); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 75f4809d752f..d8eda2c1ecc5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1310,7 +1310,9 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { public void doKeyguardTimeout(Bundle options) { mHandler.removeMessages(KEYGUARD_TIMEOUT); Message msg = mHandler.obtainMessage(KEYGUARD_TIMEOUT, options); - mHandler.sendMessage(msg); + // Treat these messages with priority - A call to timeout means the device should lock + // as soon as possible and not wait for other messages on the thread to process first. + mHandler.sendMessageAtFrontOfQueue(msg); } /** @@ -1497,12 +1499,15 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { * @see #handleShow */ private void showLocked(Bundle options) { - Trace.beginSection("KeyguardViewMediator#showLocked aqcuiring mShowKeyguardWakeLock"); + Trace.beginSection("KeyguardViewMediator#showLocked acquiring mShowKeyguardWakeLock"); if (DEBUG) Log.d(TAG, "showLocked"); // ensure we stay awake until we are finished displaying the keyguard mShowKeyguardWakeLock.acquire(); Message msg = mHandler.obtainMessage(SHOW, options); - mHandler.sendMessage(msg); + // Treat these messages with priority - This call can originate from #doKeyguardTimeout, + // meaning the device should lock as soon as possible and not wait for other messages on + // the thread to process first. + mHandler.sendMessageAtFrontOfQueue(msg); Trace.endSection(); } @@ -1664,6 +1669,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { case KEYGUARD_TIMEOUT: synchronized (KeyguardViewMediator.this) { doKeyguardLocked((Bundle) msg.obj); + notifyDefaultDisplayCallbacks(mShowing); } break; case DISMISS: @@ -2293,7 +2299,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { for (int i = size - 1; i >= 0; i--) { IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i); try { - callback.onShowingStateChanged(showing); + callback.onShowingStateChanged(showing, KeyguardUpdateMonitor.getCurrentUser()); } catch (RemoteException e) { Slog.w(TAG, "Failed to call onShowingStateChanged", e); if (e instanceof DeadObjectException) { @@ -2342,7 +2348,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { mKeyguardStateCallbacks.add(callback); try { callback.onSimSecureStateChanged(mUpdateMonitor.isSimPinSecure()); - callback.onShowingStateChanged(mShowing); + callback.onShowingStateChanged(mShowing, KeyguardUpdateMonitor.getCurrentUser()); callback.onInputRestrictedStateChanged(mInputRestricted); callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust( KeyguardUpdateMonitor.getCurrentUser())); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 03a44131bdd2..f9fbda357075 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1353,6 +1353,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityServiceInfo accessibilityServiceInfo; try { accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); + if (!accessibilityServiceInfo.isWithinParcelableSize()) { + Slog.e(LOG_TAG, "Skipping service " + + accessibilityServiceInfo.getResolveInfo().getComponentInfo() + + " because service info size is larger than safe parcelable limits."); + continue; + } if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) { // Restore the crashed attribute. accessibilityServiceInfo.crashed = true; diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 7cdcc01bc00d..d3246a6f7136 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -28,6 +28,7 @@ import static android.os.UserHandle.USER_SYSTEM; import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; @@ -270,6 +271,8 @@ class AlarmManagerService extends SystemService { * Broadcast options to use for FLAG_ALLOW_WHILE_IDLE. */ Bundle mIdleOptions; + ActivityOptions mActivityOptsRestrictBal = ActivityOptions.makeBasic(); + BroadcastOptions mBroadcastOptsRestrictBal = BroadcastOptions.makeBasic(); private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser = new SparseArray<>(); @@ -486,6 +489,7 @@ class AlarmManagerService extends SystemService { mLastAllowWhileIdleWhitelistDuration = ALLOW_WHILE_IDLE_WHITELIST_DURATION; BroadcastOptions opts = BroadcastOptions.makeBasic(); opts.setTemporaryAppWhitelistDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION); + opts.setPendingIntentBackgroundActivityLaunchAllowed(false); mIdleOptions = opts.toBundle(); } } @@ -1480,6 +1484,8 @@ class AlarmManagerService extends SystemService { @Override public void onStart() { mInjector.init(); + mActivityOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false); + mBroadcastOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false); mListenerDeathRecipient = new IBinder.DeathRecipient() { @Override @@ -4160,6 +4166,13 @@ class AlarmManagerService extends SystemService { return alarm.creatorUid; } + private Bundle getAlarmOperationBundle(Alarm alarm) { + if (alarm.operation.isActivity()) { + return mActivityOptsRestrictBal.toBundle(); + } + return mBroadcastOptsRestrictBal.toBundle(); + } + @VisibleForTesting class AlarmHandler extends Handler { public static final int ALARM_EVENT = 1; @@ -4198,7 +4211,11 @@ class AlarmManagerService extends SystemService { for (int i=0; i<triggerList.size(); i++) { Alarm alarm = triggerList.get(i); try { - alarm.operation.send(); + // Disallow AlarmManager to start random background activity. + final Bundle bundle = getAlarmOperationBundle(alarm); + alarm.operation.send(/* context */ null, /* code */0, /* intent */ + null, /* onFinished */null, /* handler */ + null, /* requiredPermission */ null, bundle); } catch (PendingIntent.CanceledException e) { if (alarm.repeatInterval > 0) { // This IntentSender is no longer valid, but this @@ -4734,7 +4751,7 @@ class AlarmManagerService extends SystemService { mBackgroundIntent.putExtra( Intent.EXTRA_ALARM_COUNT, alarm.count), mDeliveryTracker, mHandler, null, - allowWhileIdle ? mIdleOptions : null); + allowWhileIdle ? mIdleOptions : getAlarmOperationBundle(alarm)); } catch (PendingIntent.CanceledException e) { if (alarm.repeatInterval > 0) { // This IntentSender is no longer valid, but this diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index ad41c129e96a..8d4337accc20 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -3430,8 +3430,7 @@ public class AccountManagerService Bundle.setDefusable(result, true); mNumResults++; Intent intent = null; - if (result != null - && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) { + if (result != null) { if (!checkKeyIntent( Binder.getCallingUid(), result)) { @@ -4790,8 +4789,10 @@ public class AccountManagerService EventLog.writeEvent(0x534e4554, "250588548", authUid, ""); return false; } - Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT); + if (intent == null) { + return true; + } // Explicitly set an empty ClipData to ensure that we don't offer to // promote any Uris contained inside for granting purposes if (intent.getClipData() == null) { @@ -4844,7 +4845,10 @@ public class AccountManagerService p.recycle(); Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT); Intent simulateIntent = simulateBundle.getParcelable(AccountManager.KEY_INTENT); - return (intent.filterEquals(simulateIntent)); + if (intent == null) { + return (simulateIntent == null); + } + return intent.filterEquals(simulateIntent); } private boolean isExportedSystemActivity(ActivityInfo activityInfo) { @@ -4989,8 +4993,7 @@ public class AccountManagerService } } } - if (result != null - && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) { + if (result != null) { if (!checkKeyIntent( Binder.getCallingUid(), result)) { diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index f22cb0b0db50..7fcf2dc76718 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2517,6 +2517,11 @@ public final class ActiveServices { throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + className + " is not an isolatedProcess"); } + if (AppGlobals.getPackageManager().getPackageUid(callingPackage, + 0, userId) != callingUid) { + throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + + "calling package not owned by calling UID "); + } // Run the service under the calling package's application. ApplicationInfo aInfo = AppGlobals.getPackageManager().getApplicationInfo( callingPackage, ActivityManagerService.STOCK_PM_FLAGS, userId); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ec158a6eb579..c69176464127 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -4288,20 +4288,24 @@ public class ActivityManagerService extends IActivityManager.Stub finishForceStopPackageLocked(packageName, appInfo.uid); } } - final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED, - Uri.fromParts("package", packageName, null)); - intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); - intent.putExtra(Intent.EXTRA_UID, (appInfo != null) ? appInfo.uid : -1); - intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId); - if (isInstantApp) { - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); - broadcastIntentInPackage("android", null, SYSTEM_UID, uid, pid, intent, - null, null, 0, null, null, permission.ACCESS_INSTANT_APPS, null, - false, false, resolvedUserId, false); - } else { - broadcastIntentInPackage("android", null, SYSTEM_UID, uid, pid, intent, - null, null, 0, null, null, null, null, false, false, resolvedUserId, - false); + + if (succeeded) { + final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED, + Uri.fromParts("package", packageName, null /* fragment */)); + intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + intent.putExtra(Intent.EXTRA_UID, + (appInfo != null) ? appInfo.uid : Process.INVALID_UID); + intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId); + if (isInstantApp) { + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); + } + + broadcastIntentInPackage("android", null /* featureId */, SYSTEM_UID, + uid, pid, intent, null /* resolvedType */, null /* resultTo */, + 0 /* resultCode */, null /* resultData */, null /* resultExtras */, + isInstantApp ? permission.ACCESS_INSTANT_APPS : null, + null /* bOptions */, false /* serialized */, false /* sticky */, + resolvedUserId, false /* allowBackgroundActivityStarts */); } if (observer != null) { @@ -16955,6 +16959,17 @@ public class ActivityManagerService extends IActivityManager.Stub reportStartInstrumentationFailureLocked(watcher, className, msg); throw new SecurityException(msg); } + if (!Build.IS_DEBUGGABLE && callingUid != ROOT_UID && callingUid != SHELL_UID + && callingUid != SYSTEM_UID && !hasActiveInstrumentationLocked(callingPid)) { + // If it's not debug build and not called from root/shell/system uid, reject it. + final String msg = "Permission Denial: instrumentation test " + + className + " from pid=" + callingPid + ", uid=" + callingUid + + ", pkgName=" + getPackageNameByPid(callingPid) + + " not allowed because it's not started from SHELL"; + Slog.wtfQuiet(TAG, msg); + reportStartInstrumentationFailureLocked(watcher, className, msg); + throw new SecurityException(msg); + } ActiveInstrumentation activeInstr = new ActiveInstrumentation(this); activeInstr.mClass = className; @@ -17023,6 +17038,29 @@ public class ActivityManagerService extends IActivityManager.Stub return true; } + @GuardedBy("this") + private boolean hasActiveInstrumentationLocked(int pid) { + if (pid == 0) { + return false; + } + synchronized (mPidsSelfLocked) { + ProcessRecord process = mPidsSelfLocked.get(pid); + return process != null && process.getActiveInstrumentation() != null; + } + } + + private String getPackageNameByPid(int pid) { + synchronized (mPidsSelfLocked) { + final ProcessRecord app = mPidsSelfLocked.get(pid); + + if (app != null && app.info != null) { + return app.info.packageName; + } + + return null; + } + } + private boolean isCallerShell() { final int callingUid = Binder.getCallingUid(); return callingUid == SHELL_UID || callingUid == ROOT_UID; diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 1997dbd6fc37..52379d5f9ab0 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -286,6 +286,25 @@ public final class PendingIntentRecord extends IIntentSender.Stub { requiredPermission, null, null, 0, 0, 0, options); } + /** + * Return true if the activity options allows PendingIntent to use caller's BAL permission. + */ + public static boolean isPendingIntentBalAllowedByCaller( + @Nullable ActivityOptions activityOptions) { + if (activityOptions == null) { + return ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT; + } + return isPendingIntentBalAllowedByCaller(activityOptions.toBundle()); + } + + private static boolean isPendingIntentBalAllowedByCaller(@Nullable Bundle options) { + if (options == null) { + return ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT; + } + return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, + ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT); + } + public int sendInner(int code, Intent intent, String resolvedType, IBinder whitelistToken, IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo, String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle options) { @@ -398,7 +417,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { // temporarily allow receivers and services to open activities from background if the // PendingIntent.send() caller was foreground at the time of sendInner() call final boolean allowTrampoline = uid != callingUid - && controller.mAtmInternal.isUidForeground(callingUid); + && controller.mAtmInternal.isUidForeground(callingUid) + && isPendingIntentBalAllowedByCaller(options); // note: we on purpose don't pass in the information about the PendingIntent's creator, // like pid or ProcessRecord, to the ActivityTaskManagerInternal calls below, because diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ea4c5c4d3ea7..82464a0450b0 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -59,7 +59,6 @@ import static android.content.pm.PackageManager.MATCH_ALL; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.media.AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; @@ -1727,34 +1726,39 @@ public class NotificationManagerService extends SystemService { return (haystack & needle) != 0; } - public boolean isInLockDownMode() { - return mIsInLockDownMode; + // Return whether the user is in lockdown mode. + // If the flag is not set, we assume the user is not in lockdown. + public boolean isInLockDownMode(int userId) { + return mUserInLockDownMode.get(userId, false); } @Override public synchronized void onStrongAuthRequiredChanged(int userId) { boolean userInLockDownModeNext = containsFlag(getStrongAuthForUser(userId), STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); - mUserInLockDownMode.put(userId, userInLockDownModeNext); - boolean isInLockDownModeNext = mUserInLockDownMode.indexOfValue(true) != -1; - if (mIsInLockDownMode == isInLockDownModeNext) { + // Nothing happens if the lockdown mode of userId keeps the same. + if (userInLockDownModeNext == isInLockDownMode(userId)) { return; } - if (isInLockDownModeNext) { - cancelNotificationsWhenEnterLockDownMode(); + // When the lockdown mode is changed, we perform the following steps. + // If the userInLockDownModeNext is true, all the function calls to + // notifyPostedLocked and notifyRemovedLocked will not be executed. + // The cancelNotificationsWhenEnterLockDownMode calls notifyRemovedLocked + // and postNotificationsWhenExitLockDownMode calls notifyPostedLocked. + // So we shall call cancelNotificationsWhenEnterLockDownMode before + // we set mUserInLockDownMode as true. + // On the other hand, if the userInLockDownModeNext is false, we shall call + // postNotificationsWhenExitLockDownMode after we put false into mUserInLockDownMode + if (userInLockDownModeNext) { + cancelNotificationsWhenEnterLockDownMode(userId); } - // When the mIsInLockDownMode is true, both notifyPostedLocked and - // notifyRemovedLocked will be dismissed. So we shall call - // cancelNotificationsWhenEnterLockDownMode before we set mIsInLockDownMode - // as true and call postNotificationsWhenExitLockDownMode after we set - // mIsInLockDownMode as false. - mIsInLockDownMode = isInLockDownModeNext; + mUserInLockDownMode.put(userId, userInLockDownModeNext); - if (!isInLockDownModeNext) { - postNotificationsWhenExitLockDownMode(); + if (!userInLockDownModeNext) { + postNotificationsWhenExitLockDownMode(userId); } } } @@ -4482,10 +4486,10 @@ public class NotificationManagerService extends SystemService { } enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule"); - // If the caller is system, take the package name from the rule's owner rather than - // from the caller's package. + // If the calling app is the system (from any user), take the package name from the + // rule's owner rather than from the caller's package. String rulePkg = pkg; - if (isCallingUidSystem()) { + if (isCallingAppIdSystem()) { if (automaticZenRule.getOwner() != null) { rulePkg = automaticZenRule.getOwner().getPackageName(); } @@ -8610,11 +8614,14 @@ public class NotificationManagerService extends SystemService { } } - private void cancelNotificationsWhenEnterLockDownMode() { + private void cancelNotificationsWhenEnterLockDownMode(int userId) { synchronized (mNotificationLock) { int numNotifications = mNotificationList.size(); for (int i = 0; i < numNotifications; i++) { NotificationRecord rec = mNotificationList.get(i); + if (rec.getUser().getIdentifier() != userId) { + continue; + } mListeners.notifyRemovedLocked(rec, REASON_CANCEL_ALL, rec.getStats()); } @@ -8622,14 +8629,23 @@ public class NotificationManagerService extends SystemService { } } - private void postNotificationsWhenExitLockDownMode() { + private void postNotificationsWhenExitLockDownMode(int userId) { synchronized (mNotificationLock) { int numNotifications = mNotificationList.size(); + // Set the delay to spread out the burst of notifications. + long delay = 0; for (int i = 0; i < numNotifications; i++) { NotificationRecord rec = mNotificationList.get(i); - mListeners.notifyPostedLocked(rec, rec); + if (rec.getUser().getIdentifier() != userId) { + continue; + } + mHandler.postDelayed(() -> { + synchronized (mNotificationLock) { + mListeners.notifyPostedLocked(rec, rec); + } + }, delay); + delay += 20; } - } } @@ -8644,6 +8660,12 @@ public class NotificationManagerService extends SystemService { return uid == Process.SYSTEM_UID; } + protected boolean isCallingAppIdSystem() { + final int uid = Binder.getCallingUid(); + final int appid = UserHandle.getAppId(uid); + return appid == Process.SYSTEM_UID; + } + protected boolean isUidSystemOrPhone(int uid) { final int appid = UserHandle.getAppId(uid); return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID @@ -8832,12 +8854,15 @@ public class NotificationManagerService extends SystemService { * notifications visible to the given listener. */ @GuardedBy("mNotificationLock") - private NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) { + NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) { final int N = mNotificationList.size(); final ArrayList<NotificationListenerService.Ranking> rankings = new ArrayList<>(); for (int i = 0; i < N; i++) { NotificationRecord record = mNotificationList.get(i); + if (isInLockDownMode(record.getUser().getIdentifier())) { + continue; + } if (!isVisibleToListener(record.getSbn(), info)) { continue; } @@ -8876,8 +8901,8 @@ public class NotificationManagerService extends SystemService { rankings.toArray(new NotificationListenerService.Ranking[0])); } - boolean isInLockDownMode() { - return mStrongAuthTracker.isInLockDownMode(); + boolean isInLockDownMode(int userId) { + return mStrongAuthTracker.isInLockDownMode(userId); } boolean hasCompanionDevice(ManagedServiceInfo info) { @@ -8912,7 +8937,8 @@ public class NotificationManagerService extends SystemService { ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE)); } - private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) { + @VisibleForTesting + boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) { if (!listener.enabledAndUserMatches(sbn.getUserId())) { return false; } @@ -9598,7 +9624,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") void notifyPostedLocked(NotificationRecord r, NotificationRecord old, boolean notifyAllListeners) { - if (isInLockDownMode()) { + if (isInLockDownMode(r.getUser().getIdentifier())) { return; } @@ -9698,7 +9724,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") public void notifyRemovedLocked(NotificationRecord r, int reason, NotificationStats notificationStats) { - if (isInLockDownMode()) { + if (isInLockDownMode(r.getUser().getIdentifier())) { return; } @@ -9747,10 +9773,6 @@ public class NotificationManagerService extends SystemService { */ @GuardedBy("mNotificationLock") public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) { - if (isInLockDownMode()) { - return; - } - boolean isHiddenRankingUpdate = changedHiddenNotifications != null && changedHiddenNotifications.size() > 0; diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 98d9e9a7468f..5c9bb689ba89 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -92,7 +92,9 @@ public class PreferencesHelper implements RankingConfig { private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":"; @VisibleForTesting - static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 50000; + static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000; + @VisibleForTesting + static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 6000; private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000; private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000; @@ -234,6 +236,7 @@ public class PreferencesHelper implements RankingConfig { } } boolean skipWarningLogged = false; + boolean skipGroupWarningLogged = false; boolean hasSAWPermission = false; if (upgradeForBubbles && uid != UNKNOWN_UID) { hasSAWPermission = mAppOps.noteOpNoThrow( @@ -284,6 +287,14 @@ public class PreferencesHelper implements RankingConfig { String tagName = parser.getName(); // Channel groups if (TAG_GROUP.equals(tagName)) { + if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) { + if (!skipGroupWarningLogged) { + Slog.w(TAG, "Skipping further groups for " + r.pkg + + "; app has too many"); + skipGroupWarningLogged = true; + } + continue; + } String id = parser.getAttributeValue(null, ATT_ID); CharSequence groupName = parser.getAttributeValue(null, ATT_NAME); @@ -779,6 +790,9 @@ public class PreferencesHelper implements RankingConfig { } if (fromTargetApp) { group.setBlocked(false); + if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) { + throw new IllegalStateException("Limit exceed; cannot create more groups"); + } } final NotificationChannelGroup oldGroup = r.groups.get(group.getId()); if (oldGroup != null) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 3d6fe77bb836..896b17ec7b12 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -23,6 +23,7 @@ import android.Manifest; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.BroadcastOptions; import android.app.Notification; import android.app.NotificationManager; import android.app.PackageDeleteObserver; @@ -1149,7 +1150,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements PackageInstaller.STATUS_PENDING_USER_ACTION); fillIn.putExtra(Intent.EXTRA_INTENT, intent); try { - mTarget.sendIntent(mContext, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (SendIntentException ignored) { } } @@ -1174,7 +1178,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements PackageManager.deleteStatusToString(returnCode, msg)); fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode); try { - mTarget.sendIntent(mContext, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (SendIntentException ignored) { } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 016ee328fbee..94a13f9e7340 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -51,6 +51,7 @@ import static com.android.server.pm.PackageInstallerService.prepareStageDir; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.BroadcastOptions; import android.app.Notification; import android.app.NotificationManager; import android.app.admin.DevicePolicyEventLogger; @@ -1300,13 +1301,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { try { intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, PackageInstallerSession.this.sessionId); - mStatusReceiver.sendIntent(mContext, 0, intent, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + mStatusReceiver.sendIntent(mContext, 0, intent, + null /* onFinished*/, null /* handler */, + null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignore) { } } } else if (PackageInstaller.STATUS_PENDING_USER_ACTION == status) { try { - mStatusReceiver.sendIntent(mContext, 0, intent, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + mStatusReceiver.sendIntent(mContext, 0, intent, + null /* onFinished*/, null /* handler */, + null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignore) { } } else { // failure, let's forward and clean up this session. @@ -1314,7 +1323,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { PackageInstallerSession.this.sessionId); mChildSessionsRemaining.clear(); // we're done. Don't send any more. try { - mStatusReceiver.sendIntent(mContext, 0, intent, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + mStatusReceiver.sendIntent(mContext, 0, intent, + null /* onFinished*/, null /* handler */, + null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignore) { } } @@ -3260,7 +3273,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION); fillIn.putExtra(Intent.EXTRA_INTENT, intent); try { - target.sendIntent(context, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + target.sendIntent(context, 0, fillIn, null /* onFinished */, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignored) { } } @@ -3300,7 +3316,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } try { - target.sendIntent(context, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + target.sendIntent(context, 0, fillIn, null /* onFinished */, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignored) { } } @@ -3326,7 +3345,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready"); } try { - statusReceiver.sendIntent(mContext, 0, intent, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + statusReceiver.sendIntent(mContext, 0, intent, null /* onFinished*/, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignored) { } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 639c81a82ed2..cb99c62bf5ec 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -5057,7 +5057,10 @@ public class PackageManagerService extends IPackageManager.Stub } if (pi != null) { try { - pi.sendIntent(null, success ? 1 : 0, null, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + pi.sendIntent(null, success ? 1 : 0, null /* intent */, null /* onFinished*/, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (SendIntentException e) { Slog.w(TAG, e); } @@ -13335,7 +13338,10 @@ public class PackageManagerService extends IPackageManager.Stub fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageManager.installStatusToPublicStatus(returnCode)); try { - target.sendIntent(context, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + target.sendIntent(context, 0, fillIn, null /* onFinished*/, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (SendIntentException ignored) { } } @@ -14986,10 +14992,10 @@ public class PackageManagerService extends IPackageManager.Stub // will be null whereas dataOwnerPkg will contain information about the package // which was uninstalled while keeping its data. AndroidPackage dataOwnerPkg = installedPkg; + PackageSetting dataOwnerPs = mSettings.mPackages.get(packageName); if (dataOwnerPkg == null) { - PackageSetting ps = mSettings.mPackages.get(packageName); - if (ps != null) { - dataOwnerPkg = ps.pkg; + if (dataOwnerPs != null) { + dataOwnerPkg = dataOwnerPs.pkg; } } @@ -15013,12 +15019,36 @@ public class PackageManagerService extends IPackageManager.Stub if (dataOwnerPkg != null) { if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags, dataOwnerPkg.isDebuggable())) { + // Downgrade is not permitted; a lower version of the app will not be + // allowed try { checkDowngrade(dataOwnerPkg, pkgLite); } catch (PackageManagerException e) { Slog.w(TAG, "Downgrade detected: " + e.getMessage()); return PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE; } + } else if (dataOwnerPs.isSystem()) { + // Downgrade is permitted, but system apps can't be downgraded below + // the version preloaded onto the system image + final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr( + dataOwnerPs); + if (disabledPs != null) { + dataOwnerPkg = disabledPs.pkg; + } + if (!Build.IS_DEBUGGABLE && !dataOwnerPkg.isDebuggable()) { + // Only restrict non-debuggable builds and non-debuggable version of + // the app + try { + checkDowngrade(dataOwnerPkg, pkgLite); + } catch (PackageManagerException e) { + String errorMsg = "System app: " + packageName + + " cannot be downgraded to" + + " older than its preloaded version on the system image. " + + e.getMessage(); + Slog.w(TAG, errorMsg); + return PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE; + } + } } } 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 575208933658..a48349da31b7 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -2347,7 +2347,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { final PackageSetting ps = (PackageSetting) mPackageManagerInt.getPackageSetting(newPackage.getPackageName()); if (grantSignaturePermission(Manifest.permission.SYSTEM_ALERT_WINDOW, newPackage, ps, saw, - ps.getPermissionsState())) { + ps.getPermissionsState(), true)) { return; } for (int userId : mUserManagerInt.getUserIds()) { @@ -3597,6 +3597,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { private boolean grantSignaturePermission(String perm, AndroidPackage pkg, PackageSetting pkgSetting, BasePermission bp, PermissionsState origPermissions) { + return grantSignaturePermission(perm, pkg, pkgSetting, bp, origPermissions, false); + } + + + private boolean grantSignaturePermission(String perm, AndroidPackage pkg, + PackageSetting pkgSetting, BasePermission bp, PermissionsState origPermissions, + boolean isApi23Upgrade) { boolean oemPermission = bp.isOEM(); boolean vendorPrivilegedPermission = bp.isVendorPrivileged(); boolean privilegedPermission = bp.isPrivileged() || bp.isVendorPrivileged(); @@ -3771,7 +3778,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // Any pre-installed system app is allowed to get this permission. allowed = true; } - if (!allowed && bp.isDevelopment()) { + if (!allowed && bp.isDevelopment() && !(bp.isPre23() && isApi23Upgrade)) { // For development permissions, a development permission // is granted only if it was already granted. allowed = origPermissions.hasInstallPermission(perm); diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java index 4e848686254a..cf5c587e0494 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -192,6 +192,12 @@ public class KeyguardServiceWrapper implements IKeyguardService { @Override // Binder interface public void doKeyguardTimeout(Bundle options) { + int userId = mKeyguardStateMonitor.getCurrentUser(); + if (mKeyguardStateMonitor.isSecure(userId)) { + // Preemptively inform the cache that the keyguard will soon be showing, as calls to + // doKeyguardTimeout are a signal to lock the device as soon as possible. + mKeyguardStateMonitor.onShowingStateChanged(true, userId); + } try { mService.doKeyguardTimeout(options); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java index add0b01f1879..f0f62edf8779 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java @@ -83,8 +83,14 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { return mHasLockscreenWallpaper; } + public int getCurrentUser() { + return mCurrentUserId; + } + @Override // Binder interface - public void onShowingStateChanged(boolean showing) { + public void onShowingStateChanged(boolean showing, int userId) { + if (userId != mCurrentUserId) return; + mIsShowing = showing; mCallback.onShowingChanged(); diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java index 95b5cec9a144..95a6e8b8b88f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java +++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java @@ -86,6 +86,10 @@ class ActivityRecordInputSink { mInputWindowHandle.layoutParamsFlags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; changed |= notTouchable; } + if (mInputWindowHandle.displayId != mActivityRecord.getDisplayId()) { + mInputWindowHandle.displayId = mActivityRecord.getDisplayId(); + changed = true; + } return changed; } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 54ad4acaca4c..5d4549631572 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1004,6 +1004,10 @@ class ActivityStarter { abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid, callingPackage); + // Merge the two options bundles, while realCallerOptions takes precedence. + ActivityOptions checkedOptions = options != null + ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null; + boolean restrictedBgActivity = false; if (!abort) { try { @@ -1012,15 +1016,12 @@ class ActivityStarter { restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, realCallingUid, realCallingPid, callerApp, request.originatingPendingIntent, request.allowBackgroundActivityStart, - intent); + intent, checkedOptions); } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } } - // Merge the two options bundles, while realCallerOptions takes precedence. - ActivityOptions checkedOptions = options != null - ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null; if (request.allowPendingRemoteAnimationRegistryLookup) { checkedOptions = mService.getActivityStartController() .getPendingRemoteAnimationRegistry() @@ -1234,7 +1235,7 @@ class ActivityStarter { boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid, final String callingPackage, int realCallingUid, int realCallingPid, WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent, - boolean allowBackgroundActivityStart, Intent intent) { + boolean allowBackgroundActivityStart, Intent intent, ActivityOptions checkedOptions) { // don't abort for the most important UIDs final int callingAppId = UserHandle.getAppId(callingUid); if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID @@ -1278,7 +1279,11 @@ class ActivityStarter { ? isCallingUidPersistentSystemProcess : (realCallingAppId == Process.SYSTEM_UID) || realCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI; - if (realCallingUid != callingUid) { + // Legacy behavior allows to use caller foreground state to bypass BAL restriction. + final boolean balAllowedByPiSender = + PendingIntentRecord.isPendingIntentBalAllowedByCaller(checkedOptions); + + if (balAllowedByPiSender && realCallingUid != callingUid) { // don't abort if the realCallingUid has a visible window if (realCallingUidHasAnyVisibleWindow) { if (DEBUG_ACTIVITY_STARTS) { @@ -1346,9 +1351,10 @@ class ActivityStarter { // If we don't have callerApp at this point, no caller was provided to startActivity(). // That's the case for PendingIntent-based starts, since the creator's process might not be // up and alive. If that's the case, we retrieve the WindowProcessController for the send() - // caller, so that we can make the decision based on its foreground/whitelisted state. + // caller if caller allows, so that we can make the decision + // based on its foreground/whitelisted state. int callerAppUid = callingUid; - if (callerApp == null) { + if (callerApp == null && balAllowedByPiSender) { callerApp = mService.getProcessController(realCallingPid, realCallingUid); callerAppUid = realCallingUid; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 8dbd661cff91..f7f810e5b3d1 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2523,7 +2523,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final ActivityStarter starter = getActivityStartController().obtainStarter( null /* intent */, "moveTaskToFront"); if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, -1, - -1, callerApp, null, false, null)) { + -1, callerApp, null, false, null, null)) { if (!isBackgroundActivityStartsEnabled()) { return; } diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 2fd5963d653b..edd086df78aa 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -112,7 +112,7 @@ class AppTaskImpl extends IAppTask.Stub { final ActivityStarter starter = mService.getActivityStartController().obtainStarter( null /* intent */, "moveToFront"); if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid, - callingPackage, -1, -1, callerApp, null, false, null)) { + callingPackage, -1, -1, callerApp, null, false, null, null)) { if (!mService.isBackgroundActivityStartsEnabled()) { return; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index c0e4262863e6..a72763b97dbd 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3665,12 +3665,39 @@ class Task extends WindowContainer<WindowContainer> { if (info.topActivityInfo != null && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) { - info.topActivity = null; - info.topActivityInfo = null; + // Making a copy to prevent eliminating the info in the original ActivityRecord. + info.topActivityInfo = new ActivityInfo(info.topActivityInfo); + info.topActivityInfo.applicationInfo = + new ApplicationInfo(info.topActivityInfo.applicationInfo); + + // Strip the sensitive info. + info.topActivity = new ComponentName("", ""); + info.topActivityInfo.packageName = ""; + info.topActivityInfo.taskAffinity = ""; + info.topActivityInfo.processName = ""; + info.topActivityInfo.name = ""; + info.topActivityInfo.parentActivityName = ""; + info.topActivityInfo.targetActivity = ""; + info.topActivityInfo.splitName = ""; + info.topActivityInfo.applicationInfo.className = ""; + info.topActivityInfo.applicationInfo.credentialProtectedDataDir = ""; + info.topActivityInfo.applicationInfo.dataDir = ""; + info.topActivityInfo.applicationInfo.deviceProtectedDataDir = ""; + info.topActivityInfo.applicationInfo.manageSpaceActivityName = ""; + info.topActivityInfo.applicationInfo.nativeLibraryDir = ""; + info.topActivityInfo.applicationInfo.nativeLibraryRootDir = ""; + info.topActivityInfo.applicationInfo.processName = ""; + info.topActivityInfo.applicationInfo.publicSourceDir = ""; + info.topActivityInfo.applicationInfo.scanPublicSourceDir = ""; + info.topActivityInfo.applicationInfo.scanSourceDir = ""; + info.topActivityInfo.applicationInfo.sourceDir = ""; + info.topActivityInfo.applicationInfo.taskAffinity = ""; + info.topActivityInfo.applicationInfo.name = ""; + info.topActivityInfo.applicationInfo.packageName = ""; } if (task.effectiveUid != baseActivityUid) { - info.baseActivity = null; + info.baseActivity = new ComponentName("", ""); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java index 7244fcdda731..a4727a09570c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.when; import android.app.INotificationManager; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.os.UserHandle; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.testing.TestableContext; @@ -40,8 +41,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.internal.util.reflection.FieldSetter; -import java.util.List; - public class NotificationListenersTest extends UiServiceTestCase { @Mock @@ -71,63 +70,112 @@ public class NotificationListenersTest extends UiServiceTestCase { @Test public void testNotifyPostedLockedInLockdownMode() { - NotificationRecord r = mock(NotificationRecord.class); - NotificationRecord old = mock(NotificationRecord.class); - - // before the lockdown mode - when(mNm.isInLockDownMode()).thenReturn(false); - mListeners.notifyPostedLocked(r, old, true); - mListeners.notifyPostedLocked(r, old, false); - verify(r, atLeast(2)).getSbn(); - - // in the lockdown mode - reset(r); - reset(old); - when(mNm.isInLockDownMode()).thenReturn(true); - mListeners.notifyPostedLocked(r, old, true); - mListeners.notifyPostedLocked(r, old, false); - verify(r, never()).getSbn(); - } - - @Test - public void testnotifyRankingUpdateLockedInLockdownMode() { - List chn = mock(List.class); - - // before the lockdown mode - when(mNm.isInLockDownMode()).thenReturn(false); - mListeners.notifyRankingUpdateLocked(chn); - verify(chn, atLeast(1)).size(); - - // in the lockdown mode - reset(chn); - when(mNm.isInLockDownMode()).thenReturn(true); - mListeners.notifyRankingUpdateLocked(chn); - verify(chn, never()).size(); + NotificationRecord r0 = mock(NotificationRecord.class); + NotificationRecord old0 = mock(NotificationRecord.class); + UserHandle uh0 = mock(UserHandle.class); + + NotificationRecord r1 = mock(NotificationRecord.class); + NotificationRecord old1 = mock(NotificationRecord.class); + UserHandle uh1 = mock(UserHandle.class); + + // Neither user0 and user1 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(false); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + + mListeners.notifyPostedLocked(r0, old0, true); + mListeners.notifyPostedLocked(r0, old0, false); + verify(r0, atLeast(2)).getSbn(); + + mListeners.notifyPostedLocked(r1, old1, true); + mListeners.notifyPostedLocked(r1, old1, false); + verify(r1, atLeast(2)).getSbn(); + + // Reset + reset(r0); + reset(old0); + reset(r1); + reset(old1); + + // Only user 0 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(true); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + + mListeners.notifyPostedLocked(r0, old0, true); + mListeners.notifyPostedLocked(r0, old0, false); + verify(r0, never()).getSbn(); + + mListeners.notifyPostedLocked(r1, old1, true); + mListeners.notifyPostedLocked(r1, old1, false); + verify(r1, atLeast(2)).getSbn(); } @Test public void testNotifyRemovedLockedInLockdownMode() throws NoSuchFieldException { - NotificationRecord r = mock(NotificationRecord.class); - NotificationStats rs = mock(NotificationStats.class); + NotificationRecord r0 = mock(NotificationRecord.class); + NotificationStats rs0 = mock(NotificationStats.class); + UserHandle uh0 = mock(UserHandle.class); + + NotificationRecord r1 = mock(NotificationRecord.class); + NotificationStats rs1 = mock(NotificationStats.class); + UserHandle uh1 = mock(UserHandle.class); + StatusBarNotification sbn = mock(StatusBarNotification.class); FieldSetter.setField(mNm, NotificationManagerService.class.getDeclaredField("mHandler"), mock(NotificationManagerService.WorkerHandler.class)); - // before the lockdown mode - when(mNm.isInLockDownMode()).thenReturn(false); - when(r.getSbn()).thenReturn(sbn); - mListeners.notifyRemovedLocked(r, 0, rs); - mListeners.notifyRemovedLocked(r, 0, rs); - verify(r, atLeast(2)).getSbn(); - - // in the lockdown mode - reset(r); - reset(rs); - when(mNm.isInLockDownMode()).thenReturn(true); - when(r.getSbn()).thenReturn(sbn); - mListeners.notifyRemovedLocked(r, 0, rs); - mListeners.notifyRemovedLocked(r, 0, rs); - verify(r, never()).getSbn(); + // Neither user0 and user1 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(false); + when(r0.getSbn()).thenReturn(sbn); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + when(r1.getSbn()).thenReturn(sbn); + + mListeners.notifyRemovedLocked(r0, 0, rs0); + mListeners.notifyRemovedLocked(r0, 0, rs0); + verify(r0, atLeast(2)).getSbn(); + + mListeners.notifyRemovedLocked(r1, 0, rs1); + mListeners.notifyRemovedLocked(r1, 0, rs1); + verify(r1, atLeast(2)).getSbn(); + + // Reset + reset(r0); + reset(rs0); + reset(r1); + reset(rs1); + + // Only user 0 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(true); + when(r0.getSbn()).thenReturn(sbn); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + when(r1.getSbn()).thenReturn(sbn); + + mListeners.notifyRemovedLocked(r0, 0, rs0); + mListeners.notifyRemovedLocked(r0, 0, rs0); + verify(r0, never()).getSbn(); + + mListeners.notifyRemovedLocked(r1, 0, rs1); + mListeners.notifyRemovedLocked(r1, 0, rs1); + verify(r1, atLeast(2)).getSbn(); } } 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 c7b328717d34..df2801134f77 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -141,6 +141,7 @@ import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.ConversationChannelWrapper; import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationRankingUpdate; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.service.notification.ZenPolicy; @@ -174,6 +175,7 @@ import com.android.server.SystemService; import com.android.server.UiServiceTestCase; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; +import com.android.server.notification.ManagedServices.ManagedServiceInfo; import com.android.server.notification.NotificationManagerService.NotificationAssistants; import com.android.server.notification.NotificationManagerService.NotificationListeners; import com.android.server.pm.PackageManagerService; @@ -304,10 +306,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { StatusBarManagerInternal mStatusBar; private final FakeSystemClock mSystemClock = new FakeSystemClock(); + private NotificationManagerService.WorkerHandler mWorkerHandler; + // Use a Testable subclass so we can simulate calls from the system without failing. private static class TestableNotificationManagerService extends NotificationManagerService { int countSystemChecks = 0; boolean isSystemUid = true; + boolean isSystemAppId = true; int countLogSmartSuggestionsVisible = 0; // If true, don't enqueue the PostNotificationRunnables, just trap them boolean trapEnqueuedNotifications = false; @@ -316,6 +321,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Nullable NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback; + @Nullable + Boolean mIsVisibleToListenerReturnValue = null; + TestableNotificationManagerService( Context context, NotificationRecordLogger logger, @@ -335,6 +343,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Override + protected boolean isCallingAppIdSystem() { + countSystemChecks++; + return isSystemUid || isSystemAppId; + } + + @Override protected boolean isCallerSystemOrPhone() { countSystemChecks++; return isSystemUid; @@ -402,6 +416,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { void onGranted(ComponentName assistant, int userId, boolean granted); } + protected void setIsVisibleToListenerReturnValue(boolean value) { + mIsVisibleToListenerReturnValue = value; + } + + @Override + boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) { + if (mIsVisibleToListenerReturnValue != null) { + return mIsVisibleToListenerReturnValue; + } + return super.isVisibleToListener(sbn, listener); + } + class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker { private int mGetStrongAuthForUserReturnValue = 0; StrongAuthTrackerFake(Context context) { @@ -524,7 +550,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true); - mService.init(mService.new WorkerHandler(mTestableLooper.getLooper()), + mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper())); + mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient, mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, @@ -593,6 +620,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.unregisterDeviceConfigChange(); InstrumentationRegistry.getInstrumentation() .getUiAutomation().dropShellPermissionIdentity(); + mWorkerHandler.removeCallbacksAndMessages(null); } private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() { @@ -5955,8 +5983,30 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testAddAutomaticZenRule_systemAppIdCallTakesPackageFromOwner() throws Exception { + // The multi-user case: where the calling uid doesn't match the system uid, but the calling + // *appid* is the system. + mService.isSystemUid = false; + mService.isSystemAppId = true; + ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class); + when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) + .thenReturn(true); + mService.setZenHelper(mockZenModeHelper); + ComponentName owner = new ComponentName("android", "ProviderName"); + ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build(); + boolean isEnabled = true; + AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class), + zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled); + mBinderService.addAutomaticZenRule(rule, "com.android.settings"); + + // verify that zen mode helper gets passed in a package name of "android" + verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString()); + } + + @Test public void testAddAutomaticZenRule_nonSystemCallTakesPackageFromArg() throws Exception { mService.isSystemUid = false; + mService.isSystemAppId = false; ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class); when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) .thenReturn(true); @@ -7261,10 +7311,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mStrongAuthTracker.setGetStrongAuthForUserReturnValue( STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); - assertTrue(mStrongAuthTracker.isInLockDownMode()); - mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); + assertTrue(mStrongAuthTracker.isInLockDownMode(mContext.getUserId())); + mStrongAuthTracker.setGetStrongAuthForUserReturnValue(mContext.getUserId()); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); - assertFalse(mStrongAuthTracker.isInLockDownMode()); + assertFalse(mStrongAuthTracker.isInLockDownMode(mContext.getUserId())); } @Test @@ -7280,8 +7330,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // when entering the lockdown mode, cancel the 2 notifications. mStrongAuthTracker.setGetStrongAuthForUserReturnValue( STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); - mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); - assertTrue(mStrongAuthTracker.isInLockDownMode()); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertTrue(mStrongAuthTracker.isInLockDownMode(0)); // the notifyRemovedLocked function is called twice due to REASON_LOCKDOWN. ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class); @@ -7290,9 +7340,44 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // exit lockdown mode. mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); - mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertFalse(mStrongAuthTracker.isInLockDownMode(0)); // the notifyPostedLocked function is called twice. - verify(mListeners, times(2)).notifyPostedLocked(any(), any()); + verify(mWorkerHandler, times(2)).postDelayed(any(Runnable.class), anyLong()); + } + + @Test + public void testMakeRankingUpdateLockedInLockDownMode() { + // post 2 notifications from a same package + NotificationRecord pkgA = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgA); + NotificationRecord pkgB = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 1), mTestNotificationChannel); + mService.addNotification(pkgB); + + mService.setIsVisibleToListenerReturnValue(true); + NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(null); + assertEquals(2, nru.getRankingMap().getOrderedKeys().length); + + // when only user 0 entering the lockdown mode, its notification will be suppressed. + mStrongAuthTracker.setGetStrongAuthForUserReturnValue( + STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertTrue(mStrongAuthTracker.isInLockDownMode(0)); + assertFalse(mStrongAuthTracker.isInLockDownMode(1)); + + nru = mService.makeRankingUpdateLocked(null); + assertEquals(1, nru.getRankingMap().getOrderedKeys().length); + + // User 0 exits lockdown mode. Its notification will be resumed. + mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertFalse(mStrongAuthTracker.isInLockDownMode(0)); + assertFalse(mStrongAuthTracker.isInLockDownMode(1)); + + nru = mService.makeRankingUpdateLocked(null); + assertEquals(2, nru.getRankingMap().getOrderedKeys().length); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 06cfbea72a82..ffc12c94871e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -41,6 +41,7 @@ import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.UID_FIELD_NUMBER; import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; +import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; import static com.android.server.notification.PreferencesHelper.UNKNOWN_UID; import static com.google.common.truth.Truth.assertThat; @@ -3075,6 +3076,52 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testTooManyGroups() { + for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) { + NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i), + String.valueOf(i)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + } + try { + NotificationChannelGroup group = new NotificationChannelGroup( + String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT), + String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + fail("Allowed to create too many notification channel groups"); + } catch (IllegalStateException e) { + // great + } + } + + @Test + public void testTooManyGroups_xml() throws Exception { + String extraGroup = "EXTRA"; + String extraGroup1 = "EXTRA1"; + + // create first... many... directly so we don't need a big xml blob in this test + for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) { + NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i), + String.valueOf(i)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + } + + final String xml = "<ranking version=\"1\">\n" + + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" + + "<channelGroup id=\"" + extraGroup + "\" name=\"hi\"/>" + + "<channelGroup id=\"" + extraGroup1 + "\" name=\"hi2\"/>" + + "</package>" + + "</ranking>"; + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), + null); + parser.nextTag(); + mHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertNull(mHelper.getNotificationChannelGroup(extraGroup, PKG_O, UID_O)); + assertNull(mHelper.getNotificationChannelGroup(extraGroup1, PKG_O, UID_O)); + } + + @Test public void testRestoreMultiUser() throws Exception { String pkg = "restore_pkg"; String channelId = "channelId"; diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index 768c8eebf067..a85c85380fb7 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -483,6 +483,11 @@ public final class PhoneAccount implements Parcelable { /** * Sets the address. See {@link PhoneAccount#getAddress}. + * <p> + * Note: The entire URI value is limited to 256 characters. This check is + * enforced when registering the PhoneAccount via + * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an + * {@link IllegalArgumentException} to be thrown if URI is over 256. * * @param value The address of the phone account. * @return The builder. @@ -516,6 +521,10 @@ public final class PhoneAccount implements Parcelable { /** * Sets the icon. See {@link PhoneAccount#getIcon}. + * <p> + * Note: An {@link IllegalArgumentException} if the Icon cannot be written to memory. + * This check is enforced when registering the PhoneAccount via + * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} * * @param icon The icon to set. */ @@ -549,6 +558,10 @@ public final class PhoneAccount implements Parcelable { /** * Specifies an additional URI scheme supported by the {@link PhoneAccount}. * + * <p> + * Each URI scheme is limited to 256 characters. Adding a scheme over 256 characters will + * cause an {@link IllegalArgumentException} to be thrown when the account is registered. + * * @param uriScheme The URI scheme. * @return The builder. */ @@ -562,6 +575,12 @@ public final class PhoneAccount implements Parcelable { /** * Specifies the URI schemes supported by the {@link PhoneAccount}. * + * <p> + * A max of 10 URI schemes can be added per account. Additionally, each URI scheme is + * limited to 256 characters. Adding more than 10 URI schemes or 256 characters on any + * scheme will cause an {@link IllegalArgumentException} to be thrown when the account + * is registered. + * * @param uriSchemes The URI schemes. * @return The builder. */ |