Merge "List<SliceSpec> -> Set<SliceSpec>" into pi-dev
diff --git a/api/system-current.txt b/api/system-current.txt
index 6186a55..a559b7a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1027,7 +1027,6 @@
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 @@
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 27bbc4b..2e93d88 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2165,30 +2165,18 @@
}
@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 4a7cf62..9e47ced 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -328,7 +328,8 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* {@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 @@
* 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 @@
* 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 @@
}
/**
- * 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 a2aaf12..69852f3 100644
--- a/core/java/android/app/slice/ISliceManager.aidl
+++ b/core/java/android/app/slice/ISliceManager.aidl
@@ -25,11 +25,15 @@
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 4127a40..dc09f12 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 @@
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 @@
* @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 @@
* @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 @@
* @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 @@
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 02ce47b8..2be33e9 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -280,9 +280,6 @@
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 34e3d8b..90fc8f8 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5616,46 +5616,6 @@
}
/**
- * 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 dc58f11..b367dc7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -11791,6 +11791,14 @@
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 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 @@
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 e802232..f6d9a16 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -8305,6 +8305,12 @@
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 @@
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 @@
// 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 @@
// 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 e0f74a7..95a83da 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -388,6 +388,8 @@
*/
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 @@
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 @@
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 @@
mWindowChangeTypes = event.mWindowChangeTypes;
mEventTime = event.mEventTime;
mPackageName = event.mPackageName;
+ if (DEBUG_ORIGIN) originStackTrace = event.originStackTrace;
}
/**
@@ -1104,7 +1118,9 @@
*/
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 @@
record.recycle();
}
}
+ if (DEBUG_ORIGIN) originStackTrace = null;
}
/**
@@ -1164,7 +1181,7 @@
// 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 @@
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 @@
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 72af203..d60c481 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -326,12 +326,14 @@
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 @@
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 dee267d..cbb23f1 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.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.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 4c437dd..03f1c12 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3874,6 +3874,24 @@
| 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 c205f96..6c05601 100644
--- a/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
+++ b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
@@ -54,7 +54,7 @@
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 @@
// 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 0680924..380e315 100644
--- a/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
+++ b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
@@ -54,7 +54,7 @@
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 @@
// 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 16574a7..3900959 100644
--- a/libs/hwui/debug/gles_decls.in
+++ b/libs/hwui/debug/gles_decls.in
@@ -387,7 +387,6 @@
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, 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 4064a39..7cba0c1 100644
--- a/libs/hwui/debug/gles_stubs.in
+++ b/libs/hwui/debug/gles_stubs.in
@@ -1165,9 +1165,6 @@
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(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 addbf84..37de433 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 5588eb2..d9ed823 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 d63ad08..00cd5a7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -265,11 +265,11 @@
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 d6e59c7..426f714 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 @@
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 3d7067d..1fb1ddd 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 @@
private int mBurnInPreventionOffsetY;
/**
+ * Clock vertical padding when pulsing.
+ */
+ private int mPulsingPadding;
+
+ /**
* Doze/AOD transition amount.
*/
private float mDarkAmount;
@@ -109,9 +114,9 @@
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 @@
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 @@
mKeyguardStatusHeight = keyguardStatusHeight;
mDarkAmount = dark;
mCurrentlySecure = secure;
- mTracking = tracking;
+ mPulsing = pulsing;
mBouncerTop = bouncerTop;
}
@@ -152,7 +159,7 @@
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 @@
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 b7af84a..351633b 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 @@
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 @@
mKeyguardStatusView.getHeight(),
mDarkAmount,
mStatusBar.isKeyguardCurrentlySecure(),
- mTracking,
+ mPulsing,
mBouncerTop);
mClockPositionAlgorithm.run(mClockPositionResult);
if (animate || mClockAnimator != null) {
@@ -2689,14 +2690,8 @@
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 ab13678..4b2bc45 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 @@
}
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 5a4478f..639e49b 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 @@
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 @@
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 04d54e1..43c1655 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 @@
/**
* The controller for all operations related to locktask.
*/
- final LockTaskController mLockTaskController;
+ private final LockTaskController mLockTaskController;
final UserController mUserController;
@@ -12441,6 +12441,10 @@
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 e539bf8..4ce157f 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 @@
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 649d577..1e9edd9 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 @@
}
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 @@
// 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 @@
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 e9a1358..731a44d 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 @@
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 @@
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 @@
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 5337566..fb89e67 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 @@
// 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 @@
// 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 @@
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 @@
}
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 @@
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 ab1d7bf..ea0251e 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 @@
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 9caef4a..40b9e4f 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 @@
# 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 2b988d3..a20452b 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 @@
}
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 @@
}
// 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 ac6f01f..2de75273 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 @@
// 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 034cb2e..737105d 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 @@
}
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 @@
}
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 @@
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 b6e414a2..fecb934 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 @@
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 5b45cbe..f2d812e 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 @@
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER,
params.traceMethod, params.traceCookie);
}
- return;
}
mPendingInstalls.clear();
} else {
@@ -14021,7 +14020,7 @@
"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 @@
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 @@
scheduleWritePackageRestrictionsLocked(userId);
}
}
-
return unactionedPackages.toArray(new String[unactionedPackages.size()]);
}
@@ -14081,7 +14077,8 @@
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 @@
}
}
- @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 @@
}
}
- 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 fd4c5e9..138594c 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_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 0000000..4288edc
--- /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 0000000..e461e0d
--- /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 fd0b6f1..b7b9612 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.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.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 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 @@
@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 @@
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 @@
}
@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 {
+ 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;
+ }
+ }
+ 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;
+ }
+ }
+ }
+ // Fallback to allowing uri permissions through.
if (mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
== PERMISSION_GRANTED) {
- return SliceManager.PERMISSION_GRANTED;
+ return PackageManager.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;
- }
- }
- synchronized (mLock) {
- if (mUserGrants.contains(new SliceGrant(uri, pkg, UserHandle.getUserId(uid)))) {
- return SliceManager.PERMISSION_USER_GRANTED;
- }
- }
- return SliceManager.PERMISSION_DENIED;
+ return PackageManager.PERMISSION_DENIED;
}
@Override
@@ -238,16 +250,17 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
}
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 @@
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 @@
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 0000000..d25ec89
--- /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 0000000..6e602d5
--- /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 641a1ba..608d0aa 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 @@
}
}
- // 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) {
+
+ // 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 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 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;
+ 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 d012bba..18e842f 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_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 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.LaunchParamsController.LaunchParamsModifier;
import com.android.server.am.TaskRecord.TaskRecordFactory;
+import java.util.ArrayList;
+
/**
* Tests for the {@link ActivityStarter} class.
*
@@ -301,13 +305,14 @@
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 @@
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 @@
// 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 741901d..f5e61a1 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.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.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.Before;
import org.mockito.MockitoAnnotations;
+
/**
* A base class to handle common operations in activity related unit tests.
*/
@@ -215,6 +220,7 @@
private int mTaskId = 0;
private int mUserId = 0;
private IVoiceInteractionSession mVoiceSession;
+ private boolean mCreateStack = true;
private ActivityStack mStack;
@@ -232,6 +238,15 @@
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 @@
}
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 @@
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 @@
*/
protected static class TestActivityManagerService extends ActivityManagerService {
private ClientLifecycleManager mLifecycleManager;
+ private LockTaskController mLockTaskController;
TestActivityManagerService(Context context) {
super(context);
@@ -314,6 +351,14 @@
return mLifecycleManager;
}
+ public LockTaskController getLockTaskController() {
+ if (mLockTaskController == null) {
+ mLockTaskController = spy(super.getLockTaskController());
+ }
+
+ return mLockTaskController;
+ }
+
void setLifecycleManager(ClientLifecycleManager manager) {
mLifecycleManager = manager;
}
@@ -444,7 +489,7 @@
}
/**
- * 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 @@
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 36e4753..084f7e5 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 @@
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 0000000..1efa415
--- /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 0000000..5443e73
--- /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 0000000..5775991
--- /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 e15d35b..a37c023 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5156,7 +5156,12 @@
* {@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 @@
* {@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) {