diff options
51 files changed, 1986 insertions, 402 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index 6186a5571bad..a559b7a27f59 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1027,7 +1027,6 @@ package android.content.pm { method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String); method public abstract int getIntentVerificationStatusAsUser(java.lang.String, int); method public abstract int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle); - method public android.os.PersistableBundle getSuspendedPackageAppExtras(java.lang.String); method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public abstract int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; @@ -1039,7 +1038,6 @@ package android.content.pm { method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int); method public void setHarmfulAppWarning(java.lang.String, java.lang.CharSequence); method public java.lang.String[] setPackagesSuspended(java.lang.String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, java.lang.String); - method public void setSuspendedPackageAppExtras(java.lang.String, android.os.PersistableBundle); method public abstract void setUpdateAvailable(java.lang.String, boolean); method public abstract boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int); method public abstract void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 27bbc4bca268..2e93d88e0388 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2165,30 +2165,18 @@ public class ApplicationPackageManager extends PackageManager { } @Override - public PersistableBundle getSuspendedPackageAppExtras(String packageName) { + public Bundle getSuspendedPackageAppExtras() { + final PersistableBundle extras; try { - return mPM.getSuspendedPackageAppExtras(packageName, mContext.getUserId()); + extras = mPM.getSuspendedPackageAppExtras(mContext.getOpPackageName(), + mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - } - - @Override - public Bundle getSuspendedPackageAppExtras() { - final PersistableBundle extras = getSuspendedPackageAppExtras(mContext.getOpPackageName()); return extras != null ? new Bundle(extras.deepCopy()) : null; } @Override - public void setSuspendedPackageAppExtras(String packageName, PersistableBundle appExtras) { - try { - mPM.setSuspendedPackageAppExtras(packageName, appExtras, mContext.getUserId()); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - } - - @Override public boolean isPackageSuspendedForUser(String packageName, int userId) { try { return mPM.isPackageSuspendedForUser(packageName, userId); diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 4a7cf62356e3..9e47ced433e3 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -328,7 +328,8 @@ public final class NotificationChannel implements Parcelable { * Group information is only used for presentation, not for behavior. * * Only modifiable before the channel is submitted to - * {@link NotificationManager#notify(String, int, Notification)}. + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}, unless the + * channel is not currently part of a group. * * @param groupId the id of a group created by * {@link NotificationManager#createNotificationChannelGroup(NotificationChannelGroup)}. @@ -341,6 +342,9 @@ public final class NotificationChannel implements Parcelable { * Sets whether notifications posted to this channel can appear as application icon badges * in a Launcher. * + * Only modifiable before the channel is submitted to + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. + * * @param showBadge true if badges should be allowed to be shown. */ public void setShowBadge(boolean showBadge) { @@ -353,7 +357,7 @@ public final class NotificationChannel implements Parcelable { * least {@link NotificationManager#IMPORTANCE_DEFAULT} should have a sound. * * Only modifiable before the channel is submitted to - * {@link NotificationManager#notify(String, int, Notification)}. + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. */ public void setSound(Uri sound, AudioAttributes audioAttributes) { this.mSound = sound; @@ -365,7 +369,7 @@ public final class NotificationChannel implements Parcelable { * on devices that support that feature. * * Only modifiable before the channel is submitted to - * {@link NotificationManager#notify(String, int, Notification)}. + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. */ public void enableLights(boolean lights) { this.mLights = lights; @@ -376,7 +380,7 @@ public final class NotificationChannel implements Parcelable { * {@link #enableLights(boolean) enabled} on this channel and the device supports that feature. * * Only modifiable before the channel is submitted to - * {@link NotificationManager#notify(String, int, Notification)}. + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. */ public void setLightColor(int argb) { this.mLightColor = argb; @@ -387,7 +391,7 @@ public final class NotificationChannel implements Parcelable { * be set with {@link #setVibrationPattern(long[])}. * * Only modifiable before the channel is submitted to - * {@link NotificationManager#notify(String, int, Notification)}. + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. */ public void enableVibration(boolean vibration) { this.mVibrationEnabled = vibration; @@ -399,7 +403,7 @@ public final class NotificationChannel implements Parcelable { * vibration} as well. Otherwise, vibration will be disabled. * * Only modifiable before the channel is submitted to - * {@link NotificationManager#notify(String, int, Notification)}. + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. */ public void setVibrationPattern(long[] vibrationPattern) { this.mVibrationEnabled = vibrationPattern != null && vibrationPattern.length > 0; @@ -407,9 +411,10 @@ public final class NotificationChannel implements Parcelable { } /** - * Sets the level of interruption of this notification channel. Only - * modifiable before the channel is submitted to - * {@link NotificationManager#notify(String, int, Notification)}. + * Sets the level of interruption of this notification channel. + * + * Only modifiable before the channel is submitted to + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. * * @param importance the amount the user should be interrupted by * notifications from this channel. diff --git a/core/java/android/app/slice/ISliceManager.aidl b/core/java/android/app/slice/ISliceManager.aidl index a2aaf12432f8..69852f3ce745 100644 --- a/core/java/android/app/slice/ISliceManager.aidl +++ b/core/java/android/app/slice/ISliceManager.aidl @@ -25,11 +25,15 @@ interface ISliceManager { void unpinSlice(String pkg, in Uri uri, in IBinder token); boolean hasSliceAccess(String pkg); SliceSpec[] getPinnedSpecs(in Uri uri, String pkg); - int checkSlicePermission(in Uri uri, String pkg, int pid, int uid, - in String[] autoGrantPermissions); - void grantPermissionFromUser(in Uri uri, String pkg, String callingPkg, boolean allSlices); Uri[] getPinnedSlices(String pkg); byte[] getBackupPayload(int user); void applyRestore(in byte[] payload, int user); + + // Perms. + void grantSlicePermission(String callingPkg, String toPkg, in Uri uri); + void revokeSlicePermission(String callingPkg, String toPkg, in Uri uri); + int checkSlicePermission(in Uri uri, String pkg, int pid, int uid, + in String[] autoGrantPermissions); + void grantPermissionFromUser(in Uri uri, String pkg, String callingPkg, boolean allSlices); } diff --git a/core/java/android/app/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java index 4127a40a69ff..dc09f12db6f6 100644 --- a/core/java/android/app/slice/SliceManager.java +++ b/core/java/android/app/slice/SliceManager.java @@ -16,6 +16,8 @@ package android.app.slice; +import static android.content.pm.PackageManager.PERMISSION_DENIED; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; @@ -103,22 +105,6 @@ public class SliceManager { private final IBinder mToken = new Binder(); /** - * Permission denied. - * @hide - */ - public static final int PERMISSION_DENIED = -1; - /** - * Permission granted. - * @hide - */ - public static final int PERMISSION_GRANTED = 0; - /** - * Permission just granted by the user, and should be granted uri permission as well. - * @hide - */ - public static final int PERMISSION_USER_GRANTED = 1; - - /** * @hide */ public SliceManager(Context context, Handler handler) throws ServiceNotFoundException { @@ -436,9 +422,11 @@ public class SliceManager { * @see #grantSlicePermission(String, Uri) */ public @PermissionResult int checkSlicePermission(@NonNull Uri uri, int pid, int uid) { - // TODO: Switch off Uri permissions. - return mContext.checkUriPermission(uri, pid, uid, - Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + try { + return mService.checkSlicePermission(uri, null, pid, uid, null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -450,11 +438,11 @@ public class SliceManager { * @see #revokeSlicePermission */ public void grantSlicePermission(@NonNull String toPackage, @NonNull Uri uri) { - // TODO: Switch off Uri permissions. - mContext.grantUriPermission(toPackage, uri, - Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + try { + mService.grantSlicePermission(mContext.getPackageName(), toPackage, uri); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -472,11 +460,11 @@ public class SliceManager { * @see #grantSlicePermission */ public void revokeSlicePermission(@NonNull String toPackage, @NonNull Uri uri) { - // TODO: Switch off Uri permissions. - mContext.revokeUriPermission(toPackage, uri, - Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + try { + mService.revokeSlicePermission(mContext.getPackageName(), toPackage, uri); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -497,16 +485,6 @@ public class SliceManager { throw new SecurityException("User " + uid + " does not have slice permission for " + uri + "."); } - if (result == PERMISSION_USER_GRANTED) { - // We just had a user grant of this permission and need to grant this to the app - // permanently. - mContext.grantUriPermission(pkg, uri.buildUpon().path("").build(), - Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); - // Notify a change has happened because we just granted a permission. - mContext.getContentResolver().notifyChange(uri, null); - } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 02ce47b85a08..2be33e94dc25 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -280,9 +280,6 @@ interface IPackageManager { PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId); - void setSuspendedPackageAppExtras(String packageName, in PersistableBundle appExtras, - int userId); - /** * Backup/restore support - only the system uid may use these. */ diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 34e3d8b5c2c8..90fc8f8bd652 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -5616,46 +5616,6 @@ public abstract class PackageManager { } /** - * Retrieve the {@link PersistableBundle} that was passed as {@code appExtras} when the given - * package was suspended. - * - * <p> The caller must hold permission {@link Manifest.permission#SUSPEND_APPS} to use this - * api.</p> - * - * @param packageName The package to retrieve extras for. - * @return The {@code appExtras} for the suspended package. - * - * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String) - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.SUSPEND_APPS) - public @Nullable PersistableBundle getSuspendedPackageAppExtras(String packageName) { - throw new UnsupportedOperationException("getSuspendedPackageAppExtras not implemented"); - } - - /** - * Set the app extras for a suspended package. This method can be used to update the appExtras - * for a package that was earlier suspended using - * {@link #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, - * String)} - * Does nothing if the given package is not already in a suspended state. - * - * @param packageName The package for which the appExtras need to be updated - * @param appExtras The new appExtras for the given package - * - * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String) - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.SUSPEND_APPS) - public void setSuspendedPackageAppExtras(String packageName, - @Nullable PersistableBundle appExtras) { - throw new UnsupportedOperationException("setSuspendedPackageAppExtras not implemented"); - } - - /** * Returns any extra information supplied as {@code appExtras} to the system when the calling * app was suspended. * diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index dc58f11e83fd..b367dc7b9c50 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -11791,6 +11791,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return null; } + /** @hide */ + View getSelfOrParentImportantForA11y() { + if (isImportantForAccessibility()) return this; + ViewParent parent = getParentForAccessibility(); + if (parent instanceof View) return (View) parent; + return null; + } + /** * Adds the children of this View relevant for accessibility to the given list * as output. Since some Views are not important for accessibility the added @@ -15006,10 +15014,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) { ensureTransformationInfo(); if (mTransformationInfo.mAlpha != alpha) { - // Report visibility changes, which can affect children, to accessibility - if ((alpha == 0) ^ (mTransformationInfo.mAlpha == 0)) { - notifySubtreeAccessibilityStateChangedIfNeeded(); - } + float oldAlpha = mTransformationInfo.mAlpha; mTransformationInfo.mAlpha = alpha; if (onSetAlpha((int) (alpha * 255))) { mPrivateFlags |= PFLAG_ALPHA_SET; @@ -15021,6 +15026,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(true, false); mRenderNode.setAlpha(getFinalAlpha()); } + // Report visibility changes, which can affect children, to accessibility + if ((alpha == 0) ^ (oldAlpha == 0)) { + notifySubtreeAccessibilityStateChangedIfNeeded(); + } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index e802232bae33..f6d9a16e4246 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -8305,6 +8305,12 @@ public final class ViewRootImpl implements ViewParent, public View mSource; public long mLastEventTimeMillis; + /** + * Override for {@link AccessibilityEvent#originStackTrace} to provide the stack trace + * of the original {@link #runOrPost} call instead of one for sending the delayed event + * from a looper. + */ + public StackTraceElement[] mOrigin; @Override public void run() { @@ -8322,6 +8328,7 @@ public final class ViewRootImpl implements ViewParent, AccessibilityEvent event = AccessibilityEvent.obtain(); event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); event.setContentChangeTypes(mChangeTypes); + if (AccessibilityEvent.DEBUG_ORIGIN) event.originStackTrace = mOrigin; source.sendAccessibilityEventUnchecked(event); } else { mLastEventTimeMillis = 0; @@ -8329,6 +8336,7 @@ public final class ViewRootImpl implements ViewParent, // In any case reset to initial state. source.resetSubtreeAccessibilityStateChanged(); mChangeTypes = 0; + if (AccessibilityEvent.DEBUG_ORIGIN) mOrigin = null; } public void runOrPost(View source, int changeType) { @@ -8352,12 +8360,18 @@ public final class ViewRootImpl implements ViewParent, // If there is no common predecessor, then mSource points to // a removed view, hence in this case always prefer the source. View predecessor = getCommonPredecessor(mSource, source); + if (predecessor != null) { + predecessor = predecessor.getSelfOrParentImportantForA11y(); + } mSource = (predecessor != null) ? predecessor : source; mChangeTypes |= changeType; return; } mSource = source; mChangeTypes = changeType; + if (AccessibilityEvent.DEBUG_ORIGIN) { + mOrigin = Thread.currentThread().getStackTrace(); + } final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis; final long minEventIntevalMillis = ViewConfiguration.getSendRecurringAccessibilityEventsInterval(); diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index e0f74a7d6dd6..95a83da3fad5 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -388,6 +388,8 @@ import java.util.List; */ public final class AccessibilityEvent extends AccessibilityRecord implements Parcelable { private static final boolean DEBUG = false; + /** @hide */ + public static final boolean DEBUG_ORIGIN = false; /** * Invalid selection/focus position. @@ -748,7 +750,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par private static final int MAX_POOL_SIZE = 10; private static final SynchronizedPool<AccessibilityEvent> sPool = - new SynchronizedPool<AccessibilityEvent>(MAX_POOL_SIZE); + new SynchronizedPool<>(MAX_POOL_SIZE); private @EventType int mEventType; private CharSequence mPackageName; @@ -758,6 +760,17 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par int mContentChangeTypes; int mWindowChangeTypes; + /** + * The stack trace describing where this event originated from on the app side. + * Only populated if {@link #DEBUG_ORIGIN} is enabled + * Can be inspected(e.g. printed) from an + * {@link android.accessibilityservice.AccessibilityService} to trace where particular events + * are being dispatched from. + * + * @hide + */ + public StackTraceElement[] originStackTrace = null; + private ArrayList<AccessibilityRecord> mRecords; /* @@ -780,6 +793,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mWindowChangeTypes = event.mWindowChangeTypes; mEventTime = event.mEventTime; mPackageName = event.mPackageName; + if (DEBUG_ORIGIN) originStackTrace = event.originStackTrace; } /** @@ -1104,7 +1118,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par */ public static AccessibilityEvent obtain() { AccessibilityEvent event = sPool.acquire(); - return (event != null) ? event : new AccessibilityEvent(); + if (event == null) event = new AccessibilityEvent(); + if (DEBUG_ORIGIN) event.originStackTrace = Thread.currentThread().getStackTrace(); + return event; } /** @@ -1142,6 +1158,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par record.recycle(); } } + if (DEBUG_ORIGIN) originStackTrace = null; } /** @@ -1164,7 +1181,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par // Read the records. final int recordCount = parcel.readInt(); if (recordCount > 0) { - mRecords = new ArrayList<AccessibilityRecord>(recordCount); + mRecords = new ArrayList<>(recordCount); for (int i = 0; i < recordCount; i++) { AccessibilityRecord record = AccessibilityRecord.obtain(); readAccessibilityRecordFromParcel(record, parcel); @@ -1172,6 +1189,17 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mRecords.add(record); } } + + if (DEBUG_ORIGIN) { + originStackTrace = new StackTraceElement[parcel.readInt()]; + for (int i = 0; i < originStackTrace.length; i++) { + originStackTrace[i] = new StackTraceElement( + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readInt()); + } + } } /** @@ -1227,6 +1255,17 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par AccessibilityRecord record = mRecords.get(i); writeAccessibilityRecordToParcel(record, parcel, flags); } + + if (DEBUG_ORIGIN) { + if (originStackTrace == null) originStackTrace = Thread.currentThread().getStackTrace(); + parcel.writeInt(originStackTrace.length); + for (StackTraceElement element : originStackTrace) { + parcel.writeString(element.getClassName()); + parcel.writeString(element.getMethodName()); + parcel.writeString(element.getFileName()); + parcel.writeInt(element.getLineNumber()); + } + } } /** diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 72af203e5fab..d60c48198e38 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -326,12 +326,14 @@ public final class AccessibilityInteractionClient accessibilityWindowId, accessibilityNodeId); if (cachedInfo != null) { if (DEBUG) { - Log.i(LOG_TAG, "Node cache hit"); + Log.i(LOG_TAG, "Node cache hit for " + + idToString(accessibilityWindowId, accessibilityNodeId)); } return cachedInfo; } if (DEBUG) { - Log.i(LOG_TAG, "Node cache miss"); + Log.i(LOG_TAG, "Node cache miss for " + + idToString(accessibilityWindowId, accessibilityNodeId)); } } final int interactionId = mInteractionIdCounter.getAndIncrement(); @@ -368,6 +370,11 @@ public final class AccessibilityInteractionClient return null; } + private static String idToString(int accessibilityWindowId, long accessibilityNodeId) { + return accessibilityWindowId + "/" + + AccessibilityNodeInfo.idToString(accessibilityNodeId); + } + /** * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in * the window whose id is specified and starts from the node whose accessibility diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index dee267dfea26..cbb23f1a6c62 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -16,6 +16,8 @@ package android.view.accessibility; +import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME; + import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityServiceInfo.FeedbackType; @@ -44,6 +46,7 @@ import android.util.SparseArray; import android.view.IWindow; import android.view.View; import android.view.accessibility.AccessibilityEvent.EventType; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IntPair; @@ -51,8 +54,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME; - /** * System level service that serves as an event dispatch for {@link AccessibilityEvent}s, * and provides facilities for querying the accessibility state of the system. diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 4c437dd44652..03f1c1243755 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -3874,6 +3874,24 @@ public class AccessibilityNodeInfo implements Parcelable { | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null); } + /** @hide */ + public static String idToString(long accessibilityId) { + int accessibilityViewId = getAccessibilityViewId(accessibilityId); + int virtualDescendantId = getVirtualDescendantId(accessibilityId); + return virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID + ? idItemToString(accessibilityViewId) + : idItemToString(accessibilityViewId) + ":" + idItemToString(virtualDescendantId); + } + + private static String idItemToString(int item) { + switch (item) { + case ROOT_ITEM_ID: return "ROOT"; + case UNDEFINED_ITEM_ID: return "UNDEFINED"; + case AccessibilityNodeProvider.HOST_VIEW_ID: return "HOST"; + default: return "" + item; + } + } + /** * A class defining an action that can be performed on an {@link AccessibilityNodeInfo}. * Each action has a unique id that is mandatory and optional data. diff --git a/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java index c205f9695bb8..6c05601a31fd 100644 --- a/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java +++ b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java @@ -54,7 +54,7 @@ public class SpannableStringNoCopyTest { first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY); // Do not copy NoCopySpan if specified so. - final SpannedString copied = new SpannedString(first, false /* copyNoCopySpan */); + final SpannedString copied = new SpannedString(first, true /* ignoreNoCopySpan */); final Object[] spans = copied.getSpans(0, copied.length(), Object.class); assertNotNull(spans); assertEquals(2, spans.length); @@ -87,7 +87,7 @@ public class SpannableStringNoCopyTest { // Do not copy NoCopySpan if specified so. final SpannedString copied = new SpannedString( - new CustomSpannable(first), false /* copyNoCopySpan */); + new CustomSpannable(first), true /* ignoreNoCopySpan */); final Object[] spans = copied.getSpans(0, copied.length(), Object.class); assertNotNull(spans); assertEquals(2, spans.length); diff --git a/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java index 068092484b1b..380e3153ccda 100644 --- a/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java +++ b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java @@ -54,7 +54,7 @@ public class SpannedStringNoCopyTest { first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY); // Do not copy NoCopySpan if specified so. - final SpannedString copied = new SpannedString(first, false /* copyNoCopySpan */); + final SpannedString copied = new SpannedString(first, true /* ignoreNoCopySpan */); final Object[] spans = copied.getSpans(0, copied.length(), Object.class); assertNotNull(spans); assertEquals(2, spans.length); @@ -87,7 +87,7 @@ public class SpannedStringNoCopyTest { // Do not copy NoCopySpan if specified so. final SpannedString copied = new SpannedString( - new CustomSpanned(first), false /* copyNoCopySpan */); + new CustomSpanned(first), true /* ignoreNoCopySpan */); final Object[] spans = copied.getSpans(0, copied.length(), Object.class); assertNotNull(spans); assertEquals(2, spans.length); diff --git a/libs/hwui/debug/gles_decls.in b/libs/hwui/debug/gles_decls.in index 16574a7fb074..3900959c29de 100644 --- a/libs/hwui/debug/gles_decls.in +++ b/libs/hwui/debug/gles_decls.in @@ -387,7 +387,6 @@ GL_ENTRY(GLboolean, glIsEnablediOES, GLenum target, GLuint index) GL_ENTRY(void, glDrawElementsBaseVertexOES, GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex) GL_ENTRY(void, glDrawRangeElementsBaseVertexOES, GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex) GL_ENTRY(void, glDrawElementsInstancedBaseVertexOES, GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex) -GL_ENTRY(void, glMultiDrawElementsBaseVertexOES, GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, const GLint *basevertex) GL_ENTRY(void, glFramebufferTextureOES, GLenum target, GLenum attachment, GLuint texture, GLint level) GL_ENTRY(void, glGetProgramBinaryOES, GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary) GL_ENTRY(void, glProgramBinaryOES, GLuint program, GLenum binaryFormat, const void *binary, GLint length) @@ -541,4 +540,4 @@ GL_ENTRY(void, glTexStorage3DEXT, GLenum target, GLsizei levels, GLenum internal GL_ENTRY(void, glTextureStorage1DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width) GL_ENTRY(void, glTextureStorage2DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) GL_ENTRY(void, glTextureStorage3DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth) -GL_ENTRY(void, glTextureViewEXT, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers)
\ No newline at end of file +GL_ENTRY(void, glTextureViewEXT, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) diff --git a/libs/hwui/debug/gles_stubs.in b/libs/hwui/debug/gles_stubs.in index 4064a391a71d..7cba0c115814 100644 --- a/libs/hwui/debug/gles_stubs.in +++ b/libs/hwui/debug/gles_stubs.in @@ -1165,9 +1165,6 @@ void API_ENTRY(glDrawRangeElementsBaseVertexOES)(GLenum mode, GLuint start, GLui void API_ENTRY(glDrawElementsInstancedBaseVertexOES)(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex) { CALL_GL_API(glDrawElementsInstancedBaseVertexOES, mode, count, type, indices, instancecount, basevertex); } -void API_ENTRY(glMultiDrawElementsBaseVertexOES)(GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, const GLint *basevertex) { - CALL_GL_API(glMultiDrawElementsBaseVertexOES, mode, count, type, indices, primcount, basevertex); -} void API_ENTRY(glFramebufferTextureOES)(GLenum target, GLenum attachment, GLuint texture, GLint level) { CALL_GL_API(glFramebufferTextureOES, target, attachment, texture, level); } @@ -1629,4 +1626,4 @@ void API_ENTRY(glTextureStorage3DEXT)(GLuint texture, GLenum target, GLsizei lev } void API_ENTRY(glTextureViewEXT)(GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) { CALL_GL_API(glTextureViewEXT, texture, target, origtexture, internalformat, minlevel, numlevels, minlayer, numlayers); -}
\ No newline at end of file +} diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index addbf84cb4e8..37de4332bf43 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -47,6 +47,8 @@ <dimen name="widget_title_font_size">24sp</dimen> <!-- Slice subtitle --> <dimen name="widget_label_font_size">16sp</dimen> + <!-- Slice offset when pulsing --> + <dimen name="widget_pulsing_bottom_padding">24dp</dimen> <!-- Clock without header --> <dimen name="widget_big_font_size">64dp</dimen> <!-- Clock with header --> diff --git a/packages/SystemUI/res/drawable/rounded_ripple.xml b/packages/SystemUI/res/drawable/rounded_ripple.xml index 5588eb21ad8e..d9ed8233d886 100644 --- a/packages/SystemUI/res/drawable/rounded_ripple.xml +++ b/packages/SystemUI/res/drawable/rounded_ripple.xml @@ -23,7 +23,7 @@ </item> <item android:id="@android:id/background"> <shape android:shape="rectangle"> - <solid android:color="#FFFFFFFF"/> + <solid android:color="?android:attr/colorBackgroundFloating"/> <corners android:radius="8dp"/> </shape> </item> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java index d63ad0840734..00cd5a7b1689 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -265,11 +265,11 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout mPendingLockCheck.cancel(false); mPendingLockCheck = null; } + reset(); } @Override public void onResume(int reason) { - reset(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index d6e59c77af9c..426f71409095 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1184,6 +1184,10 @@ public class KeyguardViewMediator extends SystemUI { Trace.endSection(); } + public boolean isHiding() { + return mHiding; + } + /** * Handles SET_OCCLUDED message sent by setOccluded() */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index 3d7067d1d799..1fb1ddd5e70a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -99,6 +99,11 @@ public class KeyguardClockPositionAlgorithm { private int mBurnInPreventionOffsetY; /** + * Clock vertical padding when pulsing. + */ + private int mPulsingPadding; + + /** * Doze/AOD transition amount. */ private float mDarkAmount; @@ -109,9 +114,9 @@ public class KeyguardClockPositionAlgorithm { private boolean mCurrentlySecure; /** - * If notification panel view currently has a touch. + * Dozing and receiving a notification (AOD notification.) */ - private boolean mTracking; + private boolean mPulsing; /** * Distance in pixels between the top of the screen and the first view of the bouncer. @@ -130,11 +135,13 @@ public class KeyguardClockPositionAlgorithm { R.dimen.burn_in_prevention_offset_x); mBurnInPreventionOffsetY = res.getDimensionPixelSize( R.dimen.burn_in_prevention_offset_y); + mPulsingPadding = res.getDimensionPixelSize( + R.dimen.widget_pulsing_bottom_padding); } public void setup(int minTopMargin, int maxShadeBottom, int notificationStackHeight, float expandedHeight, float maxPanelHeight, int parentHeight, int keyguardStatusHeight, - float dark, boolean secure, boolean tracking, int bouncerTop) { + float dark, boolean secure, boolean pulsing, int bouncerTop) { mMinTopMargin = minTopMargin + mContainerTopPadding; mMaxShadeBottom = maxShadeBottom; mNotificationStackHeight = notificationStackHeight; @@ -144,7 +151,7 @@ public class KeyguardClockPositionAlgorithm { mKeyguardStatusHeight = keyguardStatusHeight; mDarkAmount = dark; mCurrentlySecure = secure; - mTracking = tracking; + mPulsing = pulsing; mBouncerTop = bouncerTop; } @@ -152,7 +159,7 @@ public class KeyguardClockPositionAlgorithm { final int y = getClockY(); result.clockY = y; result.clockAlpha = getClockAlpha(y); - result.stackScrollerPadding = y + mKeyguardStatusHeight; + result.stackScrollerPadding = y + (mPulsing ? 0 : mKeyguardStatusHeight); result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount); } @@ -194,9 +201,13 @@ public class KeyguardClockPositionAlgorithm { private int getClockY() { // Dark: Align the bottom edge of the clock at about half of the screen: - final float clockYDark = getMaxClockY() + burnInPreventionOffsetY(); - final float clockYRegular = getExpandedClockPosition(); - final boolean hasEnoughSpace = mMinTopMargin + mKeyguardStatusHeight < mBouncerTop; + float clockYDark = getMaxClockY() + burnInPreventionOffsetY(); + if (mPulsing) { + clockYDark -= mPulsingPadding; + } + + float clockYRegular = getExpandedClockPosition(); + boolean hasEnoughSpace = mMinTopMargin + mKeyguardStatusHeight < mBouncerTop; float clockYTarget = mCurrentlySecure && hasEnoughSpace ? mMinTopMargin : -mKeyguardStatusHeight; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index b7af84a794c5..351633b590c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -238,6 +238,7 @@ public class NotificationPanelView extends PanelView implements private boolean mIsFullWidth; private float mDarkAmount; private float mDarkAmountTarget; + private boolean mPulsing; private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); private boolean mNoVisibleNotifications = true; private ValueAnimator mDarkAnimator; @@ -477,7 +478,7 @@ public class NotificationPanelView extends PanelView implements mKeyguardStatusView.getHeight(), mDarkAmount, mStatusBar.isKeyguardCurrentlySecure(), - mTracking, + mPulsing, mBouncerTop); mClockPositionAlgorithm.run(mClockPositionResult); if (animate || mClockAnimator != null) { @@ -2689,14 +2690,8 @@ public class NotificationPanelView extends PanelView implements positionClockAndNotifications(); } - public void setNoVisibleNotifications(boolean noNotifications) { - mNoVisibleNotifications = noNotifications; - if (mQs != null) { - mQs.setHasNotifications(!noNotifications); - } - } - public void setPulsing(boolean pulsing) { + mPulsing = pulsing; mKeyguardStatusView.setPulsing(pulsing); positionClockAndNotifications(); mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1] diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index ab13678841db..4b2bc45bf417 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -3942,7 +3942,8 @@ public class StatusBar extends SystemUI implements DemoMode, } private void showBouncerIfKeyguard() { - if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { + if ((mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) + && !mKeyguardViewMediator.isHiding()) { showBouncer(true /* animated */); } } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java index 5a4478f072e0..639e49b1a17f 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java @@ -58,7 +58,7 @@ public class TunerServiceImpl extends TunerService { private static final String TUNER_VERSION = "sysui_tuner_version"; - private static final int CURRENT_TUNER_VERSION = 2; + private static final int CURRENT_TUNER_VERSION = 3; private final Observer mObserver = new Observer(); // Map of Uris we listen on to their settings keys. @@ -119,6 +119,10 @@ public class TunerServiceImpl extends TunerService { if (oldVersion < 2) { setTunerEnabled(mContext, false); } + if (oldVersion < 3) { + // Delay this so that we can wait for everything to be registered first. + new Handler(Dependency.get(Dependency.BG_LOOPER)).postDelayed(() -> clearAll(), 5000); + } setValue(TUNER_VERSION, newVersion); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 04d54e1dcb8d..43c16555b8d4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -767,7 +767,7 @@ public class ActivityManagerService extends IActivityManager.Stub /** * The controller for all operations related to locktask. */ - final LockTaskController mLockTaskController; + private final LockTaskController mLockTaskController; final UserController mUserController; @@ -12441,6 +12441,10 @@ public class ActivityManagerService extends IActivityManager.Stub return mActivityStartController; } + LockTaskController getLockTaskController() { + return mLockTaskController; + } + ClientLifecycleManager getLifecycleManager() { return mLifecycleManager; } diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index e539bf803542..4ce157fde648 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -1589,14 +1589,20 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo void pauseKeyDispatchingLocked() { if (!keysPaused) { keysPaused = true; - mWindowContainerController.pauseKeyDispatching(); + + if (mWindowContainerController != null) { + mWindowContainerController.pauseKeyDispatching(); + } } } void resumeKeyDispatchingLocked() { if (keysPaused) { keysPaused = false; - mWindowContainerController.resumeKeyDispatching(); + + if (mWindowContainerController != null) { + mWindowContainerController.resumeKeyDispatching(); + } } } diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 649d5770f6a9..1e9edd9a1b44 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -3743,7 +3743,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } if (endTask) { - mService.mLockTaskController.clearLockedTask(task); + mService.getLockTaskController().clearLockedTask(task); } } else if (!r.isState(PAUSING)) { // If the activity is PAUSING, we will complete the finish once @@ -4639,7 +4639,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // In LockTask mode, moving a locked task to the back of the stack may expose unlocked // ones. Therefore we need to check if this operation is allowed. - if (!mService.mLockTaskController.canMoveTaskToBack(tr)) { + if (!mService.getLockTaskController().canMoveTaskToBack(tr)) { return false; } @@ -5084,7 +5084,12 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai onActivityRemovedFromStack(record); } - mTaskHistory.remove(task); + final boolean removed = mTaskHistory.remove(task); + + if (removed) { + EventLog.writeEvent(EventLogTags.AM_REMOVE_TASK, task.taskId, getStackId()); + } + removeActivitiesFromLRUListLocked(task); updateTaskMovement(task, true); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index e9a13585d8fb..731a44d35cf2 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -1372,12 +1372,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mService.updateLruProcessLocked(app, true, null); mService.updateOomAdjLocked(); + final LockTaskController lockTaskController = mService.getLockTaskController(); if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE || task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV || (task.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED - && mService.mLockTaskController.getLockTaskModeState() - == LOCK_TASK_MODE_LOCKED)) { - mService.mLockTaskController.startLockTaskMode(task, false, 0 /* blank UID */); + && lockTaskController.getLockTaskModeState() + == LOCK_TASK_MODE_LOCKED)) { + lockTaskController.startLockTaskMode(task, false, 0 /* blank UID */); } try { @@ -2900,7 +2901,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (tr != null) { tr.removeTaskActivitiesLocked(pauseImmediately, reason); cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents); - mService.mLockTaskController.clearLockedTask(tr); + mService.getLockTaskController().clearLockedTask(tr); if (tr.isPersistable) { mService.notifyTaskPersisterLocked(null, true); } @@ -3814,7 +3815,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D pw.print(mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser)); getKeyguardController().dump(pw, prefix); - mService.mLockTaskController.dump(pw, prefix); + mService.getLockTaskController().dump(pw, prefix); } public void writeToProto(ProtoOutputStream proto, long fieldId) { diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 5337566211a1..fb89e6711a18 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -1156,9 +1156,10 @@ class ActivityStarter { // If we are not able to proceed, disassociate the activity from the task. Leaving an // activity in an incomplete state can lead to issues, such as performing operations // without a window container. - if (!ActivityManager.isStartResultSuccessful(result) - && mStartActivity.getTask() != null) { - mStartActivity.getTask().removeActivity(mStartActivity); + final ActivityStack stack = mStartActivity.getStack(); + if (!ActivityManager.isStartResultSuccessful(result) && stack != null) { + stack.finishActivityLocked(mStartActivity, RESULT_CANCELED, + null /* intentResultData */, "startActivity", true /* oomAdj */); } mService.mWindowManager.continueSurfaceLayout(); } @@ -1208,7 +1209,7 @@ class ActivityStarter { // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but // still needs to be a lock task mode violation since the task gets cleared out and // the device would otherwise leave the locked task. - if (mService.mLockTaskController.isLockTaskModeViolation(reusedActivity.getTask(), + if (mService.getLockTaskController().isLockTaskModeViolation(reusedActivity.getTask(), (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) { Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode"); @@ -2020,7 +2021,7 @@ class ActivityStarter { mStartActivity.setTaskToAffiliateWith(taskToAffiliate); } - if (mService.mLockTaskController.isLockTaskModeViolation(mStartActivity.getTask())) { + if (mService.getLockTaskController().isLockTaskModeViolation(mStartActivity.getTask())) { Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity); return START_RETURN_LOCK_TASK_MODE_VIOLATION; } @@ -2043,7 +2044,7 @@ class ActivityStarter { } private int setTaskFromSourceRecord() { - if (mService.mLockTaskController.isLockTaskModeViolation(mSourceRecord.getTask())) { + if (mService.getLockTaskController().isLockTaskModeViolation(mSourceRecord.getTask())) { Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity); return START_RETURN_LOCK_TASK_MODE_VIOLATION; } @@ -2137,7 +2138,7 @@ class ActivityStarter { private int setTaskFromInTask() { // The caller is asking that the new activity be started in an explicit // task it has provided to us. - if (mService.mLockTaskController.isLockTaskModeViolation(mInTask)) { + if (mService.getLockTaskController().isLockTaskModeViolation(mInTask)) { Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity); return START_RETURN_LOCK_TASK_MODE_VIOLATION; } diff --git a/services/core/java/com/android/server/am/AppWarnings.java b/services/core/java/com/android/server/am/AppWarnings.java index ab1d7bf2ad7e..ea0251e2ce6a 100644 --- a/services/core/java/com/android/server/am/AppWarnings.java +++ b/services/core/java/com/android/server/am/AppWarnings.java @@ -122,8 +122,9 @@ class AppWarnings { return; } - if (ActivityManager.isRunningInTestHarness() - && !mAlwaysShowUnsupportedCompileSdkWarningActivities.contains(r.realActivity)) { + // TODO(b/77862563): temp. fix while P is being finalized. To be reverted + if (/*ActivityManager.isRunningInTestHarness() + &&*/ !mAlwaysShowUnsupportedCompileSdkWarningActivities.contains(r.realActivity)) { // Don't show warning if we are running in a test harness and we don't have to always // show for this activity. return; diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index 9caef4a001ae..40b9e4fde874 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -133,4 +133,7 @@ option java_package com.android.server.am # The activity's onStart has been called. 30059 am_on_start_called (User|1|5),(Component Name|3),(Reason|3) # The activity's onDestroy has been called. -30060 am_on_destroy_called (User|1|5),(Component Name|3),(Reason|3)
\ No newline at end of file +30060 am_on_destroy_called (User|1|5),(Component Name|3),(Reason|3) + +# The task is being removed from its parent stack +30061 am_remove_task (Task ID|1|5), (Stack ID|1|5)
\ No newline at end of file diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java index 2b988d30f4bb..a20452bb5074 100644 --- a/services/core/java/com/android/server/am/RecentTasks.java +++ b/services/core/java/com/android/server/am/RecentTasks.java @@ -523,7 +523,7 @@ class RecentTasks { } for (int i = mTasks.size() - 1; i >= 0; --i) { final TaskRecord tr = mTasks.get(i); - if (tr.userId == userId && !mService.mLockTaskController.isTaskWhitelisted(tr)) { + if (tr.userId == userId && !mService.getLockTaskController().isTaskWhitelisted(tr)) { remove(tr); } } @@ -1156,7 +1156,7 @@ class RecentTasks { } // If we're in lock task mode, ignore the root task - if (task == mService.mLockTaskController.getRootTask()) { + if (task == mService.getLockTaskController().getRootTask()) { return false; } diff --git a/services/core/java/com/android/server/am/SafeActivityOptions.java b/services/core/java/com/android/server/am/SafeActivityOptions.java index ac6f01fa855f..2de752731ab6 100644 --- a/services/core/java/com/android/server/am/SafeActivityOptions.java +++ b/services/core/java/com/android/server/am/SafeActivityOptions.java @@ -210,7 +210,7 @@ class SafeActivityOptions { // Check if someone tries to launch an unwhitelisted activity into LockTask mode. final boolean lockTaskMode = options.getLockTaskMode(); if (aInfo != null && lockTaskMode - && !supervisor.mService.mLockTaskController.isPackageWhitelisted( + && !supervisor.mService.getLockTaskController().isPackageWhitelisted( UserHandle.getUserId(callingUid), aInfo.packageName)) { final String msg = "Permission Denial: starting " + getIntentString(intent) + " from " + callerApp + " (pid=" + callingPid diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 034cb2e373d5..737105d8bdf1 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -451,7 +451,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } void removeWindowContainer() { - mService.mLockTaskController.clearLockedTask(this); + mService.getLockTaskController().clearLockedTask(this); mWindowContainerController.removeContainer(); if (!getWindowConfiguration().persistTaskBounds()) { // Reset current bounds for task whose bounds shouldn't be persisted so it uses @@ -1446,9 +1446,10 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } final String pkg = (realActivity != null) ? realActivity.getPackageName() : null; + final LockTaskController lockTaskController = mService.getLockTaskController(); switch (r.lockTaskLaunchMode) { case LOCK_TASK_LAUNCH_MODE_DEFAULT: - mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg) + mLockTaskAuth = lockTaskController.isPackageWhitelisted(userId, pkg) ? LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE; break; @@ -1461,7 +1462,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi break; case LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED: - mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg) + mLockTaskAuth = lockTaskController.isPackageWhitelisted(userId, pkg) ? LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE; break; } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index b6e414a2f84d..fecb93456605 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -2215,7 +2215,7 @@ class UserController implements Handler.Callback { protected void clearAllLockedTasks(String reason) { synchronized (mService) { - mService.mLockTaskController.clearLockedTasks(reason); + mService.getLockTaskController().clearLockedTasks(reason); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 5b45cbe1f658..f2d812e8a38f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1523,7 +1523,6 @@ public class PackageManagerService extends IPackageManager.Stub Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, params.traceMethod, params.traceCookie); } - return; } mPendingInstalls.clear(); } else { @@ -14021,7 +14020,7 @@ public class PackageManagerService extends IPackageManager.Stub "setPackagesSuspended for user " + userId); if (callingUid != Process.ROOT_UID && !UserHandle.isSameApp(getPackageUid(callingPackage, 0, userId), callingUid)) { - throw new IllegalArgumentException("callingPackage " + callingPackage + " does not" + throw new IllegalArgumentException("CallingPackage " + callingPackage + " does not" + " belong to calling app id " + UserHandle.getAppId(callingUid)); } @@ -14045,20 +14044,18 @@ public class PackageManagerService extends IPackageManager.Stub final PackageSetting pkgSetting = mSettings.mPackages.get(packageName); if (pkgSetting == null || filterAppAccessLPr(pkgSetting, callingUid, userId)) { - Slog.w(TAG, "Could not find package setting for package \"" + packageName - + "\". Skipping suspending/un-suspending."); + Slog.w(TAG, "Could not find package setting for package: " + packageName + + ". Skipping suspending/un-suspending."); unactionedPackages.add(packageName); continue; } - if (pkgSetting.getSuspended(userId) != suspended) { - if (!canSuspendPackageForUserLocked(packageName, userId)) { - unactionedPackages.add(packageName); - continue; - } - pkgSetting.setSuspended(suspended, callingPackage, dialogMessage, appExtras, - launcherExtras, userId); - changedPackagesList.add(packageName); + if (!canSuspendPackageForUserLocked(packageName, userId)) { + unactionedPackages.add(packageName); + continue; } + pkgSetting.setSuspended(suspended, callingPackage, dialogMessage, appExtras, + launcherExtras, userId); + changedPackagesList.add(packageName); } } } finally { @@ -14073,7 +14070,6 @@ public class PackageManagerService extends IPackageManager.Stub scheduleWritePackageRestrictionsLocked(userId); } } - return unactionedPackages.toArray(new String[unactionedPackages.size()]); } @@ -14081,7 +14077,8 @@ public class PackageManagerService extends IPackageManager.Stub public PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId) { final int callingUid = Binder.getCallingUid(); if (getPackageUid(packageName, 0, userId) != callingUid) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, null); + throw new SecurityException("Calling package " + packageName + + " does not belong to calling uid " + callingUid); } synchronized (mPackages) { final PackageSetting ps = mSettings.mPackages.get(packageName); @@ -14096,25 +14093,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override - public void setSuspendedPackageAppExtras(String packageName, PersistableBundle appExtras, - int userId) { - final int callingUid = Binder.getCallingUid(); - mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, null); - synchronized (mPackages) { - final PackageSetting ps = mSettings.mPackages.get(packageName); - if (ps == null || filterAppAccessLPr(ps, callingUid, userId)) { - throw new IllegalArgumentException("Unknown target package: " + packageName); - } - final PackageUserState packageUserState = ps.readUserState(userId); - if (packageUserState.suspended) { - packageUserState.suspendedAppExtras = appExtras; - sendMyPackageSuspendedOrUnsuspended(new String[] {packageName}, true, appExtras, - userId); - } - } - } - private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended, PersistableBundle appExtras, int userId) { final String action; @@ -14169,18 +14147,26 @@ public class PackageManagerService extends IPackageManager.Stub } } - void onSuspendingPackageRemoved(String packageName, int userId) { - final int[] userIds = (userId == UserHandle.USER_ALL) ? sUserManager.getUserIds() - : new int[] {userId}; - synchronized (mPackages) { - for (PackageSetting ps : mSettings.mPackages.values()) { - for (int user : userIds) { - final PackageUserState pus = ps.readUserState(user); + void onSuspendingPackageRemoved(String packageName, int removedForUser) { + final int[] userIds = (removedForUser == UserHandle.USER_ALL) ? sUserManager.getUserIds() + : new int[] {removedForUser}; + for (int userId : userIds) { + List<String> affectedPackages = new ArrayList<>(); + synchronized (mPackages) { + for (PackageSetting ps : mSettings.mPackages.values()) { + final PackageUserState pus = ps.readUserState(userId); if (pus.suspended && packageName.equals(pus.suspendingPackage)) { - ps.setSuspended(false, null, null, null, null, user); + ps.setSuspended(false, null, null, null, null, userId); + affectedPackages.add(ps.name); } } } + if (!affectedPackages.isEmpty()) { + final String[] packageArray = affectedPackages.toArray( + new String[affectedPackages.size()]); + sendMyPackageSuspendedOrUnsuspended(packageArray, false, null, userId); + sendPackagesSuspendedForUser(packageArray, userId, false, null); + } } } diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index fd4c5e901b7a..138594ccb4d6 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -20,8 +20,6 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; -import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; - import android.content.pm.ApplicationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageManager; diff --git a/services/core/java/com/android/server/slice/DirtyTracker.java b/services/core/java/com/android/server/slice/DirtyTracker.java new file mode 100644 index 000000000000..4288edc8cbf8 --- /dev/null +++ b/services/core/java/com/android/server/slice/DirtyTracker.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.server.slice; + +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; + +/** + * A parent object that cares when a Persistable changes and will schedule a serialization + * in response to the onPersistableDirty callback. + */ +public interface DirtyTracker { + void onPersistableDirty(Persistable obj); + + /** + * An object that can be written to XML. + */ + interface Persistable { + String getFileName(); + void writeTo(XmlSerializer out) throws IOException; + } +} diff --git a/services/core/java/com/android/server/slice/SliceClientPermissions.java b/services/core/java/com/android/server/slice/SliceClientPermissions.java new file mode 100644 index 000000000000..e461e0d43735 --- /dev/null +++ b/services/core/java/com/android/server/slice/SliceClientPermissions.java @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.server.slice; + +import android.annotation.NonNull; +import android.content.ContentResolver; +import android.net.Uri; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.server.slice.DirtyTracker.Persistable; +import com.android.server.slice.SlicePermissionManager.PkgUser; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class SliceClientPermissions implements DirtyTracker, Persistable { + + private static final String TAG = "SliceClientPermissions"; + + static final String TAG_CLIENT = "client"; + private static final String TAG_AUTHORITY = "authority"; + private static final String TAG_PATH = "path"; + private static final String NAMESPACE = null; + + private static final String ATTR_PKG = "pkg"; + private static final String ATTR_AUTHORITY = "authority"; + private static final String ATTR_FULL_ACCESS = "fullAccess"; + + private final PkgUser mPkg; + // Keyed off (authority, userId) rather than the standard (pkg, userId) + private final ArrayMap<PkgUser, SliceAuthority> mAuths = new ArrayMap<>(); + private final DirtyTracker mTracker; + private boolean mHasFullAccess; + + public SliceClientPermissions(@NonNull PkgUser pkg, @NonNull DirtyTracker tracker) { + mPkg = pkg; + mTracker = tracker; + } + + public PkgUser getPkg() { + return mPkg; + } + + public synchronized Collection<SliceAuthority> getAuthorities() { + return new ArrayList<>(mAuths.values()); + } + + public synchronized SliceAuthority getOrCreateAuthority(PkgUser authority, PkgUser provider) { + SliceAuthority ret = mAuths.get(authority); + if (ret == null) { + ret = new SliceAuthority(authority.getPkg(), provider, this); + mAuths.put(authority, ret); + onPersistableDirty(ret); + } + return ret; + } + + public synchronized SliceAuthority getAuthority(PkgUser authority) { + return mAuths.get(authority); + } + + public boolean hasFullAccess() { + return mHasFullAccess; + } + + public void setHasFullAccess(boolean hasFullAccess) { + if (mHasFullAccess == hasFullAccess) return; + mHasFullAccess = hasFullAccess; + mTracker.onPersistableDirty(this); + } + + public void removeAuthority(String authority, int userId) { + if (mAuths.remove(new PkgUser(authority, userId)) != null) { + mTracker.onPersistableDirty(this); + } + } + + public synchronized boolean hasPermission(Uri uri, int userId) { + if (!Objects.equals(ContentResolver.SCHEME_CONTENT, uri.getScheme())) return false; + SliceAuthority authority = getAuthority(new PkgUser(uri.getAuthority(), userId)); + return authority != null && authority.hasPermission(uri.getPathSegments()); + } + + public void grantUri(Uri uri, PkgUser providerPkg) { + SliceAuthority authority = getOrCreateAuthority( + new PkgUser(uri.getAuthority(), providerPkg.getUserId()), + providerPkg); + authority.addPath(uri.getPathSegments()); + } + + public void revokeUri(Uri uri, PkgUser providerPkg) { + SliceAuthority authority = getOrCreateAuthority( + new PkgUser(uri.getAuthority(), providerPkg.getUserId()), + providerPkg); + authority.removePath(uri.getPathSegments()); + } + + public void clear() { + if (!mHasFullAccess && mAuths.isEmpty()) return; + mHasFullAccess = false; + mAuths.clear(); + onPersistableDirty(this); + } + + @Override + public void onPersistableDirty(Persistable obj) { + mTracker.onPersistableDirty(this); + } + + @Override + public String getFileName() { + return getFileName(mPkg); + } + + public synchronized void writeTo(XmlSerializer out) throws IOException { + out.startTag(NAMESPACE, TAG_CLIENT); + out.attribute(NAMESPACE, ATTR_PKG, mPkg.toString()); + out.attribute(NAMESPACE, ATTR_FULL_ACCESS, mHasFullAccess ? "1" : "0"); + + final int N = mAuths.size(); + for (int i = 0; i < N; i++) { + out.startTag(NAMESPACE, TAG_AUTHORITY); + out.attribute(NAMESPACE, ATTR_AUTHORITY, mAuths.valueAt(i).mAuthority); + out.attribute(NAMESPACE, ATTR_PKG, mAuths.valueAt(i).mPkg.toString()); + + mAuths.valueAt(i).writeTo(out); + + out.endTag(NAMESPACE, TAG_AUTHORITY); + } + + out.endTag(NAMESPACE, TAG_CLIENT); + } + + public static SliceClientPermissions createFrom(XmlPullParser parser, DirtyTracker tracker) + throws XmlPullParserException, IOException { + // Get to the beginning of the provider. + while (parser.getEventType() != XmlPullParser.START_TAG + || !TAG_CLIENT.equals(parser.getName())) { + parser.next(); + } + int depth = parser.getDepth(); + PkgUser pkgUser = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG)); + SliceClientPermissions provider = new SliceClientPermissions(pkgUser, tracker); + String fullAccess = parser.getAttributeValue(NAMESPACE, ATTR_FULL_ACCESS); + if (fullAccess == null) { + fullAccess = "0"; + } + provider.mHasFullAccess = Integer.parseInt(fullAccess) != 0; + parser.next(); + + while (parser.getDepth() > depth) { + if (parser.getEventType() == XmlPullParser.START_TAG + && TAG_AUTHORITY.equals(parser.getName())) { + try { + PkgUser pkg = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG)); + SliceAuthority authority = new SliceAuthority( + parser.getAttributeValue(NAMESPACE, ATTR_AUTHORITY), pkg, provider); + authority.readFrom(parser); + provider.mAuths.put(new PkgUser(authority.getAuthority(), pkg.getUserId()), + authority); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Couldn't read PkgUser", e); + } + } + + parser.next(); + } + return provider; + } + + public static String getFileName(PkgUser pkg) { + return String.format("client_%s", pkg.toString()); + } + + public static class SliceAuthority implements Persistable { + public static final String DELIMITER = "/"; + private final String mAuthority; + private final DirtyTracker mTracker; + private final PkgUser mPkg; + private final ArraySet<String[]> mPaths = new ArraySet<>(); + + public SliceAuthority(String authority, PkgUser pkg, DirtyTracker tracker) { + mAuthority = authority; + mPkg = pkg; + mTracker = tracker; + } + + public String getAuthority() { + return mAuthority; + } + + public PkgUser getPkg() { + return mPkg; + } + + void addPath(List<String> path) { + String[] pathSegs = path.toArray(new String[path.size()]); + for (int i = mPaths.size() - 1; i >= 0; i--) { + String[] existing = mPaths.valueAt(i); + if (isPathPrefixMatch(existing, pathSegs)) { + // Nothing to add here. + return; + } + if (isPathPrefixMatch(pathSegs, existing)) { + mPaths.removeAt(i); + } + } + mPaths.add(pathSegs); + mTracker.onPersistableDirty(this); + } + + void removePath(List<String> path) { + boolean changed = false; + String[] pathSegs = path.toArray(new String[path.size()]); + for (int i = mPaths.size() - 1; i >= 0; i--) { + String[] existing = mPaths.valueAt(i); + if (isPathPrefixMatch(pathSegs, existing)) { + changed = true; + mPaths.removeAt(i); + } + } + if (changed) { + mTracker.onPersistableDirty(this); + } + } + + public synchronized Collection<String[]> getPaths() { + return new ArraySet<>(mPaths); + } + + public boolean hasPermission(List<String> path) { + for (String[] p : mPaths) { + if (isPathPrefixMatch(p, path.toArray(new String[path.size()]))) { + return true; + } + } + return false; + } + + private boolean isPathPrefixMatch(String[] prefix, String[] path) { + final int prefixSize = prefix.length; + if (path.length < prefixSize) return false; + + for (int i = 0; i < prefixSize; i++) { + if (!Objects.equals(path[i], prefix[i])) { + return false; + } + } + + return true; + } + + @Override + public String getFileName() { + return null; + } + + public synchronized void writeTo(XmlSerializer out) throws IOException { + final int N = mPaths.size(); + for (int i = 0; i < N; i++) { + out.startTag(NAMESPACE, TAG_PATH); + out.text(encodeSegments(mPaths.valueAt(i))); + out.endTag(NAMESPACE, TAG_PATH); + } + } + + public synchronized void readFrom(XmlPullParser parser) + throws IOException, XmlPullParserException { + parser.next(); + int depth = parser.getDepth(); + while (parser.getDepth() >= depth) { + if (parser.getEventType() == XmlPullParser.START_TAG + && TAG_PATH.equals(parser.getName())) { + mPaths.add(decodeSegments(parser.nextText())); + } + parser.next(); + } + } + + private String encodeSegments(String[] s) { + String[] out = new String[s.length]; + for (int i = 0; i < s.length; i++) { + out[i] = Uri.encode(s[i]); + } + return TextUtils.join(DELIMITER, out); + } + + private String[] decodeSegments(String s) { + String[] sets = s.split(DELIMITER, -1); + for (int i = 0; i < sets.length; i++) { + sets[i] = Uri.decode(sets[i]); + } + return sets; + } + + /** + * Only for testing, no deep equality of these are done normally. + */ + @Override + public boolean equals(Object obj) { + if (!getClass().equals(obj != null ? obj.getClass() : null)) return false; + SliceAuthority other = (SliceAuthority) obj; + if (mPaths.size() != other.mPaths.size()) return false; + ArrayList<String[]> p1 = new ArrayList<>(mPaths); + ArrayList<String[]> p2 = new ArrayList<>(other.mPaths); + p1.sort(Comparator.comparing(o -> TextUtils.join(",", o))); + p2.sort(Comparator.comparing(o -> TextUtils.join(",", o))); + for (int i = 0; i < p1.size(); i++) { + String[] a1 = p1.get(i); + String[] a2 = p2.get(i); + if (a1.length != a2.length) return false; + for (int j = 0; j < a1.length; j++) { + if (!Objects.equals(a1[j], a2[j])) return false; + } + } + return Objects.equals(mAuthority, other.mAuthority) + && Objects.equals(mPkg, other.mPkg); + } + + @Override + public String toString() { + return String.format("(%s, %s: %s)", mAuthority, mPkg.toString(), pathToString(mPaths)); + } + + private String pathToString(ArraySet<String[]> paths) { + return TextUtils.join(", ", paths.stream().map(s -> TextUtils.join("/", s)) + .collect(Collectors.toList())); + } + } +} diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java index fd0b6f1eb457..b7b96126fa21 100644 --- a/services/core/java/com/android/server/slice/SliceManagerService.java +++ b/services/core/java/com/android/server/slice/SliceManagerService.java @@ -31,14 +31,15 @@ import android.app.AppOpsManager; import android.app.ContentProviderHolder; import android.app.IActivityManager; import android.app.slice.ISliceManager; -import android.app.slice.SliceManager; import android.app.slice.SliceSpec; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentProvider; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.net.Uri; @@ -51,7 +52,6 @@ import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml.Encoding; @@ -72,7 +72,6 @@ import org.xmlpull.v1.XmlSerializer; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -91,13 +90,9 @@ public class SliceManagerService extends ISliceManager.Stub { @GuardedBy("mLock") private final ArrayMap<Uri, PinnedSliceState> mPinnedSlicesByUri = new ArrayMap<>(); - @GuardedBy("mLock") - private final ArraySet<SliceGrant> mUserGrants = new ArraySet<>(); private final Handler mHandler; - @GuardedBy("mSliceAccessFile") - private final AtomicFile mSliceAccessFile; - @GuardedBy("mAccessList") - private final SliceFullAccessList mAccessList; + + private final SlicePermissionManager mPermissions; private final UsageStatsManagerInternal mAppUsageStats; public SliceManagerService(Context context) { @@ -113,24 +108,9 @@ public class SliceManagerService extends ISliceManager.Stub { mAssistUtils = new AssistUtils(context); mHandler = new Handler(looper); - final File systemDir = new File(Environment.getDataDirectory(), "system"); - mSliceAccessFile = new AtomicFile(new File(systemDir, "slice_access.xml")); - mAccessList = new SliceFullAccessList(mContext); mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); - synchronized (mSliceAccessFile) { - if (!mSliceAccessFile.exists()) return; - try { - InputStream input = mSliceAccessFile.openRead(); - XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); - parser.setInput(input, Encoding.UTF_8.name()); - synchronized (mAccessList) { - mAccessList.readXml(parser); - } - } catch (IOException | XmlPullParserException e) { - Slog.d(TAG, "Can't read slice access file", e); - } - } + mPermissions = new SlicePermissionManager(mContext, looper); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); @@ -211,26 +191,58 @@ public class SliceManagerService extends ISliceManager.Stub { } @Override + public void grantSlicePermission(String pkg, String toPkg, Uri uri) throws RemoteException { + verifyCaller(pkg); + int user = Binder.getCallingUserHandle().getIdentifier(); + enforceOwner(pkg, uri, user); + mPermissions.grantSliceAccess(toPkg, user, pkg, user, uri); + } + + @Override + public void revokeSlicePermission(String pkg, String toPkg, Uri uri) throws RemoteException { + verifyCaller(pkg); + int user = Binder.getCallingUserHandle().getIdentifier(); + enforceOwner(pkg, uri, user); + mPermissions.revokeSliceAccess(toPkg, user, pkg, user, uri); + } + + @Override public int checkSlicePermission(Uri uri, String pkg, int pid, int uid, - String[] autoGrantPermissions) throws RemoteException { - if (mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - == PERMISSION_GRANTED) { - return SliceManager.PERMISSION_GRANTED; - } - if (hasFullSliceAccess(pkg, UserHandle.getUserId(uid))) { - return SliceManager.PERMISSION_GRANTED; - } - for (String perm : autoGrantPermissions) { - if (mContext.checkPermission(perm, pid, uid) == PERMISSION_GRANTED) { - return SliceManager.PERMISSION_USER_GRANTED; + String[] autoGrantPermissions) { + int userId = UserHandle.getUserId(uid); + if (pkg == null) { + for (String p : mContext.getPackageManager().getPackagesForUid(uid)) { + if (checkSlicePermission(uri, p, pid, uid, autoGrantPermissions) + == PERMISSION_GRANTED) { + return PERMISSION_GRANTED; + } } - } - synchronized (mLock) { - if (mUserGrants.contains(new SliceGrant(uri, pkg, UserHandle.getUserId(uid)))) { - return SliceManager.PERMISSION_USER_GRANTED; + return PERMISSION_DENIED; + } + if (hasFullSliceAccess(pkg, userId)) { + return PackageManager.PERMISSION_GRANTED; + } + if (mPermissions.hasPermission(pkg, userId, uri)) { + return PackageManager.PERMISSION_GRANTED; + } + if (autoGrantPermissions != null) { + // Need to own the Uri to call in with permissions to grant. + enforceOwner(pkg, uri, userId); + for (String perm : autoGrantPermissions) { + if (mContext.checkPermission(perm, pid, uid) == PERMISSION_GRANTED) { + int providerUser = ContentProvider.getUserIdFromUri(uri, userId); + String providerPkg = getProviderPkg(uri, providerUser); + mPermissions.grantSliceAccess(pkg, userId, providerPkg, providerUser, uri); + return PackageManager.PERMISSION_GRANTED; + } } } - return SliceManager.PERMISSION_DENIED; + // Fallback to allowing uri permissions through. + if (mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + == PERMISSION_GRANTED) { + return PackageManager.PERMISSION_GRANTED; + } + return PackageManager.PERMISSION_DENIED; } @Override @@ -238,16 +250,17 @@ public class SliceManagerService extends ISliceManager.Stub { verifyCaller(callingPkg); getContext().enforceCallingOrSelfPermission(permission.MANAGE_SLICE_PERMISSIONS, "Slice granting requires MANAGE_SLICE_PERMISSIONS"); + int userId = Binder.getCallingUserHandle().getIdentifier(); if (allSlices) { - synchronized (mAccessList) { - mAccessList.grantFullAccess(pkg, Binder.getCallingUserHandle().getIdentifier()); - } - mHandler.post(mSaveAccessList); + mPermissions.grantFullAccess(pkg, userId); } else { - synchronized (mLock) { - mUserGrants.add(new SliceGrant(uri, pkg, - Binder.getCallingUserHandle().getIdentifier())); - } + // When granting, grant to all slices in the provider. + Uri grantUri = uri.buildUpon() + .path("") + .build(); + int providerUser = ContentProvider.getUserIdFromUri(grantUri, userId); + String providerPkg = getProviderPkg(grantUri, providerUser); + mPermissions.grantSliceAccess(pkg, userId, providerPkg, providerUser, grantUri); } long ident = Binder.clearCallingIdentity(); try { @@ -268,19 +281,17 @@ public class SliceManagerService extends ISliceManager.Stub { Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user); return null; } - synchronized(mSliceAccessFile) { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer(); - out.setOutput(baos, Encoding.UTF_8.name()); - synchronized (mAccessList) { - mAccessList.writeXml(out, user); - } - out.flush(); - return baos.toByteArray(); - } catch (IOException | XmlPullParserException e) { - Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e); - } + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer(); + out.setOutput(baos, Encoding.UTF_8.name()); + + mPermissions.writeBackup(out); + + out.flush(); + return baos.toByteArray(); + } catch (IOException | XmlPullParserException e) { + Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e); } return null; } @@ -299,27 +310,21 @@ public class SliceManagerService extends ISliceManager.Stub { Slog.w(TAG, "applyRestore: cannot restore policy for user " + user); return; } - synchronized(mSliceAccessFile) { - final ByteArrayInputStream bais = new ByteArrayInputStream(payload); - try { - XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); - parser.setInput(bais, Encoding.UTF_8.name()); - synchronized (mAccessList) { - mAccessList.readXml(parser); - } - mHandler.post(mSaveAccessList); - } catch (NumberFormatException | XmlPullParserException | IOException e) { - Slog.w(TAG, "applyRestore: error reading payload", e); - } + final ByteArrayInputStream bais = new ByteArrayInputStream(payload); + try { + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setInput(bais, Encoding.UTF_8.name()); + mPermissions.readRestore(parser); + } catch (NumberFormatException | XmlPullParserException | IOException e) { + Slog.w(TAG, "applyRestore: error reading payload", e); } } /// ----- internal code ----- - private void removeFullAccess(String pkg, int userId) { - synchronized (mAccessList) { - mAccessList.removeGrant(pkg, userId); + private void enforceOwner(String pkg, Uri uri, int user) { + if (!Objects.equals(getProviderPkg(uri, user), pkg) || pkg == null) { + throw new SecurityException("Caller must own " + uri); } - mHandler.post(mSaveAccessList); } protected void removePinnedSlice(Uri uri) { @@ -368,19 +373,7 @@ public class SliceManagerService extends ISliceManager.Stub { } protected int checkAccess(String pkg, Uri uri, int uid, int pid) { - int user = UserHandle.getUserId(uid); - // Check for default launcher/assistant. - if (!hasFullSliceAccess(pkg, user)) { - // Also allow things with uri access. - if (getContext().checkUriPermission(uri, pid, uid, - Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != PERMISSION_GRANTED) { - // Last fallback (if the calling app owns the authority, then it can have access). - if (!Objects.equals(getProviderPkg(uri, user), pkg)) { - return PERMISSION_DENIED; - } - } - } - return PERMISSION_GRANTED; + return checkSlicePermission(uri, pkg, uid, pid, null); } private String getProviderPkg(Uri uri, int user) { @@ -425,15 +418,11 @@ public class SliceManagerService extends ISliceManager.Stub { private void enforceAccess(String pkg, Uri uri) throws RemoteException { if (checkAccess(pkg, uri, Binder.getCallingUid(), Binder.getCallingPid()) != PERMISSION_GRANTED) { - throw new SecurityException("Access to slice " + uri + " is required"); - } - enforceCrossUser(pkg, uri); - } - - private void enforceFullAccess(String pkg, String name, Uri uri) { - int user = Binder.getCallingUserHandle().getIdentifier(); - if (!hasFullSliceAccess(pkg, user)) { - throw new SecurityException(String.format("Call %s requires full slice access", name)); + int userId = ContentProvider.getUserIdFromUri(uri, + Binder.getCallingUserHandle().getIdentifier()); + if (!Objects.equals(pkg, getProviderPkg(uri, userId))) { + throw new SecurityException("Access to slice " + uri + " is required"); + } } enforceCrossUser(pkg, uri); } @@ -513,9 +502,7 @@ public class SliceManagerService extends ISliceManager.Stub { } private boolean isGrantedFullAccess(String pkg, int userId) { - synchronized (mAccessList) { - return mAccessList.hasFullAccess(pkg, userId); - } + return mPermissions.hasFullAccess(pkg, userId); } private static ServiceThread createHandler() { @@ -525,34 +512,6 @@ public class SliceManagerService extends ISliceManager.Stub { return handlerThread; } - private final Runnable mSaveAccessList = new Runnable() { - @Override - public void run() { - synchronized (mSliceAccessFile) { - final FileOutputStream stream; - try { - stream = mSliceAccessFile.startWrite(); - } catch (IOException e) { - Slog.w(TAG, "Failed to save access file", e); - return; - } - - try { - XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer(); - out.setOutput(stream, Encoding.UTF_8.name()); - synchronized (mAccessList) { - mAccessList.writeXml(out, UserHandle.USER_ALL); - } - out.flush(); - mSliceAccessFile.finishWrite(stream); - } catch (IOException | XmlPullParserException e) { - Slog.w(TAG, "Failed to save access file, restoring backup", e); - mSliceAccessFile.failWrite(stream); - } - } - } - }; - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -572,11 +531,11 @@ public class SliceManagerService extends ISliceManager.Stub { final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); if (!replacing) { - removeFullAccess(pkg, userId); + mPermissions.removePkg(pkg, userId); } break; case Intent.ACTION_PACKAGE_DATA_CLEARED: - removeFullAccess(pkg, userId); + mPermissions.removePkg(pkg, userId); break; } } diff --git a/services/core/java/com/android/server/slice/SlicePermissionManager.java b/services/core/java/com/android/server/slice/SlicePermissionManager.java new file mode 100644 index 000000000000..d25ec89e0057 --- /dev/null +++ b/services/core/java/com/android/server/slice/SlicePermissionManager.java @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.server.slice; + +import android.content.ContentProvider; +import android.content.Context; +import android.net.Uri; +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.text.format.DateUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.AtomicFile; +import android.util.Log; +import android.util.Slog; +import android.util.Xml.Encoding; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.XmlUtils; +import com.android.server.slice.SliceProviderPermissions.SliceAuthority; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; + +public class SlicePermissionManager implements DirtyTracker { + + private static final String TAG = "SlicePermissionManager"; + + /** + * The amount of time we'll cache a SliceProviderPermissions or SliceClientPermissions + * in case they are used again. + */ + private static final long PERMISSION_CACHE_PERIOD = 5 * DateUtils.MINUTE_IN_MILLIS; + + /** + * The amount of time we delay flushing out permission changes to disk because they usually + * come in short bursts. + */ + private static final long WRITE_GRACE_PERIOD = 500; + + private static final String SLICE_DIR = "slice"; + + // If/when this bumps again we'll need to write it out in the disk somewhere. + // Currently we don't have a central file for this in version 2 and there is no + // reason to add one until we actually have incompatible version bumps. + // This does however block us from reading backups from P-DP1 which may contain + // a very different XML format for perms. + static final int DB_VERSION = 2; + + private static final String TAG_LIST = "slice-access-list"; + private final String ATT_VERSION = "version"; + + private final File mSliceDir; + private final Context mContext; + private final Handler mHandler; + private final ArrayMap<PkgUser, SliceProviderPermissions> mCachedProviders = new ArrayMap<>(); + private final ArrayMap<PkgUser, SliceClientPermissions> mCachedClients = new ArrayMap<>(); + private final ArraySet<Persistable> mDirty = new ArraySet<>(); + + @VisibleForTesting + SlicePermissionManager(Context context, Looper looper, File sliceDir) { + mContext = context; + mHandler = new H(looper); + mSliceDir = sliceDir; + } + + public SlicePermissionManager(Context context, Looper looper) { + this(context, looper, new File(Environment.getDataDirectory(), "system/" + SLICE_DIR)); + } + + public void grantFullAccess(String pkg, int userId) { + PkgUser pkgUser = new PkgUser(pkg, userId); + SliceClientPermissions client = getClient(pkgUser); + client.setHasFullAccess(true); + } + + public void grantSliceAccess(String pkg, int userId, String providerPkg, int providerUser, + Uri uri) { + PkgUser pkgUser = new PkgUser(pkg, userId); + PkgUser providerPkgUser = new PkgUser(providerPkg, providerUser); + + SliceClientPermissions client = getClient(pkgUser); + client.grantUri(uri, providerPkgUser); + + SliceProviderPermissions provider = getProvider(providerPkgUser); + provider.getOrCreateAuthority(ContentProvider.getUriWithoutUserId(uri).getAuthority()) + .addPkg(pkgUser); + } + + public void revokeSliceAccess(String pkg, int userId, String providerPkg, int providerUser, + Uri uri) { + PkgUser pkgUser = new PkgUser(pkg, userId); + PkgUser providerPkgUser = new PkgUser(providerPkg, providerUser); + + SliceClientPermissions client = getClient(pkgUser); + client.revokeUri(uri, providerPkgUser); + } + + public void removePkg(String pkg, int userId) { + PkgUser pkgUser = new PkgUser(pkg, userId); + SliceProviderPermissions provider = getProvider(pkgUser); + + for (SliceAuthority authority : provider.getAuthorities()) { + for (PkgUser p : authority.getPkgs()) { + getClient(p).removeAuthority(authority.getAuthority(), userId); + } + } + SliceClientPermissions client = getClient(pkgUser); + client.clear(); + mHandler.obtainMessage(H.MSG_REMOVE, pkgUser); + } + + public boolean hasFullAccess(String pkg, int userId) { + PkgUser pkgUser = new PkgUser(pkg, userId); + return getClient(pkgUser).hasFullAccess(); + } + + public boolean hasPermission(String pkg, int userId, Uri uri) { + PkgUser pkgUser = new PkgUser(pkg, userId); + SliceClientPermissions client = getClient(pkgUser); + int providerUserId = ContentProvider.getUserIdFromUri(uri, userId); + return client.hasFullAccess() + || client.hasPermission(ContentProvider.getUriWithoutUserId(uri), providerUserId); + } + + @Override + public void onPersistableDirty(Persistable obj) { + mHandler.removeMessages(H.MSG_PERSIST); + mHandler.obtainMessage(H.MSG_ADD_DIRTY, obj).sendToTarget(); + mHandler.sendEmptyMessageDelayed(H.MSG_PERSIST, WRITE_GRACE_PERIOD); + } + + public void writeBackup(XmlSerializer out) throws IOException, XmlPullParserException { + synchronized (this) { + out.startTag(null, TAG_LIST); + out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION)); + + // Don't do anything with changes from the backup, because there shouldn't be any. + DirtyTracker tracker = obj -> { }; + if (mHandler.hasMessages(H.MSG_PERSIST)) { + mHandler.removeMessages(H.MSG_PERSIST); + handlePersist(); + } + for (String file : new File(mSliceDir.getAbsolutePath()).list()) { + if (file.isEmpty()) continue; + try (ParserHolder parser = getParser(file)) { + Persistable p; + while (parser.parser.getEventType() != XmlPullParser.START_TAG) { + parser.parser.next(); + } + if (SliceClientPermissions.TAG_CLIENT.equals(parser.parser.getName())) { + p = SliceClientPermissions.createFrom(parser.parser, tracker); + } else { + p = SliceProviderPermissions.createFrom(parser.parser, tracker); + } + p.writeTo(out); + } + } + + out.endTag(null, TAG_LIST); + } + } + + public void readRestore(XmlPullParser parser) throws IOException, XmlPullParserException { + synchronized (this) { + while ((parser.getEventType() != XmlPullParser.START_TAG + || !TAG_LIST.equals(parser.getName())) + && parser.getEventType() != XmlPullParser.END_DOCUMENT) { + parser.next(); + } + int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0); + if (xmlVersion < DB_VERSION) { + // No conversion support right now. + return; + } + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + if (parser.getEventType() == XmlPullParser.START_TAG) { + if (SliceClientPermissions.TAG_CLIENT.equals(parser.getName())) { + SliceClientPermissions client = SliceClientPermissions.createFrom(parser, + this); + synchronized (mCachedClients) { + mCachedClients.put(client.getPkg(), client); + } + onPersistableDirty(client); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, client.getPkg()), + PERMISSION_CACHE_PERIOD); + } else if (SliceProviderPermissions.TAG_PROVIDER.equals(parser.getName())) { + SliceProviderPermissions provider = SliceProviderPermissions.createFrom( + parser, this); + synchronized (mCachedProviders) { + mCachedProviders.put(provider.getPkg(), provider); + } + onPersistableDirty(provider); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, provider.getPkg()), + PERMISSION_CACHE_PERIOD); + } else { + parser.next(); + } + } else { + parser.next(); + } + } + } + } + + private SliceClientPermissions getClient(PkgUser pkgUser) { + SliceClientPermissions client; + synchronized (mCachedClients) { + client = mCachedClients.get(pkgUser); + } + if (client == null) { + try (ParserHolder parser = getParser(SliceClientPermissions.getFileName(pkgUser))) { + client = SliceClientPermissions.createFrom(parser.parser, this); + synchronized (mCachedClients) { + mCachedClients.put(pkgUser, client); + } + mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, pkgUser), + PERMISSION_CACHE_PERIOD); + return client; + } catch (FileNotFoundException e) { + // No client exists yet. + } catch (IOException e) { + Log.e(TAG, "Can't read client", e); + } catch (XmlPullParserException e) { + Log.e(TAG, "Can't read client", e); + } + // Can't read or no permissions exist, create a clean object. + client = new SliceClientPermissions(pkgUser, this); + } + return client; + } + + private SliceProviderPermissions getProvider(PkgUser pkgUser) { + SliceProviderPermissions provider; + synchronized (mCachedProviders) { + provider = mCachedProviders.get(pkgUser); + } + if (provider == null) { + try (ParserHolder parser = getParser(SliceProviderPermissions.getFileName(pkgUser))) { + provider = SliceProviderPermissions.createFrom(parser.parser, this); + synchronized (mCachedProviders) { + mCachedProviders.put(pkgUser, provider); + } + mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, pkgUser), + PERMISSION_CACHE_PERIOD); + return provider; + } catch (FileNotFoundException e) { + // No provider exists yet. + } catch (IOException e) { + Log.e(TAG, "Can't read provider", e); + } catch (XmlPullParserException e) { + Log.e(TAG, "Can't read provider", e); + } + // Can't read or no permissions exist, create a clean object. + provider = new SliceProviderPermissions(pkgUser, this); + } + return provider; + } + + private ParserHolder getParser(String fileName) + throws FileNotFoundException, XmlPullParserException { + AtomicFile file = getFile(fileName); + ParserHolder holder = new ParserHolder(); + holder.input = file.openRead(); + holder.parser = XmlPullParserFactory.newInstance().newPullParser(); + holder.parser.setInput(holder.input, Encoding.UTF_8.name()); + return holder; + } + + private AtomicFile getFile(String fileName) { + if (!mSliceDir.exists()) { + mSliceDir.mkdir(); + } + return new AtomicFile(new File(mSliceDir, fileName)); + } + + private void handlePersist() { + synchronized (this) { + for (Persistable persistable : mDirty) { + AtomicFile file = getFile(persistable.getFileName()); + final FileOutputStream stream; + try { + stream = file.startWrite(); + } catch (IOException e) { + Slog.w(TAG, "Failed to save access file", e); + return; + } + + try { + XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer(); + out.setOutput(stream, Encoding.UTF_8.name()); + + persistable.writeTo(out); + + out.flush(); + file.finishWrite(stream); + } catch (IOException | XmlPullParserException e) { + Slog.w(TAG, "Failed to save access file, restoring backup", e); + file.failWrite(stream); + } + } + mDirty.clear(); + } + } + + private void handleRemove(PkgUser pkgUser) { + getFile(SliceClientPermissions.getFileName(pkgUser)).delete(); + getFile(SliceProviderPermissions.getFileName(pkgUser)).delete(); + mDirty.remove(mCachedClients.remove(pkgUser)); + mDirty.remove(mCachedProviders.remove(pkgUser)); + } + + private final class H extends Handler { + private static final int MSG_ADD_DIRTY = 1; + private static final int MSG_PERSIST = 2; + private static final int MSG_REMOVE = 3; + private static final int MSG_CLEAR_CLIENT = 4; + private static final int MSG_CLEAR_PROVIDER = 5; + + public H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ADD_DIRTY: + mDirty.add((Persistable) msg.obj); + break; + case MSG_PERSIST: + handlePersist(); + break; + case MSG_REMOVE: + handleRemove((PkgUser) msg.obj); + break; + case MSG_CLEAR_CLIENT: + synchronized (mCachedClients) { + mCachedClients.remove(msg.obj); + } + break; + case MSG_CLEAR_PROVIDER: + synchronized (mCachedProviders) { + mCachedProviders.remove(msg.obj); + } + break; + } + } + } + + public static class PkgUser { + private static final String SEPARATOR = "@"; + private static final String FORMAT = "%s" + SEPARATOR + "%d"; + private final String mPkg; + private final int mUserId; + + public PkgUser(String pkg, int userId) { + mPkg = pkg; + mUserId = userId; + } + + public PkgUser(String pkgUserStr) throws IllegalArgumentException { + try { + String[] vals = pkgUserStr.split(SEPARATOR, 2); + mPkg = vals[0]; + mUserId = Integer.parseInt(vals[1]); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + public String getPkg() { + return mPkg; + } + + public int getUserId() { + return mUserId; + } + + @Override + public int hashCode() { + return mPkg.hashCode() + mUserId; + } + + @Override + public boolean equals(Object obj) { + if (!getClass().equals(obj != null ? obj.getClass() : null)) return false; + PkgUser other = (PkgUser) obj; + return Objects.equals(other.mPkg, mPkg) && other.mUserId == mUserId; + } + + @Override + public String toString() { + return String.format(FORMAT, mPkg, mUserId); + } + } + + private class ParserHolder implements AutoCloseable { + + private InputStream input; + private XmlPullParser parser; + + @Override + public void close() throws IOException { + input.close(); + } + } +} diff --git a/services/core/java/com/android/server/slice/SliceProviderPermissions.java b/services/core/java/com/android/server/slice/SliceProviderPermissions.java new file mode 100644 index 000000000000..6e602d59c421 --- /dev/null +++ b/services/core/java/com/android/server/slice/SliceProviderPermissions.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.server.slice; + +import android.annotation.NonNull; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.server.slice.DirtyTracker.Persistable; +import com.android.server.slice.SlicePermissionManager.PkgUser; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; + +public class SliceProviderPermissions implements DirtyTracker, Persistable { + + private static final String TAG = "SliceProviderPermissions"; + + static final String TAG_PROVIDER = "provider"; + private static final String TAG_AUTHORITY = "authority"; + private static final String TAG_PKG = "pkg"; + private static final String NAMESPACE = null; + + private static final String ATTR_PKG = "pkg"; + private static final String ATTR_AUTHORITY = "authority"; + + private final PkgUser mPkg; + private final ArrayMap<String, SliceAuthority> mAuths = new ArrayMap<>(); + private final DirtyTracker mTracker; + + public SliceProviderPermissions(@NonNull PkgUser pkg, @NonNull DirtyTracker tracker) { + mPkg = pkg; + mTracker = tracker; + } + + public PkgUser getPkg() { + return mPkg; + } + + public synchronized Collection<SliceAuthority> getAuthorities() { + return new ArrayList<>(mAuths.values()); + } + + public synchronized SliceAuthority getOrCreateAuthority(String authority) { + SliceAuthority ret = mAuths.get(authority); + if (ret == null) { + ret = new SliceAuthority(authority, this); + mAuths.put(authority, ret); + onPersistableDirty(ret); + } + return ret; + } + + @Override + public void onPersistableDirty(Persistable obj) { + mTracker.onPersistableDirty(this); + } + + @Override + public String getFileName() { + return getFileName(mPkg); + } + + public synchronized void writeTo(XmlSerializer out) throws IOException { + out.startTag(NAMESPACE, TAG_PROVIDER); + out.attribute(NAMESPACE, ATTR_PKG, mPkg.toString()); + + final int N = mAuths.size(); + for (int i = 0; i < N; i++) { + out.startTag(NAMESPACE, TAG_AUTHORITY); + out.attribute(NAMESPACE, ATTR_AUTHORITY, mAuths.valueAt(i).mAuthority); + + mAuths.valueAt(i).writeTo(out); + + out.endTag(NAMESPACE, TAG_AUTHORITY); + } + + out.endTag(NAMESPACE, TAG_PROVIDER); + } + + public static SliceProviderPermissions createFrom(XmlPullParser parser, DirtyTracker tracker) + throws XmlPullParserException, IOException { + // Get to the beginning of the provider. + while (parser.getEventType() != XmlPullParser.START_TAG + || !TAG_PROVIDER.equals(parser.getName())) { + parser.next(); + } + int depth = parser.getDepth(); + PkgUser pkgUser = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG)); + SliceProviderPermissions provider = new SliceProviderPermissions(pkgUser, tracker); + parser.next(); + + while (parser.getDepth() > depth) { + if (parser.getEventType() == XmlPullParser.START_TAG + && TAG_AUTHORITY.equals(parser.getName())) { + try { + SliceAuthority authority = new SliceAuthority( + parser.getAttributeValue(NAMESPACE, ATTR_AUTHORITY), provider); + authority.readFrom(parser); + provider.mAuths.put(authority.getAuthority(), authority); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Couldn't read PkgUser", e); + } + } + + parser.next(); + } + return provider; + } + + public static String getFileName(PkgUser pkg) { + return String.format("provider_%s", pkg.toString()); + } + + public static class SliceAuthority implements Persistable { + private final String mAuthority; + private final DirtyTracker mTracker; + private final ArraySet<PkgUser> mPkgs = new ArraySet<>(); + + public SliceAuthority(String authority, DirtyTracker tracker) { + mAuthority = authority; + mTracker = tracker; + } + + public String getAuthority() { + return mAuthority; + } + + public synchronized void addPkg(PkgUser pkg) { + if (mPkgs.add(pkg)) { + mTracker.onPersistableDirty(this); + } + } + + public synchronized void removePkg(PkgUser pkg) { + if (mPkgs.remove(pkg)) { + mTracker.onPersistableDirty(this); + } + } + + public synchronized Collection<PkgUser> getPkgs() { + return new ArraySet<>(mPkgs); + } + + @Override + public String getFileName() { + return null; + } + + public synchronized void writeTo(XmlSerializer out) throws IOException { + final int N = mPkgs.size(); + for (int i = 0; i < N; i++) { + out.startTag(NAMESPACE, TAG_PKG); + out.text(mPkgs.valueAt(i).toString()); + out.endTag(NAMESPACE, TAG_PKG); + } + } + + public synchronized void readFrom(XmlPullParser parser) + throws IOException, XmlPullParserException { + parser.next(); + int depth = parser.getDepth(); + while (parser.getDepth() >= depth) { + if (parser.getEventType() == XmlPullParser.START_TAG + && TAG_PKG.equals(parser.getName())) { + mPkgs.add(new PkgUser(parser.nextText())); + } + parser.next(); + } + } + + @Override + public boolean equals(Object obj) { + if (!getClass().equals(obj != null ? obj.getClass() : null)) return false; + SliceAuthority other = (SliceAuthority) obj; + return Objects.equals(mAuthority, other.mAuthority) + && Objects.equals(mPkgs, other.mPkgs); + } + + @Override + public String toString() { + return String.format("(%s: %s)", mAuthority, mPkgs.toString()); + } + } +} diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 641a1ba68648..608d0aa5875f 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1102,35 +1102,37 @@ final class AccessibilityController { } } - // Account for the space this window takes if the window - // is not an accessibility overlay which does not change - // the reported windows. if (windowState.mAttrs.type != WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) { - unaccountedSpace.op(boundsInScreen, unaccountedSpace, - Region.Op.REVERSE_DIFFERENCE); - } - // If a window is modal it prevents other windows from being touched - if ((flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) { - // Account for all space in the task, whether the windows in it are - // touchable or not. The modal window blocks all touches from the task's - // area. - unaccountedSpace.op(windowState.getDisplayFrameLw(), unaccountedSpace, + // Account for the space this window takes if the window + // is not an accessibility overlay which does not change + // the reported windows. + unaccountedSpace.op(boundsInScreen, unaccountedSpace, Region.Op.REVERSE_DIFFERENCE); - if (task != null) { - // If the window is associated with a particular task, we can skip the - // rest of the windows for that task. - skipRemainingWindowsForTasks.add(task.mTaskId); - continue; - } else { - // If the window is not associated with a particular task, then it is - // globally modal. In this case we can skip all remaining windows. - break; + // If a window is modal it prevents other windows from being touched + if ((flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) { + // Account for all space in the task, whether the windows in it are + // touchable or not. The modal window blocks all touches from the task's + // area. + unaccountedSpace.op(windowState.getDisplayFrameLw(), unaccountedSpace, + Region.Op.REVERSE_DIFFERENCE); + + if (task != null) { + // If the window is associated with a particular task, we can skip the + // rest of the windows for that task. + skipRemainingWindowsForTasks.add(task.mTaskId); + continue; + } else { + // If the window is not associated with a particular task, then it is + // globally modal. In this case we can skip all remaining windows. + break; + } } } + // We figured out what is touchable for the entire screen - done. if (unaccountedSpace.isEmpty()) { break; diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java index d012bba375d3..18e842f3e694 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java @@ -23,6 +23,7 @@ import static android.app.ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; import static android.app.ActivityManager.START_INTENT_NOT_RESOLVED; import static android.app.ActivityManager.START_NOT_VOICE_COMPATIBLE; import static android.app.ActivityManager.START_PERMISSION_DENIED; +import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_SWITCHES_CANCELED; import static android.app.ActivityManager.START_TASK_TO_FRONT; @@ -34,6 +35,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECOND import android.app.ActivityOptions; import android.app.IApplicationThread; +import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.WindowLayout; @@ -74,6 +76,8 @@ import com.android.server.am.ActivityStarter.Factory; import com.android.server.am.LaunchParamsController.LaunchParamsModifier; import com.android.server.am.TaskRecord.TaskRecordFactory; +import java.util.ArrayList; + /** * Tests for the {@link ActivityStarter} class. * @@ -301,13 +305,14 @@ public class ActivityStarterTests extends ActivityTestsBase { anyBoolean(), any(), any(), any()); // instrument the stack and task used. - final ActivityStack stack = spy(mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */)); - final TaskRecord task = - spy(new TaskBuilder(mService.mStackSupervisor).setStack(stack).build()); + final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final TaskRecord task = new TaskBuilder(mService.mStackSupervisor) + .setCreateStack(false) + .build(); // supervisor needs a focused stack. - mService.mStackSupervisor.mFocusedStack = task.getStack(); + mService.mStackSupervisor.mFocusedStack = stack; // use factory that only returns spy task. final TaskRecordFactory factory = mock(TaskRecordFactory.class); @@ -322,14 +327,6 @@ public class ActivityStarterTests extends ActivityTestsBase { doReturn(stack).when(mService.mStackSupervisor) .getLaunchStack(any(), any(), any(), anyBoolean(), anyInt()); - // ignore the start request. - doNothing().when(stack) - .startActivityLocked(any(), any(), anyBoolean(), anyBoolean(), any()); - - // ignore requests to create window container. - doNothing().when(task).createWindowContainer(anyBoolean(), anyBoolean()); - - final Intent intent = new Intent(); intent.addFlags(launchFlags); intent.setComponent(ActivityBuilder.getDefaultComponent()); @@ -448,4 +445,30 @@ public class ActivityStarterTests extends ActivityTestsBase { // Ensure result is moving task to front. assertEquals(result, START_TASK_TO_FRONT); } + + /** + * Tests activity is cleaned up properly in a task mode violation. + */ + @Test + public void testTaskModeViolation() { + final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay(); + assertNoTasks(display); + + final ActivityStarter starter = prepareStarter(0); + + final LockTaskController lockTaskController = mService.getLockTaskController(); + doReturn(true).when(lockTaskController).isLockTaskModeViolation(any()); + + final int result = starter.setReason("testTaskModeViolation").execute(); + + assertEquals(START_RETURN_LOCK_TASK_MODE_VIOLATION, result); + assertNoTasks(display); + } + + private void assertNoTasks(ActivityDisplay display) { + for (int i = display.getChildCount() - 1; i >= 0; --i) { + final ActivityStack stack = display.getChildAt(i); + assertTrue(stack.getAllTasks().isEmpty()); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java index 741901d2e5dc..f5e61a1db341 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; +import android.app.ActivityOptions; import com.android.server.wm.DisplayWindowController; import org.junit.Rule; @@ -51,6 +52,9 @@ import android.service.voice.IVoiceInteractionSession; import android.support.test.InstrumentationRegistry; import android.testing.DexmakerShareClassLoaderRule; + +import com.android.internal.app.IVoiceInteractor; + import com.android.server.AttributeCache; import com.android.server.wm.AppWindowContainerController; import com.android.server.wm.PinnedStackWindowController; @@ -62,6 +66,7 @@ import org.junit.After; import org.junit.Before; import org.mockito.MockitoAnnotations; + /** * A base class to handle common operations in activity related unit tests. */ @@ -215,6 +220,7 @@ public class ActivityTestsBase { private int mTaskId = 0; private int mUserId = 0; private IVoiceInteractionSession mVoiceSession; + private boolean mCreateStack = true; private ActivityStack mStack; @@ -232,6 +238,15 @@ public class ActivityTestsBase { return this; } + /** + * Set to {@code true} by default, set to {@code false} to prevent the task from + * automatically creating a parent stack. + */ + TaskBuilder setCreateStack(boolean createStack) { + mCreateStack = createStack; + return this; + } + TaskBuilder setVoiceSession(IVoiceInteractionSession session) { mVoiceSession = session; return this; @@ -258,7 +273,7 @@ public class ActivityTestsBase { } TaskRecord build() { - if (mStack == null) { + if (mStack == null && mCreateStack) { mStack = mSupervisor.getDefaultDisplay().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); } @@ -276,17 +291,38 @@ public class ActivityTestsBase { intent.setComponent(mComponent); intent.setFlags(mFlags); - final TaskRecord task = new TaskRecord(mSupervisor.mService, mTaskId, aInfo, + final TestTaskRecord task = new TestTaskRecord(mSupervisor.mService, mTaskId, aInfo, intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/); task.userId = mUserId; - mSupervisor.setFocusStackUnchecked("test", mStack); - mStack.addTask(task, true, "creating test task"); - task.setStack(mStack); - task.setWindowContainerController(mock(TaskWindowContainerController.class)); + + if (mStack != null) { + mSupervisor.setFocusStackUnchecked("test", mStack); + mStack.addTask(task, true, "creating test task"); + task.setStack(mStack); + task.setWindowContainerController(); + } + task.touchActiveTime(); return task; } + + private static class TestTaskRecord extends TaskRecord { + TestTaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, + Intent _intent, IVoiceInteractionSession _voiceSession, + IVoiceInteractor _voiceInteractor) { + super(service, _taskId, info, _intent, _voiceSession, _voiceInteractor); + } + + @Override + void createWindowContainer(boolean onTop, boolean showForAllUsers) { + setWindowContainerController(); + } + + private void setWindowContainerController() { + setWindowContainerController(mock(TaskWindowContainerController.class)); + } + } } /** @@ -295,6 +331,7 @@ public class ActivityTestsBase { */ protected static class TestActivityManagerService extends ActivityManagerService { private ClientLifecycleManager mLifecycleManager; + private LockTaskController mLockTaskController; TestActivityManagerService(Context context) { super(context); @@ -314,6 +351,14 @@ public class ActivityTestsBase { return mLifecycleManager; } + public LockTaskController getLockTaskController() { + if (mLockTaskController == null) { + mLockTaskController = spy(super.getLockTaskController()); + } + + return mLockTaskController; + } + void setLifecycleManager(ClientLifecycleManager manager) { mLifecycleManager = manager; } @@ -444,7 +489,7 @@ public class ActivityTestsBase { } /** - * Overrided of {@link ActivityStack} that tracks test metrics, such as the number of times a + * Overridden {@link ActivityStack} that tracks test metrics, such as the number of times a * method is called. Note that its functionality depends on the implementations of the * construction arguments. */ @@ -530,5 +575,11 @@ public class ActivityTestsBase { return super.supportsSplitScreenWindowingMode(); } } + + @Override + void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity, + boolean newTask, boolean keepCurTransition, + ActivityOptions options) { + } } } diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java index 36e475378e38..084f7e531858 100644 --- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java @@ -297,7 +297,7 @@ public class SuspendPackagesTest { intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS)); final PersistableBundle extras2 = getExtras("testMyPackageSuspendedOnChangingExtras", 2, "2", 0.2); - mPackageManager.setSuspendedPackageAppExtras(TEST_APP_PACKAGE_NAME, extras2); + suspendTestPackage(extras2, null, null); intentFromApp = mAppCommsReceiver.receiveIntentFromApp(); assertEquals("MY_PACKAGE_SUSPENDED delivery not reported", ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction()); diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceClientPermissionsTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceClientPermissionsTest.java new file mode 100644 index 000000000000..1efa4153073a --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceClientPermissionsTest.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.server.slice; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.ContentResolver; +import android.net.Uri; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; +import android.util.Xml.Encoding; + +import com.android.server.UiServiceTestCase; +import com.android.server.slice.SlicePermissionManager.PkgUser; +import com.android.server.slice.SliceClientPermissions.SliceAuthority; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlSerializer; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class SliceClientPermissionsTest extends UiServiceTestCase { + + @Test + public void testRemoveBasic() { + PkgUser pkg = new PkgUser("com.android.pkg", 0); + DirtyTracker tracker = mock(DirtyTracker.class); + SliceClientPermissions client = new SliceClientPermissions(pkg, tracker); + Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority("com.android.pkg.slices").build(); + + PkgUser testPkg = new PkgUser("other", 2); + + client.grantUri(base.buildUpon() + .appendPath("first") + .build(), testPkg); + client.revokeUri(base.buildUpon() + .appendPath("first") + .build(), testPkg); + + assertFalse(client.hasPermission(base.buildUpon() + .appendPath("first") + .appendPath("third") + .build(), testPkg.getUserId())); + + ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities()); + assertEquals(0, authorities.get(0).getPaths().size()); + } + + @Test + public void testRemoveSubtrees() { + PkgUser pkg = new PkgUser("com.android.pkg", 0); + DirtyTracker tracker = mock(DirtyTracker.class); + SliceClientPermissions client = new SliceClientPermissions(pkg, tracker); + Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority("com.android.pkg.slices").build(); + + PkgUser testPkg = new PkgUser("other", 2); + + client.grantUri(base.buildUpon() + .appendPath("first") + .appendPath("second") + .build(), testPkg); + client.grantUri(base.buildUpon() + .appendPath("first") + .appendPath("third") + .build(), testPkg); + client.revokeUri(base.buildUpon() + .appendPath("first") + .build(), testPkg); + + assertFalse(client.hasPermission(base.buildUpon() + .appendPath("first") + .appendPath("fourth") + .build(), testPkg.getUserId())); + + ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities()); + assertEquals(0, authorities.get(0).getPaths().size()); + } + + @Test + public void testAddConsolidate_addFirst() { + PkgUser pkg = new PkgUser("com.android.pkg", 0); + DirtyTracker tracker = mock(DirtyTracker.class); + SliceClientPermissions client = new SliceClientPermissions(pkg, tracker); + Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority("com.android.pkg.slices").build(); + + PkgUser testPkg = new PkgUser("other", 2); + + client.grantUri(base.buildUpon() + .appendPath("first") + .build(), testPkg); + client.grantUri(base.buildUpon() + .appendPath("first") + .appendPath("second") + .build(), testPkg); + + assertTrue(client.hasPermission(base.buildUpon() + .appendPath("first") + .appendPath("third") + .build(), testPkg.getUserId())); + + ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities()); + assertEquals(1, authorities.get(0).getPaths().size()); + } + + @Test + public void testAddConsolidate_addSecond() { + PkgUser pkg = new PkgUser("com.android.pkg", 0); + DirtyTracker tracker = mock(DirtyTracker.class); + SliceClientPermissions client = new SliceClientPermissions(pkg, tracker); + Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority("com.android.pkg.slices").build(); + + PkgUser testPkg = new PkgUser("other", 2); + + client.grantUri(base.buildUpon() + .appendPath("first") + .appendPath("second") + .build(), testPkg); + client.grantUri(base.buildUpon() + .appendPath("first") + .build(), testPkg); + + assertTrue(client.hasPermission(base.buildUpon() + .appendPath("first") + .appendPath("third") + .build(), testPkg.getUserId())); + + ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities()); + assertEquals(1, authorities.get(0).getPaths().size()); + } + + @Test + public void testDirty_addAuthority() { + PkgUser pkg = new PkgUser("com.android.pkg", 0); + DirtyTracker tracker = mock(DirtyTracker.class); + SliceClientPermissions client = new SliceClientPermissions(pkg, tracker); + + client.getOrCreateAuthority(new PkgUser("some_auth", 2), new PkgUser("com.pkg", 2)); + + verify(tracker).onPersistableDirty(eq(client)); + } + + @Test + public void testDirty_addPkg() { + PkgUser pkg = new PkgUser("com.android.pkg", 0); + DirtyTracker tracker = mock(DirtyTracker.class); + SliceClientPermissions client = new SliceClientPermissions(pkg, tracker); + + SliceAuthority auth = client.getOrCreateAuthority( + new PkgUser("some_auth", 2), + new PkgUser("com.pkg", 2)); + clearInvocations(tracker); + + auth.addPath(Arrays.asList("/something/")); + + verify(tracker).onPersistableDirty(eq(client)); + } + + @Test + public void testCreation() { + SliceClientPermissions client = createClient(); + ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities()); + authorities.sort(Comparator.comparing(SliceAuthority::getAuthority)); + + assertEquals(2, authorities.size()); + assertEquals("com.android.pkg", authorities.get(0).getAuthority()); + assertEquals("com.android.pkg.slices", authorities.get(1).getAuthority()); + + assertEquals(1, authorities.get(0).getPaths().size()); + assertEquals(2, authorities.get(1).getPaths().size()); + } + + @Test + public void testSerialization() throws XmlPullParserException, IOException { + SliceClientPermissions client = createClient(); + client.setHasFullAccess(true); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer(); + serializer.setOutput(output, Encoding.UTF_8.name()); + + client.writeTo(serializer); + serializer.flush(); + + ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray()); + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setInput(input, Encoding.UTF_8.name()); + + SliceClientPermissions deser = SliceClientPermissions.createFrom(parser, + mock(DirtyTracker.class)); + + assertEquivalent(client, deser); + } + + private void assertEquivalent(SliceClientPermissions o1, SliceClientPermissions o2) { + assertEquals(o1.getPkg(), o2.getPkg()); + ArrayList<SliceAuthority> a1 = new ArrayList<>(o1.getAuthorities()); + ArrayList<SliceAuthority> a2 = new ArrayList<>(o2.getAuthorities()); + a1.sort(Comparator.comparing(SliceAuthority::getAuthority)); + a2.sort(Comparator.comparing(SliceAuthority::getAuthority)); + assertEquals(a1, a2); + } + + private static SliceClientPermissions createClient() { + PkgUser pkg = new PkgUser("com.android.pkg", 2); + DirtyTracker tracker = mock(DirtyTracker.class); + SliceClientPermissions client = new SliceClientPermissions(pkg, tracker); + + SliceAuthority auth = client.getOrCreateAuthority( + new PkgUser("com.android.pkg.slices", 3), + new PkgUser("com.android.pkg", 3)); + auth.addPath(Arrays.asList("/something/")); + auth.addPath(Arrays.asList("/something/else")); + + auth = client.getOrCreateAuthority( + new PkgUser("com.android.pkg", 3), + new PkgUser("com.pkg", 1)); + auth.addPath(Arrays.asList("/somewhere")); + return client; + } + +}
\ No newline at end of file diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java new file mode 100644 index 000000000000..5443e73dd94f --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.server.slice; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.net.Uri; +import android.net.Uri.Builder; +import android.os.FileUtils; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; +import android.util.Log; +import android.util.Xml.Encoding; + +import com.android.server.UiServiceTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlSerializer; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class SlicePermissionManagerTest extends UiServiceTestCase { + + @Test + public void testBackup() throws XmlPullParserException, IOException { + File sliceDir = new File(mContext.getDataDir(), "system/slices"); + Uri uri = new Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority("authority") + .path("something").build(); + SlicePermissionManager permissions = new SlicePermissionManager(mContext, + TestableLooper.get(this).getLooper(), sliceDir); + + permissions.grantFullAccess("com.android.mypkg", 10); + permissions.grantSliceAccess("com.android.otherpkg", 0, "com.android.lastpkg", 1, uri); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer(); + serializer.setOutput(output, Encoding.UTF_8.name()); + + + TestableLooper.get(this).processAllMessages(); + permissions.writeBackup(serializer); + serializer.flush(); + + ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray()); + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setInput(input, Encoding.UTF_8.name()); + + permissions = new SlicePermissionManager(mContext, + TestableLooper.get(this).getLooper()); + permissions.readRestore(parser); + + assertTrue(permissions.hasFullAccess("com.android.mypkg", 10)); + assertTrue(permissions.hasPermission("com.android.otherpkg", 0, + ContentProvider.maybeAddUserId(uri, 1))); + permissions.removePkg("com.android.lastpkg", 1); + assertFalse(permissions.hasPermission("com.android.otherpkg", 0, + ContentProvider.maybeAddUserId(uri, 1))); + + // Cleanup. + assertTrue(FileUtils.deleteContentsAndDir(sliceDir)); + } + +}
\ No newline at end of file diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceProviderPermissionsTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceProviderPermissionsTest.java new file mode 100644 index 000000000000..5775991b8ba6 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceProviderPermissionsTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.server.slice; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; +import android.util.Xml.Encoding; + +import com.android.server.UiServiceTestCase; +import com.android.server.slice.SlicePermissionManager.PkgUser; +import com.android.server.slice.SliceProviderPermissions.SliceAuthority; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlSerializer; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class SliceProviderPermissionsTest extends UiServiceTestCase { + + @Test + public void testDirty_addAuthority() { + PkgUser pkg = new PkgUser("com.android.pkg", 0); + DirtyTracker tracker = mock(DirtyTracker.class); + SliceProviderPermissions provider = new SliceProviderPermissions(pkg, tracker); + + provider.getOrCreateAuthority("some_auth"); + + verify(tracker).onPersistableDirty(eq(provider)); + } + + @Test + public void testDirty_addPkg() { + PkgUser pkg = new PkgUser("com.android.pkg", 0); + DirtyTracker tracker = mock(DirtyTracker.class); + SliceProviderPermissions provider = new SliceProviderPermissions(pkg, tracker); + + SliceAuthority auth = provider.getOrCreateAuthority("some_auth"); + clearInvocations(tracker); + + auth.addPkg(new PkgUser("pkg", 0)); + + verify(tracker).onPersistableDirty(eq(provider)); + } + + @Test + public void testCreation() { + SliceProviderPermissions provider = createProvider(); + ArrayList<SliceAuthority> authorities = new ArrayList<>(provider.getAuthorities()); + authorities.sort(Comparator.comparing(SliceAuthority::getAuthority)); + + assertEquals(2, authorities.size()); + assertEquals("com.android.pkg", authorities.get(0).getAuthority()); + assertEquals("com.android.pkg.slices", authorities.get(1).getAuthority()); + + assertEquals(1, authorities.get(0).getPkgs().size()); + assertEquals(2, authorities.get(1).getPkgs().size()); + } + + @Test + public void testSerialization() throws XmlPullParserException, IOException { + SliceProviderPermissions provider = createProvider(); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer(); + serializer.setOutput(output, Encoding.UTF_8.name()); + + provider.writeTo(serializer); + serializer.flush(); + + ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray()); + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setInput(input, Encoding.UTF_8.name()); + + SliceProviderPermissions deser = SliceProviderPermissions.createFrom(parser, + mock(DirtyTracker.class)); + + assertEquivalent(provider, deser); + } + + private void assertEquivalent(SliceProviderPermissions o1, SliceProviderPermissions o2) { + assertEquals(o1.getPkg(), o2.getPkg()); + assertEquals(o1.getAuthorities(), o2.getAuthorities()); + } + + private static SliceProviderPermissions createProvider() { + PkgUser pkg = new PkgUser("com.android.pkg", 2); + DirtyTracker tracker = mock(DirtyTracker.class); + SliceProviderPermissions provider = new SliceProviderPermissions(pkg, tracker); + + SliceAuthority auth = provider.getOrCreateAuthority("com.android.pkg.slices"); + auth.addPkg(new PkgUser("com.example.pkg", 0)); + auth.addPkg(new PkgUser("example.pkg.com", 10)); + + auth = provider.getOrCreateAuthority("com.android.pkg"); + auth.addPkg(new PkgUser("com.example.pkg", 2)); + return provider; + } + +}
\ No newline at end of file diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index e15d35b5f744..a37c0236604c 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -5156,7 +5156,12 @@ public class TelephonyManager { * {@link #AUTHTYPE_EAP_SIM} * @param data authentication challenge data, base64 encoded. * See 3GPP TS 31.102 7.1.2 for more details. - * @return the response of authentication, or null if not available + * @return the response of authentication. This value will be null in the following cases: + * Authentication error, incorrect MAC + * Authentication error, security context not supported + * Key freshness failure + * Authentication error, no memory space available + * Authentication error, no memory space available in EFMUK */ // TODO(b/73660190): This should probably require MODIFY_PHONE_STATE, not // READ_PRIVILEGED_PHONE_STATE. It certainly shouldn't reference the permission in Javadoc since @@ -5177,7 +5182,13 @@ public class TelephonyManager { * {@link #AUTHTYPE_EAP_SIM} * @param data authentication challenge data, base64 encoded. * See 3GPP TS 31.102 7.1.2 for more details. - * @return the response of authentication, or null if not available + * @return the response of authentication. This value will be null in the following cases only + * (see 3GPP TS 31.102 7.3.1): + * Authentication error, incorrect MAC + * Authentication error, security context not supported + * Key freshness failure + * Authentication error, no memory space available + * Authentication error, no memory space available in EFMUK * @hide */ public String getIccAuthentication(int subId, int appType, int authType, String data) { |