diff options
author | Jason Monk <jmonk@google.com> | 2017-10-19 18:17:25 +0000 |
---|---|---|
committer | Jason Monk <jmonk@google.com> | 2017-10-19 18:17:25 +0000 |
commit | 07f9f65561c2b81bcd189b895b31bb2ad0438d74 (patch) | |
tree | 49f76f879a89c256a4f65b674086be50760bdffb | |
parent | d439404c9988df6001e4ff8bce31537e2692660e (diff) | |
download | android-28-07f9f65561c2b81bcd189b895b31bb2ad0438d74.tar.gz |
Revert "Import Android SDK Platform P [4402356]"
This reverts commit d439404c9988df6001e4ff8bce31537e2692660e.
Change-Id: I825790bdf38523800388bc1bb531cecfcd7e60bd
506 files changed, 24440 insertions, 21420 deletions
diff --git a/android/accessibilityservice/GestureDescription.java b/android/accessibilityservice/GestureDescription.java index 56f4ae2b..92567d75 100644 --- a/android/accessibilityservice/GestureDescription.java +++ b/android/accessibilityservice/GestureDescription.java @@ -428,18 +428,6 @@ public final class GestureDescription { } @Override - public String toString() { - return "TouchPoint{" - + "mStrokeId=" + mStrokeId - + ", mContinuedStrokeId=" + mContinuedStrokeId - + ", mIsStartOfPath=" + mIsStartOfPath - + ", mIsEndOfPath=" + mIsEndOfPath - + ", mX=" + mX - + ", mY=" + mY - + '}'; - } - - @Override public int describeContents() { return 0; } diff --git a/android/app/Activity.java b/android/app/Activity.java index 85f73bb7..e0ac9113 100644 --- a/android/app/Activity.java +++ b/android/app/Activity.java @@ -542,9 +542,9 @@ import java.util.List; * <ul> * <li> <p>When creating a new document, the backing database entry or file for * it is created immediately. For example, if the user chooses to write - * a new email, a new entry for that email is created as soon as they + * a new e-mail, a new entry for that e-mail is created as soon as they * start entering data, so that if they go to any other activity after - * that point this email will now appear in the list of drafts.</p> + * that point this e-mail will now appear in the list of drafts.</p> * <li> <p>When an activity's <code>onPause()</code> method is called, it should * commit to the backing content provider or file any changes the user * has made. This ensures that those changes will be seen by any other @@ -1879,7 +1879,7 @@ public class Activity extends ContextThemeWrapper if (isFinishing()) { if (mAutoFillResetNeeded) { - getAutofillManager().onActivityFinished(); + getAutofillManager().commit(); } else if (mIntent != null && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) { // Activity was launched when user tapped a link in the Autofill Save UI - since @@ -6259,8 +6259,6 @@ public class Activity extends ContextThemeWrapper final AutofillManager afm = getAutofillManager(); if (afm != null) { afm.dump(prefix, writer); - } else { - writer.print(prefix); writer.println("No AutofillManager"); } } diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java index fc4c8d7f..5e61727f 100644 --- a/android/app/ActivityManager.java +++ b/android/app/ActivityManager.java @@ -16,8 +16,14 @@ package android.app; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; + import android.Manifest; -import android.annotation.DrawableRes; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -664,6 +670,138 @@ public class ActivityManager { /** Invalid stack ID. */ public static final int INVALID_STACK_ID = -1; + /** First static stack ID. + * @hide */ + private static final int FIRST_STATIC_STACK_ID = 0; + + /** ID of stack where fullscreen activities are normally launched into. + * @hide */ + public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1; + + /** ID of stack where freeform/resized activities are normally launched into. + * @hide */ + public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1; + + /** ID of stack that occupies a dedicated region of the screen. + * @hide */ + public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1; + + /** ID of stack that always on top (always visible) when it exist. + * @hide */ + public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1; + + /** Last static stack stack ID. + * @hide */ + private static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID; + + /** Start of ID range used by stacks that are created dynamically. + * @hide */ + public static final int FIRST_DYNAMIC_STACK_ID = LAST_STATIC_STACK_ID + 1; + + // TODO: Figure-out a way to remove this. + /** @hide */ + public static boolean isStaticStack(int stackId) { + return stackId >= FIRST_STATIC_STACK_ID && stackId <= LAST_STATIC_STACK_ID; + } + + // TODO: It seems this mostly means a stack on a secondary display now. Need to see if + // there are other meanings. If not why not just use information from the display? + /** @hide */ + public static boolean isDynamicStack(int stackId) { + return stackId >= FIRST_DYNAMIC_STACK_ID; + } + + /** + * Returns true if we try to maintain focus in the current stack when the top activity + * finishes. + * @hide + */ + // TODO: Figure-out a way to remove. Probably isn't needed in the new world... + public static boolean keepFocusInStackIfPossible(int stackId) { + return stackId == FREEFORM_WORKSPACE_STACK_ID + || stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID; + } + + /** + * Returns true if the windows of tasks being moved to the target stack from the source + * stack should be replaced, meaning that window manager will keep the old window around + * until the new is ready. + * @hide + */ + public static boolean replaceWindowsOnTaskMove(int sourceStackId, int targetStackId) { + return sourceStackId == FREEFORM_WORKSPACE_STACK_ID + || targetStackId == FREEFORM_WORKSPACE_STACK_ID; + } + + /** + * Returns true if the top task in the task is allowed to return home when finished and + * there are other tasks in the stack. + * @hide + */ + public static boolean allowTopTaskToReturnHome(int stackId) { + return stackId != PINNED_STACK_ID; + } + + /** + * Returns true if the stack should be resized to match the bounds specified by + * {@link ActivityOptions#setLaunchBounds} when launching an activity into the stack. + * @hide + */ + public static boolean resizeStackWithLaunchBounds(int stackId) { + return stackId == PINNED_STACK_ID; + } + + /** + * Returns true if a window from the specified stack with {@param stackId} are normally + * fullscreen, i. e. they can become the top opaque fullscreen window, meaning that it + * controls system bars, lockscreen occluded/dismissing state, screen rotation animation, + * etc. + * @hide + */ + // TODO: What about the other side of docked stack if we move this to WindowConfiguration? + public static boolean normallyFullscreenWindows(int stackId) { + return stackId != PINNED_STACK_ID && stackId != FREEFORM_WORKSPACE_STACK_ID + && stackId != DOCKED_STACK_ID; + } + + /** Returns the stack id for the input windowing mode. + * @hide */ + // TODO: To be removed once we are not using stack id for stuff... + public static int getStackIdForWindowingMode(int windowingMode) { + switch (windowingMode) { + case WINDOWING_MODE_PINNED: return PINNED_STACK_ID; + case WINDOWING_MODE_FREEFORM: return FREEFORM_WORKSPACE_STACK_ID; + case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return DOCKED_STACK_ID; + case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return FULLSCREEN_WORKSPACE_STACK_ID; + case WINDOWING_MODE_FULLSCREEN: return FULLSCREEN_WORKSPACE_STACK_ID; + default: return INVALID_STACK_ID; + } + } + + /** Returns the windowing mode that should be used for this input stack id. + * @hide */ + // TODO: To be removed once we are not using stack id for stuff... + public static int getWindowingModeForStackId(int stackId, boolean inSplitScreenMode) { + final int windowingMode; + switch (stackId) { + case FULLSCREEN_WORKSPACE_STACK_ID: + windowingMode = inSplitScreenMode + ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_FULLSCREEN; + break; + case PINNED_STACK_ID: + windowingMode = WINDOWING_MODE_PINNED; + break; + case DOCKED_STACK_ID: + windowingMode = WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; + break; + case FREEFORM_WORKSPACE_STACK_ID: + windowingMode = WINDOWING_MODE_FREEFORM; + break; + default : + windowingMode = WINDOWING_MODE_UNDEFINED; + } + return windowingMode; + } } /** @@ -942,14 +1080,11 @@ public class ActivityManager { ATTR_TASKDESCRIPTION_PREFIX + "color"; private static final String ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND = ATTR_TASKDESCRIPTION_PREFIX + "colorBackground"; - private static final String ATTR_TASKDESCRIPTIONICON_FILENAME = + private static final String ATTR_TASKDESCRIPTIONICONFILENAME = ATTR_TASKDESCRIPTION_PREFIX + "icon_filename"; - private static final String ATTR_TASKDESCRIPTIONICON_RESOURCE = - ATTR_TASKDESCRIPTION_PREFIX + "icon_resource"; private String mLabel; private Bitmap mIcon; - private int mIconRes; private String mIconFilename; private int mColorPrimary; private int mColorBackground; @@ -963,27 +1098,9 @@ public class ActivityManager { * @param icon An icon that represents the current state of this task. * @param colorPrimary A color to override the theme's primary color. This color must be * opaque. - * @deprecated use TaskDescription constructor with icon resource instead */ - @Deprecated public TaskDescription(String label, Bitmap icon, int colorPrimary) { - this(label, icon, 0, null, colorPrimary, 0, 0, 0); - if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) { - throw new RuntimeException("A TaskDescription's primary color should be opaque"); - } - } - - /** - * Creates the TaskDescription to the specified values. - * - * @param label A label and description of the current state of this task. - * @param iconRes A drawable resource of an icon that represents the current state of this - * activity. - * @param colorPrimary A color to override the theme's primary color. This color must be - * opaque. - */ - public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) { - this(label, null, iconRes, null, colorPrimary, 0, 0, 0); + this(label, icon, null, colorPrimary, 0, 0, 0); if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) { throw new RuntimeException("A TaskDescription's primary color should be opaque"); } @@ -994,22 +1111,9 @@ public class ActivityManager { * * @param label A label and description of the current state of this activity. * @param icon An icon that represents the current state of this activity. - * @deprecated use TaskDescription constructor with icon resource instead */ - @Deprecated public TaskDescription(String label, Bitmap icon) { - this(label, icon, 0, null, 0, 0, 0, 0); - } - - /** - * Creates the TaskDescription to the specified values. - * - * @param label A label and description of the current state of this activity. - * @param iconRes A drawable resource of an icon that represents the current state of this - * activity. - */ - public TaskDescription(String label, @DrawableRes int iconRes) { - this(label, null, iconRes, null, 0, 0, 0, 0); + this(label, icon, null, 0, 0, 0, 0); } /** @@ -1018,22 +1122,21 @@ public class ActivityManager { * @param label A label and description of the current state of this activity. */ public TaskDescription(String label) { - this(label, null, 0, null, 0, 0, 0, 0); + this(label, null, null, 0, 0, 0, 0); } /** * Creates an empty TaskDescription. */ public TaskDescription() { - this(null, null, 0, null, 0, 0, 0, 0); + this(null, null, null, 0, 0, 0, 0); } /** @hide */ - public TaskDescription(String label, Bitmap bitmap, int iconRes, String iconFilename, - int colorPrimary, int colorBackground, int statusBarColor, int navigationBarColor) { + public TaskDescription(String label, Bitmap icon, String iconFilename, int colorPrimary, + int colorBackground, int statusBarColor, int navigationBarColor) { mLabel = label; - mIcon = bitmap; - mIconRes = iconRes; + mIcon = icon; mIconFilename = iconFilename; mColorPrimary = colorPrimary; mColorBackground = colorBackground; @@ -1055,7 +1158,6 @@ public class ActivityManager { public void copyFrom(TaskDescription other) { mLabel = other.mLabel; mIcon = other.mIcon; - mIconRes = other.mIconRes; mIconFilename = other.mIconFilename; mColorPrimary = other.mColorPrimary; mColorBackground = other.mColorBackground; @@ -1071,7 +1173,6 @@ public class ActivityManager { public void copyFromPreserveHiddenFields(TaskDescription other) { mLabel = other.mLabel; mIcon = other.mIcon; - mIconRes = other.mIconRes; mIconFilename = other.mIconFilename; mColorPrimary = other.mColorPrimary; if (other.mColorBackground != 0) { @@ -1144,14 +1245,6 @@ public class ActivityManager { } /** - * Sets the icon resource for this task description. - * @hide - */ - public void setIcon(int iconRes) { - mIconRes = iconRes; - } - - /** * Moves the icon bitmap reference from an actual Bitmap to a file containing the * bitmap. * @hide @@ -1179,13 +1272,6 @@ public class ActivityManager { } /** @hide */ - @TestApi - public int getIconResource() { - return mIconRes; - } - - /** @hide */ - @TestApi public String getIconFilename() { return mIconFilename; } @@ -1251,10 +1337,7 @@ public class ActivityManager { Integer.toHexString(mColorBackground)); } if (mIconFilename != null) { - out.attribute(null, ATTR_TASKDESCRIPTIONICON_FILENAME, mIconFilename); - } - if (mIconRes != 0) { - out.attribute(null, ATTR_TASKDESCRIPTIONICON_RESOURCE, Integer.toString(mIconRes)); + out.attribute(null, ATTR_TASKDESCRIPTIONICONFILENAME, mIconFilename); } } @@ -1266,10 +1349,8 @@ public class ActivityManager { setPrimaryColor((int) Long.parseLong(attrValue, 16)); } else if (ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND.equals(attrName)) { setBackgroundColor((int) Long.parseLong(attrValue, 16)); - } else if (ATTR_TASKDESCRIPTIONICON_FILENAME.equals(attrName)) { + } else if (ATTR_TASKDESCRIPTIONICONFILENAME.equals(attrName)) { setIconFilename(attrValue); - } else if (ATTR_TASKDESCRIPTIONICON_RESOURCE.equals(attrName)) { - setIcon(Integer.parseInt(attrValue, 10)); } } @@ -1292,7 +1373,6 @@ public class ActivityManager { dest.writeInt(1); mIcon.writeToParcel(dest, 0); } - dest.writeInt(mIconRes); dest.writeInt(mColorPrimary); dest.writeInt(mColorBackground); dest.writeInt(mStatusBarColor); @@ -1308,7 +1388,6 @@ public class ActivityManager { public void readFromParcel(Parcel source) { mLabel = source.readInt() > 0 ? source.readString() : null; mIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null; - mIconRes = source.readInt(); mColorPrimary = source.readInt(); mColorBackground = source.readInt(); mStatusBarColor = source.readInt(); @@ -1329,8 +1408,8 @@ public class ActivityManager { @Override public String toString() { return "TaskDescription Label: " + mLabel + " Icon: " + mIcon + - " IconRes: " + mIconRes + " IconFilename: " + mIconFilename + - " colorPrimary: " + mColorPrimary + " colorBackground: " + mColorBackground + + " IconFilename: " + mIconFilename + " colorPrimary: " + mColorPrimary + + " colorBackground: " + mColorBackground + " statusBarColor: " + mColorBackground + " navigationBarColor: " + mNavigationBarColor; } @@ -1492,6 +1571,7 @@ public class ActivityManager { } dest.writeInt(stackId); dest.writeInt(userId); + dest.writeLong(firstActiveTime); dest.writeLong(lastActiveTime); dest.writeInt(affiliatedTaskId); dest.writeInt(affiliatedTaskColor); @@ -1520,6 +1600,7 @@ public class ActivityManager { TaskDescription.CREATOR.createFromParcel(source) : null; stackId = source.readInt(); userId = source.readInt(); + firstActiveTime = source.readLong(); lastActiveTime = source.readLong(); affiliatedTaskId = source.readInt(); affiliatedTaskColor = source.readInt(); @@ -1562,6 +1643,31 @@ public class ActivityManager { public static final int RECENT_IGNORE_UNAVAILABLE = 0x0002; /** + * Provides a list that contains recent tasks for all + * profiles of a user. + * @hide + */ + public static final int RECENT_INCLUDE_PROFILES = 0x0004; + + /** + * Ignores all tasks that are on the home stack. + * @hide + */ + public static final int RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS = 0x0008; + + /** + * Ignores the top task in the docked stack. + * @hide + */ + public static final int RECENT_INGORE_DOCKED_STACK_TOP_TASK = 0x0010; + + /** + * Ignores all tasks that are on the pinned stack. + * @hide + */ + public static final int RECENT_INGORE_PINNED_STACK_TASKS = 0x0020; + + /** * <p></p>Return a list of the tasks that the user has recently launched, with * the most recent being first and older ones after in order. * @@ -1596,7 +1702,33 @@ public class ActivityManager { public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags) throws SecurityException { try { - return getService().getRecentTasks(maxNum, flags, UserHandle.myUserId()).getList(); + return getService().getRecentTasks(maxNum, + flags, UserHandle.myUserId()).getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Same as {@link #getRecentTasks(int, int)} but returns the recent tasks for a + * specific user. It requires holding + * the {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission. + * @param maxNum The maximum number of entries to return in the list. The + * actual number returned may be smaller, depending on how many tasks the + * user has started and the maximum number the system can remember. + * @param flags Information about what to return. May be any combination + * of {@link #RECENT_WITH_EXCLUDED} and {@link #RECENT_IGNORE_UNAVAILABLE}. + * + * @return Returns a list of RecentTaskInfo records describing each of + * the recent tasks. Most recently activated tasks go first. + * + * @hide + */ + public List<RecentTaskInfo> getRecentTasksForUser(int maxNum, int flags, int userId) + throws SecurityException { + try { + return getService().getRecentTasks(maxNum, + flags, userId).getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1889,6 +2021,22 @@ public class ActivityManager { } /** + * Completely remove the given task. + * + * @param taskId Identifier of the task to be removed. + * @return Returns true if the given task was found and removed. + * + * @hide + */ + public boolean removeTask(int taskId) throws SecurityException { + try { + return getService().removeTask(taskId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Sets the windowing mode for a specific task. Only works on tasks of type * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} * @param taskId The id of the task to set the windowing mode for. diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java index b62e4c2d..a68c3a5c 100644 --- a/android/app/ActivityOptions.java +++ b/android/app/ActivityOptions.java @@ -17,13 +17,13 @@ package android.app; import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.INVALID_DISPLAY; import android.annotation.Nullable; import android.annotation.TestApi; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; @@ -159,12 +159,6 @@ public class ActivityOptions { private static final String KEY_ANIM_SPECS = "android:activity.animSpecs"; /** - * Whether the activity should be launched into LockTask mode. - * @see #setLockTaskMode(boolean) - */ - private static final String KEY_LOCK_TASK_MODE = "android:activity.lockTaskMode"; - - /** * The display id the activity should be launched into. * @see #setLaunchDisplayId(int) * @hide @@ -285,7 +279,6 @@ public class ActivityOptions { private int mResultCode; private int mExitCoordinatorIndex; private PendingIntent mUsageTimeReport; - private boolean mLockTaskMode = false; private int mLaunchDisplayId = INVALID_DISPLAY; @WindowConfiguration.WindowingMode private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED; @@ -877,7 +870,6 @@ public class ActivityOptions { mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX); break; } - mLockTaskMode = opts.getBoolean(KEY_LOCK_TASK_MODE, false); mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY); mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED); mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED); @@ -1064,37 +1056,6 @@ public class ActivityOptions { } /** - * Gets whether the activity is to be launched into LockTask mode. - * @return {@code true} if the activity is to be launched into LockTask mode. - * @see Activity#startLockTask() - * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[]) - */ - public boolean getLockTaskMode() { - return mLockTaskMode; - } - - /** - * Sets whether the activity is to be launched into LockTask mode. - * - * Use this option to start an activity in LockTask mode. Note that only apps permitted by - * {@link android.app.admin.DevicePolicyManager} can run in LockTask mode. Therefore, if - * {@link android.app.admin.DevicePolicyManager#isLockTaskPermitted(String)} returns - * {@code false} for the package of the target activity, a {@link SecurityException} will be - * thrown during {@link Context#startActivity(Intent, Bundle)}. - * - * Defaults to {@code false} if not set. - * - * @param lockTaskMode {@code true} if the activity is to be launched into LockTask mode. - * @return {@code this} {@link ActivityOptions} instance. - * @see Activity#startLockTask() - * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[]) - */ - public ActivityOptions setLockTaskMode(boolean lockTaskMode) { - mLockTaskMode = lockTaskMode; - return this; - } - - /** * Gets the id of the display where activity should be launched. * @return The id of the display where activity should be launched, * {@link android.view.Display#INVALID_DISPLAY} if not set. @@ -1287,7 +1248,6 @@ public class ActivityOptions { mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex; break; } - mLockTaskMode = otherOptions.mLockTaskMode; mAnimSpecs = otherOptions.mAnimSpecs; mAnimationFinishedListener = otherOptions.mAnimationFinishedListener; mSpecsFuture = otherOptions.mSpecsFuture; @@ -1362,7 +1322,6 @@ public class ActivityOptions { b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex); break; } - b.putBoolean(KEY_LOCK_TASK_MODE, mLockTaskMode); b.putInt(KEY_LAUNCH_DISPLAY_ID, mLaunchDisplayId); b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode); b.putInt(KEY_LAUNCH_ACTIVITY_TYPE, mLaunchActivityType); diff --git a/android/app/KeyguardManager.java b/android/app/KeyguardManager.java index 1fe29004..54f74b15 100644 --- a/android/app/KeyguardManager.java +++ b/android/app/KeyguardManager.java @@ -387,6 +387,8 @@ public class KeyguardManager { * such as the Home key and the right soft keys, don't work. * * @return true if in keyguard restricted input mode. + * + * @see android.view.WindowManagerPolicy#inKeyguardRestrictedKeyInputMode */ public boolean inKeyguardRestrictedInputMode() { try { diff --git a/android/app/NotificationChannel.java b/android/app/NotificationChannel.java index c06ad3f3..47063f08 100644 --- a/android/app/NotificationChannel.java +++ b/android/app/NotificationChannel.java @@ -32,8 +32,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.util.Preconditions; -import com.android.internal.util.Preconditions; - import org.json.JSONException; import org.json.JSONObject; import org.xmlpull.v1.XmlPullParser; diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java index a52dc1e4..eb52cb7f 100644 --- a/android/app/NotificationManager.java +++ b/android/app/NotificationManager.java @@ -934,14 +934,8 @@ public class NotificationManager { public static final int PRIORITY_CATEGORY_CALLS = 1 << 3; /** Calls from repeat callers are prioritized. */ public static final int PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 4; - /** Alarms are prioritized */ - public static final int PRIORITY_CATEGORY_ALARMS = 1 << 5; - /** Media, system, game (catch-all for non-never suppressible sounds) are prioritized */ - public static final int PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER = 1 << 6; private static final int[] ALL_PRIORITY_CATEGORIES = { - PRIORITY_CATEGORY_ALARMS, - PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER, PRIORITY_CATEGORY_REMINDERS, PRIORITY_CATEGORY_EVENTS, PRIORITY_CATEGORY_MESSAGES, @@ -1141,9 +1135,6 @@ public class NotificationManager { case PRIORITY_CATEGORY_MESSAGES: return "PRIORITY_CATEGORY_MESSAGES"; case PRIORITY_CATEGORY_CALLS: return "PRIORITY_CATEGORY_CALLS"; case PRIORITY_CATEGORY_REPEAT_CALLERS: return "PRIORITY_CATEGORY_REPEAT_CALLERS"; - case PRIORITY_CATEGORY_ALARMS: return "PRIORITY_CATEGORY_ALARMS"; - case PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER: - return "PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER"; default: return "PRIORITY_CATEGORY_UNKNOWN_" + priorityCategory; } } diff --git a/android/app/StatusBarManager.java b/android/app/StatusBarManager.java index 23c4166d..8987bc02 100644 --- a/android/app/StatusBarManager.java +++ b/android/app/StatusBarManager.java @@ -73,16 +73,15 @@ public class StatusBarManager { public static final int DISABLE2_QUICK_SETTINGS = 1; public static final int DISABLE2_SYSTEM_ICONS = 1 << 1; public static final int DISABLE2_NOTIFICATION_SHADE = 1 << 2; - public static final int DISABLE2_GLOBAL_ACTIONS = 1 << 3; public static final int DISABLE2_NONE = 0x00000000; public static final int DISABLE2_MASK = DISABLE2_QUICK_SETTINGS | DISABLE2_SYSTEM_ICONS - | DISABLE2_NOTIFICATION_SHADE | DISABLE2_GLOBAL_ACTIONS; + | DISABLE2_NOTIFICATION_SHADE; @IntDef(flag = true, value = {DISABLE2_NONE, DISABLE2_MASK, DISABLE2_QUICK_SETTINGS, DISABLE2_SYSTEM_ICONS, - DISABLE2_NOTIFICATION_SHADE, DISABLE2_GLOBAL_ACTIONS}) + DISABLE2_NOTIFICATION_SHADE}) @Retention(RetentionPolicy.SOURCE) public @interface Disable2Flags {} diff --git a/android/app/TaskStackListener.java b/android/app/TaskStackListener.java index 895d12a7..402e2095 100644 --- a/android/app/TaskStackListener.java +++ b/android/app/TaskStackListener.java @@ -77,7 +77,7 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub { } @Override - public void onTaskRemovalStarted(int taskId) throws RemoteException { + public void onTaskRemovalStarted(int taskId) { } @Override @@ -91,10 +91,11 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub { } @Override - public void onTaskProfileLocked(int taskId, int userId) throws RemoteException { + public void onTaskProfileLocked(int taskId, int userId) { } @Override - public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) throws RemoteException { + public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) + throws RemoteException { } } diff --git a/android/app/WallpaperManager.java b/android/app/WallpaperManager.java index 081bd814..942cc995 100644 --- a/android/app/WallpaperManager.java +++ b/android/app/WallpaperManager.java @@ -388,12 +388,11 @@ public class WallpaperManager { public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault, @SetWallpaperFlags int which) { - return peekWallpaperBitmap(context, returnDefault, which, context.getUserId(), - false /* hardware */); + return peekWallpaperBitmap(context, returnDefault, which, context.getUserId()); } public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault, - @SetWallpaperFlags int which, int userId, boolean hardware) { + @SetWallpaperFlags int which, int userId) { if (mService != null) { try { if (!mService.isWallpaperSupported(context.getOpPackageName())) { @@ -410,7 +409,7 @@ public class WallpaperManager { mCachedWallpaper = null; mCachedWallpaperUserId = 0; try { - mCachedWallpaper = getCurrentWallpaperLocked(context, userId, hardware); + mCachedWallpaper = getCurrentWallpaperLocked(context, userId); mCachedWallpaperUserId = userId; } catch (OutOfMemoryError e) { Log.w(TAG, "Out of memory loading the current wallpaper: " + e); @@ -448,7 +447,7 @@ public class WallpaperManager { } } - private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware) { + private Bitmap getCurrentWallpaperLocked(Context context, int userId) { if (mService == null) { Log.w(TAG, "WallpaperService not running"); return null; @@ -461,9 +460,6 @@ public class WallpaperManager { if (fd != null) { try { BitmapFactory.Options options = new BitmapFactory.Options(); - if (hardware) { - options.inPreferredConfig = Bitmap.Config.HARDWARE; - } return BitmapFactory.decodeFileDescriptor( fd.getFileDescriptor(), null, options); } catch (OutOfMemoryError e) { @@ -818,23 +814,12 @@ public class WallpaperManager { } /** - * Like {@link #getDrawable()} but returns a Bitmap with default {@link Bitmap.Config}. - * - * @hide - */ - public Bitmap getBitmap() { - return getBitmap(false); - } - - /** * Like {@link #getDrawable()} but returns a Bitmap. * - * @param hardware Asks for a hardware backed bitmap. - * @see Bitmap.Config#HARDWARE * @hide */ - public Bitmap getBitmap(boolean hardware) { - return getBitmapAsUser(mContext.getUserId(), hardware); + public Bitmap getBitmap() { + return getBitmapAsUser(mContext.getUserId()); } /** @@ -842,8 +827,8 @@ public class WallpaperManager { * * @hide */ - public Bitmap getBitmapAsUser(int userId, boolean hardware) { - return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId, hardware); + public Bitmap getBitmapAsUser(int userId) { + return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId); } /** diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java index 251863ca..6b405384 100644 --- a/android/app/WindowConfiguration.java +++ b/android/app/WindowConfiguration.java @@ -511,8 +511,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED; } - /** @hide */ - public static String windowingModeToString(@WindowingMode int windowingMode) { + private static String windowingModeToString(@WindowingMode int windowingMode) { switch (windowingMode) { case WINDOWING_MODE_UNDEFINED: return "undefined"; case WINDOWING_MODE_FULLSCREEN: return "fullscreen"; diff --git a/android/app/assist/AssistStructure.java b/android/app/assist/AssistStructure.java index e491a4f9..d9b7cd7e 100644 --- a/android/app/assist/AssistStructure.java +++ b/android/app/assist/AssistStructure.java @@ -616,9 +616,6 @@ public class AssistStructure implements Parcelable { CharSequence[] mAutofillOptions; boolean mSanitized; HtmlInfo mHtmlInfo; - int mMinEms = -1; - int mMaxEms = -1; - int mMaxLength = -1; // POJO used to override some autofill-related values when the node is parcelized. // Not written to parcel. @@ -716,9 +713,6 @@ public class AssistStructure implements Parcelable { if (p instanceof HtmlInfo) { mHtmlInfo = (HtmlInfo) p; } - mMinEms = in.readInt(); - mMaxEms = in.readInt(); - mMaxLength = in.readInt(); } if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) { mX = in.readInt(); @@ -882,9 +876,6 @@ public class AssistStructure implements Parcelable { } else { out.writeParcelable(null, 0); } - out.writeInt(mMinEms); - out.writeInt(mMaxEms); - out.writeInt(mMaxLength); } if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) { out.writeInt(mX); @@ -1453,39 +1444,6 @@ public class AssistStructure implements Parcelable { public ViewNode getChildAt(int index) { return mChildren[index]; } - - /** - * Returns the minimum width in ems of the text associated with this node, or {@code -1} - * if not supported by the node. - * - * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes, - * not for assist purposes. - */ - public int getMinTextEms() { - return mMinEms; - } - - /** - * Returns the maximum width in ems of the text associated with this node, or {@code -1} - * if not supported by the node. - * - * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes, - * not for assist purposes. - */ - public int getMaxTextEms() { - return mMaxEms; - } - - /** - * Returns the maximum length of the text associated with this node node, or {@code -1} - * if not supported by the node or not set. - * - * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes, - * not for assist purposes. - */ - public int getMaxTextLength() { - return mMaxLength; - } } /** @@ -1818,21 +1776,6 @@ public class AssistStructure implements Parcelable { } @Override - public void setMinTextEms(int minEms) { - mNode.mMinEms = minEms; - } - - @Override - public void setMaxTextEms(int maxEms) { - mNode.mMaxEms = maxEms; - } - - @Override - public void setMaxTextLength(int maxLength) { - mNode.mMaxLength = maxLength; - } - - @Override public void setDataIsSensitive(boolean sensitive) { mNode.mSanitized = !sensitive; } diff --git a/android/app/job/JobScheduler.java b/android/app/job/JobScheduler.java index 0deb2e13..3868439f 100644 --- a/android/app/job/JobScheduler.java +++ b/android/app/job/JobScheduler.java @@ -24,6 +24,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.ClipData; import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.os.PersistableBundle; @@ -39,18 +40,16 @@ import java.util.List; * and how to construct them. You will construct these JobInfo objects and pass them to the * JobScheduler with {@link #schedule(JobInfo)}. When the criteria declared are met, the * system will execute this job on your application's {@link android.app.job.JobService}. - * You identify the service component that implements the logic for your job when you - * construct the JobInfo using + * You identify which JobService is meant to execute the logic for your job when you create the + * JobInfo with * {@link android.app.job.JobInfo.Builder#JobInfo.Builder(int,android.content.ComponentName)}. * </p> * <p> - * The framework will be intelligent about when it executes jobs, and attempt to batch - * and defer them as much as possible. Typically if you don't specify a deadline on a job, it - * can be run at any moment depending on the current state of the JobScheduler's internal queue. - * <p> - * While a job is running, the system holds a wakelock on behalf of your app. For this reason, - * you do not need to take any action to guarantee that the device stays awake for the - * duration of the job. + * The framework will be intelligent about when you receive your callbacks, and attempt to batch + * and defer them as much as possible. Typically if you don't specify a deadline on your job, it + * can be run at any moment depending on the current state of the JobScheduler's internal queue, + * however it might be deferred as long as until the next time the device is connected to a power + * source. * </p> * <p>You do not * instantiate this class directly; instead, retrieve it through @@ -142,34 +141,30 @@ public abstract class JobScheduler { int userId, String tag); /** - * Cancel the specified job. If the job is currently executing, it is stopped - * immediately and the return value from its {@link JobService#onStopJob(JobParameters)} - * method is ignored. - * - * @param jobId unique identifier for the job to be canceled, as supplied to - * {@link JobInfo.Builder#JobInfo.Builder(int, android.content.ComponentName) - * JobInfo.Builder(int, android.content.ComponentName)}. + * Cancel a job that is pending in the JobScheduler. + * @param jobId unique identifier for this job. Obtain this value from the jobs returned by + * {@link #getAllPendingJobs()}. */ public abstract void cancel(int jobId); /** - * Cancel <em>all</em> jobs that have been scheduled by the calling application. + * Cancel all jobs that have been registered with the JobScheduler by this package. */ public abstract void cancelAll(); /** - * Retrieve all jobs that have been scheduled by the calling application. + * Retrieve all jobs for this package that are pending in the JobScheduler. * - * @return a list of all of the app's scheduled jobs. This includes jobs that are - * currently started as well as those that are still waiting to run. + * @return a list of all the jobs registered by this package that have not + * yet been executed. */ public abstract @NonNull List<JobInfo> getAllPendingJobs(); /** - * Look up the description of a scheduled job. + * Retrieve a specific job for this package that is pending in the + * JobScheduler. * - * @return The {@link JobInfo} description of the given scheduled job, or {@code null} - * if the supplied job ID does not correspond to any job. + * @return job registered by this package that has not yet been executed. */ public abstract @Nullable JobInfo getPendingJob(int jobId); } diff --git a/android/app/job/JobService.java b/android/app/job/JobService.java index 69afed20..9096b47b 100644 --- a/android/app/job/JobService.java +++ b/android/app/job/JobService.java @@ -18,7 +18,16 @@ package android.app.job; import android.app.Service; import android.content.Intent; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.lang.ref.WeakReference; /** * <p>Entry point for the callback from the {@link android.app.job.JobScheduler}.</p> @@ -46,7 +55,7 @@ public abstract class JobService extends Service { * </pre> * * <p>If a job service is declared in the manifest but not protected with this - * permission, that service will be ignored by the system. + * permission, that service will be ignored by the OS. */ public static final String PERMISSION_BIND = "android.permission.BIND_JOB_SERVICE"; @@ -72,36 +81,14 @@ public abstract class JobService extends Service { } /** - * Called to indicate that the job has begun executing. Override this method with the - * logic for your job. Like all other component lifecycle callbacks, this method executes - * on your application's main thread. - * <p> - * Return {@code true} from this method if your job needs to continue running. If you - * do this, the job remains active until you call - * {@link #jobFinished(JobParameters, boolean)} to tell the system that it has completed - * its work, or until the job's required constraints are no longer satisfied. For - * example, if the job was scheduled using - * {@link JobInfo.Builder#setRequiresCharging(boolean) setRequiresCharging(true)}, - * it will be immediately halted by the system if the user unplugs the device from power, - * the job's {@link #onStopJob(JobParameters)} callback will be invoked, and the app - * will be expected to shut down all ongoing work connected with that job. - * <p> - * The system holds a wakelock on behalf of your app as long as your job is executing. - * This wakelock is acquired before this method is invoked, and is not released until either - * you call {@link #jobFinished(JobParameters, boolean)}, or after the system invokes - * {@link #onStopJob(JobParameters)} to notify your job that it is being shut down - * prematurely. - * <p> - * Returning {@code false} from this method means your job is already finished. The - * system's wakelock for the job will be released, and {@link #onStopJob(JobParameters)} - * will not be invoked. + * Override this method with the callback logic for your job. Any such logic needs to be + * performed on a separate thread, as this function is executed on your application's main + * thread. * - * @param params Parameters specifying info about this job, including the optional - * extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle). - * This object serves to identify this specific running job instance when calling - * {@link #jobFinished(JobParameters, boolean)}. - * @return {@code true} if your service will continue running, using a separate thread - * when appropriate. {@code false} means that this job has completed its work. + * @param params Parameters specifying info about this job, including the extras bundle you + * optionally provided at job-creation time. + * @return True if your service needs to process the work (on a separate thread). False if + * there's no more work to be done for this job. */ public abstract boolean onStartJob(JobParameters params); @@ -114,44 +101,37 @@ public abstract class JobService extends Service { * {@link android.app.job.JobInfo.Builder#setRequiredNetworkType(int)}, yet while your * job was executing the user toggled WiFi. Another example is if you had specified * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its - * idle maintenance window. You are solely responsible for the behavior of your application - * upon receipt of this message; your app will likely start to misbehave if you ignore it. - * <p> - * Once this method returns, the system releases the wakelock that it is holding on - * behalf of the job.</p> + * idle maintenance window. You are solely responsible for the behaviour of your application + * upon receipt of this message; your app will likely start to misbehave if you ignore it. One + * immediate repercussion is that the system will cease holding a wakelock for you.</p> * - * @param params The parameters identifying this job, as supplied to - * the job in the {@link #onStartJob(JobParameters)} callback. - * @return {@code true} to indicate to the JobManager whether you'd like to reschedule - * this job based on the retry criteria provided at job creation-time; or {@code false} - * to end the job entirely. Regardless of the value returned, your job must stop executing. + * @param params Parameters specifying info about this job. + * @return True to indicate to the JobManager whether you'd like to reschedule this job based + * on the retry criteria provided at job creation-time. False to drop the job. Regardless of + * the value returned, your job must stop executing. */ public abstract boolean onStopJob(JobParameters params); /** - * Call this to inform the JobScheduler that the job has finished its work. When the - * system receives this message, it releases the wakelock being held for the job. + * Call this to inform the JobManager you've finished executing. This can be called from any + * thread, as it will ultimately be run on your application's main thread. When the system + * receives this message it will release the wakelock being held. * <p> - * You can request that the job be scheduled again by passing {@code true} as - * the <code>wantsReschedule</code> parameter. This will apply back-off policy - * for the job; this policy can be adjusted through the - * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)} method - * when the job is originally scheduled. The job's initial - * requirements are preserved when jobs are rescheduled, regardless of backed-off - * policy. - * <p class="note"> - * A job running while the device is dozing will not be rescheduled with the normal back-off - * policy. Instead, the job will be re-added to the queue and executed again during - * a future idle maintenance window. + * You can specify post-execution behaviour to the scheduler here with + * <code>needsReschedule </code>. This will apply a back-off timer to your job based on + * the default, or what was set with + * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)}. The original + * requirements are always honoured even for a backed-off job. Note that a job running in + * idle mode will not be backed-off. Instead what will happen is the job will be re-added + * to the queue and re-executed within a future idle maintenance window. * </p> * - * @param params The parameters identifying this job, as supplied to - * the job in the {@link #onStartJob(JobParameters)} callback. - * @param wantsReschedule {@code true} if this job should be rescheduled according - * to the back-off criteria specified when it was first scheduled; {@code false} - * otherwise. + * @param params Parameters specifying system-provided info about this job, this was given to + * your application in {@link #onStartJob(JobParameters)}. + * @param needsReschedule True if this job should be rescheduled according to the back-off + * criteria specified at schedule-time. False otherwise. */ - public final void jobFinished(JobParameters params, boolean wantsReschedule) { - mEngine.jobFinished(params, wantsReschedule); + public final void jobFinished(JobParameters params, boolean needsReschedule) { + mEngine.jobFinished(params, needsReschedule); } -} +}
\ No newline at end of file diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java index fd579fce..051dccbd 100644 --- a/android/app/usage/UsageStatsManager.java +++ b/android/app/usage/UsageStatsManager.java @@ -48,10 +48,10 @@ import java.util.Map; * </pre> * A request for data in the middle of a time interval will include that interval. * <p/> - * <b>NOTE:</b> This API requires the permission android.permission.PACKAGE_USAGE_STATS. - * However, declaring the permission implies intention to use the API and the user of the device - * still needs to grant permission through the Settings application. - * See {@link android.provider.Settings#ACTION_USAGE_ACCESS_SETTINGS} + * <b>NOTE:</b> This API requires the permission android.permission.PACKAGE_USAGE_STATS, which + * is a system-level permission and will not be granted to third-party apps. However, declaring + * the permission implies intention to use the API and the user of the device can grant permission + * through the Settings application. */ @SystemService(Context.USAGE_STATS_SERVICE) public final class UsageStatsManager { @@ -122,7 +122,7 @@ public final class UsageStatsManager { * @param intervalType The time interval by which the stats are aggregated. * @param beginTime The inclusive beginning of the range of stats to include in the results. * @param endTime The exclusive end of the range of stats to include in the results. - * @return A list of {@link UsageStats} + * @return A list of {@link UsageStats} or null if none are available. * * @see #INTERVAL_DAILY * @see #INTERVAL_WEEKLY @@ -139,7 +139,7 @@ public final class UsageStatsManager { return slice.getList(); } } catch (RemoteException e) { - // fallthrough and return the empty list. + // fallthrough and return null. } return Collections.emptyList(); } @@ -152,7 +152,7 @@ public final class UsageStatsManager { * @param intervalType The time interval by which the stats are aggregated. * @param beginTime The inclusive beginning of the range of stats to include in the results. * @param endTime The exclusive end of the range of stats to include in the results. - * @return A list of {@link ConfigurationStats} + * @return A list of {@link ConfigurationStats} or null if none are available. */ public List<ConfigurationStats> queryConfigurations(int intervalType, long beginTime, long endTime) { @@ -185,7 +185,7 @@ public final class UsageStatsManager { return iter; } } catch (RemoteException e) { - // fallthrough and return empty result. + // fallthrough and return null } return sEmptyResults; } @@ -197,7 +197,8 @@ public final class UsageStatsManager { * * @param beginTime The inclusive beginning of the range of stats to include in the results. * @param endTime The exclusive end of the range of stats to include in the results. - * @return A {@link java.util.Map} keyed by package name + * @return A {@link java.util.Map} keyed by package name, or null if no stats are + * available. */ public Map<String, UsageStats> queryAndAggregateUsageStats(long beginTime, long endTime) { List<UsageStats> stats = queryUsageStats(INTERVAL_BEST, beginTime, endTime); diff --git a/android/arch/lifecycle/ActivityFullLifecycleTest.java b/android/arch/lifecycle/ActivityFullLifecycleTest.java index 78dd0150..ee4e661a 100644 --- a/android/arch/lifecycle/ActivityFullLifecycleTest.java +++ b/android/arch/lifecycle/ActivityFullLifecycleTest.java @@ -16,43 +16,48 @@ package android.arch.lifecycle; -import static android.arch.lifecycle.TestUtils.OrderedTuples.CREATE; -import static android.arch.lifecycle.TestUtils.OrderedTuples.DESTROY; -import static android.arch.lifecycle.TestUtils.OrderedTuples.PAUSE; -import static android.arch.lifecycle.TestUtils.OrderedTuples.RESUME; -import static android.arch.lifecycle.TestUtils.OrderedTuples.START; -import static android.arch.lifecycle.TestUtils.OrderedTuples.STOP; -import static android.arch.lifecycle.TestUtils.flatMap; +import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE; +import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY; +import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE; +import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME; +import static android.arch.lifecycle.Lifecycle.Event.ON_START; +import static android.arch.lifecycle.Lifecycle.Event.ON_STOP; +import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK; +import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import android.app.Activity; import android.arch.lifecycle.Lifecycle.Event; -import android.arch.lifecycle.testapp.CollectingLifecycleOwner; -import android.arch.lifecycle.testapp.CollectingSupportActivity; +import android.arch.lifecycle.testapp.CollectingActivity; import android.arch.lifecycle.testapp.FrameworkLifecycleRegistryActivity; +import android.arch.lifecycle.testapp.FullLifecycleTestActivity; +import android.arch.lifecycle.testapp.SupportLifecycleRegistryActivity; import android.arch.lifecycle.testapp.TestEvent; import android.support.test.filters.SmallTest; import android.support.test.rule.ActivityTestRule; -import android.support.v4.util.Pair; +import android.util.Pair; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import java.util.ArrayList; import java.util.List; @SmallTest @RunWith(Parameterized.class) public class ActivityFullLifecycleTest { @Rule - public final ActivityTestRule<? extends CollectingLifecycleOwner> activityTestRule; + public ActivityTestRule activityTestRule = + new ActivityTestRule<>(FullLifecycleTestActivity.class); @Parameterized.Parameters public static Class[] params() { - return new Class[]{CollectingSupportActivity.class, + return new Class[]{FullLifecycleTestActivity.class, + SupportLifecycleRegistryActivity.class, FrameworkLifecycleRegistryActivity.class}; } @@ -63,13 +68,28 @@ public class ActivityFullLifecycleTest { @Test - public void testFullLifecycle() throws Throwable { - CollectingLifecycleOwner owner = activityTestRule.getActivity(); - TestUtils.waitTillResumed(owner, activityTestRule); - activityTestRule.finishActivity(); + public void testFullLifecycle() throws InterruptedException { + Activity activity = activityTestRule.getActivity(); + List<Pair<TestEvent, Event>> results = ((CollectingActivity) activity) + .waitForCollectedEvents(); - TestUtils.waitTillDestroyed(owner, activityTestRule); - List<Pair<TestEvent, Event>> results = owner.copyCollectedEvents(); - assertThat(results, is(flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY))); + Event[] expectedEvents = + new Event[]{ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY}; + + List<Pair<TestEvent, Event>> expected = new ArrayList<>(); + boolean beforeResume = true; + for (Event i : expectedEvents) { + if (beforeResume) { + expected.add(new Pair<>(ACTIVITY_CALLBACK, i)); + expected.add(new Pair<>(LIFECYCLE_EVENT, i)); + } else { + expected.add(new Pair<>(LIFECYCLE_EVENT, i)); + expected.add(new Pair<>(ACTIVITY_CALLBACK, i)); + } + if (i == ON_RESUME) { + beforeResume = false; + } + } + assertThat(results, is(expected)); } } diff --git a/android/arch/lifecycle/AndroidViewModel.java b/android/arch/lifecycle/AndroidViewModel.java index 106b2ef0..2c7e1739 100644 --- a/android/arch/lifecycle/AndroidViewModel.java +++ b/android/arch/lifecycle/AndroidViewModel.java @@ -16,9 +16,7 @@ package android.arch.lifecycle; -import android.annotation.SuppressLint; import android.app.Application; -import android.support.annotation.NonNull; /** * Application context aware {@link ViewModel}. @@ -27,19 +25,16 @@ import android.support.annotation.NonNull; * <p> */ public class AndroidViewModel extends ViewModel { - @SuppressLint("StaticFieldLeak") private Application mApplication; - public AndroidViewModel(@NonNull Application application) { + public AndroidViewModel(Application application) { mApplication = application; } /** * Return the application. */ - @NonNull public <T extends Application> T getApplication() { - //noinspection unchecked return (T) mApplication; } } diff --git a/android/arch/lifecycle/ClassesInfoCache.java b/android/arch/lifecycle/ClassesInfoCache.java index d88e2762..f077daed 100644 --- a/android/arch/lifecycle/ClassesInfoCache.java +++ b/android/arch/lifecycle/ClassesInfoCache.java @@ -46,7 +46,7 @@ class ClassesInfoCache { return mHasLifecycleMethods.get(klass); } - Method[] methods = getDeclaredMethods(klass); + Method[] methods = klass.getDeclaredMethods(); for (Method method : methods) { OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class); if (annotation != null) { @@ -64,18 +64,6 @@ class ClassesInfoCache { return false; } - private Method[] getDeclaredMethods(Class klass) { - try { - return klass.getDeclaredMethods(); - } catch (NoClassDefFoundError e) { - throw new IllegalArgumentException("The observer class has some methods that use " - + "newer APIs which are not available in the current OS version. Lifecycles " - + "cannot access even other methods so you should make sure that your " - + "observer classes only access framework classes that are available " - + "in your min API level OR use lifecycle:compiler annotation processor.", e); - } - } - CallbackInfo getInfo(Class klass) { CallbackInfo existing = mCallbackMap.get(klass); if (existing != null) { @@ -118,7 +106,7 @@ class ClassesInfoCache { } } - Method[] methods = declaredMethods != null ? declaredMethods : getDeclaredMethods(klass); + Method[] methods = declaredMethods != null ? declaredMethods : klass.getDeclaredMethods(); boolean hasLifecycleMethods = false; for (Method method : methods) { OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class); diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java index 1ddcb1a9..f1352446 100644 --- a/android/arch/lifecycle/ComputableLiveData.java +++ b/android/arch/lifecycle/ComputableLiveData.java @@ -1,136 +1,9 @@ -/* - * Copyright (C) 2017 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. - */ - +//ComputableLiveData interface for tests package android.arch.lifecycle; - -import android.arch.core.executor.ArchTaskExecutor; -import android.support.annotation.MainThread; -import android.support.annotation.NonNull; -import android.support.annotation.RestrictTo; -import android.support.annotation.VisibleForTesting; -import android.support.annotation.WorkerThread; - -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * A LiveData class that can be invalidated & computed on demand. - * <p> - * This is an internal class for now, might be public if we see the necessity. - * - * @param <T> The type of the live data - * @hide internal - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +import android.arch.lifecycle.LiveData; public abstract class ComputableLiveData<T> { - - private final LiveData<T> mLiveData; - - private AtomicBoolean mInvalid = new AtomicBoolean(true); - private AtomicBoolean mComputing = new AtomicBoolean(false); - - /** - * Creates a computable live data which is computed when there are active observers. - * <p> - * It can also be invalidated via {@link #invalidate()} which will result in a call to - * {@link #compute()} if there are active observers (or when they start observing) - */ - @SuppressWarnings("WeakerAccess") - public ComputableLiveData() { - mLiveData = new LiveData<T>() { - @Override - protected void onActive() { - // TODO if we make this class public, we should accept an executor - ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable); - } - }; - } - - /** - * Returns the LiveData managed by this class. - * - * @return A LiveData that is controlled by ComputableLiveData. - */ - @SuppressWarnings("WeakerAccess") - @NonNull - public LiveData<T> getLiveData() { - return mLiveData; - } - - @VisibleForTesting - final Runnable mRefreshRunnable = new Runnable() { - @WorkerThread - @Override - public void run() { - boolean computed; - do { - computed = false; - // compute can happen only in 1 thread but no reason to lock others. - if (mComputing.compareAndSet(false, true)) { - // as long as it is invalid, keep computing. - try { - T value = null; - while (mInvalid.compareAndSet(true, false)) { - computed = true; - value = compute(); - } - if (computed) { - mLiveData.postValue(value); - } - } finally { - // release compute lock - mComputing.set(false); - } - } - // check invalid after releasing compute lock to avoid the following scenario. - // Thread A runs compute() - // Thread A checks invalid, it is false - // Main thread sets invalid to true - // Thread B runs, fails to acquire compute lock and skips - // Thread A releases compute lock - // We've left invalid in set state. The check below recovers. - } while (computed && mInvalid.get()); - } - }; - - // invalidation check always happens on the main thread - @VisibleForTesting - final Runnable mInvalidationRunnable = new Runnable() { - @MainThread - @Override - public void run() { - boolean isActive = mLiveData.hasActiveObservers(); - if (mInvalid.compareAndSet(false, true)) { - if (isActive) { - // TODO if we make this class public, we should accept an executor. - ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable); - } - } - } - }; - - /** - * Invalidates the LiveData. - * <p> - * When there are active observers, this will trigger a call to {@link #compute()}. - */ - public void invalidate() { - ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable); - } - - @SuppressWarnings("WeakerAccess") - @WorkerThread - protected abstract T compute(); + public ComputableLiveData(){} + abstract protected T compute(); + public LiveData<T> getLiveData() {return null;} + public void invalidate() {} } diff --git a/android/arch/lifecycle/DispatcherActivityCallbackTest.java b/android/arch/lifecycle/DispatcherActivityCallbackTest.java new file mode 100644 index 00000000..86b25b60 --- /dev/null +++ b/android/arch/lifecycle/DispatcherActivityCallbackTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.arch.lifecycle; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class DispatcherActivityCallbackTest { + @Test + public void onCreateFrameworkActivity() { + LifecycleDispatcher.DispatcherActivityCallback callback = + new LifecycleDispatcher.DispatcherActivityCallback(); + Activity activity = mock(Activity.class); + checkReportFragment(callback, activity); + } + + @Test + public void onCreateFragmentActivity() { + LifecycleDispatcher.DispatcherActivityCallback callback = + new LifecycleDispatcher.DispatcherActivityCallback(); + FragmentActivity activity = mock(FragmentActivity.class); + FragmentManager fragmentManager = mock(FragmentManager.class); + when(activity.getSupportFragmentManager()).thenReturn(fragmentManager); + + checkReportFragment(callback, activity); + + verify(activity).getSupportFragmentManager(); + verify(fragmentManager).registerFragmentLifecycleCallbacks( + any(FragmentManager.FragmentLifecycleCallbacks.class), eq(true)); + } + + @SuppressLint("CommitTransaction") + private void checkReportFragment(LifecycleDispatcher.DispatcherActivityCallback callback, + Activity activity) { + android.app.FragmentManager fm = mock(android.app.FragmentManager.class); + FragmentTransaction transaction = mock(FragmentTransaction.class); + when(activity.getFragmentManager()).thenReturn(fm); + when(fm.beginTransaction()).thenReturn(transaction); + when(transaction.add(any(Fragment.class), anyString())).thenReturn(transaction); + callback.onActivityCreated(activity, mock(Bundle.class)); + verify(activity).getFragmentManager(); + verify(fm).beginTransaction(); + verify(transaction).add(any(ReportFragment.class), anyString()); + verify(transaction).commit(); + } +} diff --git a/android/arch/lifecycle/Lifecycle.java b/android/arch/lifecycle/Lifecycle.java index c0a2090c..02db5ff9 100644 --- a/android/arch/lifecycle/Lifecycle.java +++ b/android/arch/lifecycle/Lifecycle.java @@ -17,7 +17,6 @@ package android.arch.lifecycle; import android.support.annotation.MainThread; -import android.support.annotation.NonNull; /** * Defines an object that has an Android Lifecycle. {@link android.support.v4.app.Fragment Fragment} @@ -84,7 +83,7 @@ public abstract class Lifecycle { * @param observer The observer to notify. */ @MainThread - public abstract void addObserver(@NonNull LifecycleObserver observer); + public abstract void addObserver(LifecycleObserver observer); /** * Removes the given observer from the observers list. @@ -100,7 +99,7 @@ public abstract class Lifecycle { * @param observer The observer to be removed. */ @MainThread - public abstract void removeObserver(@NonNull LifecycleObserver observer); + public abstract void removeObserver(LifecycleObserver observer); /** * Returns the current state of the Lifecycle. @@ -194,7 +193,7 @@ public abstract class Lifecycle { * @param state State to compare with * @return true if this State is greater or equal to the given {@code state} */ - public boolean isAtLeast(@NonNull State state) { + public boolean isAtLeast(State state) { return compareTo(state) >= 0; } } diff --git a/android/arch/lifecycle/LifecycleDispatcher.java b/android/arch/lifecycle/LifecycleDispatcher.java new file mode 100644 index 00000000..9fdec959 --- /dev/null +++ b/android/arch/lifecycle/LifecycleDispatcher.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.arch.lifecycle; + +import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE; +import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY; +import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE; +import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME; +import static android.arch.lifecycle.Lifecycle.Event.ON_START; +import static android.arch.lifecycle.Lifecycle.Event.ON_STOP; +import static android.arch.lifecycle.Lifecycle.State.CREATED; + +import android.app.Activity; +import android.app.Application; +import android.arch.lifecycle.Lifecycle.State; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.VisibleForTesting; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; + +import java.util.Collection; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * When initialized, it hooks into the Activity callback of the Application and observes + * Activities. It is responsible to hook in child-fragments to activities and fragments to report + * their lifecycle events. Another responsibility of this class is to mark as stopped all lifecycle + * providers related to an activity as soon it is not safe to run a fragment transaction in this + * activity. + */ +class LifecycleDispatcher { + + private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle" + + ".LifecycleDispatcher.report_fragment_tag"; + + private static AtomicBoolean sInitialized = new AtomicBoolean(false); + + static void init(Context context) { + if (sInitialized.getAndSet(true)) { + return; + } + ((Application) context.getApplicationContext()) + .registerActivityLifecycleCallbacks(new DispatcherActivityCallback()); + } + + @SuppressWarnings("WeakerAccess") + @VisibleForTesting + static class DispatcherActivityCallback extends EmptyActivityLifecycleCallbacks { + private final FragmentCallback mFragmentCallback; + + DispatcherActivityCallback() { + mFragmentCallback = new FragmentCallback(); + } + + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + if (activity instanceof FragmentActivity) { + ((FragmentActivity) activity).getSupportFragmentManager() + .registerFragmentLifecycleCallbacks(mFragmentCallback, true); + } + ReportFragment.injectIfNeededIn(activity); + } + + @Override + public void onActivityStopped(Activity activity) { + if (activity instanceof FragmentActivity) { + markState((FragmentActivity) activity, CREATED); + } + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + if (activity instanceof FragmentActivity) { + markState((FragmentActivity) activity, CREATED); + } + } + } + + @SuppressWarnings("WeakerAccess") + public static class DestructionReportFragment extends Fragment { + @Override + public void onPause() { + super.onPause(); + dispatch(ON_PAUSE); + } + + @Override + public void onStop() { + super.onStop(); + dispatch(ON_STOP); + } + + @Override + public void onDestroy() { + super.onDestroy(); + dispatch(ON_DESTROY); + } + + protected void dispatch(Lifecycle.Event event) { + dispatchIfLifecycleOwner(getParentFragment(), event); + } + } + + private static void markState(FragmentManager manager, State state) { + Collection<Fragment> fragments = manager.getFragments(); + if (fragments == null) { + return; + } + for (Fragment fragment : fragments) { + if (fragment == null) { + continue; + } + markStateIn(fragment, state); + if (fragment.isAdded()) { + markState(fragment.getChildFragmentManager(), state); + } + } + } + + private static void markStateIn(Object object, State state) { + if (object instanceof LifecycleRegistryOwner) { + LifecycleRegistry registry = ((LifecycleRegistryOwner) object).getLifecycle(); + registry.markState(state); + } + } + + private static void markState(FragmentActivity activity, State state) { + markStateIn(activity, state); + markState(activity.getSupportFragmentManager(), state); + } + + private static void dispatchIfLifecycleOwner(Fragment fragment, Lifecycle.Event event) { + if (fragment instanceof LifecycleRegistryOwner) { + ((LifecycleRegistryOwner) fragment).getLifecycle().handleLifecycleEvent(event); + } + } + + @SuppressWarnings("WeakerAccess") + @VisibleForTesting + static class FragmentCallback extends FragmentManager.FragmentLifecycleCallbacks { + + @Override + public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) { + dispatchIfLifecycleOwner(f, ON_CREATE); + + if (!(f instanceof LifecycleRegistryOwner)) { + return; + } + + if (f.getChildFragmentManager().findFragmentByTag(REPORT_FRAGMENT_TAG) == null) { + f.getChildFragmentManager().beginTransaction().add(new DestructionReportFragment(), + REPORT_FRAGMENT_TAG).commit(); + } + } + + @Override + public void onFragmentStarted(FragmentManager fm, Fragment f) { + dispatchIfLifecycleOwner(f, ON_START); + } + + @Override + public void onFragmentResumed(FragmentManager fm, Fragment f) { + dispatchIfLifecycleOwner(f, ON_RESUME); + } + } +} diff --git a/android/arch/lifecycle/LifecycleOwner.java b/android/arch/lifecycle/LifecycleOwner.java index 068bac1b..934cf3a2 100644 --- a/android/arch/lifecycle/LifecycleOwner.java +++ b/android/arch/lifecycle/LifecycleOwner.java @@ -16,8 +16,6 @@ package android.arch.lifecycle; -import android.support.annotation.NonNull; - /** * A class that has an Android lifecycle. These events can be used by custom components to * handle lifecycle changes without implementing any code inside the Activity or the Fragment. @@ -31,6 +29,5 @@ public interface LifecycleOwner { * * @return The lifecycle of the provider. */ - @NonNull Lifecycle getLifecycle(); } diff --git a/android/arch/lifecycle/LifecycleRegistry.java b/android/arch/lifecycle/LifecycleRegistry.java index bf8aff79..b83e6b8a 100644 --- a/android/arch/lifecycle/LifecycleRegistry.java +++ b/android/arch/lifecycle/LifecycleRegistry.java @@ -29,12 +29,9 @@ import static android.arch.lifecycle.Lifecycle.State.RESUMED; import static android.arch.lifecycle.Lifecycle.State.STARTED; import android.arch.core.internal.FastSafeIterableMap; -import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.util.Log; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Iterator; import java.util.Map.Entry; @@ -47,8 +44,6 @@ import java.util.Map.Entry; */ public class LifecycleRegistry extends Lifecycle { - private static final String LOG_TAG = "LifecycleRegistry"; - /** * Custom list that keeps observers and can handle removals / additions during traversal. * @@ -64,12 +59,8 @@ public class LifecycleRegistry extends Lifecycle { private State mState; /** * The provider that owns this Lifecycle. - * Only WeakReference on LifecycleOwner is kept, so if somebody leaks Lifecycle, they won't leak - * the whole Fragment / Activity. However, to leak Lifecycle object isn't great idea neither, - * because it keeps strong references on all other listeners, so you'll leak all of them as - * well. */ - private final WeakReference<LifecycleOwner> mLifecycleOwner; + private final LifecycleOwner mLifecycleOwner; private int mAddingObserverCounter = 0; @@ -95,19 +86,19 @@ public class LifecycleRegistry extends Lifecycle { * @param provider The owner LifecycleOwner */ public LifecycleRegistry(@NonNull LifecycleOwner provider) { - mLifecycleOwner = new WeakReference<>(provider); + mLifecycleOwner = provider; mState = INITIALIZED; } /** - * Moves the Lifecycle to the given state and dispatches necessary events to the observers. + * Only marks the current state as the given value. It doesn't dispatch any event to its + * listeners. * * @param state new state */ @SuppressWarnings("WeakerAccess") - @MainThread - public void markState(@NonNull State state) { - moveToState(state); + public void markState(State state) { + mState = state; } /** @@ -118,16 +109,8 @@ public class LifecycleRegistry extends Lifecycle { * * @param event The event that was received */ - public void handleLifecycleEvent(@NonNull Lifecycle.Event event) { - State next = getStateAfter(event); - moveToState(next); - } - - private void moveToState(State next) { - if (mState == next) { - return; - } - mState = next; + public void handleLifecycleEvent(Lifecycle.Event event) { + mState = getStateAfter(event); if (mHandlingEvent || mAddingObserverCounter != 0) { mNewEventOccurred = true; // we will figure out what to do on upper level. @@ -157,7 +140,7 @@ public class LifecycleRegistry extends Lifecycle { } @Override - public void addObserver(@NonNull LifecycleObserver observer) { + public void addObserver(LifecycleObserver observer) { State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED; ObserverWithState statefulObserver = new ObserverWithState(observer, initialState); ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver); @@ -165,19 +148,15 @@ public class LifecycleRegistry extends Lifecycle { if (previous != null) { return; } - LifecycleOwner lifecycleOwner = mLifecycleOwner.get(); - if (lifecycleOwner == null) { - // it is null we should be destroyed. Fallback quickly - return; - } boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent; + State targetState = calculateTargetState(observer); mAddingObserverCounter++; while ((statefulObserver.mState.compareTo(targetState) < 0 && mObserverMap.contains(observer))) { pushParentState(statefulObserver.mState); - statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState)); + statefulObserver.dispatchEvent(mLifecycleOwner, upEvent(statefulObserver.mState)); popParentState(); // mState / subling may have been changed recalculate targetState = calculateTargetState(observer); @@ -199,7 +178,7 @@ public class LifecycleRegistry extends Lifecycle { } @Override - public void removeObserver(@NonNull LifecycleObserver observer) { + public void removeObserver(LifecycleObserver observer) { // we consciously decided not to send destruction events here in opposition to addObserver. // Our reasons for that: // 1. These events haven't yet happened at all. In contrast to events in addObservers, that @@ -279,7 +258,7 @@ public class LifecycleRegistry extends Lifecycle { throw new IllegalArgumentException("Unexpected state value " + state); } - private void forwardPass(LifecycleOwner lifecycleOwner) { + private void forwardPass() { Iterator<Entry<LifecycleObserver, ObserverWithState>> ascendingIterator = mObserverMap.iteratorWithAdditions(); while (ascendingIterator.hasNext() && !mNewEventOccurred) { @@ -288,13 +267,13 @@ public class LifecycleRegistry extends Lifecycle { while ((observer.mState.compareTo(mState) < 0 && !mNewEventOccurred && mObserverMap.contains(entry.getKey()))) { pushParentState(observer.mState); - observer.dispatchEvent(lifecycleOwner, upEvent(observer.mState)); + observer.dispatchEvent(mLifecycleOwner, upEvent(observer.mState)); popParentState(); } } } - private void backwardPass(LifecycleOwner lifecycleOwner) { + private void backwardPass() { Iterator<Entry<LifecycleObserver, ObserverWithState>> descendingIterator = mObserverMap.descendingIterator(); while (descendingIterator.hasNext() && !mNewEventOccurred) { @@ -304,7 +283,7 @@ public class LifecycleRegistry extends Lifecycle { && mObserverMap.contains(entry.getKey()))) { Event event = downEvent(observer.mState); pushParentState(getStateAfter(event)); - observer.dispatchEvent(lifecycleOwner, event); + observer.dispatchEvent(mLifecycleOwner, event); popParentState(); } } @@ -313,22 +292,16 @@ public class LifecycleRegistry extends Lifecycle { // happens only on the top of stack (never in reentrance), // so it doesn't have to take in account parents private void sync() { - LifecycleOwner lifecycleOwner = mLifecycleOwner.get(); - if (lifecycleOwner == null) { - Log.w(LOG_TAG, "LifecycleOwner is garbage collected, you shouldn't try dispatch " - + "new events from it."); - return; - } while (!isSynced()) { mNewEventOccurred = false; // no need to check eldest for nullability, because isSynced does it for us. if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) { - backwardPass(lifecycleOwner); + backwardPass(); } Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest(); if (!mNewEventOccurred && newest != null && mState.compareTo(newest.getValue().mState) > 0) { - forwardPass(lifecycleOwner); + forwardPass(); } } mNewEventOccurred = false; diff --git a/android/arch/lifecycle/LifecycleRegistryOwner.java b/android/arch/lifecycle/LifecycleRegistryOwner.java index 0c67fefe..38eeb6d3 100644 --- a/android/arch/lifecycle/LifecycleRegistryOwner.java +++ b/android/arch/lifecycle/LifecycleRegistryOwner.java @@ -16,8 +16,6 @@ package android.arch.lifecycle; -import android.support.annotation.NonNull; - /** * @deprecated Use {@code android.support.v7.app.AppCompatActivity} * which extends {@link LifecycleOwner}, so there are no use cases for this class. @@ -25,7 +23,6 @@ import android.support.annotation.NonNull; @SuppressWarnings({"WeakerAccess", "unused"}) @Deprecated public interface LifecycleRegistryOwner extends LifecycleOwner { - @NonNull @Override LifecycleRegistry getLifecycle(); } diff --git a/android/arch/lifecycle/LifecycleRegistryTest.java b/android/arch/lifecycle/LifecycleRegistryTest.java index 2a7bbad2..6506454d 100644 --- a/android/arch/lifecycle/LifecycleRegistryTest.java +++ b/android/arch/lifecycle/LifecycleRegistryTest.java @@ -566,25 +566,6 @@ public class LifecycleRegistryTest { verify(observer).onCreate(); } - private static void forceGc() { - Runtime.getRuntime().gc(); - Runtime.getRuntime().runFinalization(); - Runtime.getRuntime().gc(); - Runtime.getRuntime().runFinalization(); - } - - @Test - public void goneLifecycleOwner() { - fullyInitializeRegistry(); - mLifecycleOwner = null; - forceGc(); - TestObserver observer = mock(TestObserver.class); - mRegistry.addObserver(observer); - verify(observer, never()).onCreate(); - verify(observer, never()).onStart(); - verify(observer, never()).onResume(); - } - private void dispatchEvent(Lifecycle.Event event) { when(mLifecycle.getCurrentState()).thenReturn(LifecycleRegistry.getStateAfter(event)); mRegistry.handleLifecycleEvent(event); diff --git a/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java b/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java index 8ba297fe..ac278c0c 100644 --- a/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java +++ b/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java @@ -29,9 +29,10 @@ import android.support.annotation.RestrictTo; * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -public class ProcessLifecycleOwnerInitializer extends ContentProvider { +public class LifecycleRuntimeTrojanProvider extends ContentProvider { @Override public boolean onCreate() { + LifecycleDispatcher.init(getContext()); ProcessLifecycleOwner.init(getContext()); return true; } diff --git a/android/arch/lifecycle/LiveData.java b/android/arch/lifecycle/LiveData.java index 5b09c32f..3aea6acb 100644 --- a/android/arch/lifecycle/LiveData.java +++ b/android/arch/lifecycle/LiveData.java @@ -1,410 +1,4 @@ -/* - * Copyright (C) 2017 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. - */ - +//LiveData interface for tests package android.arch.lifecycle; - -import static android.arch.lifecycle.Lifecycle.State.DESTROYED; -import static android.arch.lifecycle.Lifecycle.State.STARTED; - -import android.arch.core.executor.ArchTaskExecutor; -import android.arch.core.internal.SafeIterableMap; -import android.arch.lifecycle.Lifecycle.State; -import android.support.annotation.MainThread; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.Iterator; -import java.util.Map; - -/** - * LiveData is a data holder class that can be observed within a given lifecycle. - * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and - * this observer will be notified about modifications of the wrapped data only if the paired - * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is - * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via - * {@link #observeForever(Observer)} is considered as always active and thus will be always notified - * about modifications. For those observers, you should manually call - * {@link #removeObserver(Observer)}. - * - * <p> An observer added with a Lifecycle will be automatically removed if the corresponding - * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for - * activities and fragments where they can safely observe LiveData and not worry about leaks: - * they will be instantly unsubscribed when they are destroyed. - * - * <p> - * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods - * to get notified when number of active {@link Observer}s change between 0 and 1. - * This allows LiveData to release any heavy resources when it does not have any Observers that - * are actively observing. - * <p> - * This class is designed to hold individual data fields of {@link ViewModel}, - * but can also be used for sharing data between different modules in your application - * in a decoupled fashion. - * - * @param <T> The type of data held by this instance - * @see ViewModel - */ -@SuppressWarnings({"WeakerAccess", "unused"}) -// TODO: Thread checks are too strict right now, we may consider automatically moving them to main -// thread. -public abstract class LiveData<T> { - private final Object mDataLock = new Object(); - static final int START_VERSION = -1; - private static final Object NOT_SET = new Object(); - - private static final LifecycleOwner ALWAYS_ON = new LifecycleOwner() { - - private LifecycleRegistry mRegistry = init(); - - private LifecycleRegistry init() { - LifecycleRegistry registry = new LifecycleRegistry(this); - registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); - registry.handleLifecycleEvent(Lifecycle.Event.ON_START); - registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME); - return registry; - } - - @Override - public Lifecycle getLifecycle() { - return mRegistry; - } - }; - - private SafeIterableMap<Observer<T>, LifecycleBoundObserver> mObservers = - new SafeIterableMap<>(); - - // how many observers are in active state - private int mActiveCount = 0; - private volatile Object mData = NOT_SET; - // when setData is called, we set the pending data and actual data swap happens on the main - // thread - private volatile Object mPendingData = NOT_SET; - private int mVersion = START_VERSION; - - private boolean mDispatchingValue; - @SuppressWarnings("FieldCanBeLocal") - private boolean mDispatchInvalidated; - private final Runnable mPostValueRunnable = new Runnable() { - @Override - public void run() { - Object newValue; - synchronized (mDataLock) { - newValue = mPendingData; - mPendingData = NOT_SET; - } - //noinspection unchecked - setValue((T) newValue); - } - }; - - private void considerNotify(LifecycleBoundObserver observer) { - if (!observer.active) { - return; - } - // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet. - // - // we still first check observer.active to keep it as the entrance for events. So even if - // the observer moved to an active state, if we've not received that event, we better not - // notify for a more predictable notification order. - if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) { - observer.activeStateChanged(false); - return; - } - if (observer.lastVersion >= mVersion) { - return; - } - observer.lastVersion = mVersion; - //noinspection unchecked - observer.observer.onChanged((T) mData); - } - - private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) { - if (mDispatchingValue) { - mDispatchInvalidated = true; - return; - } - mDispatchingValue = true; - do { - mDispatchInvalidated = false; - if (initiator != null) { - considerNotify(initiator); - initiator = null; - } else { - for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator = - mObservers.iteratorWithAdditions(); iterator.hasNext(); ) { - considerNotify(iterator.next().getValue()); - if (mDispatchInvalidated) { - break; - } - } - } - } while (mDispatchInvalidated); - mDispatchingValue = false; - } - - /** - * Adds the given observer to the observers list within the lifespan of the given - * owner. The events are dispatched on the main thread. If LiveData already has data - * set, it will be delivered to the observer. - * <p> - * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED} - * or {@link Lifecycle.State#RESUMED} state (active). - * <p> - * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will - * automatically be removed. - * <p> - * When data changes while the {@code owner} is not active, it will not receive any updates. - * If it becomes active again, it will receive the last available data automatically. - * <p> - * LiveData keeps a strong reference to the observer and the owner as long as the - * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to - * the observer & the owner. - * <p> - * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData - * ignores the call. - * <p> - * If the given owner, observer tuple is already in the list, the call is ignored. - * If the observer is already in the list with another owner, LiveData throws an - * {@link IllegalArgumentException}. - * - * @param owner The LifecycleOwner which controls the observer - * @param observer The observer that will receive the events - */ - @MainThread - public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) { - if (owner.getLifecycle().getCurrentState() == DESTROYED) { - // ignore - return; - } - LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); - LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper); - if (existing != null && existing.owner != wrapper.owner) { - throw new IllegalArgumentException("Cannot add the same observer" - + " with different lifecycles"); - } - if (existing != null) { - return; - } - owner.getLifecycle().addObserver(wrapper); - } - - /** - * Adds the given observer to the observers list. This call is similar to - * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which - * is always active. This means that the given observer will receive all events and will never - * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop - * observing this LiveData. - * While LiveData has one of such observers, it will be considered - * as active. - * <p> - * If the observer was already added with an owner to this LiveData, LiveData throws an - * {@link IllegalArgumentException}. - * - * @param observer The observer that will receive the events - */ - @MainThread - public void observeForever(@NonNull Observer<T> observer) { - observe(ALWAYS_ON, observer); - } - - /** - * Removes the given observer from the observers list. - * - * @param observer The Observer to receive events. - */ - @MainThread - public void removeObserver(@NonNull final Observer<T> observer) { - assertMainThread("removeObserver"); - LifecycleBoundObserver removed = mObservers.remove(observer); - if (removed == null) { - return; - } - removed.owner.getLifecycle().removeObserver(removed); - removed.activeStateChanged(false); - } - - /** - * Removes all observers that are tied to the given {@link LifecycleOwner}. - * - * @param owner The {@code LifecycleOwner} scope for the observers to be removed. - */ - @MainThread - public void removeObservers(@NonNull final LifecycleOwner owner) { - assertMainThread("removeObservers"); - for (Map.Entry<Observer<T>, LifecycleBoundObserver> entry : mObservers) { - if (entry.getValue().owner == owner) { - removeObserver(entry.getKey()); - } - } - } - - /** - * Posts a task to a main thread to set the given value. So if you have a following code - * executed in the main thread: - * <pre class="prettyprint"> - * liveData.postValue("a"); - * liveData.setValue("b"); - * </pre> - * The value "b" would be set at first and later the main thread would override it with - * the value "a". - * <p> - * If you called this method multiple times before a main thread executed a posted task, only - * the last value would be dispatched. - * - * @param value The new value - */ - protected void postValue(T value) { - boolean postTask; - synchronized (mDataLock) { - postTask = mPendingData == NOT_SET; - mPendingData = value; - } - if (!postTask) { - return; - } - ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); - } - - /** - * Sets the value. If there are active observers, the value will be dispatched to them. - * <p> - * This method must be called from the main thread. If you need set a value from a background - * thread, you can use {@link #postValue(Object)} - * - * @param value The new value - */ - @MainThread - protected void setValue(T value) { - assertMainThread("setValue"); - mVersion++; - mData = value; - dispatchingValue(null); - } - - /** - * Returns the current value. - * Note that calling this method on a background thread does not guarantee that the latest - * value set will be received. - * - * @return the current value - */ - @Nullable - public T getValue() { - Object data = mData; - if (data != NOT_SET) { - //noinspection unchecked - return (T) data; - } - return null; - } - - int getVersion() { - return mVersion; - } - - /** - * Called when the number of active observers change to 1 from 0. - * <p> - * This callback can be used to know that this LiveData is being used thus should be kept - * up to date. - */ - protected void onActive() { - - } - - /** - * Called when the number of active observers change from 1 to 0. - * <p> - * This does not mean that there are no observers left, there may still be observers but their - * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED} - * (like an Activity in the back stack). - * <p> - * You can check if there are observers via {@link #hasObservers()}. - */ - protected void onInactive() { - - } - - /** - * Returns true if this LiveData has observers. - * - * @return true if this LiveData has observers - */ - public boolean hasObservers() { - return mObservers.size() > 0; - } - - /** - * Returns true if this LiveData has active observers. - * - * @return true if this LiveData has active observers - */ - public boolean hasActiveObservers() { - return mActiveCount > 0; - } - - class LifecycleBoundObserver implements GenericLifecycleObserver { - public final LifecycleOwner owner; - public final Observer<T> observer; - public boolean active; - public int lastVersion = START_VERSION; - - LifecycleBoundObserver(LifecycleOwner owner, Observer<T> observer) { - this.owner = owner; - this.observer = observer; - } - - @Override - public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { - if (owner.getLifecycle().getCurrentState() == DESTROYED) { - removeObserver(observer); - return; - } - // immediately set active state, so we'd never dispatch anything to inactive - // owner - activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState())); - } - - void activeStateChanged(boolean newActive) { - if (newActive == active) { - return; - } - active = newActive; - boolean wasInactive = LiveData.this.mActiveCount == 0; - LiveData.this.mActiveCount += active ? 1 : -1; - if (wasInactive && active) { - onActive(); - } - if (LiveData.this.mActiveCount == 0 && !active) { - onInactive(); - } - if (active) { - dispatchingValue(this); - } - } - } - - static boolean isActiveState(State state) { - return state.isAtLeast(STARTED); - } - - private void assertMainThread(String methodName) { - if (!ArchTaskExecutor.getInstance().isMainThread()) { - throw new IllegalStateException("Cannot invoke " + methodName + " on a background" - + " thread"); - } - } +public class LiveData<T> { } diff --git a/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java b/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java deleted file mode 100644 index 836cfff0..00000000 --- a/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.arch.lifecycle; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -import android.app.Instrumentation; -import android.arch.lifecycle.testapp.CollectingSupportActivity; -import android.arch.lifecycle.testapp.CollectingSupportFragment; -import android.arch.lifecycle.testapp.NavigationDialogActivity; -import android.content.Intent; -import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.support.test.rule.ActivityTestRule; -import android.support.test.runner.AndroidJUnit4; -import android.support.v4.app.FragmentActivity; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.concurrent.atomic.AtomicInteger; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class LiveDataOnSaveInstanceStateTest { - @Rule - public ActivityTestRule<CollectingSupportActivity> mActivityTestRule = - new ActivityTestRule<>(CollectingSupportActivity.class); - - @Test - @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M) - public void liveData_partiallyObscuredActivity_maxSdkM() throws Throwable { - CollectingSupportActivity activity = mActivityTestRule.getActivity(); - - liveData_partiallyObscuredLifecycleOwner_maxSdkM(activity); - } - - @Test - @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M) - public void liveData_partiallyObscuredActivityWithFragment_maxSdkM() throws Throwable { - CollectingSupportActivity activity = mActivityTestRule.getActivity(); - CollectingSupportFragment fragment = new CollectingSupportFragment(); - mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment)); - - liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment); - } - - @Test - @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M) - public void liveData_partiallyObscuredActivityFragmentInFragment_maxSdkM() throws Throwable { - CollectingSupportActivity activity = mActivityTestRule.getActivity(); - CollectingSupportFragment fragment = new CollectingSupportFragment(); - CollectingSupportFragment fragment2 = new CollectingSupportFragment(); - mActivityTestRule.runOnUiThread(() -> { - activity.replaceFragment(fragment); - fragment.replaceFragment(fragment2); - }); - - liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment2); - } - - @Test - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) - public void liveData_partiallyObscuredActivity_minSdkN() throws Throwable { - CollectingSupportActivity activity = mActivityTestRule.getActivity(); - - liveData_partiallyObscuredLifecycleOwner_minSdkN(activity); - } - - @Test - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) - public void liveData_partiallyObscuredActivityWithFragment_minSdkN() throws Throwable { - CollectingSupportActivity activity = mActivityTestRule.getActivity(); - CollectingSupportFragment fragment = new CollectingSupportFragment(); - mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment)); - - liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment); - } - - @Test - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) - public void liveData_partiallyObscuredActivityFragmentInFragment_minSdkN() throws Throwable { - CollectingSupportActivity activity = mActivityTestRule.getActivity(); - CollectingSupportFragment fragment = new CollectingSupportFragment(); - CollectingSupportFragment fragment2 = new CollectingSupportFragment(); - mActivityTestRule.runOnUiThread(() -> { - activity.replaceFragment(fragment); - fragment.replaceFragment(fragment2); - }); - - liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment2); - } - - private void liveData_partiallyObscuredLifecycleOwner_maxSdkM(LifecycleOwner lifecycleOwner) - throws Throwable { - final AtomicInteger atomicInteger = new AtomicInteger(0); - MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>(); - mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0)); - - TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule); - - mutableLiveData.observe(lifecycleOwner, atomicInteger::set); - - final FragmentActivity dialogActivity = launchDialog(); - - TestUtils.waitTillCreated(lifecycleOwner, mActivityTestRule); - - // Change the LiveData value and assert that the observer is not called given that the - // lifecycle is in the CREATED state. - mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1)); - assertThat(atomicInteger.get(), is(0)); - - // Finish the dialog Activity, wait for the main activity to be resumed, and assert that - // the observer's onChanged method is called. - mActivityTestRule.runOnUiThread(dialogActivity::finish); - TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule); - assertThat(atomicInteger.get(), is(1)); - } - - private void liveData_partiallyObscuredLifecycleOwner_minSdkN(LifecycleOwner lifecycleOwner) - throws Throwable { - final AtomicInteger atomicInteger = new AtomicInteger(0); - MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>(); - mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0)); - - TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule); - - mutableLiveData.observe(lifecycleOwner, atomicInteger::set); - - // Launch the NavigationDialogActivity, partially obscuring the activity, and wait for the - // lifecycleOwner to hit onPause (or enter the STARTED state). On API 24 and above, this - // onPause should be the last lifecycle method called (and the STARTED state should be the - // final resting state). - launchDialog(); - TestUtils.waitTillStarted(lifecycleOwner, mActivityTestRule); - - // Change the LiveData's value and verify that the observer's onChanged method is called - // since we are in the STARTED state. - mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1)); - assertThat(atomicInteger.get(), is(1)); - } - - private FragmentActivity launchDialog() throws Throwable { - Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor( - NavigationDialogActivity.class.getCanonicalName(), null, false); - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - instrumentation.addMonitor(monitor); - - FragmentActivity activity = mActivityTestRule.getActivity(); - // helps with less flaky API 16 tests - Intent intent = new Intent(activity, NavigationDialogActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - activity.startActivity(intent); - FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity(); - TestUtils.waitTillResumed(fragmentActivity, mActivityTestRule); - return fragmentActivity; - } -} diff --git a/android/arch/lifecycle/LiveDataTest.java b/android/arch/lifecycle/LiveDataTest.java index 647d5d7a..9f0b4257 100644 --- a/android/arch/lifecycle/LiveDataTest.java +++ b/android/arch/lifecycle/LiveDataTest.java @@ -53,29 +53,18 @@ import org.mockito.Mockito; public class LiveDataTest { private PublicLiveData<String> mLiveData; private LifecycleOwner mOwner; - private LifecycleOwner mOwner2; private LifecycleRegistry mRegistry; - private LifecycleRegistry mRegistry2; private MethodExec mActiveObserversChanged; private boolean mInObserver; @Before public void init() { mLiveData = new PublicLiveData<>(); - - mActiveObserversChanged = mock(MethodExec.class); - mLiveData.activeObserversChanged = mActiveObserversChanged; - mOwner = mock(LifecycleOwner.class); - mRegistry = new LifecycleRegistry(mOwner); when(mOwner.getLifecycle()).thenReturn(mRegistry); - - mOwner2 = mock(LifecycleOwner.class); - - mRegistry2 = new LifecycleRegistry(mOwner2); - when(mOwner2.getLifecycle()).thenReturn(mRegistry2); - + mActiveObserversChanged = mock(MethodExec.class); + mLiveData.activeObserversChanged = mActiveObserversChanged; mInObserver = false; } @@ -170,11 +159,14 @@ public class LiveDataTest { @Test public void testAddSameObserverIn2LifecycleOwners() { Observer<String> observer = (Observer<String>) mock(Observer.class); + LifecycleOwner owner2 = mock(LifecycleOwner.class); + LifecycleRegistry registry2 = new LifecycleRegistry(owner2); + when(owner2.getLifecycle()).thenReturn(registry2); mLiveData.observe(mOwner, observer); Throwable throwable = null; try { - mLiveData.observe(mOwner2, observer); + mLiveData.observe(owner2, observer); } catch (Throwable t) { throwable = t; } @@ -464,210 +456,6 @@ public class LiveDataTest { inOrder.verifyNoMoreInteractions(); } - @Test - public void setValue_neverActive_observerOnChangedNotCalled() { - Observer<String> observer = (Observer<String>) mock(Observer.class); - mLiveData.observe(mOwner, observer); - - mLiveData.setValue("1"); - - verify(observer, never()).onChanged(anyString()); - } - - @Test - public void setValue_twoObserversTwoStartedOwners_onChangedCalledOnBoth() { - Observer<String> observer1 = mock(Observer.class); - Observer<String> observer2 = mock(Observer.class); - - mLiveData.observe(mOwner, observer1); - mLiveData.observe(mOwner2, observer2); - - mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); - mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START); - - mLiveData.setValue("1"); - - verify(observer1).onChanged("1"); - verify(observer2).onChanged("1"); - } - - @Test - public void setValue_twoObserversOneStartedOwner_onChangedCalledOnOneCorrectObserver() { - Observer<String> observer1 = mock(Observer.class); - Observer<String> observer2 = mock(Observer.class); - - mLiveData.observe(mOwner, observer1); - mLiveData.observe(mOwner2, observer2); - - mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); - - mLiveData.setValue("1"); - - verify(observer1).onChanged("1"); - verify(observer2, never()).onChanged(anyString()); - } - - @Test - public void setValue_twoObserversBothStartedAfterSetValue_onChangedCalledOnBoth() { - Observer<String> observer1 = mock(Observer.class); - Observer<String> observer2 = mock(Observer.class); - - mLiveData.observe(mOwner, observer1); - mLiveData.observe(mOwner2, observer2); - - mLiveData.setValue("1"); - - mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); - mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START); - - verify(observer1).onChanged("1"); - verify(observer1).onChanged("1"); - } - - @Test - public void setValue_twoObserversOneStartedAfterSetValue_onChangedCalledOnCorrectObserver() { - Observer<String> observer1 = mock(Observer.class); - Observer<String> observer2 = mock(Observer.class); - - mLiveData.observe(mOwner, observer1); - mLiveData.observe(mOwner2, observer2); - - mLiveData.setValue("1"); - - mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); - - verify(observer1).onChanged("1"); - verify(observer2, never()).onChanged(anyString()); - } - - @Test - public void setValue_twoObserversOneStarted_liveDataBecomesActive() { - Observer<String> observer1 = mock(Observer.class); - Observer<String> observer2 = mock(Observer.class); - - mLiveData.observe(mOwner, observer1); - mLiveData.observe(mOwner2, observer2); - - mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); - - verify(mActiveObserversChanged).onCall(true); - } - - @Test - public void setValue_twoObserversOneStopped_liveDataStaysActive() { - Observer<String> observer1 = mock(Observer.class); - Observer<String> observer2 = mock(Observer.class); - - mLiveData.observe(mOwner, observer1); - mLiveData.observe(mOwner2, observer2); - - mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); - mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START); - - verify(mActiveObserversChanged).onCall(true); - - reset(mActiveObserversChanged); - mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP); - - verify(mActiveObserversChanged, never()).onCall(anyBoolean()); - } - - /** - * Verifies that if a lifecycle's state changes without an event, and changes to something that - * LiveData would become inactive in response to, LiveData will detect the change upon new data - * being set and become inactive. Also verifies that once the lifecycle enters into a state - * that LiveData should become active to, that it does indeed become active. - */ - @Test - public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_oneObserver() { - Observer<String> observer = (Observer<String>) mock(Observer.class); - mLiveData.observe(mOwner, observer); - - mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); - - // Marking state as CREATED should call onInactive. - reset(mActiveObserversChanged); - mRegistry.markState(Lifecycle.State.CREATED); - verify(mActiveObserversChanged).onCall(false); - reset(mActiveObserversChanged); - - // Setting a new value should trigger LiveData to realize the Lifecycle it is observing - // is in a state where the LiveData should be inactive, so the LiveData will call onInactive - // and the Observer shouldn't be affected. - mLiveData.setValue("1"); - - // state is already CREATED so should not call again - verify(mActiveObserversChanged, never()).onCall(anyBoolean()); - verify(observer, never()).onChanged(anyString()); - - // Sanity check. Because we've only marked the state as CREATED, sending ON_START - // should re-dispatch events. - reset(mActiveObserversChanged); - reset(observer); - mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); - verify(mActiveObserversChanged).onCall(true); - verify(observer).onChanged("1"); - } - - /** - * This test verifies that LiveData will detect changes in LifecycleState that would make it - * inactive upon the setting of new data, but only if all of the Lifecycles it's observing - * are all in those states. It also makes sure that once it is inactive, that it will become - * active again once one of the lifecycles it's observing moves to an appropriate state. - */ - @Test - public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_twoObservers() { - Observer<String> observer1 = mock(Observer.class); - Observer<String> observer2 = mock(Observer.class); - - mLiveData.observe(mOwner, observer1); - mLiveData.observe(mOwner2, observer2); - - mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); - mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START); - - // Marking the state to created won't change LiveData to be inactive. - reset(mActiveObserversChanged); - mRegistry.markState(Lifecycle.State.CREATED); - verify(mActiveObserversChanged, never()).onCall(anyBoolean()); - - // After setting a value, the LiveData will stay active because there is still a STARTED - // lifecycle being observed. The one Observer associated with the STARTED lifecycle will - // also have been called, but the other Observer will not have been called. - reset(observer1); - reset(observer2); - mLiveData.setValue("1"); - verify(mActiveObserversChanged, never()).onCall(anyBoolean()); - verify(observer1, never()).onChanged(anyString()); - verify(observer2).onChanged("1"); - - // Now we set the other Lifecycle to be inactive, live data should become inactive. - reset(observer1); - reset(observer2); - mRegistry2.markState(Lifecycle.State.CREATED); - verify(mActiveObserversChanged).onCall(false); - verify(observer1, never()).onChanged(anyString()); - verify(observer2, never()).onChanged(anyString()); - - // Now we post another value, because both lifecycles are in the Created state, live data - // will not dispatch any values - reset(mActiveObserversChanged); - mLiveData.setValue("2"); - verify(mActiveObserversChanged, never()).onCall(anyBoolean()); - verify(observer1, never()).onChanged(anyString()); - verify(observer2, never()).onChanged(anyString()); - - // Now that the first Lifecycle has been moved back to the Resumed state, the LiveData will - // be made active and it's associated Observer will be called with the new value, but the - // Observer associated with the Lifecycle that is still in the Created state won't be - // called. - reset(mActiveObserversChanged); - mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME); - verify(mActiveObserversChanged).onCall(true); - verify(observer1).onChanged("2"); - verify(observer2, never()).onChanged(anyString()); - } - @SuppressWarnings("WeakerAccess") static class PublicLiveData<T> extends LiveData<T> { // cannot spy due to internal calls diff --git a/android/arch/lifecycle/MediatorLiveData.java b/android/arch/lifecycle/MediatorLiveData.java index 58647394..672b3a3b 100644 --- a/android/arch/lifecycle/MediatorLiveData.java +++ b/android/arch/lifecycle/MediatorLiveData.java @@ -19,49 +19,16 @@ package android.arch.lifecycle; import android.arch.core.internal.SafeIterableMap; import android.support.annotation.CallSuper; import android.support.annotation.MainThread; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; import java.util.Map; /** - * {@link LiveData} subclass which may observe other {@code LiveData} objects and react on + * {@link LiveData} subclass which may observer other {@code LiveData} objects and react on * {@code OnChanged} events from them. * <p> * This class correctly propagates its active/inactive states down to source {@code LiveData} * objects. - * <p> - * Consider the following scenario: we have 2 instances of {@code LiveData}, let's name them - * {@code liveData1} and {@code liveData2}, and we want to merge their emissions in one object: - * {@code liveDataMerger}. Then, {@code liveData1} and {@code liveData2} will become sources for - * the {@code MediatorLiveData liveDataMerger} and every time {@code onChanged} callback - * is called for either of them, we set a new value in {@code liveDataMerger}. - * - * <pre> - * LiveData<Integer> liveData1 = ...; - * LiveData<Integer> liveData2 = ...; - * - * MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>(); - * liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value)); - * liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value)); - * </pre> - * <p> - * Let's consider that we only want 10 values emitted by {@code liveData1}, to be - * merged in the {@code liveDataMerger}. Then, after 10 values, we can stop listening to {@code - * liveData1} and remove it as a source. - * <pre> - * liveDataMerger.addSource(liveData1, new Observer<Integer>() { - * private int count = 1; - * - * {@literal @}Override public void onChanged(@Nullable Integer s) { - * count++; - * liveDataMerger.setValue(s); - * if (count > 10) { - * liveDataMerger.removeSource(liveData1); - * } - * } - * }); - * </pre> * * @param <T> The type of data hold by this instance */ @@ -82,7 +49,7 @@ public class MediatorLiveData<T> extends MutableLiveData<T> { * @param <S> The type of data hold by {@code source} LiveData */ @MainThread - public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<S> onChanged) { + public <S> void addSource(LiveData<S> source, Observer<S> onChanged) { Source<S> e = new Source<>(source, onChanged); Source<?> existing = mSources.putIfAbsent(source, e); if (existing != null && existing.mObserver != onChanged) { @@ -104,7 +71,7 @@ public class MediatorLiveData<T> extends MutableLiveData<T> { * @param <S> the type of data hold by {@code source} LiveData */ @MainThread - public <S> void removeSource(@NonNull LiveData<S> toRemote) { + public <S> void removeSource(LiveData<S> toRemote) { Source<?> source = mSources.remove(toRemote); if (source != null) { source.unplug(); diff --git a/android/arch/lifecycle/MissingClassTest.java b/android/arch/lifecycle/MissingClassTest.java deleted file mode 100644 index 81a07564..00000000 --- a/android/arch/lifecycle/MissingClassTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.arch.lifecycle; - -import android.app.PictureInPictureParams; -import android.os.Build; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1) -@SmallTest -public class MissingClassTest { - public static class ObserverWithMissingClasses { - @SuppressWarnings("unused") - public void newApiMethod(PictureInPictureParams params) {} - - @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) - public void onResume() {} - } - - @Test(expected = IllegalArgumentException.class) - public void testMissingApi() { - new ReflectiveGenericLifecycleObserver(new ObserverWithMissingClasses()); - } -} diff --git a/android/arch/lifecycle/PartiallyCoveredActivityTest.java b/android/arch/lifecycle/PartiallyCoveredActivityTest.java deleted file mode 100644 index 07a9dc5a..00000000 --- a/android/arch/lifecycle/PartiallyCoveredActivityTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.arch.lifecycle; - -import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME; -import static android.arch.lifecycle.Lifecycle.Event.ON_START; -import static android.arch.lifecycle.Lifecycle.Event.ON_STOP; -import static android.arch.lifecycle.TestUtils.OrderedTuples.CREATE; -import static android.arch.lifecycle.TestUtils.OrderedTuples.DESTROY; -import static android.arch.lifecycle.TestUtils.OrderedTuples.PAUSE; -import static android.arch.lifecycle.TestUtils.OrderedTuples.RESUME; -import static android.arch.lifecycle.TestUtils.OrderedTuples.START; -import static android.arch.lifecycle.TestUtils.OrderedTuples.STOP; -import static android.arch.lifecycle.TestUtils.flatMap; -import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT; -import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; - -import android.app.Instrumentation; -import android.arch.lifecycle.testapp.CollectingLifecycleOwner; -import android.arch.lifecycle.testapp.CollectingSupportActivity; -import android.arch.lifecycle.testapp.CollectingSupportFragment; -import android.arch.lifecycle.testapp.NavigationDialogActivity; -import android.arch.lifecycle.testapp.TestEvent; -import android.content.Intent; -import android.os.Build; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.rule.ActivityTestRule; -import android.support.v4.app.FragmentActivity; -import android.support.v4.util.Pair; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.util.List; -import java.util.concurrent.ExecutionException; - -/** - * Runs tests about the state when an activity is partially covered by another activity. Pre - * API 24, framework behavior changes so the test rely on whether state is saved or not and makes - * assertions accordingly. - */ -@SuppressWarnings("unchecked") -@RunWith(Parameterized.class) -@LargeTest -public class PartiallyCoveredActivityTest { - private static final List[] IF_SAVED = new List[]{ - // when overlaid - flatMap(CREATE, START, RESUME, PAUSE, - singletonList(new Pair<>(LIFECYCLE_EVENT, ON_STOP))), - // post dialog dismiss - asList(new Pair<>(OWNER_CALLBACK, ON_RESUME), - new Pair<>(LIFECYCLE_EVENT, ON_START), - new Pair<>(LIFECYCLE_EVENT, ON_RESUME)), - // post finish - flatMap(PAUSE, STOP, DESTROY)}; - - private static final List[] IF_NOT_SAVED = new List[]{ - // when overlaid - flatMap(CREATE, START, RESUME, PAUSE), - // post dialog dismiss - flatMap(RESUME), - // post finish - flatMap(PAUSE, STOP, DESTROY)}; - - private static final boolean sShouldSave = Build.VERSION.SDK_INT < Build.VERSION_CODES.N; - private static final List<Pair<TestEvent, Lifecycle.Event>>[] EXPECTED = - sShouldSave ? IF_SAVED : IF_NOT_SAVED; - - @Rule - public ActivityTestRule<CollectingSupportActivity> activityRule = - new ActivityTestRule<CollectingSupportActivity>( - CollectingSupportActivity.class) { - @Override - protected Intent getActivityIntent() { - // helps with less flaky API 16 tests - Intent intent = new Intent(InstrumentationRegistry.getTargetContext(), - CollectingSupportActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - return intent; - } - }; - private final boolean mDismissDialog; - - @Parameterized.Parameters(name = "dismissDialog_{0}") - public static List<Boolean> dismissDialog() { - return asList(true, false); - } - - public PartiallyCoveredActivityTest(boolean dismissDialog) { - mDismissDialog = dismissDialog; - } - - @Test - public void coveredWithDialog_activity() throws Throwable { - final CollectingSupportActivity activity = activityRule.getActivity(); - runTest(activity); - } - - @Test - public void coveredWithDialog_fragment() throws Throwable { - CollectingSupportFragment fragment = new CollectingSupportFragment(); - activityRule.runOnUiThread(() -> activityRule.getActivity().replaceFragment(fragment)); - runTest(fragment); - } - - @Test - public void coveredWithDialog_childFragment() throws Throwable { - CollectingSupportFragment parentFragment = new CollectingSupportFragment(); - CollectingSupportFragment childFragment = new CollectingSupportFragment(); - activityRule.runOnUiThread(() -> { - activityRule.getActivity().replaceFragment(parentFragment); - parentFragment.replaceFragment(childFragment); - }); - runTest(childFragment); - } - - private void runTest(CollectingLifecycleOwner owner) throws Throwable { - TestUtils.waitTillResumed(owner, activityRule); - FragmentActivity dialog = launchDialog(); - assertStateSaving(); - waitForIdle(); - assertThat(owner.copyCollectedEvents(), is(EXPECTED[0])); - List<Pair<TestEvent, Lifecycle.Event>> expected; - if (mDismissDialog) { - dialog.finish(); - TestUtils.waitTillResumed(activityRule.getActivity(), activityRule); - assertThat(owner.copyCollectedEvents(), is(flatMap(EXPECTED[0], EXPECTED[1]))); - expected = flatMap(EXPECTED[0], EXPECTED[1], EXPECTED[2]); - } else { - expected = flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY); - } - CollectingSupportActivity activity = activityRule.getActivity(); - activityRule.finishActivity(); - TestUtils.waitTillDestroyed(activity, activityRule); - assertThat(owner.copyCollectedEvents(), is(expected)); - } - - // test sanity - private void assertStateSaving() throws ExecutionException, InterruptedException { - final CollectingSupportActivity activity = activityRule.getActivity(); - if (sShouldSave) { - // state should be saved. wait for it to be saved - assertThat("test sanity", - activity.waitForStateSave(20), is(true)); - assertThat("test sanity", activity.getSupportFragmentManager() - .isStateSaved(), is(true)); - } else { - // should should not be saved - assertThat("test sanity", activity.getSupportFragmentManager() - .isStateSaved(), is(false)); - } - } - - private void waitForIdle() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - } - - private FragmentActivity launchDialog() throws Throwable { - Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor( - NavigationDialogActivity.class.getCanonicalName(), null, false); - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - instrumentation.addMonitor(monitor); - - FragmentActivity activity = activityRule.getActivity(); - - Intent intent = new Intent(activity, NavigationDialogActivity.class); - // disabling animations helps with less flaky API 16 tests - intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - activity.startActivity(intent); - FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity(); - TestUtils.waitTillResumed(fragmentActivity, activityRule); - return fragmentActivity; - } -} diff --git a/android/arch/lifecycle/ProcessLifecycleOwner.java b/android/arch/lifecycle/ProcessLifecycleOwner.java index 179e2c47..e2a12563 100644 --- a/android/arch/lifecycle/ProcessLifecycleOwner.java +++ b/android/arch/lifecycle/ProcessLifecycleOwner.java @@ -22,7 +22,6 @@ import android.arch.lifecycle.ReportFragment.ActivityInitializationListener; import android.content.Context; import android.os.Bundle; import android.os.Handler; -import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; /** @@ -157,8 +156,7 @@ public class ProcessLifecycleOwner implements LifecycleOwner { app.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - ReportFragment.injectIfNeededIn(activity); - ReportFragment.get(activity).setProcessListener(mInitializationListener); + ReportFragment .get(activity).setProcessListener(mInitializationListener); } @Override @@ -173,7 +171,6 @@ public class ProcessLifecycleOwner implements LifecycleOwner { }); } - @NonNull @Override public Lifecycle getLifecycle() { return mRegistry; diff --git a/android/arch/lifecycle/ProcessOwnerTest.java b/android/arch/lifecycle/ProcessOwnerTest.java index 77baf94c..37bdcdb4 100644 --- a/android/arch/lifecycle/ProcessOwnerTest.java +++ b/android/arch/lifecycle/ProcessOwnerTest.java @@ -31,7 +31,6 @@ import android.arch.lifecycle.Lifecycle.Event; import android.arch.lifecycle.testapp.NavigationDialogActivity; import android.arch.lifecycle.testapp.NavigationTestActivityFirst; import android.arch.lifecycle.testapp.NavigationTestActivitySecond; -import android.arch.lifecycle.testapp.NonSupportActivity; import android.content.Context; import android.content.Intent; import android.support.test.InstrumentationRegistry; @@ -96,22 +95,6 @@ public class ProcessOwnerTest { } @Test - public void testNavigationToNonSupport() throws Throwable { - FragmentActivity firstActivity = setupObserverOnResume(); - Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor( - NonSupportActivity.class.getCanonicalName(), null, false); - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - instrumentation.addMonitor(monitor); - - Intent intent = new Intent(firstActivity, NonSupportActivity.class); - firstActivity.finish(); - firstActivity.startActivity(intent); - NonSupportActivity secondActivity = (NonSupportActivity) monitor.waitForActivity(); - assertThat("Failed to navigate", secondActivity, notNullValue()); - checkProcessObserverSilent(secondActivity); - } - - @Test public void testRecreation() throws Throwable { FragmentActivity activity = setupObserverOnResume(); FragmentActivity recreated = TestUtils.recreateActivity(activity, activityTestRule); @@ -181,11 +164,4 @@ public class ProcessOwnerTest { activityTestRule.runOnUiThread(() -> ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver)); } - - private void checkProcessObserverSilent(NonSupportActivity activity) throws Throwable { - assertThat(activity.awaitResumedState(), is(true)); - assertThat(mObserver.mChangedState, is(false)); - activityTestRule.runOnUiThread(() -> - ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver)); - } } diff --git a/android/arch/lifecycle/ReportFragment.java b/android/arch/lifecycle/ReportFragment.java index 16a89ce8..3e4ece82 100644 --- a/android/arch/lifecycle/ReportFragment.java +++ b/android/arch/lifecycle/ReportFragment.java @@ -28,6 +28,7 @@ import android.support.annotation.RestrictTo; */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class ReportFragment extends Fragment { + private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle" + ".LifecycleDispatcher.report_fragment_tag"; diff --git a/android/arch/lifecycle/TestUtils.java b/android/arch/lifecycle/TestUtils.java index f7f9bbe5..f0214bfb 100644 --- a/android/arch/lifecycle/TestUtils.java +++ b/android/arch/lifecycle/TestUtils.java @@ -16,35 +16,16 @@ package android.arch.lifecycle; -import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE; -import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY; -import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE; -import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME; -import static android.arch.lifecycle.Lifecycle.Event.ON_START; -import static android.arch.lifecycle.Lifecycle.Event.ON_STOP; -import static android.arch.lifecycle.Lifecycle.State.CREATED; -import static android.arch.lifecycle.Lifecycle.State.DESTROYED; import static android.arch.lifecycle.Lifecycle.State.RESUMED; -import static android.arch.lifecycle.Lifecycle.State.STARTED; -import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT; -import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; import android.app.Activity; import android.app.Instrumentation; import android.app.Instrumentation.ActivityMonitor; -import android.arch.lifecycle.testapp.TestEvent; import android.support.test.InstrumentationRegistry; import android.support.test.rule.ActivityTestRule; -import android.support.v4.util.Pair; +import android.support.v4.app.FragmentActivity; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; class TestUtils { @@ -80,88 +61,23 @@ class TestUtils { return result; } - static void waitTillCreated(final LifecycleOwner owner, ActivityTestRule<?> activityRule) - throws Throwable { - waitTillState(owner, activityRule, CREATED); - } - - static void waitTillStarted(final LifecycleOwner owner, ActivityTestRule<?> activityRule) - throws Throwable { - waitTillState(owner, activityRule, STARTED); - } - - static void waitTillResumed(final LifecycleOwner owner, ActivityTestRule<?> activityRule) - throws Throwable { - waitTillState(owner, activityRule, RESUMED); - } - - static void waitTillDestroyed(final LifecycleOwner owner, ActivityTestRule<?> activityRule) - throws Throwable { - waitTillState(owner, activityRule, DESTROYED); - } - - static void waitTillState(final LifecycleOwner owner, ActivityTestRule<?> activityRule, - Lifecycle.State state) + static void waitTillResumed(final FragmentActivity a, ActivityTestRule<?> activityRule) throws Throwable { final CountDownLatch latch = new CountDownLatch(1); activityRule.runOnUiThread(() -> { - Lifecycle.State currentState = owner.getLifecycle().getCurrentState(); - if (currentState == state) { + Lifecycle.State currentState = a.getLifecycle().getCurrentState(); + if (currentState == RESUMED) { latch.countDown(); - } else { - owner.getLifecycle().addObserver(new LifecycleObserver() { - @OnLifecycleEvent(Lifecycle.Event.ON_ANY) - public void onStateChanged(LifecycleOwner provider) { - if (provider.getLifecycle().getCurrentState() == state) { - latch.countDown(); - provider.getLifecycle().removeObserver(this); - } - } - }); } + a.getLifecycle().addObserver(new LifecycleObserver() { + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) + public void onStateChanged(LifecycleOwner provider) { + latch.countDown(); + provider.getLifecycle().removeObserver(this); + } + }); }); - boolean latchResult = latch.await(1, TimeUnit.MINUTES); - assertThat("expected " + state + " never happened. Current state:" - + owner.getLifecycle().getCurrentState(), latchResult, is(true)); - - // wait for another loop to ensure all observers are called - activityRule.runOnUiThread(() -> { - // do nothing - }); + latch.await(); } - @SafeVarargs - static <T> List<T> flatMap(List<T>... items) { - ArrayList<T> result = new ArrayList<>(); - for (List<T> item : items) { - result.addAll(item); - } - return result; - } - - /** - * Event tuples of {@link TestEvent} and {@link Lifecycle.Event} - * in the order they should arrive. - */ - @SuppressWarnings("unchecked") - static class OrderedTuples { - static final List<Pair<TestEvent, Lifecycle.Event>> CREATE = - Arrays.asList(new Pair(OWNER_CALLBACK, ON_CREATE), - new Pair(LIFECYCLE_EVENT, ON_CREATE)); - static final List<Pair<TestEvent, Lifecycle.Event>> START = - Arrays.asList(new Pair(OWNER_CALLBACK, ON_START), - new Pair(LIFECYCLE_EVENT, ON_START)); - static final List<Pair<TestEvent, Lifecycle.Event>> RESUME = - Arrays.asList(new Pair(OWNER_CALLBACK, ON_RESUME), - new Pair(LIFECYCLE_EVENT, ON_RESUME)); - static final List<Pair<TestEvent, Lifecycle.Event>> PAUSE = - Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_PAUSE), - new Pair(OWNER_CALLBACK, ON_PAUSE)); - static final List<Pair<TestEvent, Lifecycle.Event>> STOP = - Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_STOP), - new Pair(OWNER_CALLBACK, ON_STOP)); - static final List<Pair<TestEvent, Lifecycle.Event>> DESTROY = - Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_DESTROY), - new Pair(OWNER_CALLBACK, ON_DESTROY)); - } } diff --git a/android/arch/lifecycle/Transformations.java b/android/arch/lifecycle/Transformations.java index c735f8ba..9ce9cbb7 100644 --- a/android/arch/lifecycle/Transformations.java +++ b/android/arch/lifecycle/Transformations.java @@ -18,7 +18,6 @@ package android.arch.lifecycle; import android.arch.core.util.Function; import android.support.annotation.MainThread; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; /** @@ -61,8 +60,7 @@ public class Transformations { * @return a LiveData which emits resulting values */ @MainThread - public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source, - @NonNull final Function<X, Y> func) { + public static <X, Y> LiveData<Y> map(LiveData<X> source, final Function<X, Y> func) { final MediatorLiveData<Y> result = new MediatorLiveData<>(); result.addSource(source, new Observer<X>() { @Override @@ -122,8 +120,8 @@ public class Transformations { * @param <Y> a type of resulting LiveData */ @MainThread - public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger, - @NonNull final Function<X, LiveData<Y>> func) { + public static <X, Y> LiveData<Y> switchMap(LiveData<X> trigger, + final Function<X, LiveData<Y>> func) { final MediatorLiveData<Y> result = new MediatorLiveData<>(); result.addSource(trigger, new Observer<X>() { LiveData<Y> mSource; diff --git a/android/arch/lifecycle/ViewModelProvider.java b/android/arch/lifecycle/ViewModelProvider.java index 29cbab8e..7ef591f3 100644 --- a/android/arch/lifecycle/ViewModelProvider.java +++ b/android/arch/lifecycle/ViewModelProvider.java @@ -43,8 +43,7 @@ public class ViewModelProvider { * @param <T> The type parameter for the ViewModel. * @return a newly created ViewModel */ - @NonNull - <T extends ViewModel> T create(@NonNull Class<T> modelClass); + <T extends ViewModel> T create(Class<T> modelClass); } private final Factory mFactory; @@ -71,7 +70,7 @@ public class ViewModelProvider { * @param factory factory a {@code Factory} which will be used to instantiate * new {@code ViewModels} */ - public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) { + public ViewModelProvider(ViewModelStore store, Factory factory) { mFactory = factory; this.mViewModelStore = store; } @@ -89,8 +88,7 @@ public class ViewModelProvider { * @param <T> The type parameter for the ViewModel. * @return A ViewModel that is an instance of the given type {@code T}. */ - @NonNull - public <T extends ViewModel> T get(@NonNull Class<T> modelClass) { + public <T extends ViewModel> T get(Class<T> modelClass) { String canonicalName = modelClass.getCanonicalName(); if (canonicalName == null) { throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels"); @@ -138,9 +136,8 @@ public class ViewModelProvider { */ public static class NewInstanceFactory implements Factory { - @NonNull @Override - public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { + public <T extends ViewModel> T create(Class<T> modelClass) { //noinspection TryWithIdenticalCatches try { return modelClass.newInstance(); diff --git a/android/arch/lifecycle/ViewModelProviders.java b/android/arch/lifecycle/ViewModelProviders.java index b4b20aa4..746162a9 100644 --- a/android/arch/lifecycle/ViewModelProviders.java +++ b/android/arch/lifecycle/ViewModelProviders.java @@ -139,9 +139,8 @@ public class ViewModelProviders { mApplication = application; } - @NonNull @Override - public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { + public <T extends ViewModel> T create(Class<T> modelClass) { if (AndroidViewModel.class.isAssignableFrom(modelClass)) { //noinspection TryWithIdenticalCatches try { diff --git a/android/arch/lifecycle/ViewModelStoreOwner.java b/android/arch/lifecycle/ViewModelStoreOwner.java index e26fa325..50583056 100644 --- a/android/arch/lifecycle/ViewModelStoreOwner.java +++ b/android/arch/lifecycle/ViewModelStoreOwner.java @@ -16,8 +16,6 @@ package android.arch.lifecycle; -import android.support.annotation.NonNull; - /** * A scope that owns {@link ViewModelStore}. * <p> @@ -32,6 +30,5 @@ public interface ViewModelStoreOwner { * * @return a {@code ViewModelStore} */ - @NonNull ViewModelStore getViewModelStore(); } diff --git a/android/arch/lifecycle/ViewModelStores.java b/android/arch/lifecycle/ViewModelStores.java index d7d769d6..8c17dd98 100644 --- a/android/arch/lifecycle/ViewModelStores.java +++ b/android/arch/lifecycle/ViewModelStores.java @@ -19,7 +19,6 @@ package android.arch.lifecycle; import static android.arch.lifecycle.HolderFragment.holderFragmentFor; import android.support.annotation.MainThread; -import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; @@ -39,7 +38,7 @@ public class ViewModelStores { * @return a {@code ViewModelStore} */ @MainThread - public static ViewModelStore of(@NonNull FragmentActivity activity) { + public static ViewModelStore of(FragmentActivity activity) { return holderFragmentFor(activity).getViewModelStore(); } @@ -50,7 +49,7 @@ public class ViewModelStores { * @return a {@code ViewModelStore} */ @MainThread - public static ViewModelStore of(@NonNull Fragment fragment) { + public static ViewModelStore of(Fragment fragment) { return holderFragmentFor(fragment).getViewModelStore(); } } diff --git a/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java b/android/arch/lifecycle/testapp/CollectingActivity.java index 4213cab9..6e243b6c 100644 --- a/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java +++ b/android/arch/lifecycle/testapp/CollectingActivity.java @@ -17,20 +17,21 @@ package android.arch.lifecycle.testapp; import android.arch.lifecycle.Lifecycle; -import android.arch.lifecycle.LifecycleOwner; -import android.support.v4.util.Pair; +import android.util.Pair; import java.util.List; /** * For activities that collect their events. */ -public interface CollectingLifecycleOwner extends LifecycleOwner { +public interface CollectingActivity { + long TIMEOUT = 5; + /** - * Return a copy of currently collected events + * Return collected events * * @return The list of collected events. * @throws InterruptedException */ - List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents(); + List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents() throws InterruptedException; } diff --git a/android/arch/lifecycle/testapp/CollectingSupportActivity.java b/android/arch/lifecycle/testapp/CollectingSupportActivity.java deleted file mode 100644 index f38d4224..00000000 --- a/android/arch/lifecycle/testapp/CollectingSupportActivity.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.arch.lifecycle.testapp; - -import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK; - -import android.arch.lifecycle.Lifecycle.Event; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentActivity; -import android.support.v4.util.Pair; -import android.widget.FrameLayout; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * LifecycleRegistryOwner that extends FragmentActivity. - */ -public class CollectingSupportActivity extends FragmentActivity implements - CollectingLifecycleOwner { - - private final List<Pair<TestEvent, Event>> mCollectedEvents = new ArrayList<>(); - private TestObserver mTestObserver = new TestObserver(mCollectedEvents); - private CountDownLatch mSavedStateLatch = new CountDownLatch(1); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - FrameLayout layout = new FrameLayout(this); - layout.setId(R.id.fragment_container); - setContentView(layout); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_CREATE)); - getLifecycle().addObserver(mTestObserver); - } - - /** - * replaces the main content fragment w/ the given fragment. - */ - public void replaceFragment(Fragment fragment) { - getSupportFragmentManager() - .beginTransaction() - .add(R.id.fragment_container, fragment) - .commitNow(); - } - - @Override - protected void onStart() { - super.onStart(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_START)); - } - - @Override - protected void onResume() { - super.onResume(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_RESUME)); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_DESTROY)); - } - - @Override - protected void onStop() { - super.onStop(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_STOP)); - } - - @Override - protected void onPause() { - super.onPause(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_PAUSE)); - // helps with less flaky API 16 tests. - overridePendingTransition(0, 0); - } - - @Override - public List<Pair<TestEvent, Event>> copyCollectedEvents() { - return new ArrayList<>(mCollectedEvents); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - mSavedStateLatch.countDown(); - } - - /** - * Waits for onSaveInstanceState to be called. - */ - public boolean waitForStateSave(@SuppressWarnings("SameParameterValue") int seconds) - throws InterruptedException { - return mSavedStateLatch.await(seconds, TimeUnit.SECONDS); - } -} diff --git a/android/arch/lifecycle/testapp/CollectingSupportFragment.java b/android/arch/lifecycle/testapp/CollectingSupportFragment.java deleted file mode 100644 index 9bbbe165..00000000 --- a/android/arch/lifecycle/testapp/CollectingSupportFragment.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.arch.lifecycle.testapp; - -import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK; - -import android.annotation.SuppressLint; -import android.arch.lifecycle.Lifecycle; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.util.Pair; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import java.util.ArrayList; -import java.util.List; - -/** - * A support fragment that collects all of its events. - */ -@SuppressLint("ValidFragment") -public class CollectingSupportFragment extends Fragment implements CollectingLifecycleOwner { - private final List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents = - new ArrayList<>(); - private TestObserver mTestObserver = new TestObserver(mCollectedEvents); - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE)); - getLifecycle().addObserver(mTestObserver); - } - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - //noinspection ConstantConditions - FrameLayout layout = new FrameLayout(container.getContext()); - layout.setId(R.id.child_fragment_container); - return layout; - } - - /** - * Runs a replace fragment transaction with 'fragment' on this Fragment. - */ - public void replaceFragment(Fragment fragment) { - getChildFragmentManager() - .beginTransaction() - .add(R.id.child_fragment_container, fragment) - .commitNow(); - } - - @Override - public void onStart() { - super.onStart(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START)); - } - - @Override - public void onResume() { - super.onResume(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME)); - } - - @Override - public void onDestroy() { - super.onDestroy(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY)); - } - - @Override - public void onStop() { - super.onStop(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP)); - } - - @Override - public void onPause() { - super.onPause(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE)); - } - - @Override - public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() { - return new ArrayList<>(mCollectedEvents); - } -} diff --git a/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java b/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java index cdf577c1..d8f4fb39 100644 --- a/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java +++ b/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java @@ -16,29 +16,27 @@ package android.arch.lifecycle.testapp; -import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK; +import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK; import android.app.Activity; import android.arch.lifecycle.Lifecycle; import android.arch.lifecycle.LifecycleRegistry; import android.arch.lifecycle.LifecycleRegistryOwner; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.util.Pair; +import android.util.Pair; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; /** * LifecycleRegistryOwner that extends framework activity. */ -@SuppressWarnings("deprecation") public class FrameworkLifecycleRegistryActivity extends Activity implements - LifecycleRegistryOwner, CollectingLifecycleOwner { + LifecycleRegistryOwner, CollectingActivity { private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); - @NonNull @Override public LifecycleRegistry getLifecycle() { return mLifecycleRegistry; @@ -51,43 +49,49 @@ public class FrameworkLifecycleRegistryActivity extends Activity implements @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE)); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE)); getLifecycle().addObserver(mTestObserver); } @Override protected void onStart() { super.onStart(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START)); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START)); } @Override protected void onResume() { super.onResume(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME)); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME)); + finish(); } @Override protected void onDestroy() { super.onDestroy(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY)); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY)); mLatch.countDown(); } @Override protected void onStop() { super.onStop(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP)); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP)); } @Override protected void onPause() { super.onPause(); - mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE)); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE)); } + /** + * awaits for all events and returns them. + */ @Override - public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() { - return new ArrayList<>(mCollectedEvents); + public List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents() + throws InterruptedException { + mLatch.await(TIMEOUT, TimeUnit.SECONDS); + return mCollectedEvents; } } diff --git a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java new file mode 100644 index 00000000..5f33c282 --- /dev/null +++ b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.arch.lifecycle.testapp; + +import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK; + +import android.arch.lifecycle.Lifecycle; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.util.Pair; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Activity for testing full lifecycle + */ +public class FullLifecycleTestActivity extends FragmentActivity implements CollectingActivity { + + private List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents = new ArrayList<>(); + private TestObserver mTestObserver = new TestObserver(mCollectedEvents); + private CountDownLatch mLatch = new CountDownLatch(1); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE)); + getLifecycle().addObserver(mTestObserver); + } + + @Override + protected void onStart() { + super.onStart(); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START)); + } + + @Override + protected void onResume() { + super.onResume(); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME)); + finish(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY)); + mLatch.countDown(); + } + + @Override + protected void onStop() { + super.onStop(); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP)); + } + + @Override + protected void onPause() { + super.onPause(); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE)); + } + + /** + * awaits for all events and returns them. + */ + @Override + public List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents() + throws InterruptedException { + mLatch.await(TIMEOUT, TimeUnit.SECONDS); + return mCollectedEvents; + } +} diff --git a/android/arch/lifecycle/testapp/MainActivity.java b/android/arch/lifecycle/testapp/MainActivity.java new file mode 100644 index 00000000..b9d59142 --- /dev/null +++ b/android/arch/lifecycle/testapp/MainActivity.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.arch.lifecycle.testapp; + +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; + +/** + * Simple test activity + */ +public class MainActivity extends FragmentActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity); + } +} diff --git a/android/arch/lifecycle/testapp/NavigationDialogActivity.java b/android/arch/lifecycle/testapp/NavigationDialogActivity.java index 7d53528f..0ae94033 100644 --- a/android/arch/lifecycle/testapp/NavigationDialogActivity.java +++ b/android/arch/lifecycle/testapp/NavigationDialogActivity.java @@ -22,10 +22,4 @@ import android.support.v4.app.FragmentActivity; * an activity with Dialog theme. */ public class NavigationDialogActivity extends FragmentActivity { - @Override - protected void onPause() { - super.onPause(); - // helps with less flaky API 16 tests - overridePendingTransition(0, 0); - } } diff --git a/android/arch/lifecycle/testapp/NonSupportActivity.java b/android/arch/lifecycle/testapp/NonSupportActivity.java deleted file mode 100644 index 835d846a..00000000 --- a/android/arch/lifecycle/testapp/NonSupportActivity.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.arch.lifecycle.testapp; - -import android.app.Activity; -import android.os.Bundle; -import android.support.annotation.Nullable; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * Activity which doesn't extend FragmentActivity, to test ProcessLifecycleOwner because it - * should work anyway. - */ -public class NonSupportActivity extends Activity { - - private static final int TIMEOUT = 1; //secs - private final Lock mLock = new ReentrantLock(); - private Condition mIsResumedCondition = mLock.newCondition(); - private boolean mIsResumed = false; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Override - protected void onResume() { - super.onResume(); - mLock.lock(); - try { - mIsResumed = true; - mIsResumedCondition.signalAll(); - } finally { - mLock.unlock(); - } - } - - @Override - protected void onPause() { - super.onPause(); - mLock.lock(); - try { - mIsResumed = false; - } finally { - mLock.unlock(); - } - } - - /** - * awaits resumed state - * @return - * @throws InterruptedException - */ - public boolean awaitResumedState() throws InterruptedException { - mLock.lock(); - try { - while (!mIsResumed) { - if (!mIsResumedCondition.await(TIMEOUT, TimeUnit.SECONDS)) { - return false; - } - } - return true; - } finally { - mLock.unlock(); - } - } -} diff --git a/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java b/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java new file mode 100644 index 00000000..c46c6d3e --- /dev/null +++ b/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.arch.lifecycle.testapp; + +import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK; + +import android.arch.lifecycle.Lifecycle.Event; +import android.arch.lifecycle.LifecycleRegistry; +import android.arch.lifecycle.LifecycleRegistryOwner; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.util.Pair; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * LifecycleRegistryOwner that extends FragmentActivity. + */ +public class SupportLifecycleRegistryActivity extends FragmentActivity implements + LifecycleRegistryOwner, CollectingActivity { + private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); + @Override + public LifecycleRegistry getLifecycle() { + return mLifecycleRegistry; + } + + private List<Pair<TestEvent, Event>> mCollectedEvents = new ArrayList<>(); + private TestObserver mTestObserver = new TestObserver(mCollectedEvents); + private CountDownLatch mLatch = new CountDownLatch(1); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_CREATE)); + getLifecycle().addObserver(mTestObserver); + } + + @Override + protected void onStart() { + super.onStart(); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_START)); + } + + @Override + protected void onResume() { + super.onResume(); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_RESUME)); + finish(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_DESTROY)); + mLatch.countDown(); + } + + @Override + protected void onStop() { + super.onStop(); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_STOP)); + } + + @Override + protected void onPause() { + super.onPause(); + mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_PAUSE)); + } + + /** + * awaits for all events and returns them. + */ + @Override + public List<Pair<TestEvent, Event>> waitForCollectedEvents() throws InterruptedException { + mLatch.await(TIMEOUT, TimeUnit.SECONDS); + return mCollectedEvents; + } +} diff --git a/android/arch/lifecycle/testapp/TestEvent.java b/android/arch/lifecycle/testapp/TestEvent.java index 788045a2..0929f84a 100644 --- a/android/arch/lifecycle/testapp/TestEvent.java +++ b/android/arch/lifecycle/testapp/TestEvent.java @@ -17,6 +17,6 @@ package android.arch.lifecycle.testapp; public enum TestEvent { - OWNER_CALLBACK, - LIFECYCLE_EVENT, + ACTIVITY_CALLBACK, + LIFECYCLE_EVENT } diff --git a/android/arch/lifecycle/testapp/TestObserver.java b/android/arch/lifecycle/testapp/TestObserver.java index 00b8e16d..c6112396 100644 --- a/android/arch/lifecycle/testapp/TestObserver.java +++ b/android/arch/lifecycle/testapp/TestObserver.java @@ -28,7 +28,7 @@ import android.arch.lifecycle.Lifecycle.Event; import android.arch.lifecycle.LifecycleObserver; import android.arch.lifecycle.LifecycleOwner; import android.arch.lifecycle.OnLifecycleEvent; -import android.support.v4.util.Pair; +import android.util.Pair; import java.util.List; diff --git a/android/arch/paging/BoundedDataSource.java b/android/arch/paging/BoundedDataSource.java index 06564907..664ab16c 100644 --- a/android/arch/paging/BoundedDataSource.java +++ b/android/arch/paging/BoundedDataSource.java @@ -21,6 +21,7 @@ import android.support.annotation.RestrictTo; import android.support.annotation.WorkerThread; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -74,6 +75,7 @@ public abstract class BoundedDataSource<Value> extends PositionalDataSource<Valu if (result.size() != loadSize) { throw new IllegalStateException("invalid number of items returned."); } + Collections.reverse(result); } return result; } diff --git a/android/arch/paging/ContiguousDataSource.java b/android/arch/paging/ContiguousDataSource.java index be9da200..afcc208c 100644 --- a/android/arch/paging/ContiguousDataSource.java +++ b/android/arch/paging/ContiguousDataSource.java @@ -26,65 +26,21 @@ import java.util.List; /** @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> { + /** + * Number of items that this DataSource can provide in total, or COUNT_UNDEFINED. + * + * @return number of items that this DataSource can provide in total, or COUNT_UNDEFINED + * if difficult or undesired to compute. + */ + public int countItems() { + return COUNT_UNDEFINED; + } + @Override boolean isContiguous() { return true; } - void loadInitial(Key key, int pageSize, boolean enablePlaceholders, - PageResult.Receiver<Key, Value> receiver) { - NullPaddedList<Value> initial = loadInitial(key, pageSize, enablePlaceholders); - if (initial != null) { - receiver.onPageResult(new PageResult<>( - PageResult.INIT, - new Page<Key, Value>(initial.mList), - initial.getLeadingNullCount(), - initial.getTrailingNullCount(), - initial.getPositionOffset())); - } else { - receiver.onPageResult(new PageResult<Key, Value>( - PageResult.INIT, null, 0, 0, 0)); - } - } - - void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize, - PageResult.Receiver<Key, Value> receiver) { - List<Value> list = loadAfter(currentEndIndex, currentEndItem, pageSize); - - Page<Key, Value> page = list != null - ? new Page<Key, Value>(list) : null; - - receiver.postOnPageResult(new PageResult<>( - PageResult.APPEND, page, 0, 0, 0)); - } - - void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize, - PageResult.Receiver<Key, Value> receiver) { - List<Value> list = loadBefore(currentBeginIndex, currentBeginItem, pageSize); - - Page<Key, Value> page = list != null - ? new Page<Key, Value>(list) : null; - - receiver.postOnPageResult(new PageResult<>( - PageResult.PREPEND, page, 0, 0, 0)); - } - - /** - * Get the key from either the position, or item, or null if position/item invalid. - * <p> - * Position may not match passed item's position - if trying to query the key from a position - * that isn't yet loaded, a fallback item (last loaded item accessed) will be passed. - */ - abstract Key getKey(int position, Value item); - - @Nullable - abstract List<Value> loadAfterImpl(int currentEndIndex, - @NonNull Value currentEndItem, int pageSize); - - @Nullable - abstract List<Value> loadBeforeImpl(int currentBeginIndex, - @NonNull Value currentBeginItem, int pageSize); - /** @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @WorkerThread @@ -92,7 +48,21 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V public abstract NullPaddedList<Value> loadInitial( Key key, int initialLoadSize, boolean enablePlaceholders); - /** @hide */ + /** + * Load data after the given position / item. + * <p> + * It's valid to return a different list size than the page size, if it's easier for this data + * source. It is generally safer to increase number loaded than reduce. + * + * @param currentEndIndex Load items after this index, starting with currentEndIndex + 1. + * @param currentEndItem Load items after this item, can be used for precise querying based on + * item contents. + * @param pageSize Suggested number of items to load. + * @return List of items, starting at position currentEndIndex + 1. Null if the data source is + * no longer valid, and should not be queried again. + * + * @hide + */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @WorkerThread @Nullable @@ -108,7 +78,24 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V return list; } - /** @hide */ + @Nullable + abstract List<Value> loadAfterImpl(int currentEndIndex, + @NonNull Value currentEndItem, int pageSize); + + /** + * Load data before the given position / item. + * <p> + * It's valid to return a different list size than the page size, if it's easier for this data + * source. It is generally safer to increase number loaded than reduce. + * + * @param currentBeginIndex Load items before this index, starting with currentBeginIndex - 1. + * @param currentBeginItem Load items after this item, can be used for precise querying based + * on item contents. + * @param pageSize Suggested number of items to load. + * @return List of items, in descending order, starting at position currentBeginIndex - 1. + * + * @hide + */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @WorkerThread @Nullable @@ -124,4 +111,15 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V return list; } + + @Nullable + abstract List<Value> loadBeforeImpl(int currentBeginIndex, + @NonNull Value currentBeginItem, int pageSize); + + /** + * Get the key from either the position, or item. Position may not match passed item's position, + * if trying to query the key from a position that isn't yet loaded, so a fallback item must be + * used. + */ + abstract Key getKey(int position, Value item); } diff --git a/android/arch/paging/PagedStorageDiffHelper.java b/android/arch/paging/ContiguousDiffHelper.java index 6fc70390..7dd194b2 100644 --- a/android/arch/paging/PagedStorageDiffHelper.java +++ b/android/arch/paging/ContiguousDiffHelper.java @@ -16,31 +16,36 @@ package android.arch.paging; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; import android.support.v7.recyclerview.extensions.DiffCallback; import android.support.v7.util.DiffUtil; import android.support.v7.util.ListUpdateCallback; -class PagedStorageDiffHelper { - private PagedStorageDiffHelper() { +/** @hide */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +class ContiguousDiffHelper { + private ContiguousDiffHelper() { } + @NonNull static <T> DiffUtil.DiffResult computeDiff( - final PagedStorage<?, T> oldList, - final PagedStorage<?, T> newList, - final DiffCallback<T> diffCallback) { - final int oldOffset = oldList.computeLeadingNulls(); - final int newOffset = newList.computeLeadingNulls(); - - final int oldSize = oldList.size() - oldOffset - oldList.computeTrailingNulls(); - final int newSize = newList.size() - newOffset - newList.computeTrailingNulls(); + final NullPaddedList<T> oldList, final NullPaddedList<T> newList, + final DiffCallback<T> diffCallback, boolean detectMoves) { + if (!oldList.isImmutable()) { + throw new IllegalArgumentException("list must be immutable to safely perform diff"); + } + if (!newList.isImmutable()) { + throw new IllegalArgumentException("list must be immutable to safely perform diff"); + } return DiffUtil.calculateDiff(new DiffUtil.Callback() { @Nullable @Override public Object getChangePayload(int oldItemPosition, int newItemPosition) { - T oldItem = oldList.get(oldItemPosition + oldOffset); - T newItem = newList.get(newItemPosition + newList.getLeadingNullCount()); + T oldItem = oldList.mList.get(oldItemPosition); + T newItem = newList.mList.get(newItemPosition); if (oldItem == null || newItem == null) { return null; } @@ -49,22 +54,21 @@ class PagedStorageDiffHelper { @Override public int getOldListSize() { - return oldSize; + return oldList.mList.size(); } @Override public int getNewListSize() { - return newSize; + return newList.mList.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - T oldItem = oldList.get(oldItemPosition + oldOffset); - T newItem = newList.get(newItemPosition + newList.getLeadingNullCount()); + T oldItem = oldList.mList.get(oldItemPosition); + T newItem = newList.mList.get(newItemPosition); if (oldItem == newItem) { return true; } - //noinspection SimplifiableIfStatement if (oldItem == null || newItem == null) { return false; } @@ -73,19 +77,18 @@ class PagedStorageDiffHelper { @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - T oldItem = oldList.get(oldItemPosition + oldOffset); - T newItem = newList.get(newItemPosition + newList.getLeadingNullCount()); + T oldItem = oldList.mList.get(oldItemPosition); + T newItem = newList.mList.get(newItemPosition); if (oldItem == newItem) { return true; } - //noinspection SimplifiableIfStatement if (oldItem == null || newItem == null) { return false; } return diffCallback.areContentsTheSame(oldItem, newItem); } - }, true); + }, detectMoves); } private static class OffsettingListUpdateCallback implements ListUpdateCallback { @@ -131,25 +134,21 @@ class PagedStorageDiffHelper { * immediately after dispatching this diff. */ static <T> void dispatchDiff(ListUpdateCallback callback, - final PagedStorage<?, T> oldList, - final PagedStorage<?, T> newList, + final NullPaddedList<T> oldList, final NullPaddedList<T> newList, final DiffUtil.DiffResult diffResult) { - final int trailingOld = oldList.computeTrailingNulls(); - final int trailingNew = newList.computeTrailingNulls(); - final int leadingOld = oldList.computeLeadingNulls(); - final int leadingNew = newList.computeLeadingNulls(); - - if (trailingOld == 0 - && trailingNew == 0 - && leadingOld == 0 - && leadingNew == 0) { + if (oldList.getLeadingNullCount() == 0 + && oldList.getTrailingNullCount() == 0 + && newList.getLeadingNullCount() == 0 + && newList.getTrailingNullCount() == 0) { // Simple case, dispatch & return diffResult.dispatchUpdatesTo(callback); return; } // First, remove or insert trailing nulls + final int trailingOld = oldList.getTrailingNullCount(); + final int trailingNew = newList.getTrailingNullCount(); if (trailingOld > trailingNew) { int count = trailingOld - trailingNew; callback.onRemoved(oldList.size() - count, count); @@ -158,6 +157,8 @@ class PagedStorageDiffHelper { } // Second, remove or insert leading nulls + final int leadingOld = oldList.getLeadingNullCount(); + final int leadingNew = newList.getLeadingNullCount(); if (leadingOld > leadingNew) { callback.onRemoved(0, leadingOld - leadingNew); } else if (leadingOld < leadingNew) { diff --git a/android/arch/paging/PagedStorageDiffHelperTest.java b/android/arch/paging/ContiguousDiffHelperTest.java index 8cb92246..4f221b34 100644 --- a/android/arch/paging/PagedStorageDiffHelperTest.java +++ b/android/arch/paging/ContiguousDiffHelperTest.java @@ -16,9 +16,6 @@ package android.arch.paging; -import static junit.framework.Assert.assertEquals; - -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; @@ -32,12 +29,11 @@ import android.support.v7.util.ListUpdateCallback; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; - -import java.util.Arrays; +import org.mockito.Mockito; @SmallTest @RunWith(JUnit4.class) -public class PagedStorageDiffHelperTest { +public class ContiguousDiffHelperTest { private interface CallbackValidator { void validate(ListUpdateCallback callback); } @@ -55,18 +51,13 @@ public class PagedStorageDiffHelperTest { } }; - public static Page<Integer, String> createPage(String... items) { - return new Page<>(Arrays.asList(items)); - } - - private static void validateTwoListDiff(PagedStorage<?, String> oldList, - PagedStorage<?, String> newList, + private void validateTwoListDiff(StringPagedList oldList, StringPagedList newList, CallbackValidator callbackValidator) { - DiffUtil.DiffResult diffResult = PagedStorageDiffHelper.computeDiff( - oldList, newList, DIFF_CALLBACK); + DiffUtil.DiffResult diffResult = ContiguousDiffHelper.computeDiff(oldList, newList, + DIFF_CALLBACK, false); - ListUpdateCallback listUpdateCallback = mock(ListUpdateCallback.class); - PagedStorageDiffHelper.dispatchDiff(listUpdateCallback, oldList, newList, diffResult); + ListUpdateCallback listUpdateCallback = Mockito.mock(ListUpdateCallback.class); + ContiguousDiffHelper.dispatchDiff(listUpdateCallback, oldList, newList, diffResult); callbackValidator.validate(listUpdateCallback); } @@ -74,35 +65,8 @@ public class PagedStorageDiffHelperTest { @Test public void sameListNoUpdates() { validateTwoListDiff( - new PagedStorage<>(5, createPage("a", "b", "c"), 5), - new PagedStorage<>(5, createPage("a", "b", "c"), 5), - new CallbackValidator() { - @Override - public void validate(ListUpdateCallback callback) { - verifyZeroInteractions(callback); - } - } - ); - } - - @Test - public void sameListNoUpdatesPlaceholder() { - PagedStorage<Integer, String> storageNoPlaceholder = - new PagedStorage<>(0, createPage("a", "b", "c"), 10); - - PagedStorage<Integer, String> storageWithPlaceholder = - new PagedStorage<>(0, createPage("a", "b", "c"), 10); - storageWithPlaceholder.allocatePlaceholders(3, 0, 3, - /* ignored */ mock(PagedStorage.Callback.class)); - - // even though one has placeholders, and null counts are different... - assertEquals(10, storageNoPlaceholder.getTrailingNullCount()); - assertEquals(7, storageWithPlaceholder.getTrailingNullCount()); - - // ... should be no interactions, since content still same - validateTwoListDiff( - storageNoPlaceholder, - storageWithPlaceholder, + new StringPagedList(5, 5, "a", "b", "c"), + new StringPagedList(5, 5, "a", "b", "c"), new CallbackValidator() { @Override public void validate(ListUpdateCallback callback) { @@ -115,8 +79,8 @@ public class PagedStorageDiffHelperTest { @Test public void appendFill() { validateTwoListDiff( - new PagedStorage<>(5, createPage("a", "b"), 5), - new PagedStorage<>(5, createPage("a", "b", "c"), 4), + new StringPagedList(5, 5, "a", "b"), + new StringPagedList(5, 4, "a", "b", "c"), new CallbackValidator() { @Override public void validate(ListUpdateCallback callback) { @@ -132,8 +96,8 @@ public class PagedStorageDiffHelperTest { @Test public void prependFill() { validateTwoListDiff( - new PagedStorage<>(5, createPage("b", "c"), 5), - new PagedStorage<>(4, createPage("a", "b", "c"), 5), + new StringPagedList(5, 5, "b", "c"), + new StringPagedList(4, 5, "a", "b", "c"), new CallbackValidator() { @Override public void validate(ListUpdateCallback callback) { @@ -149,8 +113,8 @@ public class PagedStorageDiffHelperTest { @Test public void change() { validateTwoListDiff( - new PagedStorage<>(5, createPage("a1", "b1", "c1"), 5), - new PagedStorage<>(5, createPage("a2", "b1", "c2"), 5), + new StringPagedList(5, 5, "a1", "b1", "c1"), + new StringPagedList(5, 5, "a2", "b1", "c2"), new CallbackValidator() { @Override public void validate(ListUpdateCallback callback) { @@ -161,5 +125,4 @@ public class PagedStorageDiffHelperTest { } ); } - } diff --git a/android/arch/paging/ContiguousPagedList.java b/android/arch/paging/ContiguousPagedList.java index 7835dbe3..d8907c3b 100644 --- a/android/arch/paging/ContiguousPagedList.java +++ b/android/arch/paging/ContiguousPagedList.java @@ -16,136 +16,101 @@ package android.arch.paging; -import android.support.annotation.AnyThread; import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; +import android.support.annotation.WorkerThread; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +/** @hide */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +class ContiguousPagedList<T> extends NullPaddedList<T> { + + private final ContiguousDataSource<?, T> mDataSource; + private final Executor mMainThreadExecutor; + private final Executor mBackgroundThreadExecutor; + private final Config mConfig; -class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback { - private final ContiguousDataSource<K, V> mDataSource; private boolean mPrependWorkerRunning = false; private boolean mAppendWorkerRunning = false; private int mPrependItemsRequested = 0; private int mAppendItemsRequested = 0; - @SuppressWarnings("unchecked") - private final PagedStorage<K, V> mKeyedStorage = (PagedStorage<K, V>) mStorage; - - private final PageResult.Receiver<K, V> mReceiver = new PageResult.Receiver<K, V>() { - @AnyThread - @Override - public void postOnPageResult(@NonNull final PageResult<K, V> pageResult) { - // NOTE: if we're already on main thread, this can delay page receive by a frame - mMainThreadExecutor.execute(new Runnable() { - @Override - public void run() { - onPageResult(pageResult); - } - }); - } + private int mLastLoad = 0; + private T mLastItem = null; - @MainThread - @Override - public void onPageResult(@NonNull PageResult<K, V> pageResult) { - if (pageResult.page == null) { - detach(); - return; - } + private AtomicBoolean mDetached = new AtomicBoolean(false); - if (isDetached()) { - // No op, have detached - return; - } + private ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); - Page<K, V> page = pageResult.page; - if (pageResult.type == PageResult.INIT) { - mKeyedStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls, - pageResult.positionOffset, ContiguousPagedList.this); - notifyInserted(0, mKeyedStorage.size()); - } else if (pageResult.type == PageResult.APPEND) { - mKeyedStorage.appendPage(page, ContiguousPagedList.this); - } else if (pageResult.type == PageResult.PREPEND) { - mKeyedStorage.prependPage(page, ContiguousPagedList.this); - } - } - }; - - ContiguousPagedList( - @NonNull ContiguousDataSource<K, V> dataSource, + @WorkerThread + <K> ContiguousPagedList(@NonNull ContiguousDataSource<K, T> dataSource, @NonNull Executor mainThreadExecutor, @NonNull Executor backgroundThreadExecutor, - @NonNull Config config, - final @Nullable K key) { - super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor, config); - mDataSource = dataSource; - - // blocking init just triggers the initial load on the construction thread - - // Could still be posted with callback, if desired. - mDataSource.loadInitial(key, - mConfig.mInitialLoadSizeHint, - mConfig.mEnablePlaceholders, - mReceiver); - } - - @MainThread - @Override - void dispatchUpdatesSinceSnapshot( - @NonNull PagedList<V> pagedListSnapshot, @NonNull Callback callback) { - - final PagedStorage<?, V> snapshot = pagedListSnapshot.mStorage; - - final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended(); - final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended(); - - final int previousTrailing = snapshot.getTrailingNullCount(); - final int previousLeading = snapshot.getLeadingNullCount(); - - // Validate that the snapshot looks like a previous version of this list - if it's not, - // we can't be sure we'll dispatch callbacks safely - if (newlyAppended < 0 - || newlyPrepended < 0 - || mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0) - || mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0) - || (mStorage.getStorageCount() - != snapshot.getStorageCount() + newlyAppended + newlyPrepended)) { - throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear" - + " to be a snapshot of this PagedList"); - } - - if (newlyAppended != 0) { - final int changedCount = Math.min(previousTrailing, newlyAppended); - final int addedCount = newlyAppended - changedCount; + Config config, + @Nullable K key) { + super(); - final int endPosition = snapshot.getLeadingNullCount() + snapshot.getStorageCount(); - if (changedCount != 0) { - callback.onChanged(endPosition, changedCount); + mDataSource = dataSource; + mMainThreadExecutor = mainThreadExecutor; + mBackgroundThreadExecutor = backgroundThreadExecutor; + mConfig = config; + NullPaddedList<T> initialState = dataSource.loadInitial( + key, config.mInitialLoadSizeHint, config.mEnablePlaceholders); + + if (initialState != null) { + mPositionOffset = initialState.getPositionOffset(); + + mLeadingNullCount = initialState.getLeadingNullCount(); + mList = new ArrayList<>(initialState.mList); + mTrailingNullCount = initialState.getTrailingNullCount(); + + if (initialState.getLeadingNullCount() == 0 + && initialState.getTrailingNullCount() == 0 + && config.mPrefetchDistance < 1) { + throw new IllegalArgumentException("Null padding is required to support the 0" + + " prefetch case - require either null items or prefetching to fetch" + + " beyond initial load."); } - if (addedCount != 0) { - callback.onInserted(endPosition + changedCount, addedCount); + + if (initialState.size() != 0) { + mLastLoad = mLeadingNullCount + mList.size() / 2; + mLastItem = mList.get(mList.size() / 2); } + } else { + mList = new ArrayList<>(); + detach(); + } + if (mList.size() == 0) { + // Empty initial state, so don't try and fetch data. + mPrependWorkerRunning = true; + mAppendWorkerRunning = true; } - if (newlyPrepended != 0) { - final int changedCount = Math.min(previousLeading, newlyPrepended); - final int addedCount = newlyPrepended - changedCount; + } - if (changedCount != 0) { - callback.onChanged(previousLeading, changedCount); - } - if (addedCount != 0) { - callback.onInserted(0, addedCount); - } + @Override + public T get(int index) { + T item = super.get(index); + if (item != null) { + mLastItem = item; } + return item; } - @MainThread @Override - protected void loadAroundInternal(int index) { - int prependItems = mConfig.mPrefetchDistance - (index - mStorage.getLeadingNullCount()); - int appendItems = index + mConfig.mPrefetchDistance - - (mStorage.getLeadingNullCount() + mStorage.getStorageCount()); + public void loadAround(int index) { + mLastLoad = index + mPositionOffset; + + int prependItems = mConfig.mPrefetchDistance - (index - mLeadingNullCount); + int appendItems = index + mConfig.mPrefetchDistance - (mLeadingNullCount + mList.size()); mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested); if (mPrependItemsRequested > 0) { @@ -158,6 +123,21 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal } } + @Override + public int getLoadedCount() { + return mList.size(); + } + + @Override + public int getLeadingNullCount() { + return mLeadingNullCount; + } + + @Override + public int getTrailingNullCount() { + return mTrailingNullCount; + } + @MainThread private void schedulePrepend() { if (mPrependWorkerRunning) { @@ -165,17 +145,29 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal } mPrependWorkerRunning = true; - final int position = mStorage.getLeadingNullCount() + mStorage.getPositionOffset(); - - // safe to access first item here - mStorage can't be empty if we're prepending - final V item = mStorage.getFirstContiguousItem(); + final int position = mLeadingNullCount + mPositionOffset; + final T item = mList.get(0); mBackgroundThreadExecutor.execute(new Runnable() { @Override public void run() { - if (isDetached()) { + if (mDetached.get()) { return; } - mDataSource.loadBefore(position, item, mConfig.mPageSize, mReceiver); + + final List<T> data = mDataSource.loadBefore(position, item, mConfig.mPageSize); + if (data != null) { + mMainThreadExecutor.execute(new Runnable() { + @Override + public void run() { + if (mDetached.get()) { + return; + } + prependImpl(data); + } + }); + } else { + detach(); + } } }); } @@ -187,44 +179,56 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal } mAppendWorkerRunning = true; - final int position = mStorage.getLeadingNullCount() - + mStorage.getStorageCount() - 1 + mStorage.getPositionOffset(); - - // safe to access first item here - mStorage can't be empty if we're appending - final V item = mStorage.getLastContiguousItem(); + final int position = mLeadingNullCount + mList.size() - 1 + mPositionOffset; + final T item = mList.get(mList.size() - 1); mBackgroundThreadExecutor.execute(new Runnable() { @Override public void run() { - if (isDetached()) { + if (mDetached.get()) { return; } - mDataSource.loadAfter(position, item, mConfig.mPageSize, mReceiver); + + final List<T> data = mDataSource.loadAfter(position, item, mConfig.mPageSize); + if (data != null) { + mMainThreadExecutor.execute(new Runnable() { + @Override + public void run() { + if (mDetached.get()) { + return; + } + appendImpl(data); + } + }); + } else { + detach(); + } } }); } - @Override - boolean isContiguous() { - return true; - } + @MainThread + private void prependImpl(List<T> before) { + final int count = before.size(); + if (count == 0) { + // Nothing returned from source, stop loading in this direction + return; + } - @Nullable - @Override - public Object getLastKey() { - return mDataSource.getKey(mLastLoad, mLastItem); - } + Collections.reverse(before); + mList.addAll(0, before); - @MainThread - @Override - public void onInitialized(int count) { - notifyInserted(0, count); - } + final int changedCount = Math.min(mLeadingNullCount, count); + final int addedCount = count - changedCount; - @MainThread - @Override - public void onPagePrepended(int leadingNulls, int changedCount, int addedCount) { - // consider whether to post more work, now that a page is fully prepended - mPrependItemsRequested = mPrependItemsRequested - changedCount - addedCount; + if (changedCount != 0) { + mLeadingNullCount -= changedCount; + } + mPositionOffset -= addedCount; + mNumberPrepended += count; + + + // only try to post more work after fully prepended (with offsets / null counts updated) + mPrependItemsRequested -= count; mPrependWorkerRunning = false; if (mPrependItemsRequested > 0) { // not done prepending, keep going @@ -232,16 +236,39 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal } // finally dispatch callbacks, after prepend may have already been scheduled - notifyChanged(leadingNulls, changedCount); - notifyInserted(0, addedCount); + for (WeakReference<Callback> weakRef : mCallbacks) { + Callback callback = weakRef.get(); + if (callback != null) { + if (changedCount != 0) { + callback.onChanged(mLeadingNullCount, changedCount); + } + if (addedCount != 0) { + callback.onInserted(0, addedCount); + } + } + } } @MainThread - @Override - public void onPageAppended(int endPosition, int changedCount, int addedCount) { - // consider whether to post more work, now that a page is fully appended + private void appendImpl(List<T> after) { + final int count = after.size(); + if (count == 0) { + // Nothing returned from source, stop loading in this direction + return; + } + + mList.addAll(after); - mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount; + final int changedCount = Math.min(mTrailingNullCount, count); + final int addedCount = count - changedCount; + + if (changedCount != 0) { + mTrailingNullCount -= changedCount; + } + mNumberAppended += count; + + // only try to post more work after fully appended (with null counts updated) + mAppendItemsRequested -= count; mAppendWorkerRunning = false; if (mAppendItemsRequested > 0) { // not done appending, keep going @@ -249,19 +276,100 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal } // finally dispatch callbacks, after append may have already been scheduled - notifyChanged(endPosition, changedCount); - notifyInserted(endPosition + changedCount, addedCount); + for (WeakReference<Callback> weakRef : mCallbacks) { + Callback callback = weakRef.get(); + if (callback != null) { + final int endPosition = mLeadingNullCount + mList.size() - count; + if (changedCount != 0) { + callback.onChanged(endPosition, changedCount); + } + if (addedCount != 0) { + callback.onInserted(endPosition + changedCount, addedCount); + } + } + } } - @MainThread @Override - public void onPagePlaceholderInserted(int pageIndex) { - throw new IllegalStateException("Tiled callback on ContiguousPagedList"); + public boolean isImmutable() { + // TODO: return true if had nulls, and now getLoadedCount() == size(). Is that safe? + // Currently we don't prevent DataSources from returning more items than their null counts + return isDetached(); } - @MainThread @Override - public void onPageInserted(int start, int count) { - throw new IllegalStateException("Tiled callback on ContiguousPagedList"); + public void addWeakCallback(@Nullable PagedList<T> previousSnapshot, + @NonNull Callback callback) { + NullPaddedList<T> snapshot = (NullPaddedList<T>) previousSnapshot; + if (snapshot != this && snapshot != null) { + final int newlyAppended = mNumberAppended - snapshot.getNumberAppended(); + final int newlyPrepended = mNumberPrepended - snapshot.getNumberPrepended(); + + final int previousTrailing = snapshot.getTrailingNullCount(); + final int previousLeading = snapshot.getLeadingNullCount(); + + // Validate that the snapshot looks like a previous version of this list - if it's not, + // we can't be sure we'll dispatch callbacks safely + if (newlyAppended < 0 + || newlyPrepended < 0 + || mTrailingNullCount != Math.max(previousTrailing - newlyAppended, 0) + || mLeadingNullCount != Math.max(previousLeading - newlyPrepended, 0) + || snapshot.getLoadedCount() + newlyAppended + newlyPrepended != mList.size()) { + throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear" + + " to be a snapshot of this list"); + } + + if (newlyAppended != 0) { + final int changedCount = Math.min(previousTrailing, newlyAppended); + final int addedCount = newlyAppended - changedCount; + + final int endPosition = + snapshot.getLeadingNullCount() + snapshot.getLoadedCount(); + if (changedCount != 0) { + callback.onChanged(endPosition, changedCount); + } + if (addedCount != 0) { + callback.onInserted(endPosition + changedCount, addedCount); + } + } + if (newlyPrepended != 0) { + final int changedCount = Math.min(previousLeading, newlyPrepended); + final int addedCount = newlyPrepended - changedCount; + + if (changedCount != 0) { + callback.onChanged(previousLeading, changedCount); + } + if (addedCount != 0) { + callback.onInserted(0, addedCount); + } + } + } + mCallbacks.add(new WeakReference<>(callback)); + } + + @Override + public void removeWeakCallback(@NonNull Callback callback) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + Callback currentCallback = mCallbacks.get(i).get(); + if (currentCallback == null || currentCallback == callback) { + mCallbacks.remove(i); + } + } + } + + @Override + public boolean isDetached() { + return mDetached.get(); + } + + @SuppressWarnings("WeakerAccess") + public void detach() { + mDetached.set(true); + } + + @Nullable + @Override + public Object getLastKey() { + return mDataSource.getKey(mLastLoad, mLastItem); } } diff --git a/android/arch/paging/ContiguousPagedListTest.java b/android/arch/paging/ContiguousPagedListTest.java index 43f556a8..ee7ea6a4 100644 --- a/android/arch/paging/ContiguousPagedListTest.java +++ b/android/arch/paging/ContiguousPagedListTest.java @@ -16,7 +16,6 @@ package android.arch.paging; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.mockito.Mockito.mock; @@ -110,7 +109,6 @@ public class ContiguousPagedListTest { private void verifyRange(int start, int count, NullPaddedList<Item> actual) { if (mCounted) { - //noinspection UnnecessaryLocalVariable int expectedLeading = start; int expectedTrailing = ITEMS.size() - start - count; assertEquals(ITEMS.size(), actual.size()); @@ -134,38 +132,6 @@ public class ContiguousPagedListTest { } } - @SuppressWarnings("SuspiciousSystemArraycopy") - private void verifyRange(int start, int count, PagedStorage<?, Item> actual) { - if (mCounted) { - Item[] expected = new Item[ITEMS.size()]; - System.arraycopy(ITEMS.toArray(), start, expected, start, count); - assertArrayEquals(expected, actual.toArray()); - - //noinspection UnnecessaryLocalVariable - int expectedLeading = start; - int expectedTrailing = ITEMS.size() - start - count; - assertEquals(ITEMS.size(), actual.size()); - assertEquals(ITEMS.size() - expectedLeading - expectedTrailing, - actual.getStorageCount()); - assertEquals(expectedLeading, actual.getLeadingNullCount()); - assertEquals(expectedTrailing, actual.getTrailingNullCount()); - - } else { - Item[] expected = new Item[count]; - System.arraycopy(ITEMS.toArray(), start, expected, 0, count); - assertArrayEquals(expected, actual.toArray()); - - assertEquals(count, actual.size()); - assertEquals(actual.size(), actual.getStorageCount()); - assertEquals(0, actual.getLeadingNullCount()); - assertEquals(0, actual.getTrailingNullCount()); - } - } - - private void verifyRange(int start, int count, PagedList<Item> actual) { - verifyRange(start, count, actual.mStorage); - } - private void verifyCallback(PagedList.Callback callback, int countedPosition, int uncountedPosition) { if (mCounted) { @@ -188,7 +154,7 @@ public class ContiguousPagedListTest { } - private ContiguousPagedList<Integer, Item> createCountedPagedList( + private ContiguousPagedList<Item> createCountedPagedList( PagedList.Config config, int initialPosition) { TestSource source = new TestSource(); return new ContiguousPagedList<>( @@ -197,7 +163,7 @@ public class ContiguousPagedListTest { initialPosition); } - private ContiguousPagedList<Integer, Item> createCountedPagedList(int initialPosition) { + private ContiguousPagedList<Item> createCountedPagedList(int initialPosition) { return createCountedPagedList( new PagedList.Config.Builder() .setInitialLoadSizeHint(40) @@ -208,14 +174,8 @@ public class ContiguousPagedListTest { } @Test - public void construct() { - ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(0); - verifyRange(0, 40, pagedList); - } - - @Test public void append() { - ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(0); + ContiguousPagedList<Item> pagedList = createCountedPagedList(0); PagedList.Callback callback = mock(PagedList.Callback.class); pagedList.addWeakCallback(null, callback); verifyRange(0, 40, pagedList); @@ -232,7 +192,7 @@ public class ContiguousPagedListTest { @Test public void prepend() { - ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(80); + ContiguousPagedList<Item> pagedList = createCountedPagedList(80); PagedList.Callback callback = mock(PagedList.Callback.class); pagedList.addWeakCallback(null, callback); verifyRange(60, 40, pagedList); @@ -248,7 +208,7 @@ public class ContiguousPagedListTest { @Test public void outwards() { - ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(50); + ContiguousPagedList<Item> pagedList = createCountedPagedList(50); PagedList.Callback callback = mock(PagedList.Callback.class); pagedList.addWeakCallback(null, callback); verifyRange(30, 40, pagedList); @@ -271,7 +231,7 @@ public class ContiguousPagedListTest { @Test public void multiAppend() { - ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(0); + ContiguousPagedList<Item> pagedList = createCountedPagedList(0); PagedList.Callback callback = mock(PagedList.Callback.class); pagedList.addWeakCallback(null, callback); verifyRange(0, 40, pagedList); @@ -288,7 +248,7 @@ public class ContiguousPagedListTest { @Test public void distantPrefetch() { - ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList( + ContiguousPagedList<Item> pagedList = createCountedPagedList( new PagedList.Config.Builder() .setInitialLoadSizeHint(10) .setPageSize(10) @@ -314,7 +274,7 @@ public class ContiguousPagedListTest { @Test public void appendCallbackAddedLate() { - ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(0); + ContiguousPagedList<Item> pagedList = createCountedPagedList(0); verifyRange(0, 40, pagedList); pagedList.loadAround(35); @@ -322,7 +282,7 @@ public class ContiguousPagedListTest { verifyRange(0, 60, pagedList); // snapshot at 60 items - PagedList<Item> snapshot = (PagedList<Item>) pagedList.snapshot(); + NullPaddedList<Item> snapshot = (NullPaddedList<Item>) pagedList.snapshot(); verifyRange(0, 60, snapshot); @@ -340,7 +300,7 @@ public class ContiguousPagedListTest { @Test public void prependCallbackAddedLate() { - ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(80); + ContiguousPagedList<Item> pagedList = createCountedPagedList(80); verifyRange(60, 40, pagedList); pagedList.loadAround(mCounted ? 65 : 5); @@ -348,7 +308,7 @@ public class ContiguousPagedListTest { verifyRange(40, 60, pagedList); // snapshot at 60 items - PagedList<Item> snapshot = (PagedList<Item>) pagedList.snapshot(); + NullPaddedList<Item> snapshot = (NullPaddedList<Item>) pagedList.snapshot(); verifyRange(40, 60, snapshot); diff --git a/android/arch/paging/DataSource.java b/android/arch/paging/DataSource.java index 524e570a..48fbec5f 100644 --- a/android/arch/paging/DataSource.java +++ b/android/arch/paging/DataSource.java @@ -17,7 +17,6 @@ package android.arch.paging; import android.support.annotation.AnyThread; -import android.support.annotation.NonNull; import android.support.annotation.WorkerThread; import java.util.concurrent.CopyOnWriteArrayList; @@ -61,6 +60,15 @@ public abstract class DataSource<Key, Value> { public static int COUNT_UNDEFINED = -1; /** + * Number of items that this DataSource can provide in total, or {@link #COUNT_UNDEFINED}. + * + * @return number of items that this DataSource can provide in total, or + * {@link #COUNT_UNDEFINED} if expensive or undesired to compute. + */ + @WorkerThread + public abstract int countItems(); + + /** * Returns true if the data source guaranteed to produce a contiguous set of items, * never producing gaps. */ @@ -103,7 +111,7 @@ public abstract class DataSource<Key, Value> { */ @AnyThread @SuppressWarnings("WeakerAccess") - public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) { + public void addInvalidatedCallback(InvalidatedCallback onInvalidatedCallback) { mOnInvalidatedCallbacks.add(onInvalidatedCallback); } @@ -114,7 +122,7 @@ public abstract class DataSource<Key, Value> { */ @AnyThread @SuppressWarnings("WeakerAccess") - public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) { + public void removeInvalidatedCallback(InvalidatedCallback onInvalidatedCallback) { mOnInvalidatedCallbacks.remove(onInvalidatedCallback); } diff --git a/android/arch/paging/KeyedDataSource.java b/android/arch/paging/KeyedDataSource.java index 0d452946..8cf6829c 100644 --- a/android/arch/paging/KeyedDataSource.java +++ b/android/arch/paging/KeyedDataSource.java @@ -103,6 +103,10 @@ import java.util.List; * @param <Value> Type of items being loaded by the DataSource. */ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> { + @Override + public final int countItems() { + return 0; // method not called, can't be overridden + } @Nullable @Override @@ -114,14 +118,7 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K @Override List<Value> loadBeforeImpl( int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize) { - List<Value> list = loadBefore(getKey(currentBeginItem), pageSize); - - if (list != null && list.size() > 1) { - // TODO: move out of keyed entirely, into the DB DataSource. - list = new ArrayList<>(list); - Collections.reverse(list); - } - return list; + return loadBefore(getKey(currentBeginItem), pageSize); } @Nullable @@ -194,8 +191,6 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K /** @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @WorkerThread - @Override public NullPaddedList<Value> loadInitial( @Nullable Key key, int initialLoadSize, boolean enablePlaceholders) { if (isInvalid()) { diff --git a/android/arch/paging/LivePagedListProvider.java b/android/arch/paging/LivePagedListProvider.java index 07dd84bf..b7c68dd6 100644 --- a/android/arch/paging/LivePagedListProvider.java +++ b/android/arch/paging/LivePagedListProvider.java @@ -16,133 +16,5 @@ package android.arch.paging; -import android.arch.core.executor.ArchTaskExecutor; -import android.arch.lifecycle.ComputableLiveData; -import android.arch.lifecycle.LiveData; -import android.support.annotation.AnyThread; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; - -/** - * Provides a {@code LiveData<PagedList>}, given a means to construct a DataSource. - * <p> - * Return type for data-loading system of an application or library to produce a - * {@code LiveData<PagedList>}, while leaving the details of the paging mechanism up to the - * consumer. - * <p> - * If you're using Room, it can generate a LivePagedListProvider from a query: - * <pre> - * {@literal @}Dao - * interface UserDao { - * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC") - * public abstract LivePagedListProvider<Integer, User> usersByLastName(); - * }</pre> - * In the above sample, {@code Integer} is used because it is the {@code Key} type of - * {@link TiledDataSource}. Currently, Room can only generate a {@code LIMIT}/{@code OFFSET}, - * position based loader that uses TiledDataSource under the hood, and specifying {@code Integer} - * here lets you pass an initial loading position as an integer. - * <p> - * In the future, Room plans to offer other key types to support paging content with a - * {@link KeyedDataSource}. - * - * @param <Key> Type of input valued used to load data from the DataSource. Must be integer if - * you're using TiledDataSource. - * @param <Value> Data type produced by the DataSource, and held by the PagedLists. - * - * @see PagedListAdapter - * @see DataSource - * @see PagedList - */ -public abstract class LivePagedListProvider<Key, Value> { - - /** - * Construct a new data source to be wrapped in a new PagedList, which will be returned - * through the LiveData. - * - * @return The data source. - */ - @WorkerThread - protected abstract DataSource<Key, Value> createDataSource(); - - /** - * Creates a LiveData of PagedLists, given the page size. - * <p> - * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a - * {@link android.support.v7.widget.RecyclerView}. - * - * @param initialLoadKey Initial key used to load initial data from the data source. - * @param pageSize Page size defining how many items are loaded from a data source at a time. - * Recommended to be multiple times the size of item displayed at once. - * - * @return The LiveData of PagedLists. - */ - @AnyThread - @NonNull - public LiveData<PagedList<Value>> create(@Nullable Key initialLoadKey, int pageSize) { - return create(initialLoadKey, - new PagedList.Config.Builder() - .setPageSize(pageSize) - .build()); - } - - /** - * Creates a LiveData of PagedLists, given the PagedList.Config. - * <p> - * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a - * {@link android.support.v7.widget.RecyclerView}. - * - * @param initialLoadKey Initial key to pass to the data source to initialize data with. - * @param config PagedList.Config to use with created PagedLists. This specifies how the - * lists will load data. - * - * @return The LiveData of PagedLists. - */ - @AnyThread - @NonNull - public LiveData<PagedList<Value>> create(@Nullable final Key initialLoadKey, - final PagedList.Config config) { - return new ComputableLiveData<PagedList<Value>>() { - @Nullable - private PagedList<Value> mList; - @Nullable - private DataSource<Key, Value> mDataSource; - - private final DataSource.InvalidatedCallback mCallback = - new DataSource.InvalidatedCallback() { - @Override - public void onInvalidated() { - invalidate(); - } - }; - - @Override - protected PagedList<Value> compute() { - @Nullable Key initializeKey = initialLoadKey; - if (mList != null) { - //noinspection unchecked - initializeKey = (Key) mList.getLastKey(); - } - - do { - if (mDataSource != null) { - mDataSource.removeInvalidatedCallback(mCallback); - } - - mDataSource = createDataSource(); - mDataSource.addInvalidatedCallback(mCallback); - - mList = new PagedList.Builder<Key, Value>() - .setDataSource(mDataSource) - .setMainThreadExecutor(ArchTaskExecutor.getMainThreadExecutor()) - .setBackgroundThreadExecutor( - ArchTaskExecutor.getIOThreadExecutor()) - .setConfig(config) - .setInitialKey(initializeKey) - .build(); - } while (mList.isDetached()); - return mList; - } - }.getLiveData(); - } -} +abstract public class LivePagedListProvider<K, T> { +}
\ No newline at end of file diff --git a/android/arch/paging/NullPaddedList.java b/android/arch/paging/NullPaddedList.java index c7b0b231..43000302 100644 --- a/android/arch/paging/NullPaddedList.java +++ b/android/arch/paging/NullPaddedList.java @@ -16,9 +16,11 @@ package android.arch.paging; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.RestrictTo; -import java.util.AbstractList; +import java.util.ArrayList; import java.util.List; /** @@ -29,11 +31,18 @@ import java.util.List; * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -public class NullPaddedList<Type> extends AbstractList<Type> { +public class NullPaddedList<Type> extends PagedList<Type> { List<Type> mList; - private int mTrailingNullCount; - private int mLeadingNullCount; - private int mPositionOffset; + int mTrailingNullCount; + int mLeadingNullCount; + int mPositionOffset; + + // track the items prepended/appended since the PagedList was initialized + int mNumberPrepended; + int mNumberAppended; + + NullPaddedList() { + } @Override public String toString() { @@ -82,6 +91,20 @@ public class NullPaddedList<Type> extends AbstractList<Type> { mPositionOffset = positionOffset; } + /** + * Create a copy of the passed NullPaddedList. + * + * @param other Other list to copy. + */ + NullPaddedList(NullPaddedList<Type> other) { + mLeadingNullCount = other.getLeadingNullCount(); + mList = other.isImmutable() ? other.mList : new ArrayList<>(other.mList); + mTrailingNullCount = other.getTrailingNullCount(); + + mNumberPrepended = other.getNumberPrepended(); + mNumberAppended = other.getNumberAppended(); + } + // --------------- PagedList API --------------- @Override @@ -101,12 +124,46 @@ public class NullPaddedList<Type> extends AbstractList<Type> { } @Override + public void loadAround(int index) { + // do nothing - immutable, so no fetching will be done + } + + @Override public final int size() { return getLoadedCount() + getLeadingNullCount() + getTrailingNullCount(); } + public boolean isImmutable() { + return true; + } + + @Override + public PagedList<Type> snapshot() { + if (isImmutable()) { + return this; + } + return new NullPaddedList<>(this); + } + + @Override + boolean isContiguous() { + return true; + } + + @Override + public void addWeakCallback(@Nullable PagedList<Type> previousSnapshot, + @NonNull Callback callback) { + // no op, immutable + } + + @Override + public void removeWeakCallback(Callback callback) { + // no op, immutable + } + // --------------- Contiguous API --------------- + @Override public int getPositionOffset() { return mPositionOffset; } @@ -137,4 +194,12 @@ public class NullPaddedList<Type> extends AbstractList<Type> { public int getTrailingNullCount() { return mTrailingNullCount; } + + int getNumberPrepended() { + return mNumberPrepended; + } + + int getNumberAppended() { + return mNumberAppended; + } } diff --git a/android/arch/paging/Page.java b/android/arch/paging/Page.java deleted file mode 100644 index e9890ed4..00000000 --- a/android/arch/paging/Page.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.arch.paging; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.List; - -/** - * Immutable class representing a page of data loaded from a DataSource. - * <p> - * Optionally stores before/after keys for cases where they cannot be computed, but the DataSource - * can provide them as part of loading a page. - * <p> - * A page's list must never be modified. - */ -class Page<K, V> { - @SuppressWarnings("WeakerAccess") - @Nullable - public final K beforeKey; - @NonNull - public final List<V> items; - @SuppressWarnings("WeakerAccess") - @Nullable - public K afterKey; - - Page(@NonNull List<V> items) { - this(null, items, null); - } - - Page(@Nullable K beforeKey, @NonNull List<V> items, @Nullable K afterKey) { - this.beforeKey = beforeKey; - this.items = items; - this.afterKey = afterKey; - } -} diff --git a/android/arch/paging/PageArrayList.java b/android/arch/paging/PageArrayList.java new file mode 100644 index 00000000..b90d055a --- /dev/null +++ b/android/arch/paging/PageArrayList.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.arch.paging; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; + +import java.util.ArrayList; +import java.util.List; + +/** @hide */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +class PageArrayList<T> extends PagedList<T> { + // partial list of pages, doesn't include pages below the lowest accessed, or above the highest + final ArrayList<List<T>> mPages; + + // to access page at index N, do mPages.get(N - mPageIndexOffset), but do bounds checking first! + int mPageIndexOffset; + + final int mPageSize; + final int mCount; + final int mMaxPageCount; + + PageArrayList(int pageSize, int count) { + mPages = new ArrayList<>(); + mPageSize = pageSize; + mCount = count; + mMaxPageCount = (mCount + mPageSize - 1) / mPageSize; + } + + private PageArrayList(PageArrayList<T> other) { + mPages = other.isImmutable() ? other.mPages : new ArrayList<>(other.mPages); + mPageIndexOffset = other.mPageIndexOffset; + mPageSize = other.mPageSize; + mCount = other.size(); + mMaxPageCount = other.mMaxPageCount; + } + + @Override + public T get(int index) { + if (index < 0 || index >= mCount) { + throw new IllegalArgumentException(); + } + + int localPageIndex = getLocalPageIndex(index); + + List<T> page = getPage(localPageIndex); + + if (page == null) { + // page empty + return null; + } + + return page.get(index % mPageSize); + } + + @Nullable + private List<T> getPage(int localPageIndex) { + if (localPageIndex < 0 || localPageIndex >= mPages.size()) { + // page not present + return null; + } + + return mPages.get(localPageIndex); + } + + private int getLocalPageIndex(int index) { + return index / mPageSize - mPageIndexOffset; + } + + @Override + public void loadAround(int index) { + // do nothing - immutable, so no fetching will be done + } + + @Override + public int size() { + return mCount; + } + + @Override + public boolean isImmutable() { + return true; + } + + boolean hasPage(int pageIndex) { + final int localPageIndex = pageIndex - mPageIndexOffset; + List<T> page = getPage(localPageIndex); + return page != null && page.size() != 0; + } + + @Override + public PagedList<T> snapshot() { + if (isImmutable()) { + return this; + } + return new PageArrayList<>(this); + } + + @Override + boolean isContiguous() { + return false; + } + + @Override + public void addWeakCallback(@Nullable PagedList<T> previousSnapshot, + @NonNull Callback callback) { + // no op, immutable + } + + @Override + public void removeWeakCallback(Callback callback) { + // no op, immutable + } +} diff --git a/android/arch/paging/PageArrayListTest.java b/android/arch/paging/PageArrayListTest.java new file mode 100644 index 00000000..135e640d --- /dev/null +++ b/android/arch/paging/PageArrayListTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.arch.paging; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; +import java.util.List; + +@RunWith(JUnit4.class) +public class PageArrayListTest { + @Test + public void simple() { + List<String> data = Arrays.asList("A", "B", "C", "D", "E", "F"); + PageArrayList<String> list = new PageArrayList<>(2, data.size()); + + assertEquals(2, list.mPageSize); + assertEquals(data.size(), list.size()); + assertEquals(3, list.mMaxPageCount); + + for (int i = 0; i < data.size(); i++) { + assertEquals(null, list.get(i)); + } + for (int i = 0; i < data.size(); i += list.mPageSize) { + list.mPages.add(data.subList(i, i + 2)); + } + for (int i = 0; i < data.size(); i++) { + assertEquals(data.get(i), list.get(i)); + } + } +} diff --git a/android/arch/paging/PageResult.java b/android/arch/paging/PageResult.java deleted file mode 100644 index a4090f61..00000000 --- a/android/arch/paging/PageResult.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.arch.paging; - -import android.support.annotation.AnyThread; -import android.support.annotation.MainThread; -import android.support.annotation.NonNull; - -class PageResult<K, V> { - static final int INIT = 0; - - // contiguous results - static final int APPEND = 1; - static final int PREPEND = 2; - - // non-contiguous, tile result - static final int TILE = 3; - - public final int type; - public final Page<K, V> page; - @SuppressWarnings("WeakerAccess") - public final int leadingNulls; - @SuppressWarnings("WeakerAccess") - public final int trailingNulls; - @SuppressWarnings("WeakerAccess") - public final int positionOffset; - - PageResult(int type, Page<K, V> page, int leadingNulls, int trailingNulls, int positionOffset) { - this.type = type; - this.page = page; - this.leadingNulls = leadingNulls; - this.trailingNulls = trailingNulls; - this.positionOffset = positionOffset; - } - - interface Receiver<K, V> { - @AnyThread - void postOnPageResult(@NonNull PageResult<K, V> pageResult); - @MainThread - void onPageResult(@NonNull PageResult<K, V> pageResult); - } -} diff --git a/android/arch/paging/PagedList.java b/android/arch/paging/PagedList.java index 1f07bfab..6a31b689 100644 --- a/android/arch/paging/PagedList.java +++ b/android/arch/paging/PagedList.java @@ -20,12 +20,9 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; -import java.lang.ref.WeakReference; import java.util.AbstractList; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; /** * Lazy loading list that pages in content from a {@link DataSource}. @@ -93,28 +90,9 @@ import java.util.concurrent.atomic.AtomicBoolean; * @param <T> The type of the entries in the list. */ public abstract class PagedList<T> extends AbstractList<T> { - final Executor mMainThreadExecutor; - final Executor mBackgroundThreadExecutor; - final Config mConfig; - - @NonNull - final PagedStorage<?, T> mStorage; - - int mLastLoad = 0; - T mLastItem = null; - - private final AtomicBoolean mDetached = new AtomicBoolean(false); - - protected final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); - - PagedList(@NonNull PagedStorage<?, T> storage, - @NonNull Executor mainThreadExecutor, - @NonNull Executor backgroundThreadExecutor, - @NonNull Config config) { - mStorage = storage; - mMainThreadExecutor = mainThreadExecutor; - mBackgroundThreadExecutor = backgroundThreadExecutor; - mConfig = config; + // Since we currently rely on implementation details of two implementations, + // prevent external subclassing + PagedList() { } /** @@ -302,13 +280,7 @@ public abstract class PagedList<T> extends AbstractList<T> { */ @Override @Nullable - public T get(int index) { - T item = mStorage.get(index); - if (item != null) { - mLastItem = item; - } - return item; - } + public abstract T get(int index); /** @@ -316,10 +288,7 @@ public abstract class PagedList<T> extends AbstractList<T> { * * @param index Index at which to load. */ - public void loadAround(int index) { - mLastLoad = index + getPositionOffset(); - loadAroundInternal(index); - } + public abstract void loadAround(int index); /** @@ -328,9 +297,7 @@ public abstract class PagedList<T> extends AbstractList<T> { * @return Current total size of the list. */ @Override - public int size() { - return mStorage.size(); - } + public abstract int size(); /** * Returns whether the list is immutable. Immutable lists may not become mutable again, and may @@ -338,25 +305,15 @@ public abstract class PagedList<T> extends AbstractList<T> { * * @return True if the PagedList is immutable. */ - @SuppressWarnings("WeakerAccess") - public boolean isImmutable() { - return isDetached(); - } + public abstract boolean isImmutable(); /** * Returns an immutable snapshot of the PagedList. If this PagedList is already * immutable, it will be returned. * - * @return Immutable snapshot of PagedList data. + * @return Immutable snapshot of PagedList, which may be the PagedList itself. */ - @NonNull - public List<T> snapshot() { - if (isImmutable()) { - return this; - } - - return new SnapshotPagedList<>(this); - } + public abstract List<T> snapshot(); abstract boolean isContiguous(); @@ -371,7 +328,9 @@ public abstract class PagedList<T> extends AbstractList<T> { * @return Key of position most recently passed to {@link #loadAround(int)}. */ @Nullable - public abstract Object getLastKey(); + public Object getLastKey() { + return null; + } /** * True if the PagedList has detached the DataSource it was loading from, and will no longer @@ -379,9 +338,8 @@ public abstract class PagedList<T> extends AbstractList<T> { * * @return True if the data source is detached. */ - @SuppressWarnings("WeakerAccess") public boolean isDetached() { - return mDetached.get(); + return true; } /** @@ -391,9 +349,7 @@ public abstract class PagedList<T> extends AbstractList<T> { * signal to stop loading. The PagedList will continue to present existing data, but will not * initiate new loads. */ - @SuppressWarnings("WeakerAccess") public void detach() { - mDetached.set(true); } /** @@ -405,7 +361,7 @@ public abstract class PagedList<T> extends AbstractList<T> { * If the DataSource is a {@link KeyedDataSource}, and thus doesn't use positions, returns 0. */ public int getPositionOffset() { - return mStorage.getPositionOffset(); + return 0; } /** @@ -429,68 +385,16 @@ public abstract class PagedList<T> extends AbstractList<T> { * @param callback Callback to dispatch to. * @see #removeWeakCallback(Callback) */ - @SuppressWarnings("WeakerAccess") - public void addWeakCallback(@Nullable List<T> previousSnapshot, @NonNull Callback callback) { - if (previousSnapshot != null && previousSnapshot != this) { - PagedList<T> storageSnapshot = (PagedList<T>) previousSnapshot; - //noinspection unchecked - dispatchUpdatesSinceSnapshot(storageSnapshot, callback); - } - - // first, clean up any empty weak refs - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - Callback currentCallback = mCallbacks.get(i).get(); - if (currentCallback == null) { - mCallbacks.remove(i); - } - } + public abstract void addWeakCallback(@Nullable PagedList<T> previousSnapshot, + @NonNull Callback callback); - // then add the new one - mCallbacks.add(new WeakReference<>(callback)); - } /** * Removes a previously added callback. * * @param callback Callback, previously added. - * @see #addWeakCallback(List, Callback) + * @see #addWeakCallback(PagedList, Callback) */ - @SuppressWarnings("WeakerAccess") - public void removeWeakCallback(@NonNull Callback callback) { - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - Callback currentCallback = mCallbacks.get(i).get(); - if (currentCallback == null || currentCallback == callback) { - // found callback, or empty weak ref - mCallbacks.remove(i); - } - } - } - - void notifyInserted(int position, int count) { - if (count != 0) { - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - Callback callback = mCallbacks.get(i).get(); - if (callback != null) { - callback.onInserted(position, count); - } - } - } - } - - void notifyChanged(int position, int count) { - if (count != 0) { - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - Callback callback = mCallbacks.get(i).get(); - if (callback != null) { - callback.onChanged(position, count); - } - } - } - } - - abstract void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> snapshot, - @NonNull Callback callback); - - abstract void loadAroundInternal(int index); + public abstract void removeWeakCallback(Callback callback); /** * Callback signaling when content is loaded into the list. @@ -641,15 +545,10 @@ public abstract class PagedList<T> extends AbstractList<T> { * Defines how many items to load when first load occurs, if you are using a * {@link KeyedDataSource}. * <p> - * This value is typically larger than page size, so on first load data there's a large - * enough range of content loaded to cover small scrolls. - * <p> - * If used with a {@link TiledDataSource}, this value is rounded to the nearest number - * of pages, with a minimum of two pages, and loaded with a single call to - * {@link TiledDataSource#loadRange(int, int)}. - * <p> - * If used with a {@link KeyedDataSource}, this value will be passed to - * {@link KeyedDataSource#loadInitial(int)}. + * If you are using an {@link TiledDataSource}, this value is currently ignored. + * Otherwise, this value will be passed to + * {@link KeyedDataSource#loadInitial(int)} to load a (typically) larger amount + * of data on first load. * <p> * If not set, defaults to three times page size. * diff --git a/android/arch/paging/PagedListAdapterHelper.java b/android/arch/paging/PagedListAdapterHelper.java index abcff415..c7b61d9f 100644 --- a/android/arch/paging/PagedListAdapterHelper.java +++ b/android/arch/paging/PagedListAdapterHelper.java @@ -25,6 +25,8 @@ import android.support.v7.util.DiffUtil; import android.support.v7.util.ListUpdateCallback; import android.support.v7.widget.RecyclerView; +import java.util.List; + /** * Helper object for mapping a {@link PagedList} into a * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}. @@ -118,15 +120,15 @@ import android.support.v7.widget.RecyclerView; * @param <T> Type of the PagedLists this helper will receive. */ public class PagedListAdapterHelper<T> { - // updateCallback notifications must only be notified *after* new data and item count are stored - // this ensures Adapter#notifyItemRangeInserted etc are accessing the new data private final ListUpdateCallback mUpdateCallback; private final ListAdapterConfig<T> mConfig; + // true if our listener is detached from mList, because it's been snapshotted + private boolean mUpdateScheduled; + private boolean mIsContiguous; - private PagedList<T> mPagedList; - private PagedList<T> mSnapshot; + private PagedList<T> mList; // Max generation of currently scheduled runnable private int mMaxScheduledGeneration; @@ -180,17 +182,12 @@ public class PagedListAdapterHelper<T> { @SuppressWarnings("WeakerAccess") @Nullable public T getItem(int index) { - if (mPagedList == null) { - if (mSnapshot == null) { - throw new IndexOutOfBoundsException( - "Item count is zero, getItem() call is invalid"); - } else { - return mSnapshot.get(index); - } + if (mList == null) { + throw new IndexOutOfBoundsException("Item count is zero, getItem() call is invalid"); } - mPagedList.loadAround(index); - return mPagedList.get(index); + mList.loadAround(index); + return mList.get(index); } /** @@ -201,11 +198,7 @@ public class PagedListAdapterHelper<T> { */ @SuppressWarnings("WeakerAccess") public int getItemCount() { - if (mPagedList != null) { - return mPagedList.size(); - } - - return mSnapshot == null ? 0 : mSnapshot.size(); + return mList == null ? 0 : mList.size(); } /** @@ -219,7 +212,7 @@ public class PagedListAdapterHelper<T> { */ public void setList(final PagedList<T> pagedList) { if (pagedList != null) { - if (mPagedList == null && mSnapshot == null) { + if (mList == null) { mIsContiguous = pagedList.isContiguous(); } else { if (pagedList.isContiguous() != mIsContiguous) { @@ -229,7 +222,7 @@ public class PagedListAdapterHelper<T> { } } - if (pagedList == mPagedList) { + if (pagedList == mList) { // nothing to do return; } @@ -238,55 +231,49 @@ public class PagedListAdapterHelper<T> { final int runGeneration = ++mMaxScheduledGeneration; if (pagedList == null) { - int removedCount = getItemCount(); - if (mPagedList != null) { - mPagedList.removeWeakCallback(mPagedListCallback); - mPagedList = null; - } else if (mSnapshot != null) { - mSnapshot = null; - } - // dispatch update callback after updating mPagedList/mSnapshot - mUpdateCallback.onRemoved(0, removedCount); + mUpdateCallback.onRemoved(0, mList.size()); + mList.removeWeakCallback(mPagedListCallback); + mList = null; return; } - if (mPagedList == null && mSnapshot == null) { + if (mList == null) { // fast simple first insert - mPagedList = pagedList; - pagedList.addWeakCallback(null, mPagedListCallback); - - // dispatch update callback after updating mPagedList/mSnapshot mUpdateCallback.onInserted(0, pagedList.size()); + mList = pagedList; + pagedList.addWeakCallback(null, mPagedListCallback); return; } - if (mPagedList != null) { + if (!mList.isImmutable()) { // first update scheduled on this list, so capture mPages as a snapshot, removing // callbacks so we don't have resolve updates against a moving target - mPagedList.removeWeakCallback(mPagedListCallback); - mSnapshot = (PagedList<T>) mPagedList.snapshot(); - mPagedList = null; - } - - if (mSnapshot == null || mPagedList != null) { - throw new IllegalStateException("must be in snapshot state to diff"); + mList.removeWeakCallback(mPagedListCallback); + mList = (PagedList<T>) mList.snapshot(); } - final PagedList<T> oldSnapshot = mSnapshot; - final PagedList<T> newSnapshot = (PagedList<T>) pagedList.snapshot(); + final PagedList<T> oldSnapshot = mList; + final List<T> newSnapshot = pagedList.snapshot(); + mUpdateScheduled = true; mConfig.getBackgroundThreadExecutor().execute(new Runnable() { @Override public void run() { final DiffUtil.DiffResult result; - result = PagedStorageDiffHelper.computeDiff( - oldSnapshot.mStorage, - newSnapshot.mStorage, - mConfig.getDiffCallback()); + if (mIsContiguous) { + result = ContiguousDiffHelper.computeDiff( + (NullPaddedList<T>) oldSnapshot, (NullPaddedList<T>) newSnapshot, + mConfig.getDiffCallback(), true); + } else { + result = SparseDiffHelper.computeDiff( + (PageArrayList<T>) oldSnapshot, (PageArrayList<T>) newSnapshot, + mConfig.getDiffCallback(), true); + } mConfig.getMainThreadExecutor().execute(new Runnable() { @Override public void run() { if (mMaxScheduledGeneration == runGeneration) { + mUpdateScheduled = false; latchPagedList(pagedList, newSnapshot, result); } } @@ -296,21 +283,16 @@ public class PagedListAdapterHelper<T> { } private void latchPagedList( - PagedList<T> newList, PagedList<T> diffSnapshot, + PagedList<T> newList, List<T> diffSnapshot, DiffUtil.DiffResult diffResult) { - if (mSnapshot == null || mPagedList != null) { - throw new IllegalStateException("must be in snapshot state to apply diff"); + if (mIsContiguous) { + ContiguousDiffHelper.dispatchDiff(mUpdateCallback, + (NullPaddedList<T>) mList, (ContiguousPagedList<T>) newList, diffResult); + } else { + SparseDiffHelper.dispatchDiff(mUpdateCallback, diffResult); } - - PagedList<T> previousSnapshot = mSnapshot; - mPagedList = newList; - mSnapshot = null; - - // dispatch update callback after updating mPagedList/mSnapshot - PagedStorageDiffHelper.dispatchDiff(mUpdateCallback, - previousSnapshot.mStorage, newList.mStorage, diffResult); - - newList.addWeakCallback(diffSnapshot, mPagedListCallback); + mList = newList; + newList.addWeakCallback((PagedList<T>) diffSnapshot, mPagedListCallback); } /** @@ -325,9 +307,6 @@ public class PagedListAdapterHelper<T> { @SuppressWarnings("WeakerAccess") @Nullable public PagedList<T> getCurrentList() { - if (mSnapshot != null) { - return mSnapshot; - } - return mPagedList; + return mList; } } diff --git a/android/arch/paging/PagedListAdapterHelperTest.java b/android/arch/paging/PagedListAdapterHelperTest.java index 963d0479..3518540c 100644 --- a/android/arch/paging/PagedListAdapterHelperTest.java +++ b/android/arch/paging/PagedListAdapterHelperTest.java @@ -21,7 +21,6 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; -import static junit.framework.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -290,64 +289,6 @@ public class PagedListAdapterHelperTest { assertFalse(helper.getCurrentList().isImmutable()); } - @Test - public void itemCountUpdatedBeforeListUpdateCallbacks() { - // verify that itemCount is updated in the helper before dispatching ListUpdateCallbacks - - final int[] expectedCount = new int[] { 0 }; - // provides access to helper, which must be constructed after callback - final PagedListAdapterHelper[] helperAccessor = new PagedListAdapterHelper[] { null }; - - ListUpdateCallback callback = new ListUpdateCallback() { - @Override - public void onInserted(int position, int count) { - assertEquals(expectedCount[0], helperAccessor[0].getItemCount()); - } - - @Override - public void onRemoved(int position, int count) { - assertEquals(expectedCount[0], helperAccessor[0].getItemCount()); - } - - @Override - public void onMoved(int fromPosition, int toPosition) { - fail("not expected"); - } - - @Override - public void onChanged(int position, int count, Object payload) { - fail("not expected"); - } - }; - - PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK); - helperAccessor[0] = helper; - - PagedList.Config config = new PagedList.Config.Builder() - .setPageSize(20) - .build(); - - - // in the fast-add case... - expectedCount[0] = 5; - assertEquals(0, helper.getItemCount()); - helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 5), 0)); - assertEquals(5, helper.getItemCount()); - - // in the slow, diff on BG thread case... - expectedCount[0] = 10; - assertEquals(5, helper.getItemCount()); - helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 10), 0)); - drain(); - assertEquals(10, helper.getItemCount()); - - // and in the fast-remove case - expectedCount[0] = 0; - assertEquals(10, helper.getItemCount()); - helper.setList(null); - assertEquals(0, helper.getItemCount()); - } - private void drainExceptDiffThread() { boolean executed; do { diff --git a/android/arch/paging/PagedStorage.java b/android/arch/paging/PagedStorage.java deleted file mode 100644 index 7f91290d..00000000 --- a/android/arch/paging/PagedStorage.java +++ /dev/null @@ -1,433 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.arch.paging; - -import android.support.annotation.NonNull; - -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -final class PagedStorage<K, V> extends AbstractList<V> { - // Always set - private int mLeadingNullCount; - /** - * List of pages in storage. - * - * Two storage modes: - * - * Contiguous - all content in mPages is valid and loaded, but may return false from isTiled(). - * Safe to access any item in any page. - * - * Non-contiguous - mPages may have nulls or a placeholder page, isTiled() always returns true. - * mPages may have nulls, or placeholder (empty) pages while content is loading. - */ - private final ArrayList<Page<K, V>> mPages; - private int mTrailingNullCount; - - private int mPositionOffset; - /** - * Number of items represented by {@link #mPages}. If tiling is enabled, unloaded items in - * {@link #mPages} may be null, but this value still counts them. - */ - private int mStorageCount; - - // If mPageSize > 0, tiling is enabled, 'mPages' may have gaps, and leadingPages is set - private int mPageSize; - - private int mNumberPrepended; - private int mNumberAppended; - - // only used in tiling case - private Page<K, V> mPlaceholderPage; - - PagedStorage() { - mLeadingNullCount = 0; - mPages = new ArrayList<>(); - mTrailingNullCount = 0; - mPositionOffset = 0; - mStorageCount = 0; - mPageSize = 1; - mNumberPrepended = 0; - mNumberAppended = 0; - } - - PagedStorage(int leadingNulls, Page<K, V> page, int trailingNulls) { - this(); - init(leadingNulls, page, trailingNulls, 0); - } - - private PagedStorage(PagedStorage<K, V> other) { - mLeadingNullCount = other.mLeadingNullCount; - mPages = new ArrayList<>(other.mPages); - mTrailingNullCount = other.mTrailingNullCount; - mPositionOffset = other.mPositionOffset; - mStorageCount = other.mStorageCount; - mPageSize = other.mPageSize; - mNumberPrepended = other.mNumberPrepended; - mNumberAppended = other.mNumberAppended; - - // preserve placeholder page so we can locate placeholder pages if needed later - mPlaceholderPage = other.mPlaceholderPage; - } - - PagedStorage<K, V> snapshot() { - return new PagedStorage<>(this); - } - - private void init(int leadingNulls, Page<K, V> page, int trailingNulls, int positionOffset) { - mLeadingNullCount = leadingNulls; - mPages.clear(); - mPages.add(page); - mTrailingNullCount = trailingNulls; - - mPositionOffset = positionOffset; - mStorageCount = page.items.size(); - - // initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled - // even if it will break if nulls convert. - mPageSize = page.items.size(); - - mNumberPrepended = 0; - mNumberAppended = 0; - } - - void init(int leadingNulls, Page<K, V> page, int trailingNulls, int positionOffset, - @NonNull Callback callback) { - init(leadingNulls, page, trailingNulls, positionOffset); - callback.onInitialized(size()); - } - - @Override - public V get(int i) { - if (i < 0 || i >= size()) { - throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + size()); - } - - // is it definitely outside 'mPages'? - int localIndex = i - mLeadingNullCount; - if (localIndex < 0 || localIndex >= mStorageCount) { - return null; - } - - int localPageIndex; - int pageInternalIndex; - - if (isTiled()) { - // it's inside mPages, and we're tiled. Jump to correct tile. - localPageIndex = localIndex / mPageSize; - pageInternalIndex = localIndex % mPageSize; - } else { - // it's inside mPages, but page sizes aren't regular. Walk to correct tile. - // Pages can only be null while tiled, so accessing page count is safe. - pageInternalIndex = localIndex; - final int localPageCount = mPages.size(); - for (localPageIndex = 0; localPageIndex < localPageCount; localPageIndex++) { - int pageSize = mPages.get(localPageIndex).items.size(); - if (pageSize > pageInternalIndex) { - // stop, found the page - break; - } - pageInternalIndex -= pageSize; - } - } - - Page<?, V> page = mPages.get(localPageIndex); - if (page == null || page.items.size() == 0) { - // can only occur in tiled case, with untouched inner/placeholder pages - return null; - } - return page.items.get(pageInternalIndex); - } - - /** - * Returns true if all pages are the same size, except for the last, which may be smaller - */ - boolean isTiled() { - return mPageSize > 0; - } - - int getLeadingNullCount() { - return mLeadingNullCount; - } - - int getTrailingNullCount() { - return mTrailingNullCount; - } - - int getStorageCount() { - return mStorageCount; - } - - int getNumberAppended() { - return mNumberAppended; - } - - int getNumberPrepended() { - return mNumberPrepended; - } - - int getPageCount() { - return mPages.size(); - } - - interface Callback { - void onInitialized(int count); - void onPagePrepended(int leadingNulls, int changed, int added); - void onPageAppended(int endPosition, int changed, int added); - void onPagePlaceholderInserted(int pageIndex); - void onPageInserted(int start, int count); - } - - int getPositionOffset() { - return mPositionOffset; - } - - @Override - public int size() { - return mLeadingNullCount + mStorageCount + mTrailingNullCount; - } - - int computeLeadingNulls() { - int total = mLeadingNullCount; - final int pageCount = mPages.size(); - for (int i = 0; i < pageCount; i++) { - Page page = mPages.get(i); - if (page != null && page != mPlaceholderPage) { - break; - } - total += mPageSize; - } - return total; - } - - int computeTrailingNulls() { - int total = mTrailingNullCount; - for (int i = mPages.size() - 1; i >= 0; i--) { - Page page = mPages.get(i); - if (page != null && page != mPlaceholderPage) { - break; - } - total += mPageSize; - } - return total; - } - - // ---------------- Contiguous API ------------------- - - V getFirstContiguousItem() { - // safe to access first page's first item here: - // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty - return mPages.get(0).items.get(0); - } - - V getLastContiguousItem() { - // safe to access last page's last item here: - // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty - Page<K, V> page = mPages.get(mPages.size() - 1); - return page.items.get(page.items.size() - 1); - } - - public void prependPage(@NonNull Page<K, V> page, @NonNull Callback callback) { - final int count = page.items.size(); - if (count == 0) { - // Nothing returned from source, stop loading in this direction - return; - } - if (mPageSize > 0 && count != mPageSize) { - if (mPages.size() == 1 && count > mPageSize) { - // prepending to a single item - update current page size to that of 'inner' page - mPageSize = count; - } else { - // no longer tiled - mPageSize = -1; - } - } - - mPages.add(0, page); - mStorageCount += count; - - final int changedCount = Math.min(mLeadingNullCount, count); - final int addedCount = count - changedCount; - - if (changedCount != 0) { - mLeadingNullCount -= changedCount; - } - mPositionOffset -= addedCount; - mNumberPrepended += count; - - callback.onPagePrepended(mLeadingNullCount, changedCount, addedCount); - } - - public void appendPage(@NonNull Page<K, V> page, @NonNull Callback callback) { - final int count = page.items.size(); - if (count == 0) { - // Nothing returned from source, stop loading in this direction - return; - } - - if (mPageSize > 0) { - // if the previous page was smaller than mPageSize, - // or if this page is larger than the previous, disable tiling - if (mPages.get(mPages.size() - 1).items.size() != mPageSize - || count > mPageSize) { - mPageSize = -1; - } - } - - mPages.add(page); - mStorageCount += count; - - final int changedCount = Math.min(mTrailingNullCount, count); - final int addedCount = count - changedCount; - - if (changedCount != 0) { - mTrailingNullCount -= changedCount; - } - mNumberAppended += count; - callback.onPageAppended(mLeadingNullCount + mStorageCount - count, - changedCount, addedCount); - } - - // ------------------ Non-Contiguous API (tiling required) ---------------------- - - public void insertPage(int position, @NonNull Page<K, V> page, Callback callback) { - final int newPageSize = page.items.size(); - if (newPageSize != mPageSize) { - // differing page size is OK in 2 cases, when the page is being added: - // 1) to the end (in which case, ignore new smaller size) - // 2) only the last page has been added so far (in which case, adopt new bigger size) - - int size = size(); - boolean addingLastPage = position == (size - size % mPageSize) - && newPageSize < mPageSize; - boolean onlyEndPagePresent = mTrailingNullCount == 0 && mPages.size() == 1 - && newPageSize > mPageSize; - - // OK only if existing single page, and it's the last one - if (!onlyEndPagePresent && !addingLastPage) { - throw new IllegalArgumentException("page introduces incorrect tiling"); - } - if (onlyEndPagePresent) { - mPageSize = newPageSize; - } - } - - int pageIndex = position / mPageSize; - - allocatePageRange(pageIndex, pageIndex); - - int localPageIndex = pageIndex - mLeadingNullCount / mPageSize; - - Page<K, V> oldPage = mPages.get(localPageIndex); - if (oldPage != null && oldPage != mPlaceholderPage) { - throw new IllegalArgumentException( - "Invalid position " + position + ": data already loaded"); - } - mPages.set(localPageIndex, page); - callback.onPageInserted(position, page.items.size()); - } - - private Page<K, V> getPlaceholderPage() { - if (mPlaceholderPage == null) { - @SuppressWarnings("unchecked") - List<V> list = Collections.emptyList(); - mPlaceholderPage = new Page<>(null, list, null); - } - return mPlaceholderPage; - } - - private void allocatePageRange(final int minimumPage, final int maximumPage) { - int leadingNullPages = mLeadingNullCount / mPageSize; - - if (minimumPage < leadingNullPages) { - for (int i = 0; i < leadingNullPages - minimumPage; i++) { - mPages.add(0, null); - } - int newStorageAllocated = (leadingNullPages - minimumPage) * mPageSize; - mStorageCount += newStorageAllocated; - mLeadingNullCount -= newStorageAllocated; - - leadingNullPages = minimumPage; - } - if (maximumPage >= leadingNullPages + mPages.size()) { - int newStorageAllocated = Math.min(mTrailingNullCount, - (maximumPage + 1 - (leadingNullPages + mPages.size())) * mPageSize); - for (int i = mPages.size(); i <= maximumPage - leadingNullPages; i++) { - mPages.add(mPages.size(), null); - } - mStorageCount += newStorageAllocated; - mTrailingNullCount -= newStorageAllocated; - } - } - - public void allocatePlaceholders(int index, int prefetchDistance, - int pageSize, Callback callback) { - if (pageSize != mPageSize) { - if (pageSize < mPageSize) { - throw new IllegalArgumentException("Page size cannot be reduced"); - } - if (mPages.size() != 1 || mTrailingNullCount != 0) { - // not in single, last page allocated case - can't change page size - throw new IllegalArgumentException( - "Page size can change only if last page is only one present"); - } - mPageSize = pageSize; - } - - final int maxPageCount = (size() + mPageSize - 1) / mPageSize; - int minimumPage = Math.max((index - prefetchDistance) / mPageSize, 0); - int maximumPage = Math.min((index + prefetchDistance) / mPageSize, maxPageCount - 1); - - allocatePageRange(minimumPage, maximumPage); - int leadingNullPages = mLeadingNullCount / mPageSize; - for (int pageIndex = minimumPage; pageIndex <= maximumPage; pageIndex++) { - int localPageIndex = pageIndex - leadingNullPages; - if (mPages.get(localPageIndex) == null) { - mPages.set(localPageIndex, getPlaceholderPage()); - callback.onPagePlaceholderInserted(pageIndex); - } - } - } - - public boolean hasPage(int pageSize, int index) { - // NOTE: we pass pageSize here to avoid in case mPageSize - // not fully initialized (when last page only one loaded) - int leadingNullPages = mLeadingNullCount / pageSize; - - if (index < leadingNullPages || index >= leadingNullPages + mPages.size()) { - return false; - } - - Page<K, V> page = mPages.get(index - leadingNullPages); - - return page != null && page != mPlaceholderPage; - } - - @Override - public String toString() { - StringBuilder ret = new StringBuilder("leading " + mLeadingNullCount - + ", storage " + mStorageCount - + ", trailing " + getTrailingNullCount()); - - for (int i = 0; i < mPages.size(); i++) { - ret.append(" ").append(mPages.get(i)); - } - return ret.toString(); - } -} diff --git a/android/arch/paging/PositionalDataSource.java b/android/arch/paging/PositionalDataSource.java index c538cb60..deb51e94 100644 --- a/android/arch/paging/PositionalDataSource.java +++ b/android/arch/paging/PositionalDataSource.java @@ -42,17 +42,6 @@ import java.util.List; */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public abstract class PositionalDataSource<Value> extends ContiguousDataSource<Integer, Value> { - - /** - * Number of items that this DataSource can provide in total, or COUNT_UNDEFINED. - * - * @return number of items that this DataSource can provide in total, or COUNT_UNDEFINED - * if difficult or undesired to compute. - */ - public int countItems() { - return COUNT_UNDEFINED; - } - @Nullable @Override List<Value> loadAfterImpl(int currentEndIndex, @NonNull Value currentEndItem, int pageSize) { @@ -66,7 +55,16 @@ public abstract class PositionalDataSource<Value> extends ContiguousDataSource<I return loadBefore(currentBeginIndex - 1, pageSize); } - /** @hide */ + + /** + * Load initial data, starting after the passed position. + * + * @param position Index just before the data to be loaded. + * @param initialLoadSize Suggested number of items to load. + * @return List of initial items, representing data starting at position. Null if the + * DataSource is no longer valid, and should not be queried again. + * @hide + */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @WorkerThread @Nullable @@ -120,9 +118,6 @@ public abstract class PositionalDataSource<Value> extends ContiguousDataSource<I @Override Integer getKey(int position, Value item) { - if (position < 0) { - return null; - } return position; } } diff --git a/android/arch/paging/SnapshotPagedList.java b/android/arch/paging/SnapshotPagedList.java deleted file mode 100644 index 7e965a0f..00000000 --- a/android/arch/paging/SnapshotPagedList.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.arch.paging; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -class SnapshotPagedList<T> extends PagedList<T> { - private final boolean mContiguous; - private final Object mLastKey; - - SnapshotPagedList(@NonNull PagedList<T> pagedList) { - super(pagedList.mStorage.snapshot(), - pagedList.mMainThreadExecutor, - pagedList.mBackgroundThreadExecutor, - pagedList.mConfig); - mContiguous = pagedList.isContiguous(); - mLastKey = pagedList.getLastKey(); - } - - @Override - public boolean isImmutable() { - return true; - } - - @Override - public boolean isDetached() { - return true; - } - - @Override - boolean isContiguous() { - return mContiguous; - } - - @Nullable - @Override - public Object getLastKey() { - return mLastKey; - } - - @Override - void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> storageSnapshot, - @NonNull Callback callback) { - } - - @Override - void loadAroundInternal(int index) { - } -} diff --git a/android/arch/paging/SparseDiffHelper.java b/android/arch/paging/SparseDiffHelper.java new file mode 100644 index 00000000..fe478973 --- /dev/null +++ b/android/arch/paging/SparseDiffHelper.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.arch.paging; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; +import android.support.v7.recyclerview.extensions.DiffCallback; +import android.support.v7.util.DiffUtil; +import android.support.v7.util.ListUpdateCallback; + +/** @hide */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +class SparseDiffHelper { + private SparseDiffHelper() { + } + + @NonNull + static <T> DiffUtil.DiffResult computeDiff( + final PageArrayList<T> oldList, final PageArrayList<T> newList, + final DiffCallback<T> diffCallback, boolean detectMoves) { + + if (!oldList.isImmutable()) { + throw new IllegalArgumentException("list must be immutable to safely perform diff"); + } + if (!newList.isImmutable()) { + throw new IllegalArgumentException("list must be immutable to safely perform diff"); + } + return DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Nullable + @Override + public Object getChangePayload(int oldItemPosition, int newItemPosition) { + T oldItem = oldList.get(oldItemPosition); + T newItem = newList.get(newItemPosition); + if (oldItem == null || newItem == null) { + return null; + } + return diffCallback.getChangePayload(oldItem, newItem); + } + + @Override + public int getOldListSize() { + return oldList.size(); + } + + @Override + public int getNewListSize() { + return newList.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + T oldItem = oldList.get(oldItemPosition); + T newItem = newList.get(newItemPosition); + if (oldItem == newItem) { + return true; + } + if (oldItem == null || newItem == null) { + return false; + } + return diffCallback.areItemsTheSame(oldItem, newItem); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + T oldItem = oldList.get(oldItemPosition); + T newItem = newList.get(newItemPosition); + if (oldItem == newItem) { + return true; + } + if (oldItem == null || newItem == null) { + return false; + } + + return diffCallback.areContentsTheSame(oldItem, newItem); + } + }, detectMoves); + } + + static <T> void dispatchDiff(ListUpdateCallback callback, + final DiffUtil.DiffResult diffResult) { + // Simple case, dispatch & return + diffResult.dispatchUpdatesTo(callback); + } +} diff --git a/android/arch/paging/StringPagedList.java b/android/arch/paging/StringPagedList.java index 880d5e9b..5318d38c 100644 --- a/android/arch/paging/StringPagedList.java +++ b/android/arch/paging/StringPagedList.java @@ -16,60 +16,10 @@ package android.arch.paging; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - import java.util.Arrays; -public class StringPagedList extends PagedList<String> implements PagedStorage.Callback { - StringPagedList(int leadingNulls, int trailingNulls, String... items) { - super(new PagedStorage<Integer, String>(), - null, null, null); - PagedStorage<Integer, String> keyedStorage = (PagedStorage<Integer, String>) mStorage; - keyedStorage.init(leadingNulls, - new Page<Integer, String>(null, Arrays.asList(items), null), - trailingNulls, - 0, - this); - } - - @Override - boolean isContiguous() { - return true; - } - - @Nullable - @Override - public Object getLastKey() { - return null; - } - - @Override - protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<String> storageSnapshot, - @NonNull Callback callback) { - } - - @Override - protected void loadAroundInternal(int index) { - } - - @Override - public void onInitialized(int count) { - } - - @Override - public void onPagePrepended(int leadingNulls, int changed, int added) { - } - - @Override - public void onPageAppended(int endPosition, int changed, int added) { - } - - @Override - public void onPagePlaceholderInserted(int pageIndex) { - } - - @Override - public void onPageInserted(int start, int count) { +public class StringPagedList extends NullPaddedList<String> { + public StringPagedList(int leadingNulls, int trailingNulls, String... items) { + super(leadingNulls, Arrays.asList(items), trailingNulls); } } diff --git a/android/arch/paging/TestExecutor.java b/android/arch/paging/TestExecutor.java index 976f7df5..30809c3e 100644 --- a/android/arch/paging/TestExecutor.java +++ b/android/arch/paging/TestExecutor.java @@ -30,7 +30,7 @@ public class TestExecutor implements Executor { mTasks.add(command); } - public boolean executeAll() { + boolean executeAll() { boolean consumed = !mTasks.isEmpty(); Runnable task; while ((task = mTasks.poll()) != null) { diff --git a/android/arch/paging/TiledDataSource.java b/android/arch/paging/TiledDataSource.java index 61dead3a..36be423d 100644 --- a/android/arch/paging/TiledDataSource.java +++ b/android/arch/paging/TiledDataSource.java @@ -19,7 +19,6 @@ package android.arch.paging; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; -import java.util.Collections; import java.util.List; /** @@ -93,6 +92,7 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> { * @return Number of items this DataSource can provide. Must be <code>0</code> or greater. */ @WorkerThread + @Override public abstract int countItems(); @Override @@ -118,61 +118,7 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> { @WorkerThread public abstract List<Type> loadRange(int startPosition, int count); - /** - * blocking, and splits pages - */ - void loadRangeInitial(int startPosition, int count, int pageSize, int itemCount, - PageResult.Receiver<Integer, Type> receiver) { - - if (itemCount == 0) { - // no data to load, just immediately return empty - receiver.onPageResult(new PageResult<>( - PageResult.INIT, new Page<Integer, Type>(Collections.<Type>emptyList()), - 0, 0, startPosition)); - return; - } - - - List<Type> list = loadRangeWrapper(startPosition, count); - - count = Math.min(count, itemCount - startPosition); - - if (list == null) { - // invalid data, pass to receiver - receiver.onPageResult(new PageResult<Integer, Type>( - PageResult.INIT, null, 0, 0, startPosition)); - return; - } - - if (list.size() != count) { - throw new IllegalStateException("Invalid list, requested size: " + count - + ", returned size: " + list.size()); - } - - // emit the results as multiple pages - int pageCount = (count + (pageSize - 1)) / pageSize; - for (int i = 0; i < pageCount; i++) { - int beginInclusive = i * pageSize; - int endExclusive = Math.min(count, (i + 1) * pageSize); - - Page<Integer, Type> page = new Page<>(list.subList(beginInclusive, endExclusive)); - - int leadingNulls = startPosition + beginInclusive; - int trailingNulls = itemCount - leadingNulls - page.items.size(); - receiver.onPageResult(new PageResult<>( - PageResult.INIT, page, leadingNulls, trailingNulls, 0)); - } - } - - void loadRange(int startPosition, int count, PageResult.Receiver<Integer, Type> receiver) { - List<Type> list = loadRangeWrapper(startPosition, count); - - Page<Integer, Type> page = list != null ? new Page<Integer, Type>(list) : null; - receiver.postOnPageResult(new PageResult<>( - PageResult.TILE, page, startPosition, 0, 0)); - } - - private List<Type> loadRangeWrapper(int startPosition, int count) { + final List<Type> loadRangeWrapper(int startPosition, int count) { if (isInvalid()) { return null; } diff --git a/android/arch/paging/TiledPagedList.java b/android/arch/paging/TiledPagedList.java index c45d029d..a2fc064b 100644 --- a/android/arch/paging/TiledPagedList.java +++ b/android/arch/paging/TiledPagedList.java @@ -16,173 +16,219 @@ package android.arch.paging; -import android.support.annotation.AnyThread; -import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; import android.support.annotation.WorkerThread; +import java.lang.ref.WeakReference; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; -class TiledPagedList<T> extends PagedList<T> - implements PagedStorage.Callback { +/** @hide */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +class TiledPagedList<T> extends PageArrayList<T> { private final TiledDataSource<T> mDataSource; + private final Executor mMainThreadExecutor; + private final Executor mBackgroundThreadExecutor; + private final Config mConfig; - @SuppressWarnings("unchecked") - private final PagedStorage<Integer, T> mKeyedStorage = (PagedStorage<Integer, T>) mStorage; - - private final PageResult.Receiver<Integer, T> mReceiver = - new PageResult.Receiver<Integer, T>() { - @AnyThread + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + private final List<T> mLoadingPlaceholder = new AbstractList<T>() { @Override - public void postOnPageResult(@NonNull final PageResult<Integer, T> pageResult) { - // NOTE: if we're already on main thread, this can delay page receive by a frame - mMainThreadExecutor.execute(new Runnable() { - @Override - public void run() { - onPageResult(pageResult); - } - }); + public T get(int i) { + return null; } - @MainThread @Override - public void onPageResult(@NonNull PageResult<Integer, T> pageResult) { - if (pageResult.page == null) { - detach(); - return; - } - - if (isDetached()) { - // No op, have detached - return; - } - - if (mStorage.getPageCount() == 0) { - mKeyedStorage.init( - pageResult.leadingNulls, pageResult.page, pageResult.trailingNulls, - pageResult.positionOffset, TiledPagedList.this); - } else { - mKeyedStorage.insertPage(pageResult.leadingNulls, pageResult.page, - TiledPagedList.this); - } + public int size() { + return 0; } }; + private int mLastLoad = -1; + + private AtomicBoolean mDetached = new AtomicBoolean(false); + + private ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); + @WorkerThread TiledPagedList(@NonNull TiledDataSource<T> dataSource, @NonNull Executor mainThreadExecutor, @NonNull Executor backgroundThreadExecutor, - @NonNull Config config, + Config config, int position) { - super(new PagedStorage<Integer, T>(), - mainThreadExecutor, backgroundThreadExecutor, config); - mDataSource = dataSource; - - final int pageSize = mConfig.mPageSize; + super(config.mPageSize, dataSource.countItems()); - final int itemCount = mDataSource.countItems(); - final int firstLoadSize = Math.min(itemCount, - (Math.max(mConfig.mInitialLoadSizeHint / pageSize, 2)) * pageSize); - final int firstLoadPosition = computeFirstLoadPosition( - position, firstLoadSize, pageSize, itemCount); + mDataSource = dataSource; + mMainThreadExecutor = mainThreadExecutor; + mBackgroundThreadExecutor = backgroundThreadExecutor; + mConfig = config; + + position = Math.min(Math.max(0, position), mCount); + + int firstPage = position / mPageSize; + List<T> firstPageData = dataSource.loadRangeWrapper(firstPage * mPageSize, mPageSize); + if (firstPageData != null) { + mPageIndexOffset = firstPage; + mPages.add(firstPageData); + mLastLoad = position; + } else { + detach(); + return; + } - mDataSource.loadRangeInitial(firstLoadPosition, firstLoadSize, pageSize, - itemCount, mReceiver); + int secondPage = (position % mPageSize < mPageSize / 2) ? firstPage - 1 : firstPage + 1; + if (secondPage < 0 || secondPage > mMaxPageCount) { + // no second page to load + return; + } + List<T> secondPageData = dataSource.loadRangeWrapper(secondPage * mPageSize, mPageSize); + if (secondPageData != null) { + boolean before = secondPage < firstPage; + mPages.add(before ? 0 : 1, secondPageData); + if (before) { + mPageIndexOffset--; + } + return; + } + detach(); } - static int computeFirstLoadPosition(int position, int firstLoadSize, int pageSize, int size) { - int idealStart = position - firstLoadSize / 2; + @Override + public void loadAround(int index) { + mLastLoad = index; - int roundedPageStart = Math.round(idealStart / pageSize) * pageSize; + int minimumPage = Math.max((index - mConfig.mPrefetchDistance) / mPageSize, 0); + int maximumPage = Math.min((index + mConfig.mPrefetchDistance) / mPageSize, + mMaxPageCount - 1); - // minimum start position is 0 - roundedPageStart = Math.max(0, roundedPageStart); + if (minimumPage < mPageIndexOffset) { + for (int i = 0; i < mPageIndexOffset - minimumPage; i++) { + mPages.add(0, null); + } + mPageIndexOffset = minimumPage; + } + if (maximumPage >= mPageIndexOffset + mPages.size()) { + for (int i = mPages.size(); i <= maximumPage - mPageIndexOffset; i++) { + mPages.add(mPages.size(), null); + } + } + for (int i = minimumPage; i <= maximumPage; i++) { + scheduleLoadPage(i); + } + } - // maximum start pos is that which will encompass end of list - int maximumLoadPage = ((size - firstLoadSize + pageSize - 1) / pageSize) * pageSize; - roundedPageStart = Math.min(maximumLoadPage, roundedPageStart); + private void scheduleLoadPage(final int pageIndex) { + final int localPageIndex = pageIndex - mPageIndexOffset; - return roundedPageStart; - } + if (mPages.get(localPageIndex) != null) { + // page is present in list, and non-null - don't need to load + return; + } + mPages.set(localPageIndex, mLoadingPlaceholder); - @Override - boolean isContiguous() { - return false; - } + mBackgroundThreadExecutor.execute(new Runnable() { + @Override + public void run() { + if (mDetached.get()) { + return; + } + final List<T> data = mDataSource.loadRangeWrapper( + pageIndex * mPageSize, mPageSize); + if (data != null) { + mMainThreadExecutor.execute(new Runnable() { + @Override + public void run() { + if (mDetached.get()) { + return; + } + loadPageImpl(pageIndex, data); + } + }); + } else { + detach(); + } + } + }); - @Nullable - @Override - public Object getLastKey() { - return mLastLoad; } - @Override - protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot, - @NonNull Callback callback) { - //noinspection UnnecessaryLocalVariable - final PagedStorage<?, T> snapshot = pagedListSnapshot.mStorage; - - // loop through each page and signal the callback for any pages that are present now, - // but not in the snapshot. - final int pageSize = mConfig.mPageSize; - final int leadingNullPages = mStorage.getLeadingNullCount() / pageSize; - final int pageCount = mStorage.getPageCount(); - for (int i = 0; i < pageCount; i++) { - int pageIndex = i + leadingNullPages; - int updatedPages = 0; - // count number of consecutive pages that were added since the snapshot... - while (updatedPages < mStorage.getPageCount() - && mStorage.hasPage(pageSize, pageIndex + updatedPages) - && !snapshot.hasPage(pageSize, pageIndex + updatedPages)) { - updatedPages++; - } - // and signal them all at once to the callback - if (updatedPages > 0) { - callback.onChanged(pageIndex * pageSize, pageSize * updatedPages); - i += updatedPages - 1; + private void loadPageImpl(int pageIndex, List<T> data) { + int localPageIndex = pageIndex - mPageIndexOffset; + + if (mPages.get(localPageIndex) != mLoadingPlaceholder) { + throw new IllegalStateException("Data inserted before requested."); + } + mPages.set(localPageIndex, data); + for (WeakReference<Callback> weakRef : mCallbacks) { + Callback callback = weakRef.get(); + if (callback != null) { + callback.onChanged(pageIndex * mPageSize, data.size()); } } } @Override - protected void loadAroundInternal(int index) { - mStorage.allocatePlaceholders(index, mConfig.mPrefetchDistance, mConfig.mPageSize, this); + public boolean isImmutable() { + // TODO: consider counting loaded pages, return true if mLoadedPages == mMaxPageCount + // Note: could at some point want to support growing past max count, or grow dynamically + return isDetached(); } @Override - public void onInitialized(int count) { - notifyInserted(0, count); + public void addWeakCallback(@Nullable PagedList<T> previousSnapshot, + @NonNull Callback callback) { + PageArrayList<T> snapshot = (PageArrayList<T>) previousSnapshot; + if (snapshot != this && snapshot != null) { + // loop through each page and signal the callback for any pages that are present now, + // but not in the snapshot. + for (int i = 0; i < mPages.size(); i++) { + int pageIndex = i + mPageIndexOffset; + int pageCount = 0; + // count number of consecutive pages that were added since the snapshot... + while (pageCount < mPages.size() + && hasPage(pageIndex + pageCount) + && !snapshot.hasPage(pageIndex + pageCount)) { + pageCount++; + } + // and signal them all at once to the callback + if (pageCount > 0) { + callback.onChanged(pageIndex * mPageSize, mPageSize * pageCount); + i += pageCount - 1; + } + } + } + mCallbacks.add(new WeakReference<>(callback)); } @Override - public void onPagePrepended(int leadingNulls, int changed, int added) { - throw new IllegalStateException("Contiguous callback on TiledPagedList"); + public void removeWeakCallback(@NonNull Callback callback) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + Callback currentCallback = mCallbacks.get(i).get(); + if (currentCallback == null || currentCallback == callback) { + mCallbacks.remove(i); + } + } } @Override - public void onPageAppended(int endPosition, int changed, int added) { - throw new IllegalStateException("Contiguous callback on TiledPagedList"); + public boolean isDetached() { + return mDetached.get(); } @Override - public void onPagePlaceholderInserted(final int pageIndex) { - // placeholder means initialize a load - mBackgroundThreadExecutor.execute(new Runnable() { - @Override - public void run() { - if (isDetached()) { - return; - } - final int pageSize = mConfig.mPageSize; - mDataSource.loadRange(pageIndex * pageSize, pageSize, mReceiver); - } - }); + public void detach() { + mDetached.set(true); } + @Nullable @Override - public void onPageInserted(int start, int count) { - notifyChanged(start, count); + public Object getLastKey() { + return mLastLoad; } } diff --git a/android/arch/paging/TiledPagedListTest.java b/android/arch/paging/TiledPagedListTest.java index 22bfd1ff..4ad02e12 100644 --- a/android/arch/paging/TiledPagedListTest.java +++ b/android/arch/paging/TiledPagedListTest.java @@ -78,107 +78,75 @@ public class TiledPagedListTest { } } - private void verifyRange(List<Item> list, Integer... loadedPages) { + private void verifyRange(PageArrayList<Item> list, Integer... loadedPages) { List<Integer> loadedPageList = Arrays.asList(loadedPages); assertEquals(ITEMS.size(), list.size()); for (int i = 0; i < list.size(); i++) { if (loadedPageList.contains(i / PAGE_SIZE)) { - assertSame("Index " + i, ITEMS.get(i), list.get(i)); + assertSame(ITEMS.get(i), list.get(i)); } else { - assertEquals("Index " + i, null, list.get(i)); + assertEquals(null, list.get(i)); } } } - private TiledPagedList<Item> createTiledPagedList(int loadPosition, int initPages) { - return createTiledPagedList(loadPosition, initPages, PAGE_SIZE); + private TiledPagedList<Item> createTiledPagedList(int loadPosition) { + return createTiledPagedList(loadPosition, PAGE_SIZE); } - private TiledPagedList<Item> createTiledPagedList(int loadPosition, int initPages, - int prefetchDistance) { + private TiledPagedList<Item> createTiledPagedList(int loadPosition, int prefetchDistance) { TestTiledSource source = new TestTiledSource(); return new TiledPagedList<>( source, mMainThread, mBackgroundThread, new PagedList.Config.Builder() .setPageSize(PAGE_SIZE) - .setInitialLoadSizeHint(PAGE_SIZE * initPages) .setPrefetchDistance(prefetchDistance) .build(), loadPosition); } @Test - public void computeFirstLoadPosition_zero() { - assertEquals(0, TiledPagedList.computeFirstLoadPosition(0, 30, 10, 100)); - } - - @Test - public void computeFirstLoadPosition_requestedPositionIncluded() { - assertEquals(0, TiledPagedList.computeFirstLoadPosition(10, 10, 10, 100)); - } - - @Test - public void computeFirstLoadPosition_endAdjusted() { - assertEquals(70, TiledPagedList.computeFirstLoadPosition(99, 30, 10, 100)); - } - - @Test - public void initialLoad_onePage() { - TiledPagedList<Item> pagedList = createTiledPagedList(0, 1); - verifyRange(pagedList, 0, 1); - } - - @Test - public void initialLoad_onePageOffset() { - TiledPagedList<Item> pagedList = createTiledPagedList(10, 1); - verifyRange(pagedList, 0, 1); - } - - @Test - public void initialLoad_full() { - TiledPagedList<Item> pagedList = createTiledPagedList(0, 100); - verifyRange(pagedList, 0, 1, 2, 3, 4); + public void initialLoad() { + TiledPagedList<Item> pagedList = createTiledPagedList(0); + verifyRange(pagedList, 0); } @Test public void initialLoad_end() { - TiledPagedList<Item> pagedList = createTiledPagedList(44, 2); + TiledPagedList<Item> pagedList = createTiledPagedList(44); verifyRange(pagedList, 3, 4); } @Test public void initialLoad_multiple() { - TiledPagedList<Item> pagedList = createTiledPagedList(9, 2); + TiledPagedList<Item> pagedList = createTiledPagedList(9); verifyRange(pagedList, 0, 1); } @Test public void initialLoad_offset() { - TiledPagedList<Item> pagedList = createTiledPagedList(41, 2); + TiledPagedList<Item> pagedList = createTiledPagedList(41); verifyRange(pagedList, 3, 4); } @Test public void append() { - TiledPagedList<Item> pagedList = createTiledPagedList(0, 1); + TiledPagedList<Item> pagedList = createTiledPagedList(0); PagedList.Callback callback = mock(PagedList.Callback.class); pagedList.addWeakCallback(null, callback); - verifyRange(pagedList, 0, 1); + verifyRange(pagedList, 0); verifyZeroInteractions(callback); - pagedList.loadAround(15); - - verifyRange(pagedList, 0, 1); - + pagedList.loadAround(5); drain(); - verifyRange(pagedList, 0, 1, 2); - verify(callback).onChanged(20, 10); + verifyRange(pagedList, 0, 1); + verify(callback).onChanged(10, 10); verifyNoMoreInteractions(callback); } @Test public void prepend() { - TiledPagedList<Item> pagedList = createTiledPagedList(44, 2); + TiledPagedList<Item> pagedList = createTiledPagedList(44); PagedList.Callback callback = mock(PagedList.Callback.class); pagedList.addWeakCallback(null, callback); verifyRange(pagedList, 3, 4); @@ -194,16 +162,16 @@ public class TiledPagedListTest { @Test public void loadWithGap() { - TiledPagedList<Item> pagedList = createTiledPagedList(0, 1); + TiledPagedList<Item> pagedList = createTiledPagedList(0); PagedList.Callback callback = mock(PagedList.Callback.class); pagedList.addWeakCallback(null, callback); - verifyRange(pagedList, 0, 1); + verifyRange(pagedList, 0); verifyZeroInteractions(callback); pagedList.loadAround(44); drain(); - verifyRange(pagedList, 0, 1, 3, 4); + verifyRange(pagedList, 0, 3, 4); verify(callback).onChanged(30, 10); verify(callback).onChanged(40, 5); verifyNoMoreInteractions(callback); @@ -211,56 +179,58 @@ public class TiledPagedListTest { @Test public void tinyPrefetchTest() { - TiledPagedList<Item> pagedList = createTiledPagedList(0, 1, 1); + TiledPagedList<Item> pagedList = createTiledPagedList(0, 1); PagedList.Callback callback = mock(PagedList.Callback.class); pagedList.addWeakCallback(null, callback); - verifyRange(pagedList, 0, 1); + verifyRange(pagedList, 0); // just 4 loaded verifyZeroInteractions(callback); - pagedList.loadAround(33); + pagedList.loadAround(23); drain(); - verifyRange(pagedList, 0, 1, 3); - verify(callback).onChanged(30, 10); + verifyRange(pagedList, 0, 2); + verify(callback).onChanged(20, 10); verifyNoMoreInteractions(callback); pagedList.loadAround(44); drain(); - verifyRange(pagedList, 0, 1, 3, 4); + verifyRange(pagedList, 0, 2, 4); verify(callback).onChanged(40, 5); verifyNoMoreInteractions(callback); } @Test public void appendCallbackAddedLate() { - TiledPagedList<Item> pagedList = createTiledPagedList(0, 1, 0); - verifyRange(pagedList, 0, 1); + TiledPagedList<Item> pagedList = createTiledPagedList(0, 0); + verifyRange(pagedList, 0); - pagedList.loadAround(25); + pagedList.loadAround(15); drain(); - verifyRange(pagedList, 0, 1, 2); + verifyRange(pagedList, 0, 1); + + // snapshot at 20 items + PageArrayList<Item> snapshot = (PageArrayList<Item>) pagedList.snapshot(); + verifyRange(snapshot, 0, 1); - // snapshot at 30 items - List<Item> snapshot = pagedList.snapshot(); - verifyRange(snapshot, 0, 1, 2); + pagedList.loadAround(25); pagedList.loadAround(35); - pagedList.loadAround(44); drain(); - verifyRange(pagedList, 0, 1, 2, 3, 4); - verifyRange(snapshot, 0, 1, 2); + verifyRange(pagedList, 0, 1, 2, 3); + verifyRange(snapshot, 0, 1); PagedList.Callback callback = mock( PagedList.Callback.class); pagedList.addWeakCallback(snapshot, callback); - verify(callback).onChanged(30, 20); + verify(callback).onChanged(20, 20); verifyNoMoreInteractions(callback); } + @Test public void prependCallbackAddedLate() { - TiledPagedList<Item> pagedList = createTiledPagedList(44, 2, 0); + TiledPagedList<Item> pagedList = createTiledPagedList(44, 0); verifyRange(pagedList, 3, 4); pagedList.loadAround(25); @@ -268,9 +238,10 @@ public class TiledPagedListTest { verifyRange(pagedList, 2, 3, 4); // snapshot at 30 items - List<Item> snapshot = pagedList.snapshot(); + PageArrayList<Item> snapshot = (PageArrayList<Item>) pagedList.snapshot(); verifyRange(snapshot, 2, 3, 4); + pagedList.loadAround(15); pagedList.loadAround(5); drain(); @@ -301,11 +272,10 @@ public class TiledPagedListTest { assertTrue(pagedList.isContiguous()); - ContiguousPagedList<Integer, Item> contiguousPagedList = - (ContiguousPagedList<Integer, Item>) pagedList; - assertEquals(0, contiguousPagedList.mStorage.getLeadingNullCount()); - assertEquals(PAGE_SIZE, contiguousPagedList.mStorage.getStorageCount()); - assertEquals(0, contiguousPagedList.mStorage.getTrailingNullCount()); + ContiguousPagedList<Item> contiguousPagedList = (ContiguousPagedList<Item>) pagedList; + assertEquals(0, contiguousPagedList.getLeadingNullCount()); + assertEquals(PAGE_SIZE, contiguousPagedList.mList.size()); + assertEquals(0, contiguousPagedList.getTrailingNullCount()); } private void drain() { diff --git a/android/arch/persistence/room/InvalidationTracker.java b/android/arch/persistence/room/InvalidationTracker.java index b31dc13a..45ec0289 100644 --- a/android/arch/persistence/room/InvalidationTracker.java +++ b/android/arch/persistence/room/InvalidationTracker.java @@ -219,7 +219,7 @@ public class InvalidationTracker { * * @param observer The observer which listens the database for changes. */ - public void addObserver(@NonNull Observer observer) { + public void addObserver(Observer observer) { final String[] tableNames = observer.mTables; int[] tableIds = new int[tableNames.length]; final int size = tableNames.length; @@ -265,7 +265,7 @@ public class InvalidationTracker { * @param observer The observer to remove. */ @SuppressWarnings("WeakerAccess") - public void removeObserver(@NonNull final Observer observer) { + public void removeObserver(final Observer observer) { ObserverWrapper wrapper; synchronized (mObserverMap) { wrapper = mObserverMap.remove(observer); diff --git a/android/arch/persistence/room/Relation.java b/android/arch/persistence/room/Relation.java index d55bbfe8..72066992 100644 --- a/android/arch/persistence/room/Relation.java +++ b/android/arch/persistence/room/Relation.java @@ -28,8 +28,6 @@ import java.lang.annotation.Target; * <pre> * {@literal @}Entity * public class Pet { - * {@literal @} PrimaryKey - * int id; * int userId; * String name; * // other fields @@ -43,8 +41,8 @@ import java.lang.annotation.Target; * * {@literal @}Dao * public interface UserPetDao { - * {@literal @}Query("SELECT id, name from User") - * public List<UserNameAndAllPets> loadUserAndPets(); + * {@literal @}Query("SELECT id, name from User WHERE age > :minAge") + * public List<UserNameAndAllPets> loadUserAndPets(int minAge); * } * </pre> * <p> @@ -65,16 +63,16 @@ import java.lang.annotation.Target; * {@literal @}Embedded * public User user; * {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class) - * public List<PetNameAndId> pets; + * public List<PetNameAndId> pets; * } * {@literal @}Dao * public interface UserPetDao { - * {@literal @}Query("SELECT * from User") - * public List<UserAllPets> loadUserAndPets(); + * {@literal @}Query("SELECT * from User WHERE age > :minAge") + * public List<UserAllPets> loadUserAndPets(int minAge); * } * </pre> * <p> - * In the example above, {@code PetNameAndId} is a regular Pojo but all of fields are fetched + * In the example above, {@code PetNameAndId} is a regular but all of fields are fetched * from the {@code entity} defined in the {@code @Relation} annotation (<i>Pet</i>). * {@code PetNameAndId} could also define its own relations all of which would also be fetched * automatically. @@ -87,7 +85,7 @@ import java.lang.annotation.Target; * public User user; * {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class, * projection = {"name"}) - * public List<String> petNames; + * public List<String> petNames; * } * </pre> * <p> @@ -95,7 +93,7 @@ import java.lang.annotation.Target; * cannot have relations. This is a design decision to avoid common pitfalls in {@link Entity} * setups. You can read more about it in the main Room documentation. When loading data, you can * simply work around this limitation by creating Pojo classes that extend the {@link Entity}. - * <p> + * * Note that the {@code @Relation} annotated field cannot be a constructor parameter, it must be * public or have a public setter. */ diff --git a/android/arch/persistence/room/Room.java b/android/arch/persistence/room/Room.java index 2850b55e..8ce4be0c 100644 --- a/android/arch/persistence/room/Room.java +++ b/android/arch/persistence/room/Room.java @@ -43,7 +43,6 @@ public class Room { * @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database. */ @SuppressWarnings("WeakerAccess") - @NonNull public static <T extends RoomDatabase> RoomDatabase.Builder<T> databaseBuilder( @NonNull Context context, @NonNull Class<T> klass, @NonNull String name) { //noinspection ConstantConditions @@ -66,7 +65,6 @@ public class Room { * @param <T> The type of the database class. * @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database. */ - @NonNull public static <T extends RoomDatabase> RoomDatabase.Builder<T> inMemoryDatabaseBuilder( @NonNull Context context, @NonNull Class<T> klass) { return new RoomDatabase.Builder<>(context, klass, null); diff --git a/android/arch/persistence/room/RoomDatabase.java b/android/arch/persistence/room/RoomDatabase.java index 8c940246..cdad868d 100644 --- a/android/arch/persistence/room/RoomDatabase.java +++ b/android/arch/persistence/room/RoomDatabase.java @@ -49,7 +49,7 @@ import java.util.concurrent.locks.ReentrantLock; * * @see Database */ -//@SuppressWarnings({"unused", "WeakerAccess"}) +@SuppressWarnings({"unused", "WeakerAccess"}) public abstract class RoomDatabase { private static final String DB_IMPL_SUFFIX = "_Impl"; // set by the generated open helper. @@ -153,9 +153,7 @@ public abstract class RoomDatabase { * * @hide */ - @SuppressWarnings("WeakerAccess") @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - // used in generated code public void assertNotMainThread() { if (mAllowMainThreadQueries) { return; @@ -300,7 +298,6 @@ public abstract class RoomDatabase { * @return True if there is an active transaction in current thread, false otherwise. * @see SupportSQLiteDatabase#inTransaction() */ - @SuppressWarnings("WeakerAccess") public boolean inTransaction() { return mOpenHelper.getWritableDatabase().inTransaction(); } @@ -310,6 +307,7 @@ public abstract class RoomDatabase { * * @param <T> The type of the abstract database class. */ + @SuppressWarnings("unused") public static class Builder<T extends RoomDatabase> { private final Class<T> mDatabaseClass; private final String mName; @@ -339,8 +337,7 @@ public abstract class RoomDatabase { * @param factory The factory to use to access the database. * @return this */ - @NonNull - public Builder<T> openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory) { + public Builder<T> openHelperFactory(SupportSQLiteOpenHelper.Factory factory) { mFactory = factory; return this; } @@ -364,7 +361,6 @@ public abstract class RoomDatabase { * changes. * @return this */ - @NonNull public Builder<T> addMigrations(Migration... migrations) { mMigrationContainer.addMigrations(migrations); return this; @@ -382,7 +378,6 @@ public abstract class RoomDatabase { * * @return this */ - @NonNull public Builder<T> allowMainThreadQueries() { mAllowMainThreadQueries = true; return this; @@ -405,7 +400,6 @@ public abstract class RoomDatabase { * * @return this */ - @NonNull public Builder<T> fallbackToDestructiveMigration() { mRequireMigration = false; return this; @@ -417,7 +411,6 @@ public abstract class RoomDatabase { * @param callback The callback. * @return this */ - @NonNull public Builder<T> addCallback(@NonNull Callback callback) { if (mCallbacks == null) { mCallbacks = new ArrayList<>(); @@ -434,7 +427,6 @@ public abstract class RoomDatabase { * * @return A new database instance. */ - @NonNull public T build() { //noinspection ConstantConditions if (mContext == null) { @@ -501,7 +493,6 @@ public abstract class RoomDatabase { * @return An ordered list of {@link Migration} objects that should be run to migrate * between the given versions. If a migration path cannot be found, returns {@code null}. */ - @SuppressWarnings("WeakerAccess") @Nullable public List<Migration> findMigrationPath(int start, int end) { if (start == end) { diff --git a/android/arch/persistence/room/RoomWarnings.java b/android/arch/persistence/room/RoomWarnings.java index f05e6be2..c64be967 100644 --- a/android/arch/persistence/room/RoomWarnings.java +++ b/android/arch/persistence/room/RoomWarnings.java @@ -125,12 +125,4 @@ public class RoomWarnings { * annotation. */ public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR"; - - /** - * Reported when a @Query method returns a Pojo that has relations but the method is not - * annotated with @Transaction. Relations are run as separate queries and if the query is not - * run inside a transaction, it might return inconsistent results from the database. - */ - public static final String RELATION_QUERY_WITHOUT_TRANSACTION = - "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION"; } diff --git a/android/arch/persistence/room/Transaction.java b/android/arch/persistence/room/Transaction.java index 3b6ede9c..914e4f41 100644 --- a/android/arch/persistence/room/Transaction.java +++ b/android/arch/persistence/room/Transaction.java @@ -22,10 +22,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Marks a method in a {@link Dao} class as a transaction method. + * Marks a method in an abstract {@link Dao} class as a transaction method. * <p> - * When used on a non-abstract method of an abstract {@link Dao} class, - * the derived implementation of the method will execute the super method in a database transaction. + * The derived implementation of the method will execute the super method in a database transaction. * All the parameters and return types are preserved. The transaction will be marked as successful * unless an exception is thrown in the method body. * <p> @@ -45,38 +44,6 @@ import java.lang.annotation.Target; * } * } * </pre> - * <p> - * When used on a {@link Query} method that has a {@code Select} statement, the generated code for - * the Query will be run in a transaction. There are 2 main cases where you may want to do that: - * <ol> - * <li>If the result of the query is fairly big, it is better to run it inside a transaction - * to receive a consistent result. Otherwise, if the query result does not fit into a single - * {@link android.database.CursorWindow CursorWindow}, the query result may be corrupted due to - * changes in the database in between cursor window swaps. - * <li>If the result of the query is a Pojo with {@link Relation} fields, these fields are - * queried separately. To receive consistent results between these queries, you probably want - * to run them in a single transaction. - * </ol> - * Example: - * <pre> - * class ProductWithReviews extends Product { - * {@literal @}Relation(parentColumn = "id", entityColumn = "productId", entity = Review.class) - * public List<Review> reviews; - * } - * {@literal @}Dao - * public interface ProductDao { - * {@literal @}Transaction {@literal @}Query("SELECT * from products") - * public List<ProductWithReviews> loadAll(); - * } - * </pre> - * If the query is an async query (e.g. returns a {@link android.arch.lifecycle.LiveData LiveData} - * or RxJava Flowable, the transaction is properly handled when the query is run, not when the - * method is called. - * <p> - * Putting this annotation on an {@link Insert}, {@link Update} or {@link Delete} method has no - * impact because they are always run inside a transaction. Similarly, if it is annotated with - * {@link Query} but runs an update or delete statement, it is automatically wrapped in a - * transaction. */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.CLASS) diff --git a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java index cdd464e4..818c46b4 100644 --- a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java +++ b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java @@ -86,7 +86,6 @@ public class RoomPagedListActivity extends AppCompatActivity { @Override protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); PagedList<Customer> list = mAdapter.getCurrentList(); if (list == null) { // Can't find anything to restore diff --git a/android/arch/persistence/room/integration/testapp/database/CustomerDao.java b/android/arch/persistence/room/integration/testapp/database/CustomerDao.java index b5df914a..9d402370 100644 --- a/android/arch/persistence/room/integration/testapp/database/CustomerDao.java +++ b/android/arch/persistence/room/integration/testapp/database/CustomerDao.java @@ -59,7 +59,7 @@ public interface CustomerDao { // Keyed - @Query("SELECT * from customer ORDER BY mLastName DESC LIMIT :limit") + @Query("SELECT * from customer ORDER BY mLastName ASC LIMIT :limit") List<Customer> customerNameInitial(int limit); @Query("SELECT * from customer WHERE mLastName < :key ORDER BY mLastName DESC LIMIT :limit") diff --git a/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java b/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java new file mode 100644 index 00000000..3cbffc8b --- /dev/null +++ b/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.arch.persistence.room.integration.testapp.db; + +import android.arch.persistence.db.SupportSQLiteDatabase; +import android.arch.persistence.db.SupportSQLiteOpenHelper; + +public class JDBCOpenHelper implements SupportSQLiteOpenHelper { + @Override + public String getDatabaseName() { + return null; + } + + @Override + public void setWriteAheadLoggingEnabled(boolean enabled) { + + } + + @Override + public SupportSQLiteDatabase getWritableDatabase() { + return null; + } + + @Override + public SupportSQLiteDatabase getReadableDatabase() { + return null; + } + + @Override + public void close() { + + } +} diff --git a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java index 33f40183..84f20ec5 100644 --- a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java +++ b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java @@ -17,17 +17,20 @@ package android.arch.persistence.room.integration.testapp.test; import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; -import android.arch.core.executor.testing.CountingTaskExecutorRule; +import android.arch.core.executor.ArchTaskExecutor; +import android.arch.core.executor.TaskExecutor; import android.arch.persistence.room.InvalidationTracker; import android.arch.persistence.room.Room; import android.arch.persistence.room.integration.testapp.TestDatabase; import android.arch.persistence.room.integration.testapp.dao.UserDao; import android.arch.persistence.room.integration.testapp.vo.User; import android.content.Context; +import android.os.Handler; +import android.os.Looper; import android.support.annotation.NonNull; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; @@ -35,13 +38,17 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; /** * Tests invalidation tracking. @@ -49,97 +56,138 @@ import java.util.concurrent.TimeoutException; @SmallTest @RunWith(AndroidJUnit4.class) public class InvalidationTest { - @Rule - public CountingTaskExecutorRule executorRule = new CountingTaskExecutorRule(); private UserDao mUserDao; private TestDatabase mDb; @Before - public void createDb() throws TimeoutException, InterruptedException { + public void createDb() { Context context = InstrumentationRegistry.getTargetContext(); mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build(); mUserDao = mDb.getUserDao(); - drain(); + } + + @Before + public void setSingleThreadedIO() { + ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() { + ExecutorService mIOExecutor = Executors.newSingleThreadExecutor(); + Handler mHandler = new Handler(Looper.getMainLooper()); + + @Override + public void executeOnDiskIO(Runnable runnable) { + mIOExecutor.execute(runnable); + } + + @Override + public void postToMainThread(Runnable runnable) { + mHandler.post(runnable); + } + + @Override + public boolean isMainThread() { + return Thread.currentThread() == Looper.getMainLooper().getThread(); + } + }); } @After - public void closeDb() throws TimeoutException, InterruptedException { - mDb.close(); - drain(); + public void clearExecutor() { + ArchTaskExecutor.getInstance().setDelegate(null); } - private void drain() throws TimeoutException, InterruptedException { - executorRule.drainTasks(1, TimeUnit.MINUTES); + private void waitUntilIOThreadIsIdle() { + FutureTask<Void> future = new FutureTask<>(new Callable<Void>() { + @Override + public Void call() throws Exception { + return null; + } + }); + ArchTaskExecutor.getInstance().executeOnDiskIO(future); + //noinspection TryWithIdenticalCatches + try { + future.get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } } @Test - public void testInvalidationOnUpdate() throws InterruptedException, TimeoutException { + public void testInvalidationOnUpdate() throws InterruptedException { User user = TestUtil.createUser(3); mUserDao.insert(user); - LoggingObserver observer = new LoggingObserver("User"); + LatchObserver observer = new LatchObserver(1, "User"); mDb.getInvalidationTracker().addObserver(observer); - drain(); mUserDao.updateById(3, "foo2"); - drain(); + waitUntilIOThreadIsIdle(); + assertThat(observer.await(), is(true)); assertThat(observer.getInvalidatedTables(), hasSize(1)); assertThat(observer.getInvalidatedTables(), hasItem("User")); } @Test - public void testInvalidationOnDelete() throws InterruptedException, TimeoutException { + public void testInvalidationOnDelete() throws InterruptedException { User user = TestUtil.createUser(3); mUserDao.insert(user); - LoggingObserver observer = new LoggingObserver("User"); + LatchObserver observer = new LatchObserver(1, "User"); mDb.getInvalidationTracker().addObserver(observer); - drain(); mUserDao.delete(user); - drain(); + waitUntilIOThreadIsIdle(); + assertThat(observer.await(), is(true)); assertThat(observer.getInvalidatedTables(), hasSize(1)); assertThat(observer.getInvalidatedTables(), hasItem("User")); } @Test - public void testInvalidationOnInsert() throws InterruptedException, TimeoutException { - LoggingObserver observer = new LoggingObserver("User"); + public void testInvalidationOnInsert() throws InterruptedException { + LatchObserver observer = new LatchObserver(1, "User"); mDb.getInvalidationTracker().addObserver(observer); - drain(); mUserDao.insert(TestUtil.createUser(3)); - drain(); + waitUntilIOThreadIsIdle(); + assertThat(observer.await(), is(true)); assertThat(observer.getInvalidatedTables(), hasSize(1)); assertThat(observer.getInvalidatedTables(), hasItem("User")); } @Test - public void testDontInvalidateOnLateInsert() throws InterruptedException, TimeoutException { - LoggingObserver observer = new LoggingObserver("User"); + public void testDontInvalidateOnLateInsert() throws InterruptedException { + LatchObserver observer = new LatchObserver(1, "User"); mUserDao.insert(TestUtil.createUser(3)); - drain(); + waitUntilIOThreadIsIdle(); mDb.getInvalidationTracker().addObserver(observer); - drain(); - assertThat(observer.getInvalidatedTables(), nullValue()); + waitUntilIOThreadIsIdle(); + assertThat(observer.await(), is(false)); } @Test - public void testMultipleTables() throws InterruptedException, TimeoutException { - LoggingObserver observer = new LoggingObserver("User", "Pet"); + public void testMultipleTables() throws InterruptedException { + LatchObserver observer = new LatchObserver(1, "User", "Pet"); mDb.getInvalidationTracker().addObserver(observer); - drain(); mUserDao.insert(TestUtil.createUser(3)); - drain(); + waitUntilIOThreadIsIdle(); + assertThat(observer.await(), is(true)); assertThat(observer.getInvalidatedTables(), hasSize(1)); assertThat(observer.getInvalidatedTables(), hasItem("User")); } - private static class LoggingObserver extends InvalidationTracker.Observer { + private static class LatchObserver extends InvalidationTracker.Observer { + CountDownLatch mLatch; + private Set<String> mInvalidatedTables; - LoggingObserver(String... tables) { + LatchObserver(int permits, String... tables) { super(tables); + mLatch = new CountDownLatch(permits); + } + + boolean await() throws InterruptedException { + return mLatch.await(5, TimeUnit.SECONDS); } @Override public void onInvalidated(@NonNull Set<String> tables) { mInvalidatedTables = tables; + mLatch.countDown(); } Set<String> getInvalidatedTables() { diff --git a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java b/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java index 2735c05a..e11117e4 100644 --- a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java +++ b/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java @@ -166,13 +166,17 @@ public class QueryDataSourceTest extends TestDatabaseTest { p = dataSource.loadBefore(15, list.get(0), 10); assertNotNull(p); - list.addAll(0, p); + for (User u : p) { + list.add(0, u); + } assertArrayEquals(Arrays.copyOfRange(expected, 5, 35), list.toArray()); p = dataSource.loadBefore(5, list.get(0), 10); assertNotNull(p); - list.addAll(0, p); + for (User u : p) { + list.add(0, u); + } assertArrayEquals(Arrays.copyOfRange(expected, 0, 35), list.toArray()); } diff --git a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java deleted file mode 100644 index 854c8627..00000000 --- a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java +++ /dev/null @@ -1,471 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.arch.persistence.room.integration.testapp.test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; - -import android.arch.core.executor.ArchTaskExecutor; -import android.arch.core.executor.testing.CountingTaskExecutorRule; -import android.arch.lifecycle.Lifecycle; -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.Observer; -import android.arch.paging.LivePagedListProvider; -import android.arch.paging.PagedList; -import android.arch.paging.TiledDataSource; -import android.arch.persistence.room.Dao; -import android.arch.persistence.room.Database; -import android.arch.persistence.room.Entity; -import android.arch.persistence.room.Ignore; -import android.arch.persistence.room.Insert; -import android.arch.persistence.room.PrimaryKey; -import android.arch.persistence.room.Query; -import android.arch.persistence.room.Relation; -import android.arch.persistence.room.Room; -import android.arch.persistence.room.RoomDatabase; -import android.arch.persistence.room.RoomWarnings; -import android.arch.persistence.room.Transaction; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.util.Collections; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; - -import io.reactivex.Flowable; -import io.reactivex.Maybe; -import io.reactivex.Single; -import io.reactivex.observers.TestObserver; -import io.reactivex.schedulers.Schedulers; -import io.reactivex.subscribers.TestSubscriber; - -@SmallTest -@RunWith(Parameterized.class) -public class QueryTransactionTest { - @Rule - public CountingTaskExecutorRule countingTaskExecutorRule = new CountingTaskExecutorRule(); - private static final AtomicInteger sStartedTransactionCount = new AtomicInteger(0); - private TransactionDb mDb; - private final boolean mUseTransactionDao; - private Entity1Dao mDao; - private final LiveDataQueryTest.TestLifecycleOwner mLifecycleOwner = new LiveDataQueryTest - .TestLifecycleOwner(); - - @NonNull - @Parameterized.Parameters(name = "useTransaction_{0}") - public static Boolean[] getParams() { - return new Boolean[]{false, true}; - } - - public QueryTransactionTest(boolean useTransactionDao) { - mUseTransactionDao = useTransactionDao; - } - - @Before - public void initDb() { - InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { - @Override - public void run() { - mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START); - } - }); - - resetTransactionCount(); - mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(), - TransactionDb.class).build(); - mDao = mUseTransactionDao ? mDb.transactionDao() : mDb.dao(); - drain(); - } - - @After - public void closeDb() { - InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { - @Override - public void run() { - mLifecycleOwner.handleEvent(Lifecycle.Event.ON_DESTROY); - } - }); - drain(); - mDb.close(); - } - - @Test - public void readList() { - mDao.insert(new Entity1(1, "foo")); - resetTransactionCount(); - - int expectedTransactionCount = mUseTransactionDao ? 1 : 0; - List<Entity1> allEntities = mDao.allEntities(); - assertTransactionCount(allEntities, expectedTransactionCount); - } - - @Test - public void liveData() { - LiveData<List<Entity1>> listLiveData = mDao.liveData(); - observeForever(listLiveData); - drain(); - assertThat(listLiveData.getValue(), is(Collections.<Entity1>emptyList())); - - resetTransactionCount(); - mDao.insert(new Entity1(1, "foo")); - drain(); - - //noinspection ConstantConditions - assertThat(listLiveData.getValue().size(), is(1)); - int expectedTransactionCount = mUseTransactionDao ? 2 : 1; - assertTransactionCount(listLiveData.getValue(), expectedTransactionCount); - } - - @Test - public void flowable() { - Flowable<List<Entity1>> flowable = mDao.flowable(); - TestSubscriber<List<Entity1>> subscriber = observe(flowable); - drain(); - assertThat(subscriber.values().size(), is(1)); - - resetTransactionCount(); - mDao.insert(new Entity1(1, "foo")); - drain(); - - List<Entity1> allEntities = subscriber.values().get(1); - assertThat(allEntities.size(), is(1)); - int expectedTransactionCount = mUseTransactionDao ? 2 : 1; - assertTransactionCount(allEntities, expectedTransactionCount); - } - - @Test - public void maybe() { - mDao.insert(new Entity1(1, "foo")); - resetTransactionCount(); - - int expectedTransactionCount = mUseTransactionDao ? 1 : 0; - Maybe<List<Entity1>> listMaybe = mDao.maybe(); - TestObserver<List<Entity1>> observer = observe(listMaybe); - drain(); - List<Entity1> allEntities = observer.values().get(0); - assertTransactionCount(allEntities, expectedTransactionCount); - } - - @Test - public void single() { - mDao.insert(new Entity1(1, "foo")); - resetTransactionCount(); - - int expectedTransactionCount = mUseTransactionDao ? 1 : 0; - Single<List<Entity1>> listMaybe = mDao.single(); - TestObserver<List<Entity1>> observer = observe(listMaybe); - drain(); - List<Entity1> allEntities = observer.values().get(0); - assertTransactionCount(allEntities, expectedTransactionCount); - } - - @Test - public void relation() { - mDao.insert(new Entity1(1, "foo")); - mDao.insert(new Child(1, 1)); - mDao.insert(new Child(2, 1)); - resetTransactionCount(); - - List<Entity1WithChildren> result = mDao.withRelation(); - int expectedTransactionCount = mUseTransactionDao ? 1 : 0; - assertTransactionCountWithChildren(result, expectedTransactionCount); - } - - @Test - public void pagedList() { - LiveData<PagedList<Entity1>> pagedList = mDao.pagedList().create(null, 10); - observeForever(pagedList); - drain(); - assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 0 : 0)); - - mDao.insert(new Entity1(1, "foo")); - drain(); - //noinspection ConstantConditions - assertThat(pagedList.getValue().size(), is(1)); - assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 2 : 1); - - mDao.insert(new Entity1(2, "bar")); - drain(); - assertThat(pagedList.getValue().size(), is(2)); - assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 4 : 2); - } - - @Test - public void dataSource() { - mDao.insert(new Entity1(2, "bar")); - drain(); - resetTransactionCount(); - TiledDataSource<Entity1> dataSource = mDao.dataSource(); - dataSource.loadRange(0, 10); - assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 1 : 0)); - } - - private void assertTransactionCount(List<Entity1> allEntities, int expectedTransactionCount) { - assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount)); - assertThat(allEntities.isEmpty(), is(false)); - for (Entity1 entity1 : allEntities) { - assertThat(entity1.transactionId, is(expectedTransactionCount)); - } - } - - private void assertTransactionCountWithChildren(List<Entity1WithChildren> allEntities, - int expectedTransactionCount) { - assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount)); - assertThat(allEntities.isEmpty(), is(false)); - for (Entity1WithChildren entity1 : allEntities) { - assertThat(entity1.transactionId, is(expectedTransactionCount)); - assertThat(entity1.children, notNullValue()); - assertThat(entity1.children.isEmpty(), is(false)); - for (Child child : entity1.children) { - assertThat(child.transactionId, is(expectedTransactionCount)); - } - } - } - - private void resetTransactionCount() { - sStartedTransactionCount.set(0); - } - - private void drain() { - try { - countingTaskExecutorRule.drainTasks(30, TimeUnit.SECONDS); - } catch (InterruptedException e) { - throw new AssertionError("interrupted", e); - } catch (TimeoutException e) { - throw new AssertionError("drain timed out", e); - } - } - - private <T> TestSubscriber<T> observe(final Flowable<T> flowable) { - TestSubscriber<T> subscriber = new TestSubscriber<>(); - flowable.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor())) - .subscribeWith(subscriber); - return subscriber; - } - - private <T> TestObserver<T> observe(final Maybe<T> maybe) { - TestObserver<T> observer = new TestObserver<>(); - maybe.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor())) - .subscribeWith(observer); - return observer; - } - - private <T> TestObserver<T> observe(final Single<T> single) { - TestObserver<T> observer = new TestObserver<>(); - single.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor())) - .subscribeWith(observer); - return observer; - } - - private <T> void observeForever(final LiveData<T> liveData) { - FutureTask<Void> futureTask = new FutureTask<>(new Callable<Void>() { - @Override - public Void call() throws Exception { - liveData.observe(mLifecycleOwner, new Observer<T>() { - @Override - public void onChanged(@Nullable T t) { - - } - }); - return null; - } - }); - ArchTaskExecutor.getMainThreadExecutor().execute(futureTask); - try { - futureTask.get(); - } catch (InterruptedException e) { - throw new AssertionError("interrupted", e); - } catch (ExecutionException e) { - throw new AssertionError("execution error", e); - } - } - - @SuppressWarnings("WeakerAccess") - static class Entity1WithChildren extends Entity1 { - @Relation(entity = Child.class, parentColumn = "id", - entityColumn = "entity1Id") - public List<Child> children; - - Entity1WithChildren(int id, String value) { - super(id, value); - } - } - - @SuppressWarnings("WeakerAccess") - @Entity - static class Child { - @PrimaryKey(autoGenerate = true) - public int id; - public int entity1Id; - @Ignore - public final int transactionId = sStartedTransactionCount.get(); - - Child(int id, int entity1Id) { - this.id = id; - this.entity1Id = entity1Id; - } - } - - @SuppressWarnings("WeakerAccess") - @Entity - static class Entity1 { - @PrimaryKey(autoGenerate = true) - public int id; - public String value; - @Ignore - public final int transactionId = sStartedTransactionCount.get(); - - Entity1(int id, String value) { - this.id = id; - this.value = value; - } - } - - // we don't support dao inheritance for queries so for now, go with this - interface Entity1Dao { - String SELECT_ALL = "select * from Entity1"; - - List<Entity1> allEntities(); - - Flowable<List<Entity1>> flowable(); - - Maybe<List<Entity1>> maybe(); - - Single<List<Entity1>> single(); - - LiveData<List<Entity1>> liveData(); - - List<Entity1WithChildren> withRelation(); - - LivePagedListProvider<Integer, Entity1> pagedList(); - - TiledDataSource<Entity1> dataSource(); - - @Insert - void insert(Entity1 entity1); - - @Insert - void insert(Child entity1); - } - - @Dao - interface EntityDao extends Entity1Dao { - @Override - @Query(SELECT_ALL) - List<Entity1> allEntities(); - - @Override - @Query(SELECT_ALL) - Flowable<List<Entity1>> flowable(); - - @Override - @Query(SELECT_ALL) - LiveData<List<Entity1>> liveData(); - - @Override - @Query(SELECT_ALL) - Maybe<List<Entity1>> maybe(); - - @Override - @Query(SELECT_ALL) - Single<List<Entity1>> single(); - - @Override - @Query(SELECT_ALL) - @SuppressWarnings(RoomWarnings.RELATION_QUERY_WITHOUT_TRANSACTION) - List<Entity1WithChildren> withRelation(); - - @Override - @Query(SELECT_ALL) - LivePagedListProvider<Integer, Entity1> pagedList(); - - @Override - @Query(SELECT_ALL) - TiledDataSource<Entity1> dataSource(); - } - - @Dao - interface TransactionDao extends Entity1Dao { - @Override - @Transaction - @Query(SELECT_ALL) - List<Entity1> allEntities(); - - @Override - @Transaction - @Query(SELECT_ALL) - Flowable<List<Entity1>> flowable(); - - @Override - @Transaction - @Query(SELECT_ALL) - LiveData<List<Entity1>> liveData(); - - @Override - @Transaction - @Query(SELECT_ALL) - Maybe<List<Entity1>> maybe(); - - @Override - @Transaction - @Query(SELECT_ALL) - Single<List<Entity1>> single(); - - @Override - @Transaction - @Query(SELECT_ALL) - List<Entity1WithChildren> withRelation(); - - @Override - @Transaction - @Query(SELECT_ALL) - LivePagedListProvider<Integer, Entity1> pagedList(); - - @Override - @Transaction - @Query(SELECT_ALL) - TiledDataSource<Entity1> dataSource(); - } - - @Database(version = 1, entities = {Entity1.class, Child.class}, exportSchema = false) - abstract static class TransactionDb extends RoomDatabase { - abstract EntityDao dao(); - - abstract TransactionDao transactionDao(); - - @Override - public void beginTransaction() { - super.beginTransaction(); - sStartedTransactionCount.incrementAndGet(); - } - } -} diff --git a/android/arch/persistence/room/migration/Migration.java b/android/arch/persistence/room/migration/Migration.java index d69ea0dc..907e624b 100644 --- a/android/arch/persistence/room/migration/Migration.java +++ b/android/arch/persistence/room/migration/Migration.java @@ -17,7 +17,6 @@ package android.arch.persistence.room.migration; import android.arch.persistence.db.SupportSQLiteDatabase; -import android.support.annotation.NonNull; /** * Base class for a database migration. @@ -59,5 +58,5 @@ public abstract class Migration { * * @param database The database instance */ - public abstract void migrate(@NonNull SupportSQLiteDatabase database); + public abstract void migrate(SupportSQLiteDatabase database); } diff --git a/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java b/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java index d72cf8cb..1467a4f0 100644 --- a/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java +++ b/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java @@ -16,18 +16,13 @@ package android.arch.persistence.room.migration.bundle; -import android.support.annotation.RestrictTo; - import com.google.gson.annotations.SerializedName; import java.util.List; /** * Holds the information about a foreign key reference. - * - * @hide */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class ForeignKeyBundle { @SerializedName("table") private String mTable; diff --git a/android/arch/persistence/room/paging/LimitOffsetDataSource.java b/android/arch/persistence/room/paging/LimitOffsetDataSource.java index 2f9a8882..800514cc 100644 --- a/android/arch/persistence/room/paging/LimitOffsetDataSource.java +++ b/android/arch/persistence/room/paging/LimitOffsetDataSource.java @@ -49,13 +49,10 @@ public abstract class LimitOffsetDataSource<T> extends TiledDataSource<T> { private final RoomDatabase mDb; @SuppressWarnings("FieldCanBeLocal") private final InvalidationTracker.Observer mObserver; - private final boolean mInTransaction; - protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query, - boolean inTransaction, String... tables) { + protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query, String... tables) { mDb = db; mSourceQuery = query; - mInTransaction = inTransaction; mCountQuery = "SELECT COUNT(*) FROM ( " + mSourceQuery.getSql() + " )"; mLimitOffsetQuery = "SELECT * FROM ( " + mSourceQuery.getSql() + " ) LIMIT ? OFFSET ?"; mObserver = new InvalidationTracker.Observer(tables) { @@ -101,30 +98,13 @@ public abstract class LimitOffsetDataSource<T> extends TiledDataSource<T> { sqLiteQuery.copyArgumentsFrom(mSourceQuery); sqLiteQuery.bindLong(sqLiteQuery.getArgCount() - 1, loadCount); sqLiteQuery.bindLong(sqLiteQuery.getArgCount(), startPosition); - if (mInTransaction) { - mDb.beginTransaction(); - Cursor cursor = null; - try { - cursor = mDb.query(sqLiteQuery); - List<T> rows = convertRows(cursor); - mDb.setTransactionSuccessful(); - return rows; - } finally { - if (cursor != null) { - cursor.close(); - } - mDb.endTransaction(); - sqLiteQuery.release(); - } - } else { - Cursor cursor = mDb.query(sqLiteQuery); - //noinspection TryFinallyCanBeTryWithResources - try { - return convertRows(cursor); - } finally { - cursor.close(); - sqLiteQuery.release(); - } + Cursor cursor = mDb.query(sqLiteQuery); + + try { + return convertRows(cursor); + } finally { + cursor.close(); + sqLiteQuery.release(); } } } diff --git a/android/arch/persistence/room/util/StringUtil.java b/android/arch/persistence/room/util/StringUtil.java index d01e3c53..bee05ddd 100644 --- a/android/arch/persistence/room/util/StringUtil.java +++ b/android/arch/persistence/room/util/StringUtil.java @@ -17,7 +17,6 @@ package android.arch.persistence.room.util; import android.support.annotation.Nullable; -import android.support.annotation.RestrictTo; import android.util.Log; import java.util.ArrayList; @@ -25,14 +24,10 @@ import java.util.List; import java.util.StringTokenizer; /** - * @hide - * * String utilities for Room */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +@SuppressWarnings("WeakerAccess") public class StringUtil { - - @SuppressWarnings("unused") public static final String[] EMPTY_STRING_ARRAY = new String[0]; /** * Returns a new StringBuilder to be used while producing SQL queries. diff --git a/android/content/ContentProvider.java b/android/content/ContentProvider.java index cdeaea3e..5b2bf456 100644 --- a/android/content/ContentProvider.java +++ b/android/content/ContentProvider.java @@ -2099,7 +2099,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 { public static Uri maybeAddUserId(Uri uri, int userId) { if (uri == null) return null; if (userId != UserHandle.USER_CURRENT - && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { + && (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) + || ContentResolver.SCHEME_SLICE.equals(uri.getScheme()))) { if (!uriHasUserId(uri)) { //We don't add the user Id if there's already one Uri.Builder builder = uri.buildUpon(); diff --git a/android/content/ContentResolver.java b/android/content/ContentResolver.java index 9ccc552f..02e70f55 100644 --- a/android/content/ContentResolver.java +++ b/android/content/ContentResolver.java @@ -47,6 +47,8 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.slice.Slice; +import android.slice.SliceProvider; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; @@ -178,6 +180,8 @@ public abstract class ContentResolver { public static final Intent ACTION_SYNC_CONN_STATUS_CHANGED = new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED"); + /** @hide */ + public static final String SCHEME_SLICE = "slice"; public static final String SCHEME_CONTENT = "content"; public static final String SCHEME_ANDROID_RESOURCE = "android.resource"; public static final String SCHEME_FILE = "file"; @@ -1718,6 +1722,36 @@ public abstract class ContentResolver { } /** + * Turns a slice Uri into slice content. + * + * @param uri The URI to a slice provider + * @return The Slice provided by the app or null if none is given. + * @see Slice + * @hide + */ + public final @Nullable Slice bindSlice(@NonNull Uri uri) { + Preconditions.checkNotNull(uri, "uri"); + IContentProvider provider = acquireProvider(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URI " + uri); + } + try { + Bundle extras = new Bundle(); + extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); + final Bundle res = provider.call(mPackageName, SliceProvider.METHOD_SLICE, null, + extras); + Bundle.setDefusable(res, true); + return res.getParcelable(SliceProvider.EXTRA_SLICE); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return null; + } finally { + releaseProvider(provider); + } + } + + /** * Returns the content provider for the given content URI. * * @param uri The URI to a content provider @@ -1725,7 +1759,7 @@ public abstract class ContentResolver { * @hide */ public final IContentProvider acquireProvider(Uri uri) { - if (!SCHEME_CONTENT.equals(uri.getScheme())) { + if (!SCHEME_CONTENT.equals(uri.getScheme()) && !SCHEME_SLICE.equals(uri.getScheme())) { return null; } final String auth = uri.getAuthority(); diff --git a/android/content/Intent.java b/android/content/Intent.java index e47de752..c9ad9519 100644 --- a/android/content/Intent.java +++ b/android/content/Intent.java @@ -53,7 +53,6 @@ import android.provider.OpenableColumns; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; -import android.util.proto.ProtoOutputStream; import com.android.internal.util.XmlUtils; @@ -9372,57 +9371,6 @@ public class Intent implements Parcelable, Cloneable { } } - /** @hide */ - public void writeToProto(ProtoOutputStream proto, long fieldId, boolean secure, boolean comp, - boolean extras, boolean clip) { - long token = proto.start(fieldId); - if (mAction != null) { - proto.write(IntentProto.ACTION, mAction); - } - if (mCategories != null) { - for (String category : mCategories) { - proto.write(IntentProto.CATEGORIES, category); - } - } - if (mData != null) { - proto.write(IntentProto.DATA, secure ? mData.toSafeString() : mData.toString()); - } - if (mType != null) { - proto.write(IntentProto.TYPE, mType); - } - if (mFlags != 0) { - proto.write(IntentProto.FLAG, "0x" + Integer.toHexString(mFlags)); - } - if (mPackage != null) { - proto.write(IntentProto.PACKAGE, mPackage); - } - if (comp && mComponent != null) { - proto.write(IntentProto.COMPONENT, mComponent.flattenToShortString()); - } - if (mSourceBounds != null) { - proto.write(IntentProto.SOURCE_BOUNDS, mSourceBounds.toShortString()); - } - if (mClipData != null) { - StringBuilder b = new StringBuilder(); - if (clip) { - mClipData.toShortString(b); - } else { - mClipData.toShortStringShortItems(b, false); - } - proto.write(IntentProto.CLIP_DATA, b.toString()); - } - if (extras && mExtras != null) { - proto.write(IntentProto.EXTRAS, mExtras.toShortString()); - } - if (mContentUserHint != 0) { - proto.write(IntentProto.CONTENT_USER_HINT, mContentUserHint); - } - if (mSelector != null) { - proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip)); - } - proto.end(token); - } - /** * Call {@link #toUri} with 0 flags. * @deprecated Use {@link #toUri} instead. diff --git a/android/content/IntentFilter.java b/android/content/IntentFilter.java index a957aed8..c9bce530 100644 --- a/android/content/IntentFilter.java +++ b/android/content/IntentFilter.java @@ -26,7 +26,6 @@ import android.text.TextUtils; import android.util.AndroidException; import android.util.Log; import android.util.Printer; -import android.util.proto.ProtoOutputStream; import com.android.internal.util.XmlUtils; @@ -919,15 +918,6 @@ public class IntentFilter implements Parcelable { dest.writeInt(mPort); } - void writeToProto(ProtoOutputStream proto, long fieldId) { - long token = proto.start(fieldId); - // The original host information is already contained in host and wild, no output now. - proto.write(AuthorityEntryProto.HOST, mHost); - proto.write(AuthorityEntryProto.WILD, mWild); - proto.write(AuthorityEntryProto.PORT, mPort); - proto.end(token); - } - public String getHost() { return mOrigHost; } @@ -1749,59 +1739,6 @@ public class IntentFilter implements Parcelable { } } - /** @hide */ - public void writeToProto(ProtoOutputStream proto, long fieldId) { - long token = proto.start(fieldId); - if (mActions.size() > 0) { - Iterator<String> it = mActions.iterator(); - while (it.hasNext()) { - proto.write(IntentFilterProto.ACTIONS, it.next()); - } - } - if (mCategories != null) { - Iterator<String> it = mCategories.iterator(); - while (it.hasNext()) { - proto.write(IntentFilterProto.CATEGORIES, it.next()); - } - } - if (mDataSchemes != null) { - Iterator<String> it = mDataSchemes.iterator(); - while (it.hasNext()) { - proto.write(IntentFilterProto.DATA_SCHEMES, it.next()); - } - } - if (mDataSchemeSpecificParts != null) { - Iterator<PatternMatcher> it = mDataSchemeSpecificParts.iterator(); - while (it.hasNext()) { - it.next().writeToProto(proto, IntentFilterProto.DATA_SCHEME_SPECS); - } - } - if (mDataAuthorities != null) { - Iterator<AuthorityEntry> it = mDataAuthorities.iterator(); - while (it.hasNext()) { - it.next().writeToProto(proto, IntentFilterProto.DATA_AUTHORITIES); - } - } - if (mDataPaths != null) { - Iterator<PatternMatcher> it = mDataPaths.iterator(); - while (it.hasNext()) { - it.next().writeToProto(proto, IntentFilterProto.DATA_PATHS); - } - } - if (mDataTypes != null) { - Iterator<String> it = mDataTypes.iterator(); - while (it.hasNext()) { - proto.write(IntentFilterProto.DATA_TYPES, it.next()); - } - } - if (mPriority != 0 || mHasPartialTypes) { - proto.write(IntentFilterProto.PRIORITY, mPriority); - proto.write(IntentFilterProto.HAS_PARTIAL_TYPES, mHasPartialTypes); - } - proto.write(IntentFilterProto.GET_AUTO_VERIFY, getAutoVerify()); - proto.end(token); - } - public void dump(Printer du, String prefix) { StringBuilder sb = new StringBuilder(256); if (mActions.size() > 0) { diff --git a/android/content/pm/FeatureInfo.java b/android/content/pm/FeatureInfo.java index ff9fd8ec..9ee6fa24 100644 --- a/android/content/pm/FeatureInfo.java +++ b/android/content/pm/FeatureInfo.java @@ -18,7 +18,6 @@ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; -import android.util.proto.ProtoOutputStream; /** * Definition of a single optional hardware or software feature of an Android @@ -114,18 +113,6 @@ public class FeatureInfo implements Parcelable { dest.writeInt(flags); } - /** @hide */ - public void writeToProto(ProtoOutputStream proto, long fieldId) { - long token = proto.start(fieldId); - if (name != null) { - proto.write(FeatureInfoProto.NAME, name); - } - proto.write(FeatureInfoProto.VERSION, version); - proto.write(FeatureInfoProto.GLES_VERSION, getGlEsVersion()); - proto.write(FeatureInfoProto.FLAGS, flags); - proto.end(token); - } - public static final Creator<FeatureInfo> CREATOR = new Creator<FeatureInfo>() { @Override public FeatureInfo createFromParcel(Parcel source) { diff --git a/android/content/pm/LauncherApps.java b/android/content/pm/LauncherApps.java index b94a410b..aa9562ff 100644 --- a/android/content/pm/LauncherApps.java +++ b/android/content/pm/LauncherApps.java @@ -20,8 +20,8 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemService; +import android.annotation.SdkConstant.SdkConstantType; import android.annotation.TestApi; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; @@ -37,10 +37,10 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Rect; -import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.graphics.drawable.AdaptiveIconDrawable; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -282,27 +282,12 @@ public class LauncherApps { public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST; /** - * @hide include all pinned shortcuts by any launchers, not just by the caller, - * in the result. - * If the caller doesn't havve the {@link android.Manifest.permission#ACCESS_SHORTCUTS} - * permission, this flag will be ignored. - */ - @TestApi - public static final int FLAG_MATCH_ALL_PINNED = 1 << 10; - - /** - * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST + * Does not retrieve CHOOSER only shortcuts. + * TODO: Add another flag for MATCH_ALL_PINNED * @hide */ public static final int FLAG_MATCH_ALL_KINDS = - FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST; - - /** - * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST | FLAG_MATCH_ALL_PINNED - * @hide - */ - public static final int FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED = - FLAG_MATCH_ALL_KINDS | FLAG_MATCH_ALL_PINNED; + FLAG_GET_DYNAMIC | FLAG_GET_PINNED | FLAG_GET_MANIFEST; /** @hide kept for unit tests */ @Deprecated @@ -334,7 +319,6 @@ public class LauncherApps { FLAG_MATCH_PINNED, FLAG_MATCH_MANIFEST, FLAG_GET_KEY_FIELDS_ONLY, - FLAG_MATCH_MANIFEST, }) @Retention(RetentionPolicy.SOURCE) public @interface QueryFlags {} @@ -694,21 +678,6 @@ public class LauncherApps { } } - private List<ShortcutInfo> maybeUpdateDisabledMessage(List<ShortcutInfo> shortcuts) { - if (shortcuts == null) { - return null; - } - for (int i = shortcuts.size() - 1; i >= 0; i--) { - final ShortcutInfo si = shortcuts.get(i); - final String message = ShortcutInfo.getDisabledReasonForRestoreIssue(mContext, - si.getDisabledReason()); - if (message != null) { - si.setDisabledMessage(message); - } - } - return shortcuts; - } - /** * Returns {@link ShortcutInfo}s that match {@code query}. * @@ -729,16 +698,10 @@ public class LauncherApps { @NonNull UserHandle user) { logErrorForInvalidProfileAccess(user); try { - // Note this is the only case we need to update the disabled message for shortcuts - // that weren't restored. - // The restore problem messages are only shown by the user, and publishers will never - // see them. The only other API that the launcher gets shortcuts is the shortcut - // changed callback, but that only returns shortcuts with the "key" information, so - // that won't return disabled message. - return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(), + return mService.getShortcuts(mContext.getPackageName(), query.mChangedSince, query.mPackage, query.mShortcutIds, query.mActivity, query.mQueryFlags, user) - .getList()); + .getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java index 143c51da..be7f921e 100644 --- a/android/content/pm/PackageManagerInternal.java +++ b/android/content/pm/PackageManagerInternal.java @@ -467,7 +467,6 @@ public abstract class PackageManagerInternal { /** Updates the flags for the given permission. */ public abstract void updatePermissionFlagsTEMP(@NonNull String permName, @NonNull String packageName, int flagMask, int flagValues, int userId); - /** Returns a PermissionGroup. */ - public abstract @Nullable PackageParser.PermissionGroup getPermissionGroupTEMP( - @NonNull String groupName); + /** temporary until mPermissionTrees is moved to PermissionManager */ + public abstract Object enforcePermissionTreeTEMP(@NonNull String permName, int callingUid); } diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java index ad36139a..6c7c8a07 100644 --- a/android/content/pm/PackageParser.java +++ b/android/content/pm/PackageParser.java @@ -3711,15 +3711,17 @@ public class PackageParser { ai.flags |= ApplicationInfo.FLAG_IS_GAME; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState, - false)) { - ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE; + if (false) { + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState, + false)) { + ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE; - // A heavy-weight application can not be in a custom process. - // We can do direct compare because we intern all strings. - if (ai.processName != null && !ai.processName.equals(ai.packageName)) { - outError[0] = "cantSaveState applications can not use custom processes"; + // A heavy-weight application can not be in a custom process. + // We can do direct compare because we intern all strings. + if (ai.processName != null && ai.processName != ai.packageName) { + outError[0] = "cantSaveState applications can not use custom processes"; + } } } } @@ -6847,11 +6849,6 @@ public class PackageParser { dest.writeParcelable(group, flags); } - /** @hide */ - public boolean isAppOp() { - return info.isAppOp(); - } - private Permission(Parcel in) { super(in); final ClassLoader boot = Object.class.getClassLoader(); diff --git a/android/content/pm/PermissionInfo.java b/android/content/pm/PermissionInfo.java index 5dd7aeda..b45c26ce 100644 --- a/android/content/pm/PermissionInfo.java +++ b/android/content/pm/PermissionInfo.java @@ -353,11 +353,6 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { return size; } - /** @hide */ - public boolean isAppOp() { - return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0; - } - public static final Creator<PermissionInfo> CREATOR = new Creator<PermissionInfo>() { @Override diff --git a/android/content/pm/ShortcutInfo.java b/android/content/pm/ShortcutInfo.java index 9ff07757..6b9c7537 100644 --- a/android/content/pm/ShortcutInfo.java +++ b/android/content/pm/ShortcutInfo.java @@ -18,7 +18,6 @@ package android.content.pm; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.annotation.UserIdInt; import android.app.TaskStackBuilder; import android.content.ComponentName; @@ -101,13 +100,6 @@ public final class ShortcutInfo implements Parcelable { /** @hide When this is set, the bitmap icon is waiting to be saved. */ public static final int FLAG_ICON_FILE_PENDING_SAVE = 1 << 11; - /** - * "Shadow" shortcuts are the ones that are restored, but the owner package hasn't been - * installed yet. - * @hide - */ - public static final int FLAG_SHADOW = 1 << 12; - /** @hide */ @IntDef(flag = true, value = { @@ -166,124 +158,6 @@ public final class ShortcutInfo implements Parcelable { public @interface CloneFlags {} /** - * Shortcut is not disabled. - */ - public static final int DISABLED_REASON_NOT_DISABLED = 0; - - /** - * Shortcut has been disabled by the publisher app with the - * {@link ShortcutManager#disableShortcuts(List)} API. - */ - public static final int DISABLED_REASON_BY_APP = 1; - - /** - * Shortcut has been disabled due to changes to the publisher app. (e.g. a manifest shortcut - * no longer exists.) - */ - public static final int DISABLED_REASON_APP_CHANGED = 2; - - /** - * A disabled reason that's equal to or bigger than this is due to backup and restore issue. - * A shortcut with such a reason wil be visible to the launcher, but not to the publisher. - * ({@link #isVisibleToPublisher()} will be false.) - */ - private static final int DISABLED_REASON_RESTORE_ISSUE_START = 100; - - /** - * Shortcut has been restored from the previous device, but the publisher app on the current - * device is of a lower version. The shortcut will not be usable until the app is upgraded to - * the same version or higher. - */ - public static final int DISABLED_REASON_VERSION_LOWER = 100; - - /** - * Shortcut has not been restored because the publisher app does not support backup and restore. - */ - public static final int DISABLED_REASON_BACKUP_NOT_SUPPORTED = 101; - - /** - * Shortcut has not been restored because the publisher app's signature has changed. - */ - public static final int DISABLED_REASON_SIGNATURE_MISMATCH = 102; - - /** - * Shortcut has not been restored for unknown reason. - */ - public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; - - /** @hide */ - @IntDef(value = { - DISABLED_REASON_NOT_DISABLED, - DISABLED_REASON_BY_APP, - DISABLED_REASON_APP_CHANGED, - DISABLED_REASON_VERSION_LOWER, - DISABLED_REASON_BACKUP_NOT_SUPPORTED, - DISABLED_REASON_SIGNATURE_MISMATCH, - DISABLED_REASON_OTHER_RESTORE_ISSUE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface DisabledReason{} - - /** - * Return a label for disabled reasons, which are *not* supposed to be shown to the user. - * @hide - */ - public static String getDisabledReasonDebugString(@DisabledReason int disabledReason) { - switch (disabledReason) { - case DISABLED_REASON_NOT_DISABLED: - return "[Not disabled]"; - case DISABLED_REASON_BY_APP: - return "[Disabled: by app]"; - case DISABLED_REASON_APP_CHANGED: - return "[Disabled: app changed]"; - case DISABLED_REASON_VERSION_LOWER: - return "[Disabled: lower version]"; - case DISABLED_REASON_BACKUP_NOT_SUPPORTED: - return "[Disabled: backup not supported]"; - case DISABLED_REASON_SIGNATURE_MISMATCH: - return "[Disabled: signature mismatch]"; - case DISABLED_REASON_OTHER_RESTORE_ISSUE: - return "[Disabled: unknown restore issue]"; - } - return "[Disabled: unknown reason:" + disabledReason + "]"; - } - - /** - * Return a label for a disabled reason for shortcuts that are disabled due to a backup and - * restore issue. If the reason is not due to backup & restore, then it'll return null. - * - * This method returns localized, user-facing strings, which will be returned by - * {@link #getDisabledMessage()}. - * - * @hide - */ - public static String getDisabledReasonForRestoreIssue(Context context, - @DisabledReason int disabledReason) { - final Resources res = context.getResources(); - - switch (disabledReason) { - case DISABLED_REASON_VERSION_LOWER: - return res.getString( - com.android.internal.R.string.shortcut_restored_on_lower_version); - case DISABLED_REASON_BACKUP_NOT_SUPPORTED: - return res.getString( - com.android.internal.R.string.shortcut_restore_not_supported); - case DISABLED_REASON_SIGNATURE_MISMATCH: - return res.getString( - com.android.internal.R.string.shortcut_restore_signature_mismatch); - case DISABLED_REASON_OTHER_RESTORE_ISSUE: - return res.getString( - com.android.internal.R.string.shortcut_restore_unknown_issue); - } - return null; - } - - /** @hide */ - public static boolean isDisabledForRestoreIssue(@DisabledReason int disabledReason) { - return disabledReason >= DISABLED_REASON_RESTORE_ISSUE_START; - } - - /** * Shortcut category for messaging related actions, such as chat. */ public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation"; @@ -366,11 +240,6 @@ public final class ShortcutInfo implements Parcelable { private final int mUserId; - /** @hide */ - public static final int VERSION_CODE_UNKNOWN = -1; - - private int mDisabledReason; - private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); @@ -483,7 +352,6 @@ public final class ShortcutInfo implements Parcelable { mActivity = source.mActivity; mFlags = source.mFlags; mLastChangedTimestamp = source.mLastChangedTimestamp; - mDisabledReason = source.mDisabledReason; // Just always keep it since it's cheep. mIconResId = source.mIconResId; @@ -747,23 +615,13 @@ public final class ShortcutInfo implements Parcelable { /** * @hide - * - * @isUpdating set true if it's "update", as opposed to "replace". */ - public void ensureUpdatableWith(ShortcutInfo source, boolean isUpdating) { - if (isUpdating) { - Preconditions.checkState(isVisibleToPublisher(), - "[Framework BUG] Invisible shortcuts can't be updated"); - } + public void ensureUpdatableWith(ShortcutInfo source) { Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match"); Preconditions.checkState(mId.equals(source.mId), "ID must match"); Preconditions.checkState(mPackageName.equals(source.mPackageName), "Package name must match"); - - if (isVisibleToPublisher()) { - // Don't do this check for restore-blocked shortcuts. - Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable"); - } + Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable"); } /** @@ -780,7 +638,7 @@ public final class ShortcutInfo implements Parcelable { * @hide */ public void copyNonNullFieldsFrom(ShortcutInfo source) { - ensureUpdatableWith(source, /*isUpdating=*/ true); + ensureUpdatableWith(source); if (source.mActivity != null) { mActivity = source.mActivity; @@ -1311,19 +1169,6 @@ public final class ShortcutInfo implements Parcelable { return mDisabledMessageResId; } - /** @hide */ - public void setDisabledReason(@DisabledReason int reason) { - mDisabledReason = reason; - } - - /** - * Returns why a shortcut has been disabled. - */ - @DisabledReason - public int getDisabledReason() { - return mDisabledReason; - } - /** * Return the shortcut's categories. * @@ -1558,21 +1403,6 @@ public final class ShortcutInfo implements Parcelable { return hasFlags(FLAG_IMMUTABLE); } - /** @hide */ - public boolean isDynamicVisible() { - return isDynamic() && isVisibleToPublisher(); - } - - /** @hide */ - public boolean isPinnedVisible() { - return isPinned() && isVisibleToPublisher(); - } - - /** @hide */ - public boolean isManifestVisible() { - return isDeclaredInManifest() && isVisibleToPublisher(); - } - /** * Return if a shortcut is immutable, in which case it cannot be modified with any of * {@link ShortcutManager} APIs. @@ -1661,18 +1491,6 @@ public final class ShortcutInfo implements Parcelable { } /** - * When the system wasn't able to restore a shortcut, it'll still be registered to the system - * but disabled, and such shortcuts will not be visible to the publisher. They're still visible - * to launchers though. - * - * @hide - */ - @TestApi - public boolean isVisibleToPublisher() { - return !isDisabledForRestoreIssue(mDisabledReason); - } - - /** * Return whether a shortcut only contains "key" information only or not. If true, only the * following fields are available. * <ul> @@ -1850,7 +1668,6 @@ public final class ShortcutInfo implements Parcelable { mFlags = source.readInt(); mIconResId = source.readInt(); mLastChangedTimestamp = source.readLong(); - mDisabledReason = source.readInt(); if (source.readInt() == 0) { return; // key information only. @@ -1894,7 +1711,6 @@ public final class ShortcutInfo implements Parcelable { dest.writeInt(mFlags); dest.writeInt(mIconResId); dest.writeLong(mLastChangedTimestamp); - dest.writeInt(mDisabledReason); if (hasKeyFieldsOnly()) { dest.writeInt(0); @@ -1992,11 +1808,6 @@ public final class ShortcutInfo implements Parcelable { sb.append(", flags=0x"); sb.append(Integer.toHexString(mFlags)); sb.append(" ["); - if ((mFlags & FLAG_SHADOW) != 0) { - // Note the shadow flag isn't actually used anywhere and it's just for dumpsys, so - // we don't have an isXxx for this. - sb.append("Sdw"); - } if (!isEnabled()) { sb.append("Dis"); } @@ -2037,9 +1848,7 @@ public final class ShortcutInfo implements Parcelable { sb.append("packageName="); sb.append(mPackageName); - addIndentOrComma(sb, indent); - - sb.append("activity="); + sb.append(", activity="); sb.append(mActivity); addIndentOrComma(sb, indent); @@ -2074,11 +1883,6 @@ public final class ShortcutInfo implements Parcelable { addIndentOrComma(sb, indent); - sb.append("disabledReason="); - sb.append(getDisabledReasonDebugString(mDisabledReason)); - - addIndentOrComma(sb, indent); - sb.append("categories="); sb.append(mCategories); @@ -2149,7 +1953,7 @@ public final class ShortcutInfo implements Parcelable { CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName, Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras, long lastChangedTimestamp, - int flags, int iconResId, String iconResName, String bitmapPath, int disabledReason) { + int flags, int iconResId, String iconResName, String bitmapPath) { mUserId = userId; mId = id; mPackageName = packageName; @@ -2174,6 +1978,5 @@ public final class ShortcutInfo implements Parcelable { mIconResId = iconResId; mIconResName = iconResName; mBitmapPath = bitmapPath; - mDisabledReason = disabledReason; } } diff --git a/android/content/pm/ShortcutServiceInternal.java b/android/content/pm/ShortcutServiceInternal.java index 7fc25d82..7b7d8ae4 100644 --- a/android/content/pm/ShortcutServiceInternal.java +++ b/android/content/pm/ShortcutServiceInternal.java @@ -46,7 +46,7 @@ public abstract class ShortcutServiceInternal { @NonNull String callingPackage, long changedSince, @Nullable String packageName, @Nullable List<String> shortcutIds, @Nullable ComponentName componentName, @ShortcutQuery.QueryFlags int flags, - int userId, int callingPid, int callingUid); + int userId); public abstract boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, @@ -58,8 +58,7 @@ public abstract class ShortcutServiceInternal { public abstract Intent[] createShortcutIntents( int launcherUserId, @NonNull String callingPackage, - @NonNull String packageName, @NonNull String shortcutId, int userId, - int callingPid, int callingUid); + @NonNull String packageName, @NonNull String shortcutId, int userId); public abstract void addListener(@NonNull ShortcutChangeListener listener); @@ -71,7 +70,7 @@ public abstract class ShortcutServiceInternal { @NonNull String packageName, @NonNull String shortcutId, int userId); public abstract boolean hasShortcutHostPermission(int launcherUserId, - @NonNull String callingPackage, int callingPid, int callingUid); + @NonNull String callingPackage); public abstract boolean requestPinAppWidget(@NonNull String callingPackage, @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras, diff --git a/android/content/res/ResourcesImpl.java b/android/content/res/ResourcesImpl.java index 386239cf..a8b8c4b5 100644 --- a/android/content/res/ResourcesImpl.java +++ b/android/content/res/ResourcesImpl.java @@ -796,7 +796,7 @@ public class ResourcesImpl { dr = Drawable.createFromResourceStream(wrapper, value, is, file, null); is.close(); } - } catch (Exception | StackOverflowError e) { + } catch (Exception e) { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); final NotFoundException rnf = new NotFoundException( "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id)); diff --git a/android/database/SQLiteDatabaseIoPerfTest.java b/android/database/SQLiteDatabaseIoPerfTest.java deleted file mode 100644 index 7c5316d2..00000000 --- a/android/database/SQLiteDatabaseIoPerfTest.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package android.database; - -import android.app.Activity; -import android.content.ContentValues; -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.os.Bundle; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.ArrayMap; -import android.util.Log; - -import com.android.internal.util.Preconditions; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.List; -import java.util.Map; - -import static org.junit.Assert.assertEquals; - -/** - * Performance tests for measuring amount of data written during typical DB operations - * - * <p>To run: bit CorePerfTests:android.database.SQLiteDatabaseIoPerfTest - */ -@RunWith(AndroidJUnit4.class) -@LargeTest -public class SQLiteDatabaseIoPerfTest { - private static final String TAG = "SQLiteDatabaseIoPerfTest"; - private static final String DB_NAME = "db_io_perftest"; - private static final int DEFAULT_DATASET_SIZE = 500; - - private Long mWriteBytes; - - private SQLiteDatabase mDatabase; - private Context mContext; - - @Before - public void setUp() { - mContext = InstrumentationRegistry.getTargetContext(); - mContext.deleteDatabase(DB_NAME); - mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null); - mDatabase.execSQL("CREATE TABLE T1 " - + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)"); - } - - @After - public void tearDown() { - mDatabase.close(); - mContext.deleteDatabase(DB_NAME); - } - - @Test - public void testDatabaseModifications() { - startMeasuringWrites(); - ContentValues cv = new ContentValues(); - String[] whereArg = new String[1]; - for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) { - cv.put("_ID", i); - cv.put("COL_A", i); - cv.put("COL_B", "NewValue"); - cv.put("COL_C", 1.0); - assertEquals(i, mDatabase.insert("T1", null, cv)); - } - cv = new ContentValues(); - for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) { - cv.put("COL_B", "UpdatedValue"); - cv.put("COL_C", 1.1); - whereArg[0] = String.valueOf(i); - assertEquals(1, mDatabase.update("T1", cv, "_ID=?", whereArg)); - } - for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) { - whereArg[0] = String.valueOf(i); - assertEquals(1, mDatabase.delete("T1", "_ID=?", whereArg)); - } - // Make sure all changes are written to disk - mDatabase.close(); - long bytes = endMeasuringWrites(); - sendResults("testDatabaseModifications" , bytes); - } - - @Test - public void testInsertsWithTransactions() { - startMeasuringWrites(); - final int txSize = 10; - ContentValues cv = new ContentValues(); - for (int i = 0; i < DEFAULT_DATASET_SIZE * 5; i++) { - if (i % txSize == 0) { - mDatabase.beginTransaction(); - } - if (i % txSize == txSize-1) { - mDatabase.setTransactionSuccessful(); - mDatabase.endTransaction(); - - } - cv.put("_ID", i); - cv.put("COL_A", i); - cv.put("COL_B", "NewValue"); - cv.put("COL_C", 1.0); - assertEquals(i, mDatabase.insert("T1", null, cv)); - } - // Make sure all changes are written to disk - mDatabase.close(); - long bytes = endMeasuringWrites(); - sendResults("testInsertsWithTransactions" , bytes); - } - - private void startMeasuringWrites() { - Preconditions.checkState(mWriteBytes == null, "Measurement already started"); - mWriteBytes = getIoStats().get("write_bytes"); - } - - private long endMeasuringWrites() { - Preconditions.checkState(mWriteBytes != null, "Measurement wasn't started"); - Long newWriteBytes = getIoStats().get("write_bytes"); - return newWriteBytes - mWriteBytes; - } - - private void sendResults(String testName, long writeBytes) { - Log.i(TAG, testName + " write_bytes: " + writeBytes); - Bundle status = new Bundle(); - status.putLong("write_bytes", writeBytes); - InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, status); - } - - private static Map<String, Long> getIoStats() { - String ioStat = "/proc/self/io"; - Map<String, Long> results = new ArrayMap<>(); - try { - List<String> lines = Files.readAllLines(new File(ioStat).toPath()); - for (String line : lines) { - line = line.trim(); - String[] split = line.split(":"); - if (split.length == 2) { - try { - String key = split[0].trim(); - Long value = Long.valueOf(split[1].trim()); - results.put(key, value); - } catch (NumberFormatException e) { - Log.e(TAG, "Cannot parse number from " + line); - } - } else if (line.isEmpty()) { - Log.e(TAG, "Cannot parse line " + line); - } - } - } catch (IOException e) { - Log.e(TAG, "Can't read: " + ioStat, e); - } - return results; - } - -} diff --git a/android/database/SQLiteDatabasePerfTest.java b/android/database/SQLiteDatabasePerfTest.java deleted file mode 100644 index 7a32c0cc..00000000 --- a/android/database/SQLiteDatabasePerfTest.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package android.database; - -import android.content.ContentValues; -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.perftests.utils.BenchmarkState; -import android.perftests.utils.PerfStatusReporter; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Random; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * Performance tests for typical CRUD operations and loading rows into the Cursor - * - * <p>To run: bit CorePerfTests:android.database.SQLiteDatabasePerfTest - */ -@RunWith(AndroidJUnit4.class) -@LargeTest -public class SQLiteDatabasePerfTest { - // TODO b/64262688 Add Concurrency tests to compare WAL vs DELETE read/write - private static final String DB_NAME = "dbperftest"; - private static final int DEFAULT_DATASET_SIZE = 1000; - - @Rule - public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - private SQLiteDatabase mDatabase; - private Context mContext; - - @Before - public void setUp() { - mContext = InstrumentationRegistry.getTargetContext(); - mContext.deleteDatabase(DB_NAME); - mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null); - mDatabase.execSQL("CREATE TABLE T1 " - + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)"); - mDatabase.execSQL("CREATE TABLE T2 (" - + "_ID INTEGER PRIMARY KEY, COL_A VARCHAR(100), T1_ID INTEGER," - + "FOREIGN KEY(T1_ID) REFERENCES T1 (_ID))"); - } - - @After - public void tearDown() { - mDatabase.close(); - mContext.deleteDatabase(DB_NAME); - } - - @Test - public void testSelect() { - insertT1TestDataSet(); - - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - - Random rnd = new Random(0); - while (state.keepRunning()) { - int index = rnd.nextInt(DEFAULT_DATASET_SIZE); - try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 " - + "WHERE _ID=?", new String[]{String.valueOf(index)})) { - assertTrue(cursor.moveToNext()); - assertEquals(index, cursor.getInt(0)); - assertEquals(index, cursor.getInt(1)); - assertEquals("T1Value" + index, cursor.getString(2)); - assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d); - } - } - } - - @Test - public void testSelectMultipleRows() { - insertT1TestDataSet(); - - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - Random rnd = new Random(0); - final int querySize = 50; - while (state.keepRunning()) { - int index = rnd.nextInt(DEFAULT_DATASET_SIZE - querySize - 1); - try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 " - + "WHERE _ID BETWEEN ? and ? ORDER BY _ID", - new String[]{String.valueOf(index), String.valueOf(index + querySize - 1)})) { - int i = 0; - while(cursor.moveToNext()) { - assertEquals(index, cursor.getInt(0)); - assertEquals(index, cursor.getInt(1)); - assertEquals("T1Value" + index, cursor.getString(2)); - assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d); - index++; - i++; - } - assertEquals(querySize, i); - } - } - } - - @Test - public void testInnerJoin() { - mDatabase.setForeignKeyConstraintsEnabled(true); - mDatabase.beginTransaction(); - insertT1TestDataSet(); - insertT2TestDataSet(); - mDatabase.setTransactionSuccessful(); - mDatabase.endTransaction(); - - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - - Random rnd = new Random(0); - while (state.keepRunning()) { - int index = rnd.nextInt(1000); - try (Cursor cursor = mDatabase.rawQuery( - "SELECT T1._ID, T1.COL_A, T1.COL_B, T1.COL_C, T2.COL_A FROM T1 " - + "INNER JOIN T2 on T2.T1_ID=T1._ID WHERE T1._ID = ?", - new String[]{String.valueOf(index)})) { - assertTrue(cursor.moveToNext()); - assertEquals(index, cursor.getInt(0)); - assertEquals(index, cursor.getInt(1)); - assertEquals("T1Value" + index, cursor.getString(2)); - assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d); - assertEquals("T2Value" + index, cursor.getString(4)); - } - } - } - - @Test - public void testInsert() { - insertT1TestDataSet(); - - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - - ContentValues cv = new ContentValues(); - cv.put("_ID", DEFAULT_DATASET_SIZE); - cv.put("COL_B", "NewValue"); - cv.put("COL_C", 1.1); - String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)}; - while (state.keepRunning()) { - assertEquals(DEFAULT_DATASET_SIZE, mDatabase.insert("T1", null, cv)); - state.pauseTiming(); - assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs)); - state.resumeTiming(); - } - } - - @Test - public void testDelete() { - insertT1TestDataSet(); - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)}; - Object[] insertsArgs = new Object[]{DEFAULT_DATASET_SIZE, DEFAULT_DATASET_SIZE, - "ValueToDelete", 1.1}; - - while (state.keepRunning()) { - state.pauseTiming(); - mDatabase.execSQL("INSERT INTO T1 VALUES (?, ?, ?, ?)", insertsArgs); - state.resumeTiming(); - assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs)); - } - } - - @Test - public void testUpdate() { - insertT1TestDataSet(); - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - - Random rnd = new Random(0); - int i = 0; - ContentValues cv = new ContentValues(); - String[] argArray = new String[1]; - while (state.keepRunning()) { - int id = rnd.nextInt(DEFAULT_DATASET_SIZE); - cv.put("COL_A", i); - cv.put("COL_B", "UpdatedValue"); - cv.put("COL_C", i); - argArray[0] = String.valueOf(id); - assertEquals(1, mDatabase.update("T1", cv, "_ID=?", argArray)); - i++; - } - } - - private void insertT1TestDataSet() { - mDatabase.beginTransaction(); - for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) { - mDatabase.execSQL("INSERT INTO T1 VALUES (?, ?, ?, ?)", - new Object[]{i, i, "T1Value" + i, i * 1.1}); - } - mDatabase.setTransactionSuccessful(); - mDatabase.endTransaction(); - } - - private void insertT2TestDataSet() { - mDatabase.beginTransaction(); - for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) { - mDatabase.execSQL("INSERT INTO T2 VALUES (?, ?, ?)", - new Object[]{i, "T2Value" + i, i}); - } - mDatabase.setTransactionSuccessful(); - mDatabase.endTransaction(); - } -} - diff --git a/android/graphics/Bitmap.java b/android/graphics/Bitmap.java index 0072012f..57c75490 100644 --- a/android/graphics/Bitmap.java +++ b/android/graphics/Bitmap.java @@ -21,7 +21,6 @@ import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; -import android.annotation.WorkerThread; import android.content.res.ResourcesImpl; import android.os.Parcel; import android.os.Parcelable; @@ -1234,7 +1233,6 @@ public final class Bitmap implements Parcelable { * @param stream The outputstream to write the compressed data. * @return true if successfully compressed to the specified stream. */ - @WorkerThread public boolean compress(CompressFormat format, int quality, OutputStream stream) { checkRecycled("Can't compress a recycled bitmap"); // do explicit check before calling the native method diff --git a/android/graphics/BitmapFactory.java b/android/graphics/BitmapFactory.java index f5bf754a..ffb39e33 100644 --- a/android/graphics/BitmapFactory.java +++ b/android/graphics/BitmapFactory.java @@ -354,7 +354,6 @@ public class BitmapFactory { * decode, in the case of which a more accurate, but slightly slower, * IDCT method will be used instead. */ - @Deprecated public boolean inPreferQualityOverSpeed; /** @@ -413,7 +412,6 @@ public class BitmapFactory { * can check, inbetween the bounds decode and the image decode, to see * if the operation is canceled. */ - @Deprecated public boolean mCancel; /** @@ -428,7 +426,6 @@ public class BitmapFactory { * or if inJustDecodeBounds is true, will set outWidth/outHeight * to -1 */ - @Deprecated public void requestCancelDecode() { mCancel = true; } diff --git a/android/media/AudioAttributes.java b/android/media/AudioAttributes.java index 20405d3b..26ead3d1 100644 --- a/android/media/AudioAttributes.java +++ b/android/media/AudioAttributes.java @@ -202,22 +202,6 @@ public final class AudioAttributes implements Parcelable { * @see #SUPPRESSIBLE_USAGES */ public final static int SUPPRESSIBLE_NEVER = 3; - /** - * @hide - * Denotes a usage for alarms, - * will be muted when the Zen mode doesn't allow alarms - * @see #SUPPRESSIBLE_USAGES - */ - public final static int SUPPRESSIBLE_ALARM = 4; - /** - * @hide - * Denotes a usage for all other sounds not caught in SUPPRESSIBLE_NOTIFICATION, - * SUPPRESSIBLE_CALL,SUPPRESSIBLE_NEVER or SUPPRESSIBLE_ALARM. - * This includes media, system, game, navigation, the assistant, and more. - * These will be muted when the Zen mode doesn't allow media/system/other. - * @see #SUPPRESSIBLE_USAGES - */ - public final static int SUPPRESSIBLE_MEDIA_SYSTEM_OTHER = 5; /** * @hide @@ -237,13 +221,6 @@ public final class AudioAttributes implements Parcelable { SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_EVENT, SUPPRESSIBLE_NOTIFICATION); SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_ACCESSIBILITY, SUPPRESSIBLE_NEVER); SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION, SUPPRESSIBLE_NEVER); - SUPPRESSIBLE_USAGES.put(USAGE_ALARM, SUPPRESSIBLE_ALARM); - SUPPRESSIBLE_USAGES.put(USAGE_MEDIA, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER); - SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_SONIFICATION, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER); - SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER); - SUPPRESSIBLE_USAGES.put(USAGE_GAME, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER); - SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION_SIGNALLING, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER); - SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANT, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER); } /** diff --git a/android/media/MediaMetadataRetriever.java b/android/media/MediaMetadataRetriever.java index 760cc49b..4ea4e381 100644 --- a/android/media/MediaMetadataRetriever.java +++ b/android/media/MediaMetadataRetriever.java @@ -395,7 +395,7 @@ public class MediaMetadataRetriever * @see #getFrameAtTime(long, int) */ /* Do not change these option values without updating their counterparts - * in include/media/MediaSource.h! + * in include/media/stagefright/MediaSource.h! */ /** * This option is used with {@link #getFrameAtTime(long, int)} to retrieve diff --git a/android/media/MediaRecorder.java b/android/media/MediaRecorder.java index 76784904..59a124fa 100644 --- a/android/media/MediaRecorder.java +++ b/android/media/MediaRecorder.java @@ -917,7 +917,7 @@ public class MediaRecorder */ public void setNextOutputFile(File file) throws IOException { - RandomAccessFile f = new RandomAccessFile(file, "rw"); + RandomAccessFile f = new RandomAccessFile(file, "rws"); try { _setNextOutputFile(f.getFD()); } finally { @@ -942,7 +942,7 @@ public class MediaRecorder public void prepare() throws IllegalStateException, IOException { if (mPath != null) { - RandomAccessFile file = new RandomAccessFile(mPath, "rw"); + RandomAccessFile file = new RandomAccessFile(mPath, "rws"); try { _setOutputFile(file.getFD()); } finally { @@ -951,7 +951,7 @@ public class MediaRecorder } else if (mFd != null) { _setOutputFile(mFd); } else if (mFile != null) { - RandomAccessFile file = new RandomAccessFile(mFile, "rw"); + RandomAccessFile file = new RandomAccessFile(mFile, "rws"); try { _setOutputFile(file.getFD()); } finally { diff --git a/android/media/tv/TvInputManager.java b/android/media/tv/TvInputManager.java index fd1f2cf6..d7a9edef 100644 --- a/android/media/tv/TvInputManager.java +++ b/android/media/tv/TvInputManager.java @@ -2590,9 +2590,12 @@ public final class TvInputManager { } } - /** @removed */ public boolean dispatchKeyEventToHdmi(KeyEvent event) { - return false; + try { + return mInterface.dispatchKeyEventToHdmi(event); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } public void overrideAudioSink(int audioType, String audioAddress, int samplingRate, diff --git a/android/net/LinkProperties.java b/android/net/LinkProperties.java index 4e474c8e..2c9fb23e 100644 --- a/android/net/LinkProperties.java +++ b/android/net/LinkProperties.java @@ -683,9 +683,9 @@ public final class LinkProperties implements Parcelable { */ public boolean hasIPv4Address() { for (LinkAddress address : mLinkAddresses) { - if (address.getAddress() instanceof Inet4Address) { - return true; - } + if (address.getAddress() instanceof Inet4Address) { + return true; + } } return false; } @@ -725,9 +725,9 @@ public final class LinkProperties implements Parcelable { */ public boolean hasIPv4DefaultRoute() { for (RouteInfo r : mRoutes) { - if (r.isIPv4Default()) { - return true; - } + if (r.isIPv4Default()) { + return true; + } } return false; } @@ -740,9 +740,9 @@ public final class LinkProperties implements Parcelable { */ public boolean hasIPv6DefaultRoute() { for (RouteInfo r : mRoutes) { - if (r.isIPv6Default()) { - return true; - } + if (r.isIPv6Default()) { + return true; + } } return false; } @@ -755,9 +755,9 @@ public final class LinkProperties implements Parcelable { */ public boolean hasIPv4DnsServer() { for (InetAddress ia : mDnses) { - if (ia instanceof Inet4Address) { - return true; - } + if (ia instanceof Inet4Address) { + return true; + } } return false; } @@ -770,9 +770,9 @@ public final class LinkProperties implements Parcelable { */ public boolean hasIPv6DnsServer() { for (InetAddress ia : mDnses) { - if (ia instanceof Inet6Address) { - return true; - } + if (ia instanceof Inet6Address) { + return true; + } } return false; } diff --git a/android/net/ip/ConnectivityPacketTracker.java b/android/net/ip/ConnectivityPacketTracker.java index 1925c39e..0230f36b 100644 --- a/android/net/ip/ConnectivityPacketTracker.java +++ b/android/net/ip/ConnectivityPacketTracker.java @@ -25,7 +25,6 @@ import android.os.Handler; import android.system.ErrnoException; import android.system.Os; import android.system.PacketSocketAddress; -import android.text.TextUtils; import android.util.Log; import android.util.LocalLog; @@ -60,14 +59,11 @@ public class ConnectivityPacketTracker { private static final boolean DBG = false; private static final String MARK_START = "--- START ---"; private static final String MARK_STOP = "--- STOP ---"; - private static final String MARK_NAMED_START = "--- START (%s) ---"; - private static final String MARK_NAMED_STOP = "--- STOP (%s) ---"; private final String mTag; private final LocalLog mLog; private final BlockingSocketReader mPacketListener; private boolean mRunning; - private String mDisplayName; public ConnectivityPacketTracker(Handler h, NetworkInterface netif, LocalLog log) { final String ifname; @@ -89,16 +85,14 @@ public class ConnectivityPacketTracker { mPacketListener = new PacketListener(h, ifindex, hwaddr, mtu); } - public void start(String displayName) { + public void start() { mRunning = true; - mDisplayName = displayName; mPacketListener.start(); } public void stop() { mPacketListener.stop(); mRunning = false; - mDisplayName = null; } private final class PacketListener extends BlockingSocketReader { @@ -139,19 +133,16 @@ public class ConnectivityPacketTracker { @Override protected void onStart() { - final String msg = TextUtils.isEmpty(mDisplayName) - ? MARK_START - : String.format(MARK_NAMED_START, mDisplayName); - mLog.log(msg); + mLog.log(MARK_START); } @Override protected void onStop() { - String msg = TextUtils.isEmpty(mDisplayName) - ? MARK_STOP - : String.format(MARK_NAMED_STOP, mDisplayName); - if (!mRunning) msg += " (packet listener stopped unexpectedly)"; - mLog.log(msg); + if (mRunning) { + mLog.log(MARK_STOP); + } else { + mLog.log(MARK_STOP + " (packet listener stopped unexpectedly)"); + } } @Override diff --git a/android/net/ip/IpManager.java b/android/net/ip/IpManager.java index e33f6c99..bc07b810 100644 --- a/android/net/ip/IpManager.java +++ b/android/net/ip/IpManager.java @@ -26,7 +26,6 @@ import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties.ProvisioningChange; import android.net.LinkProperties; -import android.net.Network; import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.StaticIpConfiguration; @@ -349,16 +348,6 @@ public class IpManager extends StateMachine { return this; } - public Builder withNetwork(Network network) { - mConfig.mNetwork = network; - return this; - } - - public Builder withDisplayName(String displayName) { - mConfig.mDisplayName = displayName; - return this; - } - public ProvisioningConfiguration build() { return new ProvisioningConfiguration(mConfig); } @@ -373,8 +362,6 @@ public class IpManager extends StateMachine { /* package */ ApfCapabilities mApfCapabilities; /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS; /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY; - /* package */ Network mNetwork = null; - /* package */ String mDisplayName = null; public ProvisioningConfiguration() {} // used by Builder @@ -387,9 +374,6 @@ public class IpManager extends StateMachine { mStaticIpConfig = other.mStaticIpConfig; mApfCapabilities = other.mApfCapabilities; mProvisioningTimeoutMs = other.mProvisioningTimeoutMs; - mIPv6AddrGenMode = other.mIPv6AddrGenMode; - mNetwork = other.mNetwork; - mDisplayName = other.mDisplayName; } @Override @@ -404,8 +388,6 @@ public class IpManager extends StateMachine { .add("mApfCapabilities: " + mApfCapabilities) .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs) .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode) - .add("mNetwork: " + mNetwork) - .add("mDisplayName: " + mDisplayName) .toString(); } @@ -1459,10 +1441,10 @@ public class IpManager extends StateMachine { @Override public void enter() { // Get the Configuration for ApfFilter from Context - final boolean filter802_3Frames = + boolean filter802_3Frames = mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames); - final int[] ethTypeBlackList = mContext.getResources().getIntArray( + int[] ethTypeBlackList = mContext.getResources().getIntArray( R.array.config_apfEthTypeBlackList); mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface, @@ -1474,7 +1456,7 @@ public class IpManager extends StateMachine { } mPacketTracker = createPacketTracker(); - if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName); + if (mPacketTracker != null) mPacketTracker.start(); if (mConfiguration.mEnableIPv6 && !startIPv6()) { doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6); @@ -1488,7 +1470,7 @@ public class IpManager extends StateMachine { return; } - final InitialConfiguration config = mConfiguration.mInitialConfig; + InitialConfiguration config = mConfiguration.mInitialConfig; if ((config != null) && !applyInitialConfig(config)) { // TODO introduce a new IpManagerEvent constant to distinguish this error case. doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); diff --git a/android/net/util/SharedLog.java b/android/net/util/SharedLog.java index bbd3d13e..343d237f 100644 --- a/android/net/util/SharedLog.java +++ b/android/net/util/SharedLog.java @@ -106,10 +106,6 @@ public class SharedLog { record(Category.NONE, msg); } - public void logf(String fmt, Object... args) { - log(String.format(fmt, args)); - } - public void mark(String msg) { record(Category.MARK, msg); } diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java index 59956964..98819279 100644 --- a/android/os/BatteryStats.java +++ b/android/os/BatteryStats.java @@ -1911,13 +1911,6 @@ public abstract class BatteryStats implements Parcelable { long elapsedRealtimeUs, int which); /** - * Returns the {@link Timer} object that tracks the given screen brightness. - * - * {@hide} - */ - public abstract Timer getScreenBrightnessTimer(int brightnessBin); - - /** * Returns the time in microseconds that power save mode has been enabled while the device was * running on battery. * @@ -2026,14 +2019,6 @@ public abstract class BatteryStats implements Parcelable { long elapsedRealtimeUs, int which); /** - * Returns the {@link Timer} object that tracks how much the phone has been trying to - * acquire a signal. - * - * {@hide} - */ - public abstract Timer getPhoneSignalScanningTimer(); - - /** * Returns the number of times the phone has entered the given signal strength. * * {@hide} @@ -2041,12 +2026,6 @@ public abstract class BatteryStats implements Parcelable { public abstract int getPhoneSignalStrengthCount(int strengthBin, int which); /** - * Return the {@link Timer} object used to track the given signal strength's duration and - * counts. - */ - protected abstract Timer getPhoneSignalStrengthTimer(int strengthBin); - - /** * Returns the time in microseconds that the mobile network has been active * (in a high power state). * @@ -2129,11 +2108,6 @@ public abstract class BatteryStats implements Parcelable { */ public abstract int getPhoneDataConnectionCount(int dataType, int which); - /** - * Returns the {@link Timer} object that tracks the phone's data connection type stats. - */ - public abstract Timer getPhoneDataConnectionTimer(int dataType); - public static final int WIFI_SUPPL_STATE_INVALID = 0; public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1; public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2; @@ -2293,13 +2267,6 @@ public abstract class BatteryStats implements Parcelable { public abstract int getWifiStateCount(int wifiState, int which); /** - * Returns the {@link Timer} object that tracks the given WiFi state. - * - * {@hide} - */ - public abstract Timer getWifiStateTimer(int wifiState); - - /** * Returns the time in microseconds that the wifi supplicant has been * in a given state. * @@ -2315,13 +2282,6 @@ public abstract class BatteryStats implements Parcelable { */ public abstract int getWifiSupplStateCount(int state, int which); - /** - * Returns the {@link Timer} object that tracks the given wifi supplicant state. - * - * {@hide} - */ - public abstract Timer getWifiSupplStateTimer(int state); - public static final int NUM_WIFI_SIGNAL_STRENGTH_BINS = 5; /** @@ -2341,13 +2301,6 @@ public abstract class BatteryStats implements Parcelable { public abstract int getWifiSignalStrengthCount(int strengthBin, int which); /** - * Returns the {@link Timer} object that tracks the given WIFI signal strength. - * - * {@hide} - */ - public abstract Timer getWifiSignalStrengthTimer(int strengthBin); - - /** * Returns the time in microseconds that the flashlight has been on while the device was * running on battery. * @@ -2534,13 +2487,13 @@ public abstract class BatteryStats implements Parcelable { public abstract int getDischargeAmountScreenOffSinceCharge(); /** - * Get the amount the battery has discharged while the screen was dozing, + * Get the amount the battery has discharged while the screen was doze, * since the last time power was unplugged. */ public abstract int getDischargeAmountScreenDoze(); /** - * Get the amount the battery has discharged while the screen was dozing, + * Get the amount the battery has discharged while the screen was doze, * since the last time the device was charged. */ public abstract int getDischargeAmountScreenDozeSinceCharge(); @@ -2673,20 +2626,20 @@ public abstract class BatteryStats implements Parcelable { * micro-Ampere-hours. This will be non-zero only if the device's battery has * a coulomb counter. */ - public abstract long getUahDischargeScreenOff(int which); + public abstract long getMahDischargeScreenOff(int which); /** * Return the amount of battery discharge while the screen was in doze mode, measured in * micro-Ampere-hours. This will be non-zero only if the device's battery has * a coulomb counter. */ - public abstract long getUahDischargeScreenDoze(int which); + public abstract long getMahDischargeScreenDoze(int which); /** * Return the amount of battery discharge measured in micro-Ampere-hours. This will be * non-zero only if the device's battery has a coulomb counter. */ - public abstract long getUahDischarge(int which); + public abstract long getMahDischarge(int which); /** * Returns the estimated real battery capacity, which may be less than the capacity @@ -3031,7 +2984,7 @@ public abstract class BatteryStats implements Parcelable { final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; final int count = timer.getCountLocked(which); - if (totalTime != 0 || count != 0) { + if (totalTime != 0) { dumpLine(pw, uid, category, type, totalTime, count); } } @@ -3047,12 +3000,12 @@ public abstract class BatteryStats implements Parcelable { * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT */ private static void dumpTimer(ProtoOutputStream proto, long fieldId, - Timer timer, long rawRealtimeUs, int which) { + Timer timer, long rawRealtime, int which) { if (timer == null) { return; } // Convert from microseconds to milliseconds with rounding - final long totalTimeMs = (timer.getTotalTimeLocked(rawRealtimeUs, which) + 500) / 1000; + final long totalTimeMs = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000; final int count = timer.getCountLocked(which); if (totalTimeMs != 0 || count != 0) { final long token = proto.start(fieldId); @@ -3161,104 +3114,71 @@ public abstract class BatteryStats implements Parcelable { final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(which); final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(which); final long powerDrainMaMs = counter.getPowerCounter().getCountLocked(which); - // Battery real time - final long totalControllerActivityTimeMs - = computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which) / 1000; long totalTxTimeMs = 0; for (LongCounter txState : counter.getTxTimeCounters()) { totalTxTimeMs += txState.getCountLocked(which); } - final long sleepTimeMs - = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + totalTxTimeMs); - sb.setLength(0); - sb.append(prefix); - sb.append(" "); - sb.append(controllerName); - sb.append(" Sleep time: "); - formatTimeMs(sb, sleepTimeMs); - sb.append("("); - sb.append(formatRatioLocked(sleepTimeMs, totalControllerActivityTimeMs)); - sb.append(")"); - pw.println(sb.toString()); + final long totalTimeMs = idleTimeMs + rxTimeMs + totalTxTimeMs; sb.setLength(0); sb.append(prefix); - sb.append(" "); + sb.append(" "); sb.append(controllerName); sb.append(" Idle time: "); formatTimeMs(sb, idleTimeMs); sb.append("("); - sb.append(formatRatioLocked(idleTimeMs, totalControllerActivityTimeMs)); + sb.append(formatRatioLocked(idleTimeMs, totalTimeMs)); sb.append(")"); pw.println(sb.toString()); sb.setLength(0); sb.append(prefix); - sb.append(" "); + sb.append(" "); sb.append(controllerName); sb.append(" Rx time: "); formatTimeMs(sb, rxTimeMs); sb.append("("); - sb.append(formatRatioLocked(rxTimeMs, totalControllerActivityTimeMs)); + sb.append(formatRatioLocked(rxTimeMs, totalTimeMs)); sb.append(")"); pw.println(sb.toString()); sb.setLength(0); sb.append(prefix); - sb.append(" "); + sb.append(" "); sb.append(controllerName); sb.append(" Tx time: "); + formatTimeMs(sb, totalTxTimeMs); + sb.append("("); + sb.append(formatRatioLocked(totalTxTimeMs, totalTimeMs)); + sb.append(")"); + pw.println(sb.toString()); - String [] powerLevel; - switch(controllerName) { - case "Cellular": - powerLevel = new String[] { - " less than 0dBm: ", - " 0dBm to 8dBm: ", - " 8dBm to 15dBm: ", - " 15dBm to 20dBm: ", - " above 20dBm: "}; - break; - default: - powerLevel = new String[] {"[0]", "[1]", "[2]", "[3]", "[4]"}; - break; - } - final int numTxLvls = Math.min(counter.getTxTimeCounters().length, powerLevel.length); + final int numTxLvls = counter.getTxTimeCounters().length; if (numTxLvls > 1) { - pw.println(sb.toString()); for (int lvl = 0; lvl < numTxLvls; lvl++) { final long txLvlTimeMs = counter.getTxTimeCounters()[lvl].getCountLocked(which); sb.setLength(0); sb.append(prefix); - sb.append(" "); - sb.append(powerLevel[lvl]); - sb.append(" "); + sb.append(" ["); + sb.append(lvl); + sb.append("] "); formatTimeMs(sb, txLvlTimeMs); sb.append("("); - sb.append(formatRatioLocked(txLvlTimeMs, totalControllerActivityTimeMs)); + sb.append(formatRatioLocked(txLvlTimeMs, totalTxTimeMs)); sb.append(")"); pw.println(sb.toString()); } - } else { - final long txLvlTimeMs = counter.getTxTimeCounters()[0].getCountLocked(which); - formatTimeMs(sb, txLvlTimeMs); - sb.append("("); - sb.append(formatRatioLocked(txLvlTimeMs, totalControllerActivityTimeMs)); - sb.append(")"); - pw.println(sb.toString()); } - if (powerDrainMaMs > 0) { - sb.setLength(0); - sb.append(prefix); - sb.append(" "); - sb.append(controllerName); - sb.append(" Battery drain: ").append( + sb.setLength(0); + sb.append(prefix); + sb.append(" "); + sb.append(controllerName); + sb.append(" Power drain: ").append( BatteryStatsHelper.makemAh(powerDrainMaMs / (double) (1000*60*60))); - sb.append("mAh"); - pw.println(sb.toString()); - } + sb.append("mAh"); + pw.println(sb.toString()); } /** @@ -3271,13 +3191,13 @@ public abstract class BatteryStats implements Parcelable { /** * Checkin server version of dump to produce more compact, computer-readable log. * - * NOTE: all times are expressed in microseconds, unless specified otherwise. + * NOTE: all times are expressed in 'ms'. */ public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid, boolean wifiOnly) { final long rawUptime = SystemClock.uptimeMillis() * 1000; - final long rawRealtimeMs = SystemClock.elapsedRealtime(); - final long rawRealtime = rawRealtimeMs * 1000; + final long rawRealtime = SystemClock.elapsedRealtime() * 1000; + final long rawRealtimeMs = (rawRealtime + 500) / 1000; final long batteryUptime = getBatteryUptime(rawUptime); final long whichBatteryUptime = computeBatteryUptime(rawUptime, which); final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which); @@ -3300,9 +3220,9 @@ public abstract class BatteryStats implements Parcelable { rawRealtime, which); final int connChanges = getNumConnectivityChange(which); final long phoneOnTime = getPhoneOnTime(rawRealtime, which); - final long dischargeCount = getUahDischarge(which); - final long dischargeScreenOffCount = getUahDischargeScreenOff(which); - final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which); + final long dischargeCount = getMahDischarge(which); + final long dischargeScreenOffCount = getMahDischargeScreenOff(which); + final long dischargeScreenDozeCount = getMahDischargeScreenDoze(which); final StringBuilder sb = new StringBuilder(128); @@ -3540,9 +3460,9 @@ public abstract class BatteryStats implements Parcelable { BatteryStatsHelper.makemAh(helper.getComputedPower()), BatteryStatsHelper.makemAh(helper.getMinDrainedPower()), BatteryStatsHelper.makemAh(helper.getMaxDrainedPower())); - int uid = 0; for (int i=0; i<sippers.size(); i++) { final BatterySipper bs = sippers.get(i); + int uid = 0; String label; switch (bs.drainType) { case IDLE: @@ -3583,9 +3503,6 @@ public abstract class BatteryStats implements Parcelable { case CAMERA: label = "camera"; break; - case MEMORY: - label = "memory"; - break; default: label = "???"; } @@ -3606,7 +3523,6 @@ public abstract class BatteryStats implements Parcelable { dumpLine(pw, 0 /* uid */, category, GLOBAL_CPU_FREQ_DATA, sb.toString()); } - // Dump stats per UID. for (int iu = 0; iu < NU; iu++) { final int uid = uidStats.keyAt(iu); if (reqUid >= 0 && uid != reqUid) { @@ -4104,7 +4020,7 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } - final long dischargeCount = getUahDischarge(which); + final long dischargeCount = getMahDischarge(which); if (dischargeCount >= 0) { sb.setLength(0); sb.append(prefix); @@ -4114,7 +4030,7 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } - final long dischargeScreenOffCount = getUahDischargeScreenOff(which); + final long dischargeScreenOffCount = getMahDischargeScreenOff(which); if (dischargeScreenOffCount >= 0) { sb.setLength(0); sb.append(prefix); @@ -4124,7 +4040,7 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } - final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which); + final long dischargeScreenDozeCount = getMahDischargeScreenDoze(which); if (dischargeScreenDozeCount >= 0) { sb.setLength(0); sb.append(prefix); @@ -4330,116 +4246,126 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } - pw.println(""); - pw.print(prefix); - sb.setLength(0); - sb.append(prefix); - sb.append(" CONNECTIVITY POWER SUMMARY START"); - pw.println(sb.toString()); - - pw.print(prefix); - sb.setLength(0); - sb.append(prefix); - sb.append(" Logging duration for connectivity statistics: "); - formatTimeMs(sb, whichBatteryRealtime / 1000); - pw.println(sb.toString()); - - sb.setLength(0); - sb.append(prefix); - sb.append(" Cellular Statistics:"); - pw.println(sb.toString()); - pw.print(prefix); + pw.print(" Mobile total received: "); pw.print(formatBytesLocked(mobileRxTotalBytes)); + pw.print(", sent: "); pw.print(formatBytesLocked(mobileTxTotalBytes)); + pw.print(" (packets received "); pw.print(mobileRxTotalPackets); + pw.print(", sent "); pw.print(mobileTxTotalPackets); pw.println(")"); sb.setLength(0); sb.append(prefix); - sb.append(" Cellular kernel active time: "); - final long mobileActiveTime = getMobileRadioActiveTime(rawRealtime, which); - formatTimeMs(sb, mobileActiveTime / 1000); - sb.append("("); sb.append(formatRatioLocked(mobileActiveTime, whichBatteryRealtime)); - sb.append(")"); - pw.println(sb.toString()); - - pw.print(" Cellular data received: "); pw.println(formatBytesLocked(mobileRxTotalBytes)); - pw.print(" Cellular data sent: "); pw.println(formatBytesLocked(mobileTxTotalBytes)); - pw.print(" Cellular packets received: "); pw.println(mobileRxTotalPackets); - pw.print(" Cellular packets sent: "); pw.println(mobileTxTotalPackets); - - sb.setLength(0); - sb.append(prefix); - sb.append(" Cellular Radio Access Technology:"); + sb.append(" Phone signal levels:"); didOne = false; - for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - final long time = getPhoneDataConnectionTime(i, rawRealtime, which); + for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) { + final long time = getPhoneSignalStrengthTime(i, rawRealtime, which); if (time == 0) { continue; } - sb.append("\n "); + sb.append("\n "); sb.append(prefix); didOne = true; - sb.append(DATA_CONNECTION_NAMES[i]); + sb.append(SignalStrength.SIGNAL_STRENGTH_NAMES[i]); sb.append(" "); formatTimeMs(sb, time/1000); sb.append("("); sb.append(formatRatioLocked(time, whichBatteryRealtime)); sb.append(") "); + sb.append(getPhoneSignalStrengthCount(i, which)); + sb.append("x"); } if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); sb.setLength(0); sb.append(prefix); - sb.append(" Cellular Rx signal strength (RSRP):"); - final String[] cellularRxSignalStrengthDescription = new String[]{ - "very poor (less than -128dBm): ", - "poor (-128dBm to -118dBm): ", - "moderate (-118dBm to -108dBm): ", - "good (-108dBm to -98dBm): ", - "great (greater than -98dBm): "}; + sb.append(" Signal scanning time: "); + formatTimeMsNoSpace(sb, getPhoneSignalScanningTime(rawRealtime, which) / 1000); + pw.println(sb.toString()); + + sb.setLength(0); + sb.append(prefix); + sb.append(" Radio types:"); didOne = false; - final int numCellularRxBins = Math.min(SignalStrength.NUM_SIGNAL_STRENGTH_BINS, - cellularRxSignalStrengthDescription.length); - for (int i=0; i<numCellularRxBins; i++) { - final long time = getPhoneSignalStrengthTime(i, rawRealtime, which); + for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { + final long time = getPhoneDataConnectionTime(i, rawRealtime, which); if (time == 0) { continue; } - sb.append("\n "); + sb.append("\n "); sb.append(prefix); didOne = true; - sb.append(cellularRxSignalStrengthDescription[i]); + sb.append(DATA_CONNECTION_NAMES[i]); sb.append(" "); formatTimeMs(sb, time/1000); sb.append("("); sb.append(formatRatioLocked(time, whichBatteryRealtime)); sb.append(") "); + sb.append(getPhoneDataConnectionCount(i, which)); + sb.append("x"); } if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); - printControllerActivity(pw, sb, prefix, "Cellular", - getModemControllerActivity(), which); + sb.setLength(0); + sb.append(prefix); + sb.append(" Mobile radio active time: "); + final long mobileActiveTime = getMobileRadioActiveTime(rawRealtime, which); + formatTimeMs(sb, mobileActiveTime / 1000); + sb.append("("); sb.append(formatRatioLocked(mobileActiveTime, whichBatteryRealtime)); + sb.append(") "); sb.append(getMobileRadioActiveCount(which)); + sb.append("x"); + pw.println(sb.toString()); + + final long mobileActiveUnknownTime = getMobileRadioActiveUnknownTime(which); + if (mobileActiveUnknownTime != 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Mobile radio active unknown time: "); + formatTimeMs(sb, mobileActiveUnknownTime / 1000); + sb.append("("); + sb.append(formatRatioLocked(mobileActiveUnknownTime, whichBatteryRealtime)); + sb.append(") "); sb.append(getMobileRadioActiveUnknownCount(which)); + sb.append("x"); + pw.println(sb.toString()); + } + + final long mobileActiveAdjustedTime = getMobileRadioActiveAdjustedTime(which); + if (mobileActiveAdjustedTime != 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Mobile radio active adjusted time: "); + formatTimeMs(sb, mobileActiveAdjustedTime / 1000); + sb.append("("); + sb.append(formatRatioLocked(mobileActiveAdjustedTime, whichBatteryRealtime)); + sb.append(")"); + pw.println(sb.toString()); + } + + printControllerActivity(pw, sb, prefix, "Radio", getModemControllerActivity(), which); pw.print(prefix); + pw.print(" Wi-Fi total received: "); pw.print(formatBytesLocked(wifiRxTotalBytes)); + pw.print(", sent: "); pw.print(formatBytesLocked(wifiTxTotalBytes)); + pw.print(" (packets received "); pw.print(wifiRxTotalPackets); + pw.print(", sent "); pw.print(wifiTxTotalPackets); pw.println(")"); sb.setLength(0); sb.append(prefix); - sb.append(" Wifi Statistics:"); + sb.append(" Wifi on: "); formatTimeMs(sb, wifiOnTime / 1000); + sb.append("("); sb.append(formatRatioLocked(wifiOnTime, whichBatteryRealtime)); + sb.append("), Wifi running: "); formatTimeMs(sb, wifiRunningTime / 1000); + sb.append("("); sb.append(formatRatioLocked(wifiRunningTime, whichBatteryRealtime)); + sb.append(")"); pw.println(sb.toString()); - pw.print(" Wifi data received: "); pw.println(formatBytesLocked(wifiRxTotalBytes)); - pw.print(" Wifi data sent: "); pw.println(formatBytesLocked(wifiTxTotalBytes)); - pw.print(" Wifi packets received: "); pw.println(wifiRxTotalPackets); - pw.print(" Wifi packets sent: "); pw.println(wifiTxTotalPackets); - sb.setLength(0); sb.append(prefix); - sb.append(" Wifi states:"); + sb.append(" Wifi states:"); didOne = false; for (int i=0; i<NUM_WIFI_STATES; i++) { final long time = getWifiStateTime(i, rawRealtime, which); if (time == 0) { continue; } - sb.append("\n "); + sb.append("\n "); didOne = true; sb.append(WIFI_STATE_NAMES[i]); sb.append(" "); @@ -4447,20 +4373,22 @@ public abstract class BatteryStats implements Parcelable { sb.append("("); sb.append(formatRatioLocked(time, whichBatteryRealtime)); sb.append(") "); + sb.append(getWifiStateCount(i, which)); + sb.append("x"); } if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); sb.setLength(0); sb.append(prefix); - sb.append(" Wifi supplicant states:"); + sb.append(" Wifi supplicant states:"); didOne = false; for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) { final long time = getWifiSupplStateTime(i, rawRealtime, which); if (time == 0) { continue; } - sb.append("\n "); + sb.append("\n "); didOne = true; sb.append(WIFI_SUPPL_STATE_NAMES[i]); sb.append(" "); @@ -4468,23 +4396,17 @@ public abstract class BatteryStats implements Parcelable { sb.append("("); sb.append(formatRatioLocked(time, whichBatteryRealtime)); sb.append(") "); + sb.append(getWifiSupplStateCount(i, which)); + sb.append("x"); } if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); sb.setLength(0); sb.append(prefix); - sb.append(" Wifi Rx signal strength (RSSI):"); - final String[] wifiRxSignalStrengthDescription = new String[]{ - "very poor (less than -88.75dBm): ", - "poor (-88.75 to -77.5dBm): ", - "moderate (-77.5dBm to -66.25dBm): ", - "good (-66.25dBm to -55dBm): ", - "great (greater than -55dBm): "}; + sb.append(" Wifi signal levels:"); didOne = false; - final int numWifiRxBins = Math.min(NUM_WIFI_SIGNAL_STRENGTH_BINS, - wifiRxSignalStrengthDescription.length); - for (int i=0; i<numWifiRxBins; i++) { + for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { final long time = getWifiSignalStrengthTime(i, rawRealtime, which); if (time == 0) { continue; @@ -4492,12 +4414,15 @@ public abstract class BatteryStats implements Parcelable { sb.append("\n "); sb.append(prefix); didOne = true; - sb.append(" "); - sb.append(wifiRxSignalStrengthDescription[i]); + sb.append("level("); + sb.append(i); + sb.append(") "); formatTimeMs(sb, time/1000); sb.append("("); sb.append(formatRatioLocked(time, whichBatteryRealtime)); sb.append(") "); + sb.append(getWifiSignalStrengthCount(i, which)); + sb.append("x"); } if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); @@ -4505,13 +4430,6 @@ public abstract class BatteryStats implements Parcelable { printControllerActivity(pw, sb, prefix, "WiFi", getWifiControllerActivity(), which); pw.print(prefix); - sb.setLength(0); - sb.append(prefix); - sb.append(" CONNECTIVITY POWER SUMMARY END"); - pw.println(sb.toString()); - pw.println(""); - - pw.print(prefix); pw.print(" Bluetooth total received: "); pw.print(formatBytesLocked(btRxTotalBytes)); pw.print(", sent: "); pw.println(formatBytesLocked(btTxTotalBytes)); @@ -6120,61 +6038,6 @@ public abstract class BatteryStats implements Parcelable { return true; } - private static void dumpDurationSteps(ProtoOutputStream proto, long fieldId, - LevelStepTracker steps) { - if (steps == null) { - return; - } - int count = steps.mNumStepDurations; - long token; - for (int i = 0; i < count; ++i) { - token = proto.start(fieldId); - proto.write(SystemProto.BatteryLevelStep.DURATION_MS, steps.getDurationAt(i)); - proto.write(SystemProto.BatteryLevelStep.LEVEL, steps.getLevelAt(i)); - - final long initMode = steps.getInitModeAt(i); - final long modMode = steps.getModModeAt(i); - - int ds = SystemProto.BatteryLevelStep.DS_MIXED; - if ((modMode & STEP_LEVEL_MODE_SCREEN_STATE) == 0) { - switch ((int) (initMode & STEP_LEVEL_MODE_SCREEN_STATE) + 1) { - case Display.STATE_OFF: - ds = SystemProto.BatteryLevelStep.DS_OFF; - break; - case Display.STATE_ON: - ds = SystemProto.BatteryLevelStep.DS_ON; - break; - case Display.STATE_DOZE: - ds = SystemProto.BatteryLevelStep.DS_DOZE; - break; - case Display.STATE_DOZE_SUSPEND: - ds = SystemProto.BatteryLevelStep.DS_DOZE_SUSPEND; - break; - default: - ds = SystemProto.BatteryLevelStep.DS_ERROR; - break; - } - } - proto.write(SystemProto.BatteryLevelStep.DISPLAY_STATE, ds); - - int psm = SystemProto.BatteryLevelStep.PSM_MIXED; - if ((modMode & STEP_LEVEL_MODE_POWER_SAVE) == 0) { - psm = (initMode & STEP_LEVEL_MODE_POWER_SAVE) != 0 - ? SystemProto.BatteryLevelStep.PSM_ON : SystemProto.BatteryLevelStep.PSM_OFF; - } - proto.write(SystemProto.BatteryLevelStep.POWER_SAVE_MODE, psm); - - int im = SystemProto.BatteryLevelStep.IM_MIXED; - if ((modMode & STEP_LEVEL_MODE_DEVICE_IDLE) == 0) { - im = (initMode & STEP_LEVEL_MODE_DEVICE_IDLE) != 0 - ? SystemProto.BatteryLevelStep.IM_ON : SystemProto.BatteryLevelStep.IM_OFF; - } - proto.write(SystemProto.BatteryLevelStep.IDLE_MODE, im); - - proto.end(token); - } - } - public static final int DUMP_CHARGED_ONLY = 1<<1; public static final int DUMP_DAILY_ONLY = 1<<2; public static final int DUMP_HISTORY_ONLY = 1<<3; @@ -6600,7 +6463,7 @@ public abstract class BatteryStats implements Parcelable { } } - /** Dump #STATS_SINCE_CHARGED batterystats data to a proto. @hide */ + /** Dump batterystats data to a proto. @hide */ public void dumpProtoLocked(Context context, FileDescriptor fd, List<ApplicationInfo> apps, int flags, long historyStart) { final ProtoOutputStream proto = new ProtoOutputStream(fd); @@ -6622,376 +6485,10 @@ public abstract class BatteryStats implements Parcelable { if ((flags & (DUMP_HISTORY_ONLY | DUMP_DAILY_ONLY)) == 0) { // TODO: implement dumpProtoAppsLocked(proto, apps); - dumpProtoSystemLocked(context, proto, (flags & DUMP_DEVICE_WIFI_ONLY) != 0); + // TODO: implement dumpProtoSystemLocked(proto); } proto.end(bToken); proto.flush(); } - - private void dumpProtoSystemLocked(Context context, ProtoOutputStream proto, boolean wifiOnly) { - final long sToken = proto.start(BatteryStatsProto.SYSTEM); - final long rawUptimeUs = SystemClock.uptimeMillis() * 1000; - final long rawRealtimeMs = SystemClock.elapsedRealtime(); - final long rawRealtimeUs = rawRealtimeMs * 1000; - final int which = STATS_SINCE_CHARGED; - - // Battery data (BATTERY_DATA) - long token = proto.start(SystemProto.BATTERY); - proto.write(SystemProto.Battery.START_CLOCK_TIME_MS, getStartClockTime()); - proto.write(SystemProto.Battery.START_COUNT, getStartCount()); - proto.write(SystemProto.Battery.TOTAL_REALTIME_MS, - computeRealtime(rawRealtimeUs, which) / 1000); - proto.write(SystemProto.Battery.TOTAL_UPTIME_MS, - computeUptime(rawUptimeUs, which) / 1000); - proto.write(SystemProto.Battery.BATTERY_REALTIME_MS, - computeBatteryRealtime(rawRealtimeUs, which) / 1000); - proto.write(SystemProto.Battery.BATTERY_UPTIME_MS, - computeBatteryUptime(rawUptimeUs, which) / 1000); - proto.write(SystemProto.Battery.SCREEN_OFF_REALTIME_MS, - computeBatteryScreenOffRealtime(rawRealtimeUs, which) / 1000); - proto.write(SystemProto.Battery.SCREEN_OFF_UPTIME_MS, - computeBatteryScreenOffUptime(rawUptimeUs, which) / 1000); - proto.write(SystemProto.Battery.SCREEN_DOZE_DURATION_MS, - getScreenDozeTime(rawRealtimeUs, which) / 1000); - proto.write(SystemProto.Battery.ESTIMATED_BATTERY_CAPACITY_MAH, - getEstimatedBatteryCapacity()); - proto.write(SystemProto.Battery.MIN_LEARNED_BATTERY_CAPACITY_UAH, - getMinLearnedBatteryCapacity()); - proto.write(SystemProto.Battery.MAX_LEARNED_BATTERY_CAPACITY_UAH, - getMaxLearnedBatteryCapacity()); - proto.end(token); - - // Battery discharge (BATTERY_DISCHARGE_DATA) - token = proto.start(SystemProto.BATTERY_DISCHARGE); - proto.write(SystemProto.BatteryDischarge.LOWER_BOUND_SINCE_CHARGE, - getLowDischargeAmountSinceCharge()); - proto.write(SystemProto.BatteryDischarge.UPPER_BOUND_SINCE_CHARGE, - getHighDischargeAmountSinceCharge()); - proto.write(SystemProto.BatteryDischarge.SCREEN_ON_SINCE_CHARGE, - getDischargeAmountScreenOnSinceCharge()); - proto.write(SystemProto.BatteryDischarge.SCREEN_OFF_SINCE_CHARGE, - getDischargeAmountScreenOffSinceCharge()); - proto.write(SystemProto.BatteryDischarge.SCREEN_DOZE_SINCE_CHARGE, - getDischargeAmountScreenDozeSinceCharge()); - proto.write(SystemProto.BatteryDischarge.TOTAL_MAH, - getUahDischarge(which) / 1000); - proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_OFF, - getUahDischargeScreenOff(which) / 1000); - proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE, - getUahDischargeScreenDoze(which) / 1000); - proto.end(token); - - // Time remaining - long timeRemainingUs = computeChargeTimeRemaining(rawRealtimeUs); - if (timeRemainingUs >= 0) { - // Charge time remaining (CHARGE_TIME_REMAIN_DATA) - proto.write(SystemProto.CHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000); - } else { - timeRemainingUs = computeBatteryTimeRemaining(rawRealtimeUs); - // Discharge time remaining (DISCHARGE_TIME_REMAIN_DATA) - if (timeRemainingUs >= 0) { - proto.write(SystemProto.DISCHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000); - } else { - proto.write(SystemProto.DISCHARGE_TIME_REMAINING_MS, -1); - } - } - - // Charge step (CHARGE_STEP_DATA) - dumpDurationSteps(proto, SystemProto.CHARGE_STEP, getChargeLevelStepTracker()); - - // Phone data connection (DATA_CONNECTION_TIME_DATA and DATA_CONNECTION_COUNT_DATA) - for (int i = 0; i < NUM_DATA_CONNECTION_TYPES; ++i) { - token = proto.start(SystemProto.DATA_CONNECTION); - proto.write(SystemProto.DataConnection.NAME, i); - dumpTimer(proto, SystemProto.DataConnection.TOTAL, getPhoneDataConnectionTimer(i), - rawRealtimeUs, which); - proto.end(token); - } - - // Discharge step (DISCHARGE_STEP_DATA) - dumpDurationSteps(proto, SystemProto.DISCHARGE_STEP, getDischargeLevelStepTracker()); - - // CPU frequencies (GLOBAL_CPU_FREQ_DATA) - final long[] cpuFreqs = getCpuFreqs(); - if (cpuFreqs != null) { - for (long i : cpuFreqs) { - proto.write(SystemProto.CPU_FREQUENCY, i); - } - } - - // Bluetooth controller (GLOBAL_BLUETOOTH_CONTROLLER_DATA) - dumpControllerActivityProto(proto, SystemProto.GLOBAL_BLUETOOTH_CONTROLLER, - getBluetoothControllerActivity(), which); - - // Modem controller (GLOBAL_MODEM_CONTROLLER_DATA) - dumpControllerActivityProto(proto, SystemProto.GLOBAL_MODEM_CONTROLLER, - getModemControllerActivity(), which); - - // Global network data (GLOBAL_NETWORK_DATA) - token = proto.start(SystemProto.GLOBAL_NETWORK); - proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_RX, - getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which)); - proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_TX, - getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which)); - proto.write(SystemProto.GlobalNetwork.MOBILE_PACKETS_RX, - getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which)); - proto.write(SystemProto.GlobalNetwork.MOBILE_PACKETS_TX, - getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which)); - proto.write(SystemProto.GlobalNetwork.WIFI_BYTES_RX, - getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which)); - proto.write(SystemProto.GlobalNetwork.WIFI_BYTES_TX, - getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which)); - proto.write(SystemProto.GlobalNetwork.WIFI_PACKETS_RX, - getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which)); - proto.write(SystemProto.GlobalNetwork.WIFI_PACKETS_TX, - getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which)); - proto.write(SystemProto.GlobalNetwork.BT_BYTES_RX, - getNetworkActivityBytes(NETWORK_BT_RX_DATA, which)); - proto.write(SystemProto.GlobalNetwork.BT_BYTES_TX, - getNetworkActivityBytes(NETWORK_BT_TX_DATA, which)); - proto.end(token); - - // Wifi controller (GLOBAL_WIFI_CONTROLLER_DATA) - dumpControllerActivityProto(proto, SystemProto.GLOBAL_WIFI_CONTROLLER, - getWifiControllerActivity(), which); - - - // Global wifi (GLOBAL_WIFI_DATA) - token = proto.start(SystemProto.GLOBAL_WIFI); - proto.write(SystemProto.GlobalWifi.ON_DURATION_MS, - getWifiOnTime(rawRealtimeUs, which) / 1000); - proto.write(SystemProto.GlobalWifi.RUNNING_DURATION_MS, - getGlobalWifiRunningTime(rawRealtimeUs, which) / 1000); - proto.end(token); - - // Kernel wakelock (KERNEL_WAKELOCK_DATA) - final Map<String, ? extends Timer> kernelWakelocks = getKernelWakelockStats(); - for (Map.Entry<String, ? extends Timer> ent : kernelWakelocks.entrySet()) { - token = proto.start(SystemProto.KERNEL_WAKELOCK); - proto.write(SystemProto.KernelWakelock.NAME, ent.getKey()); - dumpTimer(proto, SystemProto.KernelWakelock.TOTAL, ent.getValue(), - rawRealtimeUs, which); - proto.end(token); - } - - // Misc (MISC_DATA) - // Calculate wakelock times across all uids. - long fullWakeLockTimeTotalUs = 0; - long partialWakeLockTimeTotalUs = 0; - - final SparseArray<? extends Uid> uidStats = getUidStats(); - for (int iu = 0; iu < uidStats.size(); iu++) { - final Uid u = uidStats.valueAt(iu); - - final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = - u.getWakelockStats(); - for (int iw = wakelocks.size() - 1; iw >= 0; --iw) { - final Uid.Wakelock wl = wakelocks.valueAt(iw); - - final Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL); - if (fullWakeTimer != null) { - fullWakeLockTimeTotalUs += fullWakeTimer.getTotalTimeLocked(rawRealtimeUs, - which); - } - - final Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL); - if (partialWakeTimer != null) { - partialWakeLockTimeTotalUs += partialWakeTimer.getTotalTimeLocked( - rawRealtimeUs, which); - } - } - } - token = proto.start(SystemProto.MISC); - proto.write(SystemProto.Misc.SCREEN_ON_DURATION_MS, - getScreenOnTime(rawRealtimeUs, which) / 1000); - proto.write(SystemProto.Misc.PHONE_ON_DURATION_MS, - getPhoneOnTime(rawRealtimeUs, which) / 1000); - proto.write(SystemProto.Misc.FULL_WAKELOCK_TOTAL_DURATION_MS, - fullWakeLockTimeTotalUs / 1000); - proto.write(SystemProto.Misc.PARTIAL_WAKELOCK_TOTAL_DURATION_MS, - partialWakeLockTimeTotalUs / 1000); - proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_DURATION_MS, - getMobileRadioActiveTime(rawRealtimeUs, which) / 1000); - proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_ADJUSTED_TIME_MS, - getMobileRadioActiveAdjustedTime(which) / 1000); - proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_COUNT, - getMobileRadioActiveCount(which)); - proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_UNKNOWN_DURATION_MS, - getMobileRadioActiveUnknownTime(which) / 1000); - proto.write(SystemProto.Misc.INTERACTIVE_DURATION_MS, - getInteractiveTime(rawRealtimeUs, which) / 1000); - proto.write(SystemProto.Misc.BATTERY_SAVER_MODE_ENABLED_DURATION_MS, - getPowerSaveModeEnabledTime(rawRealtimeUs, which) / 1000); - proto.write(SystemProto.Misc.NUM_CONNECTIVITY_CHANGES, - getNumConnectivityChange(which)); - proto.write(SystemProto.Misc.DEEP_DOZE_ENABLED_DURATION_MS, - getDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP, rawRealtimeUs, which) / 1000); - proto.write(SystemProto.Misc.DEEP_DOZE_COUNT, - getDeviceIdleModeCount(DEVICE_IDLE_MODE_DEEP, which)); - proto.write(SystemProto.Misc.DEEP_DOZE_IDLING_DURATION_MS, - getDeviceIdlingTime(DEVICE_IDLE_MODE_DEEP, rawRealtimeUs, which) / 1000); - proto.write(SystemProto.Misc.DEEP_DOZE_IDLING_COUNT, - getDeviceIdlingCount(DEVICE_IDLE_MODE_DEEP, which)); - proto.write(SystemProto.Misc.LONGEST_DEEP_DOZE_DURATION_MS, - getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP)); - proto.write(SystemProto.Misc.LIGHT_DOZE_ENABLED_DURATION_MS, - getDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT, rawRealtimeUs, which) / 1000); - proto.write(SystemProto.Misc.LIGHT_DOZE_COUNT, - getDeviceIdleModeCount(DEVICE_IDLE_MODE_LIGHT, which)); - proto.write(SystemProto.Misc.LIGHT_DOZE_IDLING_DURATION_MS, - getDeviceIdlingTime(DEVICE_IDLE_MODE_LIGHT, rawRealtimeUs, which) / 1000); - proto.write(SystemProto.Misc.LIGHT_DOZE_IDLING_COUNT, - getDeviceIdlingCount(DEVICE_IDLE_MODE_LIGHT, which)); - proto.write(SystemProto.Misc.LONGEST_LIGHT_DOZE_DURATION_MS, - getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT)); - proto.end(token); - - final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly); - helper.create(this); - helper.refreshStats(which, UserHandle.USER_ALL); - - // Power use item (POWER_USE_ITEM_DATA) - final List<BatterySipper> sippers = helper.getUsageList(); - if (sippers != null) { - for (int i = 0; i < sippers.size(); ++i) { - final BatterySipper bs = sippers.get(i); - int n = SystemProto.PowerUseItem.UNKNOWN_SIPPER; - int uid = 0; - switch (bs.drainType) { - case IDLE: - n = SystemProto.PowerUseItem.IDLE; - break; - case CELL: - n = SystemProto.PowerUseItem.CELL; - break; - case PHONE: - n = SystemProto.PowerUseItem.PHONE; - break; - case WIFI: - n = SystemProto.PowerUseItem.WIFI; - break; - case BLUETOOTH: - n = SystemProto.PowerUseItem.BLUETOOTH; - break; - case SCREEN: - n = SystemProto.PowerUseItem.SCREEN; - break; - case FLASHLIGHT: - n = SystemProto.PowerUseItem.FLASHLIGHT; - break; - case APP: - // dumpProtoAppLocked will handle this. - continue; - case USER: - n = SystemProto.PowerUseItem.USER; - uid = UserHandle.getUid(bs.userId, 0); - break; - case UNACCOUNTED: - n = SystemProto.PowerUseItem.UNACCOUNTED; - break; - case OVERCOUNTED: - n = SystemProto.PowerUseItem.OVERCOUNTED; - break; - case CAMERA: - n = SystemProto.PowerUseItem.CAMERA; - break; - case MEMORY: - n = SystemProto.PowerUseItem.MEMORY; - break; - } - token = proto.start(SystemProto.POWER_USE_ITEM); - proto.write(SystemProto.PowerUseItem.NAME, n); - proto.write(SystemProto.PowerUseItem.UID, uid); - proto.write(SystemProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah); - proto.write(SystemProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide); - proto.write(SystemProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah); - proto.write(SystemProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH, - bs.proportionalSmearMah); - proto.end(token); - } - } - - // Power use summary (POWER_USE_SUMMARY_DATA) - token = proto.start(SystemProto.POWER_USE_SUMMARY); - proto.write(SystemProto.PowerUseSummary.BATTERY_CAPACITY_MAH, - helper.getPowerProfile().getBatteryCapacity()); - proto.write(SystemProto.PowerUseSummary.COMPUTED_POWER_MAH, helper.getComputedPower()); - proto.write(SystemProto.PowerUseSummary.MIN_DRAINED_POWER_MAH, helper.getMinDrainedPower()); - proto.write(SystemProto.PowerUseSummary.MAX_DRAINED_POWER_MAH, helper.getMaxDrainedPower()); - proto.end(token); - - // RPM stats (RESOURCE_POWER_MANAGER_DATA) - final Map<String, ? extends Timer> rpmStats = getRpmStats(); - final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats(); - for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) { - token = proto.start(SystemProto.RESOURCE_POWER_MANAGER); - proto.write(SystemProto.ResourcePowerManager.NAME, ent.getKey()); - dumpTimer(proto, SystemProto.ResourcePowerManager.TOTAL, - ent.getValue(), rawRealtimeUs, which); - dumpTimer(proto, SystemProto.ResourcePowerManager.SCREEN_OFF, - screenOffRpmStats.get(ent.getKey()), rawRealtimeUs, which); - proto.end(token); - } - - // Screen brightness (SCREEN_BRIGHTNESS_DATA) - for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; ++i) { - token = proto.start(SystemProto.SCREEN_BRIGHTNESS); - proto.write(SystemProto.ScreenBrightness.NAME, i); - dumpTimer(proto, SystemProto.ScreenBrightness.TOTAL, getScreenBrightnessTimer(i), - rawRealtimeUs, which); - proto.end(token); - } - - // Signal scanning time (SIGNAL_SCANNING_TIME_DATA) - dumpTimer(proto, SystemProto.SIGNAL_SCANNING, getPhoneSignalScanningTimer(), rawRealtimeUs, - which); - - // Phone signal strength (SIGNAL_STRENGTH_TIME_DATA and SIGNAL_STRENGTH_COUNT_DATA) - for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; ++i) { - token = proto.start(SystemProto.PHONE_SIGNAL_STRENGTH); - proto.write(SystemProto.PhoneSignalStrength.NAME, i); - dumpTimer(proto, SystemProto.PhoneSignalStrength.TOTAL, getPhoneSignalStrengthTimer(i), - rawRealtimeUs, which); - proto.end(token); - } - - // Wakeup reasons (WAKEUP_REASON_DATA) - final Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats(); - for (Map.Entry<String, ? extends Timer> ent : wakeupReasons.entrySet()) { - token = proto.start(SystemProto.WAKEUP_REASON); - proto.write(SystemProto.WakeupReason.NAME, ent.getKey()); - dumpTimer(proto, SystemProto.WakeupReason.TOTAL, ent.getValue(), rawRealtimeUs, which); - proto.end(token); - } - - // Wifi signal strength (WIFI_SIGNAL_STRENGTH_TIME_DATA and WIFI_SIGNAL_STRENGTH_COUNT_DATA) - for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; ++i) { - token = proto.start(SystemProto.WIFI_SIGNAL_STRENGTH); - proto.write(SystemProto.WifiSignalStrength.NAME, i); - dumpTimer(proto, SystemProto.WifiSignalStrength.TOTAL, getWifiSignalStrengthTimer(i), - rawRealtimeUs, which); - proto.end(token); - } - - // Wifi state (WIFI_STATE_TIME_DATA and WIFI_STATE_COUNT_DATA) - for (int i = 0; i < NUM_WIFI_STATES; ++i) { - token = proto.start(SystemProto.WIFI_STATE); - proto.write(SystemProto.WifiState.NAME, i); - dumpTimer(proto, SystemProto.WifiState.TOTAL, getWifiStateTimer(i), - rawRealtimeUs, which); - proto.end(token); - } - - // Wifi supplicant state (WIFI_SUPPL_STATE_TIME_DATA and WIFI_SUPPL_STATE_COUNT_DATA) - for (int i = 0; i < NUM_WIFI_SUPPL_STATES; ++i) { - token = proto.start(SystemProto.WIFI_SUPPLICANT_STATE); - proto.write(SystemProto.WifiSupplicantState.NAME, i); - dumpTimer(proto, SystemProto.WifiSupplicantState.TOTAL, getWifiSupplStateTimer(i), - rawRealtimeUs, which); - proto.end(token); - } - - proto.end(sToken); - } } diff --git a/android/os/Debug.java b/android/os/Debug.java index 017c2134..b46c6b16 100644 --- a/android/os/Debug.java +++ b/android/os/Debug.java @@ -1748,26 +1748,22 @@ public final class Debug public static final int MEMINFO_SHMEM = 4; /** @hide */ public static final int MEMINFO_SLAB = 5; - /** @hide */ - public static final int MEMINFO_SLAB_RECLAIMABLE = 6; - /** @hide */ - public static final int MEMINFO_SLAB_UNRECLAIMABLE = 7; /** @hide */ - public static final int MEMINFO_SWAP_TOTAL = 8; + public static final int MEMINFO_SWAP_TOTAL = 6; /** @hide */ - public static final int MEMINFO_SWAP_FREE = 9; + public static final int MEMINFO_SWAP_FREE = 7; /** @hide */ - public static final int MEMINFO_ZRAM_TOTAL = 10; + public static final int MEMINFO_ZRAM_TOTAL = 8; /** @hide */ - public static final int MEMINFO_MAPPED = 11; + public static final int MEMINFO_MAPPED = 9; /** @hide */ - public static final int MEMINFO_VM_ALLOC_USED = 12; + public static final int MEMINFO_VM_ALLOC_USED = 10; /** @hide */ - public static final int MEMINFO_PAGE_TABLES = 13; + public static final int MEMINFO_PAGE_TABLES = 11; /** @hide */ - public static final int MEMINFO_KERNEL_STACK = 14; + public static final int MEMINFO_KERNEL_STACK = 12; /** @hide */ - public static final int MEMINFO_COUNT = 15; + public static final int MEMINFO_COUNT = 13; /** * Retrieves /proc/meminfo. outSizes is filled with fields diff --git a/android/os/ParcelFileDescriptor.java b/android/os/ParcelFileDescriptor.java index 7f588adb..c091420a 100644 --- a/android/os/ParcelFileDescriptor.java +++ b/android/os/ParcelFileDescriptor.java @@ -737,9 +737,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { private void closeWithStatus(int status, String msg) { if (mClosed) return; mClosed = true; - if (mGuard != null) { - mGuard.close(); - } + mGuard.close(); // Status MUST be sent before closing actual descriptor writeCommStatusAndClose(status, msg); IoUtils.closeQuietly(mFd); diff --git a/android/os/PatternMatcher.java b/android/os/PatternMatcher.java index 76b21426..1f3a1e68 100644 --- a/android/os/PatternMatcher.java +++ b/android/os/PatternMatcher.java @@ -16,7 +16,7 @@ package android.os; -import android.util.proto.ProtoOutputStream; +import android.util.Log; import java.util.Arrays; @@ -131,17 +131,7 @@ public class PatternMatcher implements Parcelable { } return "PatternMatcher{" + type + mPattern + "}"; } - - /** @hide */ - public void writeToProto(ProtoOutputStream proto, long fieldId) { - long token = proto.start(fieldId); - proto.write(PatternMatcherProto.PATTERN, mPattern); - proto.write(PatternMatcherProto.TYPE, mType); - // PatternMatcherProto.PARSED_PATTERN is too much to dump, but the field is reserved to - // match the current data structure. - proto.end(token); - } - + public int describeContents() { return 0; } @@ -151,7 +141,7 @@ public class PatternMatcher implements Parcelable { dest.writeInt(mType); dest.writeIntArray(mParsedPattern); } - + public PatternMatcher(Parcel src) { mPattern = src.readString(); mType = src.readInt(); diff --git a/android/os/ServiceManager.java b/android/os/ServiceManager.java index 34c78455..f41848fa 100644 --- a/android/os/ServiceManager.java +++ b/android/os/ServiceManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * Copyright (C) 2007 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. @@ -16,9 +16,29 @@ package android.os; +import android.util.Log; + +import com.android.internal.os.BinderInternal; + +import java.util.HashMap; import java.util.Map; +/** @hide */ public final class ServiceManager { + private static final String TAG = "ServiceManager"; + private static IServiceManager sServiceManager; + private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>(); + + private static IServiceManager getIServiceManager() { + if (sServiceManager != null) { + return sServiceManager; + } + + // Find the service manager + sServiceManager = ServiceManagerNative + .asInterface(Binder.allowBlocking(BinderInternal.getContextObject())); + return sServiceManager; + } /** * Returns a reference to a service with the given name. @@ -27,14 +47,32 @@ public final class ServiceManager { * @return a reference to the service, or <code>null</code> if the service doesn't exist */ public static IBinder getService(String name) { + try { + IBinder service = sCache.get(name); + if (service != null) { + return service; + } else { + return Binder.allowBlocking(getIServiceManager().getService(name)); + } + } catch (RemoteException e) { + Log.e(TAG, "error in getService", e); + } return null; } /** - * Is not supposed to return null, but that is fine for layoutlib. + * Returns a reference to a service with the given name, or throws + * {@link NullPointerException} if none is found. + * + * @hide */ public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException { - throw new ServiceNotFoundException(name); + final IBinder binder = getService(name); + if (binder != null) { + return binder; + } else { + throw new ServiceNotFoundException(name); + } } /** @@ -45,7 +83,39 @@ public final class ServiceManager { * @param service the service object */ public static void addService(String name, IBinder service) { - // pass + addService(name, service, false, IServiceManager.DUMP_PRIORITY_NORMAL); + } + + /** + * Place a new @a service called @a name into the service + * manager. + * + * @param name the name of the new service + * @param service the service object + * @param allowIsolated set to true to allow isolated sandboxed processes + * to access this service + */ + public static void addService(String name, IBinder service, boolean allowIsolated) { + addService(name, service, allowIsolated, IServiceManager.DUMP_PRIORITY_NORMAL); + } + + /** + * Place a new @a service called @a name into the service + * manager. + * + * @param name the name of the new service + * @param service the service object + * @param allowIsolated set to true to allow isolated sandboxed processes + * @param dumpPriority supported dump priority levels as a bitmask + * to access this service + */ + public static void addService(String name, IBinder service, boolean allowIsolated, + int dumpPriority) { + try { + getIServiceManager().addService(name, service, allowIsolated, dumpPriority); + } catch (RemoteException e) { + Log.e(TAG, "error in addService", e); + } } /** @@ -53,7 +123,17 @@ public final class ServiceManager { * service manager. Non-blocking. */ public static IBinder checkService(String name) { - return null; + try { + IBinder service = sCache.get(name); + if (service != null) { + return service; + } else { + return Binder.allowBlocking(getIServiceManager().checkService(name)); + } + } catch (RemoteException e) { + Log.e(TAG, "error in checkService", e); + return null; + } } /** @@ -62,9 +142,12 @@ public final class ServiceManager { * case of an exception */ public static String[] listServices() { - // actual implementation returns null sometimes, so it's ok - // to return null instead of an empty list. - return null; + try { + return getIServiceManager().listServices(IServiceManager.DUMP_PRIORITY_ALL); + } catch (RemoteException e) { + Log.e(TAG, "error in listServices", e); + return null; + } } /** @@ -76,7 +159,10 @@ public final class ServiceManager { * @hide */ public static void initServiceCache(Map<String, IBinder> cache) { - // pass + if (sCache.size() != 0) { + throw new IllegalStateException("setServiceCache may only be called once"); + } + sCache.putAll(cache); } /** @@ -87,7 +173,6 @@ public final class ServiceManager { * @hide */ public static class ServiceNotFoundException extends Exception { - // identical to the original implementation public ServiceNotFoundException(String name) { super("No service published for: " + name); } diff --git a/android/os/SystemProperties.java b/android/os/SystemProperties.java index 84111fbf..560b4b31 100644 --- a/android/os/SystemProperties.java +++ b/android/os/SystemProperties.java @@ -84,6 +84,9 @@ public class SystemProperties { /** * Get the String value for the given {@code key}. * + * <b>WARNING:</b> Do not use this method if the value may not be a valid UTF string! This + * method will crash in native code. + * * @param key the key to lookup * @return an empty string if the {@code key} isn't found */ @@ -96,6 +99,9 @@ public class SystemProperties { /** * Get the String value for the given {@code key}. * + * <b>WARNING:</b> Do not use this method if the value may not be a valid UTF string! This + * method will crash in native code. + * * @param key the key to lookup * @param def the default value in case the property is not set or empty * @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty diff --git a/android/os/UserManager.java b/android/os/UserManager.java index 8c688713..430a5e3e 100644 --- a/android/os/UserManager.java +++ b/android/os/UserManager.java @@ -574,25 +574,6 @@ public class UserManager { public static final String DISALLOW_CREATE_WINDOWS = "no_create_windows"; /** - * Specifies that system error dialogs for crashed or unresponsive apps should not be shown. - * In this case, the system will force-stop the app as if the user chooses the "close app" - * option on the UI. No feedback report will be collected as there is no way for the user to - * provide explicit consent. - * - * When this user restriction is set by device owners, it's applied to all users; when it's set - * by profile owners, it's only applied to the relevant profiles. - * The default value is <code>false</code>. - * - * <p>This user restriction has no effect on managed profiles. - * <p>Key for user restrictions. - * <p>Type: Boolean - * @see DevicePolicyManager#addUserRestriction(ComponentName, String) - * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) - * @see #getUserRestrictions() - */ - public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs"; - - /** * Specifies if what is copied in the clipboard of this profile can * be pasted in related profiles. Does not restrict if the clipboard of related profiles can be * pasted in this profile. diff --git a/android/preference/SeekBarVolumizer.java b/android/preference/SeekBarVolumizer.java index 3d2e1d1f..ee8eed19 100644 --- a/android/preference/SeekBarVolumizer.java +++ b/android/preference/SeekBarVolumizer.java @@ -206,7 +206,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba try { mRingtone.setAudioAttributes(new AudioAttributes.Builder(mRingtone .getAudioAttributes()) - .setFlags(AudioAttributes.FLAG_BYPASS_MUTE) + .setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY | + AudioAttributes.FLAG_BYPASS_MUTE) .build()); mRingtone.play(); } catch (Throwable e) { diff --git a/android/provider/Settings.java b/android/provider/Settings.java index b4350746..a062db43 100644 --- a/android/provider/Settings.java +++ b/android/provider/Settings.java @@ -5708,7 +5708,6 @@ public final class Settings { * * @hide */ - @TestApi public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED = "accessibility_display_magnification_enabled"; @@ -6793,6 +6792,14 @@ public final class Settings { "lock_screen_show_notifications"; /** + * This preference stores the last stack active task time for each user, which affects what + * tasks will be visible in Overview. + * @hide + */ + public static final String OVERVIEW_LAST_STACK_ACTIVE_TIME = + "overview_last_stack_active_time"; + + /** * List of TV inputs that are currently hidden. This is a string * containing the IDs of all hidden TV inputs. Each ID is encoded by * {@link android.net.Uri#encode(String)} and separated by ':'. @@ -9568,22 +9575,6 @@ public final class Settings { public static final String DEVICE_POLICY_CONSTANTS = "device_policy_constants"; /** - * TextClassifier specific settings. - * This is encoded as a key=value list, separated by commas. Ex: - * - * <pre> - * smart_selection_dark_launch (boolean) - * smart_selection_enabled_for_edit_text (boolean) - * </pre> - * - * <p> - * Type: string - * @hide - * see also android.view.textclassifier.TextClassifierConstants - */ - public static final String TEXT_CLASSIFIER_CONSTANTS = "text_classifier_constants"; - - /** * Get the key that retrieves a bluetooth headset's priority. * @hide */ diff --git a/android/service/autofill/AutofillService.java b/android/service/autofill/AutofillService.java index 953501c7..2e59f6c5 100644 --- a/android/service/autofill/AutofillService.java +++ b/android/service/autofill/AutofillService.java @@ -65,7 +65,7 @@ import com.android.internal.os.SomeArgs; * <li>The service replies through {@link FillCallback#onSuccess(FillResponse)}. * <li>The Android System calls {@link #onDisconnected()} and unbinds from the * {@code AutofillService}. - * <li>The Android System displays an autofill UI with the options sent by the service. + * <li>The Android System displays an UI affordance with the options sent by the service. * <li>The user picks an option. * <li>The proper views are autofilled. * </ol> @@ -365,81 +365,6 @@ import com.android.internal.os.SomeArgs; * <p><b>Note:</b> The autofill service could also whitelist well-known browser apps and skip the * verifications above, as long as the service can verify the authenticity of the browser app by * checking its signing certificate. - * - * <a name="MultipleStepsSave"></a> - * <h3>Saving when data is split in multiple screens</h3> - * - * Apps often split the user data in multiple screens in the same activity, specially in - * activities used to create a new user account. For example, the first screen asks for a username, - * and if the username is available, it moves to a second screen, which asks for a password. - * - * <p>It's tricky to handle save for autofill in these situations, because the autofill service must - * wait until the user enters both fields before the autofill save UI can be shown. But it can be - * done by following the steps below: - * - * <ol> - * <li>In the first - * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service - * adds a {@link FillResponse.Builder#setClientState(android.os.Bundle) client state bundle} in - * the response, containing the autofill ids of the partial fields present in the screen. - * <li>In the second - * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service - * retrieves the {@link FillRequest#getClientState() client state bundle}, gets the autofill ids - * set in the previous request from the client state, and adds these ids and the - * {@link SaveInfo#FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} to the {@link SaveInfo} used in the second - * response. - * <li>In the {@link #onSaveRequest(SaveRequest, SaveCallback) save request}, the service uses the - * proper {@link FillContext fill contexts} to get the value of each field (there is one fill - * context per fill request). - * </ol> - * - * <p>For example, in an app that uses 2 steps for the username and password fields, the workflow - * would be: - * <pre class="prettyprint"> - * // On first fill request - * AutofillId usernameId = // parse from AssistStructure; - * Bundle clientState = new Bundle(); - * clientState.putParcelable("usernameId", usernameId); - * fillCallback.onSuccess( - * new FillResponse.Builder() - * .setClientState(clientState) - * .setSaveInfo(new SaveInfo - * .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME, new AutofillId[] {usernameId}) - * .build()) - * .build()); - * - * // On second fill request - * Bundle clientState = fillRequest.getClientState(); - * AutofillId usernameId = clientState.getParcelable("usernameId"); - * AutofillId passwordId = // parse from AssistStructure - * clientState.putParcelable("passwordId", passwordId); - * fillCallback.onSuccess( - * new FillResponse.Builder() - * .setClientState(clientState) - * .setSaveInfo(new SaveInfo - * .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD, - * new AutofillId[] {usernameId, passwordId}) - * .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) - * .build()) - * .build()); - * - * // On save request - * Bundle clientState = saveRequest.getClientState(); - * AutofillId usernameId = clientState.getParcelable("usernameId"); - * AutofillId passwordId = clientState.getParcelable("passwordId"); - * List<FillContext> fillContexts = saveRequest.getFillContexts(); - * - * FillContext usernameContext = fillContexts.get(0); - * ViewNode usernameNode = findNodeByAutofillId(usernameContext.getStructure(), usernameId); - * AutofillValue username = usernameNode.getAutofillValue().getTextValue().toString(); - * - * FillContext passwordContext = fillContexts.get(1); - * ViewNode passwordNode = findNodeByAutofillId(passwordContext.getStructure(), passwordId); - * AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString(); - * - * save(username, password); - * - * </pre> */ public abstract class AutofillService extends Service { private static final String TAG = "AutofillService"; diff --git a/android/service/autofill/SaveInfo.java b/android/service/autofill/SaveInfo.java index fde2416f..1b9240cc 100644 --- a/android/service/autofill/SaveInfo.java +++ b/android/service/autofill/SaveInfo.java @@ -68,7 +68,7 @@ import java.util.Arrays; * .build(); * </pre> * - * <p>The save type flags are used to display the appropriate strings in the autofill save UI. + * <p>The save type flags are used to display the appropriate strings in the save UI affordance. * You can pass multiple values, but try to keep it short if possible. In the above example, just * {@code SaveInfo.SAVE_DATA_TYPE_PASSWORD} would be enough. * @@ -103,17 +103,13 @@ import java.util.Arrays; * .build(); * </pre> * - * <a name="TriggeringSaveRequest"></a> - * <h3>Triggering a save request</h3> - * * <p>The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after * any of the following events: * <ul> * <li>The {@link Activity} finishes. - * <li>The app explicitly calls {@link AutofillManager#commit()}. - * <li>All required views become invisible (if the {@link SaveInfo} was created with the + * <li>The app explicitly called {@link AutofillManager#commit()}. + * <li>All required views became invisible (if the {@link SaveInfo} was created with the * {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag). - * <li>The user clicks a specific view (defined by {@link Builder#setTriggerId(AutofillId)}. * </ul> * * <p>But it is only triggered when all conditions below are met: @@ -127,13 +123,10 @@ import java.util.Arrays; * <li>There is no {@link Dataset} in the last {@link FillResponse} that completely matches the * screen state (i.e., all required and optional fields in the dataset have the same value as * the fields in the screen). - * <li>The user explicitly tapped the autofill save UI asking to save data for autofill. + * <li>The user explicitly tapped the UI affordance asking to save data for autofill. * </ul> * - * <a name="CustomizingSaveUI"></a> - * <h3>Customizing the autofill save UI</h3> - * - * <p>The service can also customize some aspects of the autofill save UI: + * <p>The service can also customize some aspects of the save UI affordance: * <ul> * <li>Add a simple subtitle by calling {@link Builder#setDescription(CharSequence)}. * <li>Add a customized subtitle by calling @@ -219,25 +212,16 @@ public final class SaveInfo implements Parcelable { @interface SaveDataType{} /** - * Usually, a save request is only automatically <a href="#TriggeringSaveRequest">triggered</a> - * once the {@link Activity} finishes. If this flag is set, it is triggered once all saved views - * become invisible. + * Usually {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} + * is called once the {@link Activity} finishes. If this flag is set it is called once all + * saved views become invisible. */ public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1; - /** - * By default, a save request is automatically <a href="#TriggeringSaveRequest">triggered</a> - * once the {@link Activity} finishes. If this flag is set, finishing the activity doesn't - * trigger a save request. - * - * <p>This flag is typically used in conjunction with {@link Builder#setTriggerId(AutofillId)}. - */ - public static final int FLAG_DONT_SAVE_ON_FINISH = 0x2; - /** @hide */ @IntDef( flag = true, - value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE, FLAG_DONT_SAVE_ON_FINISH}) + value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE}) @Retention(RetentionPolicy.SOURCE) @interface SaveInfoFlags{} @@ -252,7 +236,6 @@ public final class SaveInfo implements Parcelable { private final InternalValidator mValidator; private final InternalSanitizer[] mSanitizerKeys; private final AutofillId[][] mSanitizerValues; - private final AutofillId mTriggerId; private SaveInfo(Builder builder) { mType = builder.mType; @@ -276,7 +259,6 @@ public final class SaveInfo implements Parcelable { mSanitizerValues[i] = builder.mSanitizers.valueAt(i); } } - mTriggerId = builder.mTriggerId; } /** @hide */ @@ -338,12 +320,6 @@ public final class SaveInfo implements Parcelable { return mSanitizerValues; } - /** @hide */ - @Nullable - public AutofillId getTriggerId() { - return mTriggerId; - } - /** * A builder for {@link SaveInfo} objects. */ @@ -362,7 +338,6 @@ public final class SaveInfo implements Parcelable { private ArrayMap<InternalSanitizer, AutofillId[]> mSanitizers; // Set used to validate against duplicate ids. private ArraySet<AutofillId> mSanitizerIds; - private AutofillId mTriggerId; /** * Creates a new builder. @@ -419,15 +394,13 @@ public final class SaveInfo implements Parcelable { /** * Sets flags changing the save behavior. * - * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE}, - * {@link #FLAG_DONT_SAVE_ON_FINISH}, or {@code 0}. + * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or {@code 0}. * @return This builder. */ public @NonNull Builder setFlags(@SaveInfoFlags int flags) { throwIfDestroyed(); - mFlags = Preconditions.checkFlagsArgument(flags, - FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE | FLAG_DONT_SAVE_ON_FINISH); + mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE); return this; } @@ -520,8 +493,8 @@ public final class SaveInfo implements Parcelable { } /** - * Sets an object used to validate the user input - if the input is not valid, the - * autofill save UI is not shown. + * Sets an object used to validate the user input - if the input is not valid, the Save UI + * affordance is not shown. * * <p>Typically used to validate credit card numbers. Examples: * @@ -547,7 +520,7 @@ public final class SaveInfo implements Parcelable { * ); * </pre> * - * <p><b>Note:</b> the example above is just for illustrative purposes; the same validator + * <p><b>NOTE: </b>the example above is just for illustrative purposes; the same validator * could be created using a single regex for the {@code OR} part: * * <pre class="prettyprint"> @@ -642,27 +615,6 @@ public final class SaveInfo implements Parcelable { return this; } - /** - * Explicitly defines the view that should commit the autofill context when clicked. - * - * <p>Usually, the save request is only automatically - * <a href="#TriggeringSaveRequest">triggered</a> after the activity is - * finished or all relevant views become invisible, but there are scenarios where the - * autofill context is automatically commited too late - * —for example, when the activity manually clears the autofillable views when a - * button is tapped. This method can be used to trigger the autofill save UI earlier in - * these scenarios. - * - * <p><b>Note:</b> This method should only be used in scenarios where the automatic workflow - * is not enough, otherwise it could trigger the autofill save UI when it should not— - * for example, when the user entered invalid credentials for the autofillable views. - */ - public @NonNull Builder setTriggerId(@NonNull AutofillId id) { - throwIfDestroyed(); - mTriggerId = Preconditions.checkNotNull(id); - return this; - } - /** * Builds a new {@link SaveInfo} instance. * @@ -700,14 +652,13 @@ public final class SaveInfo implements Parcelable { .append(", description=").append(mDescription) .append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_", mNegativeButtonStyle)) - .append(", flags=").append(mFlags) - .append(", customDescription=").append(mCustomDescription) - .append(", validator=").append(mValidator) + .append(", mFlags=").append(mFlags) + .append(", mCustomDescription=").append(mCustomDescription) + .append(", validation=").append(mValidator) .append(", sanitizerKeys=") .append(mSanitizerKeys == null ? "N/A:" : mSanitizerKeys.length) .append(", sanitizerValues=") .append(mSanitizerValues == null ? "N/A:" : mSanitizerValues.length) - .append(", triggerId=").append(mTriggerId) .append("]").toString(); } @@ -736,7 +687,6 @@ public final class SaveInfo implements Parcelable { parcel.writeParcelableArray(mSanitizerValues[i], flags); } } - parcel.writeParcelable(mTriggerId, flags); parcel.writeInt(mFlags); } @@ -777,10 +727,6 @@ public final class SaveInfo implements Parcelable { builder.addSanitizer(sanitizers[i], autofillIds); } } - final AutofillId triggerId = parcel.readParcelable(null); - if (triggerId != null) { - builder.setTriggerId(triggerId); - } builder.setFlags(parcel.readInt()); return builder.build(); } diff --git a/android/service/autofill/SaveRequest.java b/android/service/autofill/SaveRequest.java index f53967bd..65fdb5c4 100644 --- a/android/service/autofill/SaveRequest.java +++ b/android/service/autofill/SaveRequest.java @@ -19,6 +19,7 @@ package android.service.autofill; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; +import android.os.CancellationSignal; import android.os.Parcel; import android.os.Parcelable; @@ -59,14 +60,9 @@ public final class SaveRequest implements Parcelable { } /** - * Gets the latest client state extra returned from the service. - * - * <p><b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, only client state - * bundles set by {@link FillResponse.Builder#setClientState(Bundle)} where considered. On - * Android {@link android.os.Build.VERSION_CODES#P} and higher, bundles set in the result of - * an authenticated request through the - * {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE} extra are - * also considered (and take precedence when set). + * Gets the extra client state returned from the last {@link + * AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)} + * fill request}. * * @return The client state. */ diff --git a/android/service/notification/ZenModeConfig.java b/android/service/notification/ZenModeConfig.java index c5615ae6..7bec898a 100644 --- a/android/service/notification/ZenModeConfig.java +++ b/android/service/notification/ZenModeConfig.java @@ -76,13 +76,10 @@ public class ZenModeConfig implements Parcelable { private static final int DAY_MINUTES = 24 * 60; private static final int ZERO_VALUE_MS = 10 * SECONDS_MS; - // Default allow categories set in readXml() from default_zen_mode_config.xml, fallback values: - private static final boolean DEFAULT_ALLOW_ALARMS = true; - private static final boolean DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER = true; - private static final boolean DEFAULT_ALLOW_CALLS = false; + private static final boolean DEFAULT_ALLOW_CALLS = true; private static final boolean DEFAULT_ALLOW_MESSAGES = false; - private static final boolean DEFAULT_ALLOW_REMINDERS = false; - private static final boolean DEFAULT_ALLOW_EVENTS = false; + private static final boolean DEFAULT_ALLOW_REMINDERS = true; + private static final boolean DEFAULT_ALLOW_EVENTS = true; private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false; private static final boolean DEFAULT_ALLOW_SCREEN_OFF = true; private static final boolean DEFAULT_ALLOW_SCREEN_ON = true; @@ -92,8 +89,6 @@ public class ZenModeConfig implements Parcelable { private static final String ZEN_ATT_VERSION = "version"; private static final String ZEN_ATT_USER = "user"; private static final String ALLOW_TAG = "allow"; - private static final String ALLOW_ATT_ALARMS = "alarms"; - private static final String ALLOW_ATT_MEDIA = "media_system_other"; private static final String ALLOW_ATT_CALLS = "calls"; private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers"; private static final String ALLOW_ATT_MESSAGES = "messages"; @@ -105,6 +100,8 @@ public class ZenModeConfig implements Parcelable { private static final String ALLOW_ATT_SCREEN_OFF = "visualScreenOff"; private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn"; + private static final String CONDITION_TAG = "condition"; + private static final String CONDITION_ATT_COMPONENT = "component"; private static final String CONDITION_ATT_ID = "id"; private static final String CONDITION_ATT_SUMMARY = "summary"; private static final String CONDITION_ATT_LINE1 = "line1"; @@ -126,8 +123,6 @@ public class ZenModeConfig implements Parcelable { private static final String RULE_ATT_CREATION_TIME = "creationTime"; private static final String RULE_ATT_ENABLER = "enabler"; - public boolean allowAlarms = DEFAULT_ALLOW_ALARMS; - public boolean allowMediaSystemOther = DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER; public boolean allowCalls = DEFAULT_ALLOW_CALLS; public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS; public boolean allowMessages = DEFAULT_ALLOW_MESSAGES; @@ -166,8 +161,6 @@ public class ZenModeConfig implements Parcelable { } allowWhenScreenOff = source.readInt() == 1; allowWhenScreenOn = source.readInt() == 1; - allowAlarms = source.readInt() == 1; - allowMediaSystemOther = source.readInt() == 1; } @Override @@ -197,23 +190,19 @@ public class ZenModeConfig implements Parcelable { } dest.writeInt(allowWhenScreenOff ? 1 : 0); dest.writeInt(allowWhenScreenOn ? 1 : 0); - dest.writeInt(allowAlarms ? 1 : 0); - dest.writeInt(allowMediaSystemOther ? 1 : 0); } @Override public String toString() { return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[') .append("user=").append(user) - .append(",allowAlarms=").append(allowAlarms) - .append(",allowMediaSystemOther=").append(allowMediaSystemOther) - .append(",allowReminders=").append(allowReminders) - .append(",allowEvents=").append(allowEvents) .append(",allowCalls=").append(allowCalls) .append(",allowRepeatCallers=").append(allowRepeatCallers) .append(",allowMessages=").append(allowMessages) .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom)) .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom)) + .append(",allowReminders=").append(allowReminders) + .append(",allowEvents=").append(allowEvents) .append(",allowWhenScreenOff=").append(allowWhenScreenOff) .append(",allowWhenScreenOn=").append(allowWhenScreenOn) .append(",automaticRules=").append(automaticRules) @@ -229,21 +218,9 @@ public class ZenModeConfig implements Parcelable { if (user != to.user) { d.addLine("user", user, to.user); } - if (allowAlarms != to.allowAlarms) { - d.addLine("allowAlarms", allowAlarms, to.allowAlarms); - } - if (allowMediaSystemOther != to.allowMediaSystemOther) { - d.addLine("allowMediaSystemOther", allowMediaSystemOther, to.allowMediaSystemOther); - } if (allowCalls != to.allowCalls) { d.addLine("allowCalls", allowCalls, to.allowCalls); } - if (allowReminders != to.allowReminders) { - d.addLine("allowReminders", allowReminders, to.allowReminders); - } - if (allowEvents != to.allowEvents) { - d.addLine("allowEvents", allowEvents, to.allowEvents); - } if (allowRepeatCallers != to.allowRepeatCallers) { d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers); } @@ -256,6 +233,12 @@ public class ZenModeConfig implements Parcelable { if (allowMessagesFrom != to.allowMessagesFrom) { d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom); } + if (allowReminders != to.allowReminders) { + d.addLine("allowReminders", allowReminders, to.allowReminders); + } + if (allowEvents != to.allowEvents) { + d.addLine("allowEvents", allowEvents, to.allowEvents); + } if (allowWhenScreenOff != to.allowWhenScreenOff) { d.addLine("allowWhenScreenOff", allowWhenScreenOff, to.allowWhenScreenOff); } @@ -352,9 +335,7 @@ public class ZenModeConfig implements Parcelable { if (!(o instanceof ZenModeConfig)) return false; if (o == this) return true; final ZenModeConfig other = (ZenModeConfig) o; - return other.allowAlarms == allowAlarms - && other.allowMediaSystemOther == allowMediaSystemOther - && other.allowCalls == allowCalls + return other.allowCalls == allowCalls && other.allowRepeatCallers == allowRepeatCallers && other.allowMessages == allowMessages && other.allowCallsFrom == allowCallsFrom @@ -370,10 +351,10 @@ public class ZenModeConfig implements Parcelable { @Override public int hashCode() { - return Objects.hash(allowAlarms, allowMediaSystemOther, allowCalls, - allowRepeatCallers, allowMessages, - allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents, - allowWhenScreenOff, allowWhenScreenOn, user, automaticRules, manualRule); + return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom, + allowMessagesFrom, allowReminders, allowEvents, allowWhenScreenOff, + allowWhenScreenOn, + user, automaticRules, manualRule); } private static String toDayList(int[] days) { @@ -432,12 +413,10 @@ public class ZenModeConfig implements Parcelable { } if (type == XmlPullParser.START_TAG) { if (ALLOW_TAG.equals(tag)) { - rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, - DEFAULT_ALLOW_CALLS); + rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false); rt.allowRepeatCallers = safeBoolean(parser, ALLOW_ATT_REPEAT_CALLERS, DEFAULT_ALLOW_REPEAT_CALLERS); - rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, - DEFAULT_ALLOW_MESSAGES); + rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false); rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS, DEFAULT_ALLOW_REMINDERS); rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS); @@ -459,9 +438,6 @@ public class ZenModeConfig implements Parcelable { safeBoolean(parser, ALLOW_ATT_SCREEN_OFF, DEFAULT_ALLOW_SCREEN_OFF); rt.allowWhenScreenOn = safeBoolean(parser, ALLOW_ATT_SCREEN_ON, DEFAULT_ALLOW_SCREEN_ON); - rt.allowAlarms = safeBoolean(parser, ALLOW_ATT_ALARMS, DEFAULT_ALLOW_ALARMS); - rt.allowMediaSystemOther = safeBoolean(parser, ALLOW_ATT_MEDIA, - DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER); } else if (MANUAL_TAG.equals(tag)) { rt.manualRule = readRuleXml(parser); } else if (AUTOMATIC_TAG.equals(tag)) { @@ -492,8 +468,6 @@ public class ZenModeConfig implements Parcelable { out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom)); out.attribute(null, ALLOW_ATT_SCREEN_OFF, Boolean.toString(allowWhenScreenOff)); out.attribute(null, ALLOW_ATT_SCREEN_ON, Boolean.toString(allowWhenScreenOn)); - out.attribute(null, ALLOW_ATT_ALARMS, Boolean.toString(allowAlarms)); - out.attribute(null, ALLOW_ATT_ALARMS, Boolean.toString(allowMediaSystemOther)); out.endTag(null, ALLOW_TAG); if (manualRule != null) { @@ -680,12 +654,6 @@ public class ZenModeConfig implements Parcelable { if (!allowWhenScreenOn) { suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_SCREEN_ON; } - if (allowAlarms) { - priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS; - } - if (allowMediaSystemOther) { - priorityCategories |= Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER; - } priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders); priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders); return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders, @@ -712,13 +680,10 @@ public class ZenModeConfig implements Parcelable { public void applyNotificationPolicy(Policy policy) { if (policy == null) return; - allowAlarms = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0; - allowMediaSystemOther = (policy.priorityCategories - & Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) != 0; - allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0; - allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0; allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0; allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0; + allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0; + allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0; allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0; allowCallsFrom = prioritySendersToSource(policy.priorityCallSenders, allowCallsFrom); diff --git a/android/app/slice/Slice.java b/android/slice/Slice.java index 7f9f74b4..57686548 100644 --- a/android/app/slice/Slice.java +++ b/android/slice/Slice.java @@ -14,35 +14,38 @@ * limitations under the License. */ -package android.app.slice; +package android.slice; + +import static android.slice.SliceItem.TYPE_ACTION; +import static android.slice.SliceItem.TYPE_COLOR; +import static android.slice.SliceItem.TYPE_IMAGE; +import static android.slice.SliceItem.TYPE_REMOTE_INPUT; +import static android.slice.SliceItem.TYPE_REMOTE_VIEW; +import static android.slice.SliceItem.TYPE_SLICE; +import static android.slice.SliceItem.TYPE_TEXT; +import static android.slice.SliceItem.TYPE_TIMESTAMP; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.StringDef; import android.app.PendingIntent; import android.app.RemoteInput; -import android.content.ContentResolver; -import android.content.IContentProvider; import android.graphics.drawable.Icon; import android.net.Uri; -import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.os.RemoteException; import android.widget.RemoteViews; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.Arrays; -import java.util.List; /** * A slice is a piece of app content and actions that can be surfaced outside of the app. * * <p>They are constructed using {@link Builder} in a tree structure * that provides the OS some information about how the content should be displayed. + * @hide */ public final class Slice implements Parcelable { @@ -50,7 +53,7 @@ public final class Slice implements Parcelable { * @hide */ @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED, - HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL}) + HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT}) public @interface SliceHint{ } /** @@ -99,12 +102,6 @@ public final class Slice implements Parcelable { * Hint to indicate that this content should not be tinted. */ public static final String HINT_NO_TINT = "no_tint"; - /** - * Hint to indicate that this slice is incomplete and an update will be sent once - * loading is complete. Slices which contain HINT_PARTIAL will not be cached by the - * OS and should not be cached by apps. - */ - public static final String HINT_PARTIAL = "partial"; // These two are coming over from prototyping, but we probably don't want in // public API, at least not right now. @@ -112,12 +109,19 @@ public final class Slice implements Parcelable { * @hide */ public static final String HINT_ALT = "alt"; + /** + * @hide + */ + public static final String HINT_PARTIAL = "partial"; private final SliceItem[] mItems; private final @SliceHint String[] mHints; private Uri mUri; - Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) { + /** + * @hide + */ + public Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) { mHints = hints; mItems = items.toArray(new SliceItem[items.size()]); mUri = uri; @@ -143,15 +147,15 @@ public final class Slice implements Parcelable { /** * @return All child {@link SliceItem}s that this Slice contains. */ - public List<SliceItem> getItems() { - return Arrays.asList(mItems); + public SliceItem[] getItems() { + return mItems; } /** * @return All hints associated with this Slice. */ - public @SliceHint List<String> getHints() { - return Arrays.asList(mHints); + public @SliceHint String[] getHints() { + return mHints; } /** @@ -159,14 +163,14 @@ public final class Slice implements Parcelable { */ public SliceItem getPrimaryIcon() { for (SliceItem item : getItems()) { - if (item.getType() == SliceItem.TYPE_IMAGE) { + if (item.getType() == TYPE_IMAGE) { return item; } - if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST)) + if (!(item.getType() == TYPE_SLICE && item.hasHint(Slice.HINT_LIST)) && !item.hasHint(Slice.HINT_ACTIONS) && !item.hasHint(Slice.HINT_LIST_ITEM) - && (item.getType() != SliceItem.TYPE_ACTION)) { - SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE); + && (item.getType() != TYPE_ACTION)) { + SliceItem icon = SliceQuery.find(item, TYPE_IMAGE); if (icon != null) return icon; } } @@ -231,18 +235,10 @@ public final class Slice implements Parcelable { } /** - * Add hints to the Slice being constructed - */ - public Builder addHints(@SliceHint List<String> hints) { - return addHints(hints.toArray(new String[hints.size()])); - } - - /** * Add a sub-slice to the slice being constructed */ public Builder addSubSlice(@NonNull Slice slice) { - mItems.add(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints().toArray( - new String[slice.getHints().size()]))); + mItems.add(new SliceItem(slice, TYPE_SLICE, slice.getHints())); return this; } @@ -250,7 +246,7 @@ public final class Slice implements Parcelable { * Add an action to the slice being constructed */ public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s) { - mItems.add(new SliceItem(action, s, SliceItem.TYPE_ACTION, new String[0])); + mItems.add(new SliceItem(action, s, TYPE_ACTION, new String[0])); return this; } @@ -258,53 +254,31 @@ public final class Slice implements Parcelable { * Add text to the slice being constructed */ public Builder addText(CharSequence text, @SliceHint String... hints) { - mItems.add(new SliceItem(text, SliceItem.TYPE_TEXT, hints)); + mItems.add(new SliceItem(text, TYPE_TEXT, hints)); return this; } /** - * Add text to the slice being constructed - */ - public Builder addText(CharSequence text, @SliceHint List<String> hints) { - return addText(text, hints.toArray(new String[hints.size()])); - } - - /** * Add an image to the slice being constructed */ public Builder addIcon(Icon icon, @SliceHint String... hints) { - mItems.add(new SliceItem(icon, SliceItem.TYPE_IMAGE, hints)); + mItems.add(new SliceItem(icon, TYPE_IMAGE, hints)); return this; } /** - * Add an image to the slice being constructed - */ - public Builder addIcon(Icon icon, @SliceHint List<String> hints) { - return addIcon(icon, hints.toArray(new String[hints.size()])); - } - - /** * @hide This isn't final */ public Builder addRemoteView(RemoteViews remoteView, @SliceHint String... hints) { - mItems.add(new SliceItem(remoteView, SliceItem.TYPE_REMOTE_VIEW, hints)); + mItems.add(new SliceItem(remoteView, TYPE_REMOTE_VIEW, hints)); return this; } /** * Add remote input to the slice being constructed */ - public Slice.Builder addRemoteInput(RemoteInput remoteInput, - @SliceHint List<String> hints) { - return addRemoteInput(remoteInput, hints.toArray(new String[hints.size()])); - } - - /** - * Add remote input to the slice being constructed - */ public Slice.Builder addRemoteInput(RemoteInput remoteInput, @SliceHint String... hints) { - mItems.add(new SliceItem(remoteInput, SliceItem.TYPE_REMOTE_INPUT, hints)); + mItems.add(new SliceItem(remoteInput, TYPE_REMOTE_INPUT, hints)); return this; } @@ -312,33 +286,19 @@ public final class Slice implements Parcelable { * Add a color to the slice being constructed */ public Builder addColor(int color, @SliceHint String... hints) { - mItems.add(new SliceItem(color, SliceItem.TYPE_COLOR, hints)); + mItems.add(new SliceItem(color, TYPE_COLOR, hints)); return this; } /** - * Add a color to the slice being constructed - */ - public Builder addColor(int color, @SliceHint List<String> hints) { - return addColor(color, hints.toArray(new String[hints.size()])); - } - - /** * Add a timestamp to the slice being constructed */ public Slice.Builder addTimestamp(long time, @SliceHint String... hints) { - mItems.add(new SliceItem(time, SliceItem.TYPE_TIMESTAMP, hints)); + mItems.add(new SliceItem(time, TYPE_TIMESTAMP, hints)); return this; } /** - * Add a timestamp to the slice being constructed - */ - public Slice.Builder addTimestamp(long time, @SliceHint List<String> hints) { - return addTimestamp(time, hints.toArray(new String[hints.size()])); - } - - /** * Construct the slice. */ public Slice build() { @@ -362,18 +322,18 @@ public final class Slice implements Parcelable { * @hide * @return A string representation of this slice. */ - public String toString() { - return toString(""); + public String getString() { + return getString(""); } - private String toString(String indent) { + private String getString(String indent) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < mItems.length; i++) { sb.append(indent); - if (mItems[i].getType() == SliceItem.TYPE_SLICE) { + if (mItems[i].getType() == TYPE_SLICE) { sb.append("slice:\n"); - sb.append(mItems[i].getSlice().toString(indent + " ")); - } else if (mItems[i].getType() == SliceItem.TYPE_TEXT) { + sb.append(mItems[i].getSlice().getString(indent + " ")); + } else if (mItems[i].getType() == TYPE_TEXT) { sb.append("text: "); sb.append(mItems[i].getText()); sb.append("\n"); @@ -384,34 +344,4 @@ public final class Slice implements Parcelable { } return sb.toString(); } - - /** - * Turns a slice Uri into slice content. - * - * @param resolver ContentResolver to be used. - * @param uri The URI to a slice provider - * @return The Slice provided by the app or null if none is given. - * @see Slice - */ - public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri) { - Preconditions.checkNotNull(uri, "uri"); - IContentProvider provider = resolver.acquireProvider(uri); - if (provider == null) { - throw new IllegalArgumentException("Unknown URI " + uri); - } - try { - Bundle extras = new Bundle(); - extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); - final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE, - null, extras); - Bundle.setDefusable(res, true); - return res.getParcelable(SliceProvider.EXTRA_SLICE); - } catch (RemoteException e) { - // Arbitrary and not worth documenting, as Activity - // Manager will kill this process shortly anyway. - return null; - } finally { - resolver.releaseProvider(provider); - } - } } diff --git a/android/app/slice/SliceItem.java b/android/slice/SliceItem.java index 6e69b051..2827ab9d 100644 --- a/android/app/slice/SliceItem.java +++ b/android/slice/SliceItem.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.app.slice; +package android.slice; import android.annotation.IntDef; import android.annotation.NonNull; @@ -23,15 +23,13 @@ import android.app.RemoteInput; import android.graphics.drawable.Icon; import android.os.Parcel; import android.os.Parcelable; +import android.slice.Slice.SliceHint; import android.text.TextUtils; import android.util.Pair; import android.widget.RemoteViews; import com.android.internal.util.ArrayUtils; -import java.util.Arrays; -import java.util.List; - /** * A SliceItem is a single unit in the tree structure of a {@link Slice}. @@ -49,6 +47,7 @@ import java.util.List; * The hints that a {@link SliceItem} are a set of strings which annotate * the content. The hints that are guaranteed to be understood by the system * are defined on {@link Slice}. + * @hide */ public final class SliceItem implements Parcelable { @@ -98,15 +97,14 @@ public final class SliceItem implements Parcelable { /** * @hide */ - protected @Slice.SliceHint - String[] mHints; + protected @SliceHint String[] mHints; private final int mType; private final Object mObj; /** * @hide */ - public SliceItem(Object obj, @SliceType int type, @Slice.SliceHint String[] hints) { + public SliceItem(Object obj, @SliceType int type, @SliceHint String[] hints) { mHints = hints; mType = type; mObj = obj; @@ -115,7 +113,7 @@ public final class SliceItem implements Parcelable { /** * @hide */ - public SliceItem(PendingIntent intent, Slice slice, int type, @Slice.SliceHint String[] hints) { + public SliceItem(PendingIntent intent, Slice slice, int type, @SliceHint String[] hints) { this(new Pair<>(intent, slice), type, hints); } @@ -123,14 +121,14 @@ public final class SliceItem implements Parcelable { * Gets all hints associated with this SliceItem. * @return Array of hints. */ - public @NonNull @Slice.SliceHint List<String> getHints() { - return Arrays.asList(mHints); + public @NonNull @SliceHint String[] getHints() { + return mHints; } /** * @hide */ - public void addHint(@Slice.SliceHint String hint) { + public void addHint(@SliceHint String hint) { mHints = ArrayUtils.appendElement(String.class, mHints, hint); } @@ -208,7 +206,7 @@ public final class SliceItem implements Parcelable { * @param hint The hint to check for * @return true if this item contains the given hint */ - public boolean hasHint(@Slice.SliceHint String hint) { + public boolean hasHint(@SliceHint String hint) { return ArrayUtils.contains(mHints, hint); } @@ -236,7 +234,7 @@ public final class SliceItem implements Parcelable { /** * @hide */ - public boolean hasHints(@Slice.SliceHint String[] hints) { + public boolean hasHints(@SliceHint String[] hints) { if (hints == null) return true; for (String hint : hints) { if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) { @@ -249,7 +247,7 @@ public final class SliceItem implements Parcelable { /** * @hide */ - public boolean hasAnyHints(@Slice.SliceHint String[] hints) { + public boolean hasAnyHints(@SliceHint String[] hints) { if (hints == null) return false; for (String hint : hints) { if (ArrayUtils.contains(mHints, hint)) { diff --git a/android/app/slice/SliceProvider.java b/android/slice/SliceProvider.java index df87b455..4e21371b 100644 --- a/android/app/slice/SliceProvider.java +++ b/android/slice/SliceProvider.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.app.slice; +package android.slice; import android.Manifest.permission; import android.content.ContentProvider; @@ -26,8 +26,6 @@ import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; import android.os.Looper; -import android.os.StrictMode; -import android.os.StrictMode.ThreadPolicy; import android.util.Log; import java.util.concurrent.CountDownLatch; @@ -53,15 +51,10 @@ import java.util.concurrent.CountDownLatch; * </pre> * * @see Slice + * @hide */ public abstract class SliceProvider extends ContentProvider { - /** - * This is the Android platform's MIME type for a slice: URI - * containing a slice implemented through {@link SliceProvider}. - */ - public static final String SLICE_TYPE = "vnd.android.slice"; - private static final String TAG = "SliceProvider"; /** * @hide @@ -80,18 +73,8 @@ public abstract class SliceProvider extends ContentProvider { /** * Implemented to create a slice. Will be called on the main thread. - * <p> - * onBindSlice should return as quickly as possible so that the UI tied - * to this slice can be responsive. No network or other IO will be allowed - * during onBindSlice. Any loading that needs to be done should happen - * off the main thread with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)} - * when the app is ready to provide the complete data in onBindSlice. - * <p> - * * @see {@link Slice}. - * @see {@link Slice#HINT_PARTIAL} */ - // TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)). public abstract Slice onBindSlice(Uri sliceUri); @Override @@ -137,11 +120,11 @@ public abstract class SliceProvider extends ContentProvider { @Override public final String getType(Uri uri) { if (DEBUG) Log.d(TAG, "getType " + uri); - return SLICE_TYPE; + return null; } @Override - public Bundle call(String method, String arg, Bundle extras) { + public final Bundle call(String method, String arg, Bundle extras) { if (method.equals(METHOD_SLICE)) { getContext().enforceCallingPermission(permission.BIND_SLICE, "Slice binding requires the permission BIND_SLICE"); @@ -160,17 +143,8 @@ public abstract class SliceProvider extends ContentProvider { CountDownLatch latch = new CountDownLatch(1); Handler mainHandler = new Handler(Looper.getMainLooper()); mainHandler.post(() -> { - ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); - try { - StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() - .detectAll() - .penaltyDeath() - .build()); - output[0] = onBindSlice(sliceUri); - } finally { - StrictMode.setThreadPolicy(oldPolicy); - latch.countDown(); - } + output[0] = onBindSlice(sliceUri); + latch.countDown(); }); try { latch.await(); diff --git a/android/app/slice/SliceQuery.java b/android/slice/SliceQuery.java index d1fe2c90..d99b26a5 100644 --- a/android/app/slice/SliceQuery.java +++ b/android/slice/SliceQuery.java @@ -14,8 +14,12 @@ * limitations under the License. */ -package android.app.slice; +package android.slice; +import static android.slice.SliceItem.TYPE_ACTION; +import static android.slice.SliceItem.TYPE_SLICE; + +import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -110,9 +114,7 @@ public class SliceQuery { * @hide */ public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) { - List<String> h = s.getHints(); - return find(new SliceItem(s, SliceItem.TYPE_SLICE, h.toArray(new String[h.size()])), type, - hints, nonHints); + return find(new SliceItem(s, TYPE_SLICE, s.getHints()), type, hints, nonHints); } /** @@ -138,9 +140,8 @@ public class SliceQuery { @Override public SliceItem next() { SliceItem item = items.poll(); - if (item.getType() == SliceItem.TYPE_SLICE - || item.getType() == SliceItem.TYPE_ACTION) { - items.addAll(item.getSlice().getItems()); + if (item.getType() == TYPE_SLICE || item.getType() == TYPE_ACTION) { + items.addAll(Arrays.asList(item.getSlice().getItems())); } return item; } diff --git a/android/app/slice/views/ActionRow.java b/android/slice/views/ActionRow.java index c7d99f7f..93e9c035 100644 --- a/android/app/slice/views/ActionRow.java +++ b/android/slice/views/ActionRow.java @@ -14,19 +14,19 @@ * limitations under the License. */ -package android.app.slice.views; +package android.slice.views; import android.app.PendingIntent; import android.app.PendingIntent.CanceledException; import android.app.RemoteInput; -import android.app.slice.Slice; -import android.app.slice.SliceItem; -import android.app.slice.SliceQuery; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.drawable.Icon; import android.os.AsyncTask; +import android.slice.Slice; +import android.slice.SliceItem; +import android.slice.SliceQuery; import android.util.TypedValue; import android.view.View; import android.view.ViewParent; diff --git a/android/app/slice/views/GridView.java b/android/slice/views/GridView.java index 6f30c507..18a90f7d 100644 --- a/android/app/slice/views/GridView.java +++ b/android/slice/views/GridView.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package android.app.slice.views; +package android.slice.views; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import android.app.slice.Slice; -import android.app.slice.SliceItem; -import android.app.slice.views.LargeSliceAdapter.SliceListView; import android.content.Context; import android.graphics.Color; +import android.slice.Slice; +import android.slice.SliceItem; +import android.slice.views.LargeSliceAdapter.SliceListView; import android.util.AttributeSet; import android.util.TypedValue; import android.view.Gravity; @@ -38,7 +38,7 @@ import android.widget.TextView; import com.android.internal.R; import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; /** * @hide @@ -76,10 +76,10 @@ public class GridView extends LinearLayout implements SliceListView { removeAllViews(); int total = 1; if (slice.getType() == SliceItem.TYPE_SLICE) { - List<SliceItem> items = slice.getSlice().getItems(); - total = items.size(); + SliceItem[] items = slice.getSlice().getItems(); + total = items.length; for (int i = 0; i < total; i++) { - SliceItem item = items.get(i); + SliceItem item = items[i]; if (isFull()) { continue; } @@ -142,7 +142,7 @@ public class GridView extends LinearLayout implements SliceListView { // TODO: Unify sporadic inflates that happen throughout the code. ArrayList<SliceItem> items = new ArrayList<>(); if (item.getType() == SliceItem.TYPE_SLICE) { - items.addAll(item.getSlice().getItems()); + items.addAll(Arrays.asList(item.getSlice().getItems())); } items.forEach(i -> { Context context = getContext(); diff --git a/android/app/slice/views/LargeSliceAdapter.java b/android/slice/views/LargeSliceAdapter.java index 6794ff98..e77a1b2a 100644 --- a/android/app/slice/views/LargeSliceAdapter.java +++ b/android/slice/views/LargeSliceAdapter.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package android.app.slice.views; +package android.slice.views; -import android.app.slice.Slice; -import android.app.slice.SliceItem; -import android.app.slice.SliceQuery; -import android.app.slice.views.LargeSliceAdapter.SliceViewHolder; import android.content.Context; +import android.slice.Slice; +import android.slice.SliceItem; +import android.slice.SliceQuery; +import android.slice.views.LargeSliceAdapter.SliceViewHolder; import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.View; diff --git a/android/app/slice/views/LargeTemplateView.java b/android/slice/views/LargeTemplateView.java index 9e225162..d53e8fcb 100644 --- a/android/app/slice/views/LargeTemplateView.java +++ b/android/slice/views/LargeTemplateView.java @@ -14,21 +14,22 @@ * limitations under the License. */ -package android.app.slice.views; +package android.slice.views; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import android.app.slice.Slice; -import android.app.slice.SliceItem; -import android.app.slice.SliceQuery; -import android.app.slice.views.SliceView.SliceModeView; import android.content.Context; +import android.slice.Slice; +import android.slice.SliceItem; +import android.slice.SliceQuery; +import android.slice.views.SliceView.SliceModeView; import android.util.TypedValue; import com.android.internal.widget.LinearLayoutManager; import com.android.internal.widget.RecyclerView; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -85,7 +86,7 @@ public class LargeTemplateView extends SliceModeView { if (slice.hasHint(Slice.HINT_LIST)) { addList(slice, items); } else { - slice.getItems().forEach(item -> { + Arrays.asList(slice.getItems()).forEach(item -> { if (item.hasHint(Slice.HINT_ACTIONS)) { return; } else if (item.getType() == SliceItem.TYPE_COLOR) { @@ -108,7 +109,7 @@ public class LargeTemplateView extends SliceModeView { } private void addList(Slice slice, List<SliceItem> items) { - List<SliceItem> sliceItems = slice.getItems(); + List<SliceItem> sliceItems = Arrays.asList(slice.getItems()); sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM)); items.addAll(sliceItems); } diff --git a/android/app/slice/views/MessageView.java b/android/slice/views/MessageView.java index 77252bf2..7b03e0bd 100644 --- a/android/app/slice/views/MessageView.java +++ b/android/slice/views/MessageView.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package android.app.slice.views; +package android.slice.views; -import android.app.slice.Slice; -import android.app.slice.SliceItem; -import android.app.slice.SliceQuery; -import android.app.slice.views.LargeSliceAdapter.SliceListView; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.Drawable; +import android.slice.Slice; +import android.slice.SliceItem; +import android.slice.SliceQuery; +import android.slice.views.LargeSliceAdapter.SliceListView; import android.text.SpannableStringBuilder; import android.util.AttributeSet; import android.util.TypedValue; diff --git a/android/app/slice/views/RemoteInputView.java b/android/slice/views/RemoteInputView.java index e53cb1ea..a29bb5c0 100644 --- a/android/app/slice/views/RemoteInputView.java +++ b/android/slice/views/RemoteInputView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.app.slice.views; +package android.slice.views; import android.animation.Animator; import android.app.Notification; diff --git a/android/app/slice/views/ShortcutView.java b/android/slice/views/ShortcutView.java index b6790c7d..8fe2f1ac 100644 --- a/android/app/slice/views/ShortcutView.java +++ b/android/slice/views/ShortcutView.java @@ -14,20 +14,20 @@ * limitations under the License. */ -package android.app.slice.views; +package android.slice.views; import android.app.PendingIntent; import android.app.PendingIntent.CanceledException; -import android.app.slice.Slice; -import android.app.slice.SliceItem; -import android.app.slice.SliceQuery; -import android.app.slice.views.SliceView.SliceModeView; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.OvalShape; import android.net.Uri; +import android.slice.Slice; +import android.slice.SliceItem; +import android.slice.SliceQuery; +import android.slice.views.SliceView.SliceModeView; import android.view.ViewGroup; import com.android.internal.R; diff --git a/android/app/slice/views/SliceView.java b/android/slice/views/SliceView.java index 32484fca..f3792481 100644 --- a/android/app/slice/views/SliceView.java +++ b/android/slice/views/SliceView.java @@ -14,25 +14,23 @@ * limitations under the License. */ -package android.app.slice.views; +package android.slice.views; import android.annotation.StringDef; -import android.app.slice.Slice; -import android.app.slice.SliceItem; -import android.app.slice.SliceQuery; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.graphics.drawable.ColorDrawable; import android.net.Uri; +import android.slice.Slice; +import android.slice.SliceItem; +import android.slice.SliceQuery; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; import android.widget.LinearLayout; -import java.util.List; - /** * A view that can display a {@link Slice} in different {@link SliceMode}'s. * @@ -122,7 +120,7 @@ public class SliceView extends LinearLayout { */ public void bindSlice(Uri sliceUri) { validate(sliceUri); - Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri); + Slice s = mContext.getContentResolver().bindSlice(sliceUri); bindSlice(s); } @@ -203,7 +201,7 @@ public class SliceView extends LinearLayout { } // TODO: Smarter mapping here from one state to the next. SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR); - List<SliceItem> items = mCurrentSlice.getItems(); + SliceItem[] items = mCurrentSlice.getItems(); SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE, Slice.HINT_ACTIONS, Slice.HINT_ALT); @@ -214,7 +212,7 @@ public class SliceView extends LinearLayout { addView(mCurrentView); addView(mActions); } - if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) { + if (items.length > 1 || (items.length != 0 && items[0] != actionRow)) { mCurrentView.setVisibility(View.VISIBLE); mCurrentView.setSlice(mCurrentSlice); } else { @@ -241,7 +239,7 @@ public class SliceView extends LinearLayout { } private static void validate(Uri sliceUri) { - if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) { + if (!ContentResolver.SCHEME_SLICE.equals(sliceUri.getScheme())) { throw new RuntimeException("Invalid uri " + sliceUri); } if (sliceUri.getPathSegments().size() == 0) { diff --git a/android/app/slice/views/SliceViewUtil.java b/android/slice/views/SliceViewUtil.java index 19e8e7c9..1b5a6d1e 100644 --- a/android/app/slice/views/SliceViewUtil.java +++ b/android/slice/views/SliceViewUtil.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.app.slice.views; +package android.slice.views; import android.annotation.ColorInt; import android.content.Context; diff --git a/android/app/slice/views/SmallTemplateView.java b/android/slice/views/SmallTemplateView.java index 42b2d213..b0b181ed 100644 --- a/android/app/slice/views/SmallTemplateView.java +++ b/android/slice/views/SmallTemplateView.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package android.app.slice.views; +package android.slice.views; import android.app.PendingIntent.CanceledException; -import android.app.slice.Slice; -import android.app.slice.SliceItem; -import android.app.slice.SliceQuery; -import android.app.slice.views.LargeSliceAdapter.SliceListView; -import android.app.slice.views.SliceView.SliceModeView; import android.content.Context; import android.os.AsyncTask; +import android.slice.Slice; +import android.slice.SliceItem; +import android.slice.SliceQuery; +import android.slice.views.LargeSliceAdapter.SliceListView; +import android.slice.views.SliceView.SliceModeView; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; @@ -34,6 +34,7 @@ import com.android.internal.R; import java.text.Format; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.List; @@ -116,7 +117,7 @@ public class SmallTemplateView extends SliceModeView implements SliceListView { int itemCount = 0; boolean hasSummary = false; ArrayList<SliceItem> sliceItems = new ArrayList<SliceItem>( - slice.getSlice().getItems()); + Arrays.asList(slice.getSlice().getItems())); for (int i = 0; i < sliceItems.size(); i++) { SliceItem item = sliceItems.get(i); if (!hasSummary && item.getType() == SliceItem.TYPE_TEXT @@ -139,9 +140,9 @@ public class SmallTemplateView extends SliceModeView implements SliceListView { mEndContainer.addView(tv); itemCount++; } else if (item.getType() == SliceItem.TYPE_SLICE) { - List<SliceItem> subItems = item.getSlice().getItems(); - for (int j = 0; j < subItems.size(); j++) { - sliceItems.add(subItems.get(j)); + SliceItem[] subItems = item.getSlice().getItems(); + for (int j = 0; j < subItems.length; j++) { + sliceItems.add(subItems[j]); } } } @@ -150,8 +151,7 @@ public class SmallTemplateView extends SliceModeView implements SliceListView { @Override public void setSlice(Slice slice) { - setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE, - slice.getHints().toArray(new String[slice.getHints().size()]))); + setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints())); } /** diff --git a/android/support/LibraryVersions.java b/android/support/LibraryVersions.java index 72f7fb18..a046d95e 100644 --- a/android/support/LibraryVersions.java +++ b/android/support/LibraryVersions.java @@ -45,17 +45,15 @@ public class LibraryVersions { */ public static final Version PAGING = new Version("1.0.0-alpha3"); - private static final Version LIFECYCLES = new Version("1.0.2"); - /** * Version code for Lifecycle libs that are required by the support library */ - public static final Version LIFECYCLES_CORE = LIFECYCLES; + public static final Version LIFECYCLES_CORE = new Version("1.0.2"); /** * Version code for Lifecycle runtime libs that are required by the support library */ - public static final Version LIFECYCLES_RUNTIME = LIFECYCLES; + public static final Version LIFECYCLES_RUNTIME = new Version("1.0.0"); /** * Version code for shared code of flatfoot diff --git a/android/support/car/drawer/CarDrawerActivity.java b/android/support/car/drawer/CarDrawerActivity.java deleted file mode 100644 index 7100218a..00000000 --- a/android/support/car/drawer/CarDrawerActivity.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.car.drawer; - -import android.content.res.Configuration; -import android.os.Bundle; -import android.support.annotation.LayoutRes; -import android.support.annotation.Nullable; -import android.support.car.R; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; - -/** - * Common base Activity for car apps that need to present a Drawer. - * - * <p>This Activity manages the overall layout. To use it, sub-classes need to: - * - * <ul> - * <li>Provide the root-items for the Drawer by implementing {@link #getRootAdapter()}. - * <li>Add their main content using {@link #setMainContent(int)} or {@link #setMainContent(View)}. - * They can also add fragments to the main-content container by obtaining its id using - * {@link #getContentContainerId()} - * </ul> - * - * <p>This class will take care of drawer toggling and display. - * - * <p>The rootAdapter can implement nested-navigation, in its click-handling, by passing the - * CarDrawerAdapter for the next level to - * {@link CarDrawerController#switchToAdapter(CarDrawerAdapter)}. - * - * <p>Any Activity's based on this class need to set their theme to CarDrawerActivityTheme or a - * derivative. - */ -public abstract class CarDrawerActivity extends AppCompatActivity { - private CarDrawerController mDrawerController; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.car_drawer_activity); - - DrawerLayout drawerLayout = findViewById(R.id.drawer_layout); - ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle( - this /* activity */, - drawerLayout, /* DrawerLayout object */ - R.string.car_drawer_open, - R.string.car_drawer_close); - - Toolbar toolbar = findViewById(R.id.car_toolbar); - setSupportActionBar(toolbar); - - mDrawerController = new CarDrawerController(toolbar, drawerLayout, drawerToggle); - mDrawerController.setRootAdapter(getRootAdapter()); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setHomeButtonEnabled(true); - } - - /** - * Returns the {@link CarDrawerController} that is responsible for handling events relating - * to the drawer in this Activity. - * - * @return The {@link CarDrawerController} linked to this Activity. This value will be - * {@code null} if this method is called before {@code onCreate()} has been called. - */ - @Nullable - protected CarDrawerController getDrawerController() { - return mDrawerController; - } - - @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - mDrawerController.syncState(); - } - - /** - * @return Adapter for root content of the Drawer. - */ - protected abstract CarDrawerAdapter getRootAdapter(); - - /** - * Set main content to display in this Activity. It will be added to R.id.content_frame in - * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(View)}. - * - * @param view View to display as main content. - */ - public void setMainContent(View view) { - ViewGroup parent = findViewById(getContentContainerId()); - parent.addView(view); - } - - /** - * Set main content to display in this Activity. It will be added to R.id.content_frame in - * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(int)}. - * - * @param resourceId Layout to display as main content. - */ - public void setMainContent(@LayoutRes int resourceId) { - ViewGroup parent = findViewById(getContentContainerId()); - LayoutInflater inflater = getLayoutInflater(); - inflater.inflate(resourceId, parent, true); - } - - /** - * Get the id of the main content Container which is a FrameLayout. Subclasses can add their own - * content/fragments inside here. - * - * @return Id of FrameLayout where main content of the subclass Activity can be added. - */ - protected int getContentContainerId() { - return R.id.content_frame; - } - - @Override - protected void onStop() { - super.onStop(); - mDrawerController.closeDrawer(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mDrawerController.onConfigurationChanged(newConfig); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - return mDrawerController.onOptionsItemSelected(item) || super.onOptionsItemSelected(item); - } -} diff --git a/android/support/car/drawer/CarDrawerAdapter.java b/android/support/car/drawer/CarDrawerAdapter.java deleted file mode 100644 index b0fd965d..00000000 --- a/android/support/car/drawer/CarDrawerAdapter.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.car.drawer; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.car.R; -import android.support.car.widget.PagedListView; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -/** - * Base adapter for displaying items in the car navigation drawer, which uses a - * {@link PagedListView}. - * - * <p>Subclasses must set the title that will be displayed when displaying the contents of the - * drawer via {@link #setTitle(CharSequence)}. The title can be updated at any point later on. The - * title of the root adapter will also be the main title showed in the toolbar when the drawer is - * closed. See {@link CarDrawerController#setRootAdapter(CarDrawerAdapter)} for more information. - * - * <p>This class also takes care of implementing the PageListView.ItemCamp contract and subclasses - * should implement {@link #getActualItemCount()}. - */ -public abstract class CarDrawerAdapter extends RecyclerView.Adapter<DrawerItemViewHolder> - implements PagedListView.ItemCap, DrawerItemClickListener { - private final boolean mShowDisabledListOnEmpty; - private final Drawable mEmptyListDrawable; - private int mMaxItems = PagedListView.ItemCap.UNLIMITED; - private CharSequence mTitle; - private TitleChangeListener mTitleChangeListener; - - /** - * Interface for a class that will be notified a new title has been set on this adapter. - */ - interface TitleChangeListener { - /** - * Called when {@link #setTitle(CharSequence)} has been called and the title has been - * changed. - */ - void onTitleChanged(CharSequence newTitle); - } - - protected CarDrawerAdapter(Context context, boolean showDisabledListOnEmpty) { - mShowDisabledListOnEmpty = showDisabledListOnEmpty; - - mEmptyListDrawable = context.getDrawable(R.drawable.ic_list_view_disable); - mEmptyListDrawable.setColorFilter(context.getColor(R.color.car_tint), - PorterDuff.Mode.SRC_IN); - } - - /** Returns the title set via {@link #setTitle(CharSequence)}. */ - CharSequence getTitle() { - return mTitle; - } - - /** Updates the title to display in the toolbar for this Adapter. */ - public final void setTitle(@NonNull CharSequence title) { - if (title == null) { - throw new IllegalArgumentException("setTitle() cannot be passed a null title!"); - } - - mTitle = title; - - if (mTitleChangeListener != null) { - mTitleChangeListener.onTitleChanged(mTitle); - } - } - - /** Sets a listener to be notified whenever the title of this adapter has been changed. */ - void setTitleChangeListener(@Nullable TitleChangeListener listener) { - mTitleChangeListener = listener; - } - - @Override - public final void setMaxItems(int maxItems) { - mMaxItems = maxItems; - } - - @Override - public final int getItemCount() { - if (shouldShowDisabledListItem()) { - return 1; - } - return mMaxItems >= 0 ? Math.min(mMaxItems, getActualItemCount()) : getActualItemCount(); - } - - /** - * Returns the absolute number of items that can be displayed in the list. - * - * <p>A class should implement this method to supply the number of items to be displayed. - * Returning 0 from this method will cause an empty list icon to be displayed in the drawer. - * - * <p>A class should override this method rather than {@link #getItemCount()} because that - * method is handling the logic of when to display the empty list icon. It will return 1 when - * {@link #getActualItemCount()} returns 0. - * - * @return The number of items to be displayed in the list. - */ - protected abstract int getActualItemCount(); - - @Override - public final int getItemViewType(int position) { - if (shouldShowDisabledListItem()) { - return R.layout.car_drawer_list_item_empty; - } - - return usesSmallLayout(position) - ? R.layout.car_drawer_list_item_small - : R.layout.car_drawer_list_item_normal; - } - - /** - * Used to indicate the layout used for the Drawer item at given position. Subclasses can - * override this to use normal layout which includes text element below title. - * - * <p>A small layout is presented by the layout {@code R.layout.car_drawer_list_item_small}. - * Otherwise, the layout {@code R.layout.car_drawer_list_item_normal} will be used. - * - * @param position Adapter position of item. - * @return Whether the item at this position will use a small layout (default) or normal layout. - */ - protected boolean usesSmallLayout(int position) { - return true; - } - - @Override - public final DrawerItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false); - return new DrawerItemViewHolder(view); - } - - @Override - public final void onBindViewHolder(DrawerItemViewHolder holder, int position) { - if (shouldShowDisabledListItem()) { - holder.getTitle().setText(null); - holder.getIcon().setImageDrawable(mEmptyListDrawable); - holder.setItemClickListener(null); - } else { - holder.setItemClickListener(this); - populateViewHolder(holder, position); - } - } - - /** - * Whether or not this adapter should be displaying an empty list icon. The icon is shown if it - * has been configured to show and there are no items to be displayed. - */ - private boolean shouldShowDisabledListItem() { - return mShowDisabledListOnEmpty && getActualItemCount() == 0; - } - - /** - * Subclasses should set all elements in {@code holder} to populate the drawer-item. If some - * element is not used, it should be nulled out since these ViewHolder/View's are recycled. - */ - protected abstract void populateViewHolder(DrawerItemViewHolder holder, int position); - - /** - * Called when this adapter has been popped off the stack and is no longer needed. Subclasses - * can override to do any necessary cleanup. - */ - public void cleanup() {} -} diff --git a/android/support/car/drawer/CarDrawerController.java b/android/support/car/drawer/CarDrawerController.java deleted file mode 100644 index 4d9f4e99..00000000 --- a/android/support/car/drawer/CarDrawerController.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.car.drawer; - -import android.content.Context; -import android.content.res.Configuration; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.car.R; -import android.support.car.widget.PagedListView; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.widget.Toolbar; -import android.view.Gravity; -import android.view.MenuItem; -import android.view.View; -import android.widget.ProgressBar; - -import java.util.Stack; - -/** - * A controller that will handle the set up of the navigation drawer. It will hook up the - * necessary buttons for up navigation, as well as expose methods to allow for a drill down - * navigation. - */ -public class CarDrawerController { - /** The amount that the drawer has been opened before its color should be switched. */ - private static final float COLOR_SWITCH_SLIDE_OFFSET = 0.25f; - - /** - * A representation of the hierarchy of navigation being displayed in the list. The ordering of - * this stack is the order that the user has visited each level. When the user navigates up, - * the adapters are poopped from this list. - */ - private final Stack<CarDrawerAdapter> mAdapterStack = new Stack<>(); - - private final Context mContext; - - private final Toolbar mToolbar; - private final DrawerLayout mDrawerLayout; - private final ActionBarDrawerToggle mDrawerToggle; - - private final PagedListView mDrawerList; - private final ProgressBar mProgressBar; - private final View mDrawerContent; - - /** - * Creates a {@link CarDrawerController} that will control the navigation of the drawer given by - * {@code drawerLayout}. - * - * <p>The given {@code drawerLayout} should either have a child View that is inflated from - * {@code R.layout.car_drawer} or ensure that it three children that have the IDs found in that - * layout. - * - * @param toolbar The {@link Toolbar} that will serve as the action bar for an Activity. - * @param drawerLayout The top-level container for the window content that shows the - * interactive drawer. - * @param drawerToggle The {@link ActionBarDrawerToggle} that bridges the given {@code toolbar} - * and {@code drawerLayout}. - */ - public CarDrawerController(Toolbar toolbar, - DrawerLayout drawerLayout, - ActionBarDrawerToggle drawerToggle) { - mToolbar = toolbar; - mContext = drawerLayout.getContext(); - - mDrawerLayout = drawerLayout; - - mDrawerContent = drawerLayout.findViewById(R.id.drawer_content); - mDrawerList = drawerLayout.findViewById(R.id.drawer_list); - mDrawerList.setMaxPages(PagedListView.ItemCap.UNLIMITED); - - mProgressBar = drawerLayout.findViewById(R.id.drawer_progress); - - mDrawerToggle = drawerToggle; - setupDrawerToggling(); - } - - /** - * Sets the {@link CarDrawerAdapter} that will function as the root adapter. The contents of - * this root adapter are shown when the drawer is first opened. It is also the top-most level of - * navigation in the drawer. - * - * @param rootAdapter The adapter that will act as the root. If this value is {@code null}, then - * this method will do nothing. - */ - public void setRootAdapter(@Nullable CarDrawerAdapter rootAdapter) { - if (rootAdapter == null) { - return; - } - - mAdapterStack.push(rootAdapter); - setToolbarTitleFrom(rootAdapter); - mDrawerList.setAdapter(rootAdapter); - } - - /** - * Switches to use the given {@link CarDrawerAdapter} as the one to supply the list to display - * in the navigation drawer. The title will also be updated from the adapter. - * - * <p>This switch is treated as a navigation to the next level in the drawer. Navigation away - * from this level will pop the given adapter off and surface contents of the previous adapter - * that was set via this method. If no such adapter exists, then the root adapter set by - * {@link #setRootAdapter(CarDrawerAdapter)} will be used instead. - * - * @param adapter Adapter for next level of content in the drawer. - */ - public final void switchToAdapter(CarDrawerAdapter adapter) { - mAdapterStack.peek().setTitleChangeListener(null); - mAdapterStack.push(adapter); - switchToAdapterInternal(adapter); - } - - /** Close the drawer. */ - public void closeDrawer() { - if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) { - mDrawerLayout.closeDrawer(Gravity.LEFT); - } - } - - /** Opens the drawer. */ - public void openDrawer() { - if (!mDrawerLayout.isDrawerOpen(Gravity.LEFT)) { - mDrawerLayout.openDrawer(Gravity.LEFT); - } - } - - /** Sets a listener to be notified of Drawer events. */ - public void addDrawerListener(@NonNull DrawerLayout.DrawerListener listener) { - mDrawerLayout.addDrawerListener(listener); - } - - /** Removes a listener to be notified of Drawer events. */ - public void removeDrawerListener(@NonNull DrawerLayout.DrawerListener listener) { - mDrawerLayout.removeDrawerListener(listener); - } - - /** - * Sets whether the loading progress bar is displayed in the navigation drawer. If {@code true}, - * the progress bar is displayed and the navigation list is hidden and vice versa. - */ - public void showLoadingProgressBar(boolean show) { - mDrawerList.setVisibility(show ? View.INVISIBLE : View.VISIBLE); - mProgressBar.setVisibility(show ? View.VISIBLE : View.GONE); - } - - /** Scroll to given position in the list. */ - public void scrollToPosition(int position) { - mDrawerList.getRecyclerView().smoothScrollToPosition(position); - } - - /** - * Retrieves the title from the given {@link CarDrawerAdapter} and set its as the title of this - * controller's internal Toolbar. - */ - private void setToolbarTitleFrom(CarDrawerAdapter adapter) { - if (adapter.getTitle() == null) { - throw new RuntimeException("CarDrawerAdapter must supply a title via setTitle()"); - } - - mToolbar.setTitle(adapter.getTitle()); - adapter.setTitleChangeListener(mToolbar::setTitle); - } - - /** - * Sets up the necessary listeners for {@link DrawerLayout} so that the navigation drawer - * hierarchy is properly displayed. - */ - private void setupDrawerToggling() { - mDrawerLayout.addDrawerListener(mDrawerToggle); - mDrawerLayout.addDrawerListener( - new DrawerLayout.DrawerListener() { - @Override - public void onDrawerSlide(View drawerView, float slideOffset) { - // Correctly set the title and arrow colors as they are different between - // the open and close states. - updateTitleAndArrowColor(slideOffset >= COLOR_SWITCH_SLIDE_OFFSET); - } - - @Override - public void onDrawerClosed(View drawerView) { - // If drawer is closed, revert stack/drawer to initial root state. - cleanupStackAndShowRoot(); - scrollToPosition(0); - } - - @Override - public void onDrawerOpened(View drawerView) {} - - @Override - public void onDrawerStateChanged(int newState) {} - }); - } - - /** Sets the title and arrow color of the drawer depending on if it is open or not. */ - private void updateTitleAndArrowColor(boolean drawerOpen) { - // When the drawer is open, use car_title, which resolves to appropriate color depending on - // day-night mode. When drawer is closed, we always use light color. - int titleColorResId = drawerOpen ? R.color.car_title : R.color.car_title_light; - int titleColor = mContext.getColor(titleColorResId); - mToolbar.setTitleTextColor(titleColor); - mDrawerToggle.getDrawerArrowDrawable().setColor(titleColor); - } - - /** - * Synchronizes the display of the drawer with its linked {@link DrawerLayout}. - * - * <p>This should be called from the associated Activity's - * {@link android.support.v7.app.AppCompatActivity#onPostCreate(Bundle)} method to synchronize - * after teh DRawerLayout's instance state has been restored, and any other time when the - * state may have diverged in such a way that this controller's associated - * {@link ActionBarDrawerToggle} had not been notified. - */ - public void syncState() { - mDrawerToggle.syncState(); - - // In case we're restarting after a config change (e.g. day, night switch), set colors - // again. Doing it here so that Drawer state is fully synced and we know if its open or not. - // NOTE: isDrawerOpen must be passed the second child of the DrawerLayout. - updateTitleAndArrowColor(mDrawerLayout.isDrawerOpen(mDrawerContent)); - } - - /** - * Notify this controller that device configurations may have changed. - * - * <p>This method should be called from the associated Activity's - * {@code onConfigurationChanged()} method. - */ - public void onConfigurationChanged(Configuration newConfig) { - // Pass any configuration change to the drawer toggle. - mDrawerToggle.onConfigurationChanged(newConfig); - } - - /** - * An analog to an Activity's {@code onOptionsItemSelected()}. This method should be called - * when the Activity's method is called and will return {@code true} if the selection has - * been handled. - * - * @return {@code true} if the item processing was handled by this class. - */ - public boolean onOptionsItemSelected(MenuItem item) { - // Handle home-click and see if we can navigate up in the drawer. - if (item != null && item.getItemId() == android.R.id.home && maybeHandleUpClick()) { - return true; - } - - // DrawerToggle gets next chance to handle up-clicks (and any other clicks). - return mDrawerToggle.onOptionsItemSelected(item); - } - - /** - * Sets the navigation drawer's title to be the one supplied by the given adapter and updates - * the navigation drawer list with the adapter's contents. - */ - private void switchToAdapterInternal(CarDrawerAdapter adapter) { - setToolbarTitleFrom(adapter); - // NOTE: We don't use swapAdapter() since different levels in the Drawer may switch between - // car_drawer_list_item_normal, car_drawer_list_item_small and car_list_empty layouts. - mDrawerList.getRecyclerView().setAdapter(adapter); - scrollToPosition(0); - } - - /** - * Switches to the previous level in the drawer hierarchy if the current list being displayed - * is not the root adapter. This is analogous to a navigate up. - * - * @return {@code true} if a navigate up was possible and executed. {@code false} otherwise. - */ - private boolean maybeHandleUpClick() { - // Check if already at the root level. - if (mAdapterStack.size() <= 1) { - return false; - } - - CarDrawerAdapter adapter = mAdapterStack.pop(); - adapter.setTitleChangeListener(null); - adapter.cleanup(); - switchToAdapterInternal(mAdapterStack.peek()); - return true; - } - - /** Clears stack down to root adapter and switches to root adapter. */ - private void cleanupStackAndShowRoot() { - while (mAdapterStack.size() > 1) { - CarDrawerAdapter adapter = mAdapterStack.pop(); - adapter.setTitleChangeListener(null); - adapter.cleanup(); - } - switchToAdapterInternal(mAdapterStack.peek()); - } -} diff --git a/android/support/car/drawer/DrawerItemViewHolder.java b/android/support/car/drawer/DrawerItemViewHolder.java deleted file mode 100644 index d016b2de..00000000 --- a/android/support/car/drawer/DrawerItemViewHolder.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.car.drawer; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.car.R; -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -/** - * Re-usable {@link RecyclerView.ViewHolder} for displaying items in the - * {@link android.support.car.drawer.CarDrawerAdapter}. - */ -public class DrawerItemViewHolder extends RecyclerView.ViewHolder { - private final ImageView mIcon; - private final TextView mTitle; - private final TextView mText; - private final ImageView mEndIcon; - - DrawerItemViewHolder(View view) { - super(view); - mIcon = view.findViewById(R.id.icon); - if (mIcon == null) { - throw new IllegalArgumentException("Icon view cannot be null!"); - } - - mTitle = view.findViewById(R.id.title); - if (mTitle == null) { - throw new IllegalArgumentException("Title view cannot be null!"); - } - - // Next two are optional and may be null. - mText = view.findViewById(R.id.text); - mEndIcon = view.findViewById(R.id.end_icon); - } - - /** Returns the view that should be used to display the main icon. */ - @NonNull - public ImageView getIcon() { - return mIcon; - } - - /** Returns the view that will display the main title. */ - @NonNull - public TextView getTitle() { - return mTitle; - } - - /** Returns the view that is used for text that is smaller than the title text. */ - @Nullable - public TextView getText() { - return mText; - } - - /** Returns the icon that is displayed at the end of the view. */ - @Nullable - public ImageView getEndIcon() { - return mEndIcon; - } - - /** - * Sets the listener that will be notified when the view held by this ViewHolder has been - * clicked. Passing {@code null} will clear any previously set listeners. - */ - void setItemClickListener(@Nullable DrawerItemClickListener listener) { - itemView.setOnClickListener(listener != null - ? v -> listener.onItemClick(getAdapterPosition()) - : null); - } -} diff --git a/android/support/car/widget/PagedListView.java b/android/support/car/widget/PagedListView.java index 46527001..8527c659 100644 --- a/android/support/car/widget/PagedListView.java +++ b/android/support/car/widget/PagedListView.java @@ -27,6 +27,7 @@ import android.os.Handler; import android.support.annotation.IdRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.Px; import android.support.annotation.RestrictTo; import android.support.annotation.UiThread; import android.support.car.R; @@ -60,6 +61,7 @@ public class PagedListView extends FrameLayout { protected final CarLayoutManager mLayoutManager; protected final Handler mHandler = new Handler(); private final boolean mScrollBarEnabled; + private final boolean mRightGutterEnabled; private final PagedScrollBarView mScrollBarView; private int mRowsPerPage = -1; @@ -96,11 +98,6 @@ public class PagedListView extends FrameLayout { */ public interface ItemCap { /** - * A value to pass to {@link #setMaxItems(int)} that indicates there should be no limit. - */ - int UNLIMITED = -1; - - /** * Sets the maximum number of items available in the adapter. A value less than '0' means * the list should not be capped. */ @@ -142,6 +139,7 @@ public class PagedListView extends FrameLayout { } LayoutInflater.from(context).inflate(layoutId, this /*root*/, true /*attachToRoot*/); + FrameLayout maxWidthLayout = (FrameLayout) findViewById(R.id.recycler_view_container); TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.PagedListView, defStyleAttrs, defStyleRes); mRecyclerView = (CarRecyclerView) findViewById(R.id.recycler_view); @@ -158,16 +156,6 @@ public class PagedListView extends FrameLayout { mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 12); mRecyclerView.setItemAnimator(new CarItemAnimator(mLayoutManager)); - boolean offsetScrollBar = a.getBoolean(R.styleable.PagedListView_offsetScrollBar, false); - if (offsetScrollBar) { - MarginLayoutParams params = (MarginLayoutParams) mRecyclerView.getLayoutParams(); - params.setMarginStart(getResources().getDimensionPixelSize( - R.dimen.car_screen_margin_size)); - params.setMarginEnd( - a.getDimensionPixelSize(R.styleable.PagedListView_listEndMargin, 0)); - mRecyclerView.setLayoutParams(params); - } - if (a.getBoolean(R.styleable.PagedListView_showPagedListViewDivider, true)) { int dividerStartMargin = a.getDimensionPixelSize( R.styleable.PagedListView_dividerStartMargin, 0); @@ -211,20 +199,47 @@ public class PagedListView extends FrameLayout { } } }); - mScrollBarView.setVisibility(mScrollBarEnabled ? VISIBLE : GONE); - // Modify the layout the Scroll Bar is not visible. - if (!mScrollBarEnabled) { - MarginLayoutParams params = (MarginLayoutParams) mRecyclerView.getLayoutParams(); - params.setMarginStart(0); - mRecyclerView.setLayoutParams(params); + // Modify the layout if the Gutter or the Scroll Bar are not visible. + mRightGutterEnabled = a.getBoolean(R.styleable.PagedListView_rightGutterEnabled, false); + if (mRightGutterEnabled || !mScrollBarEnabled) { + FrameLayout.LayoutParams maxWidthLayoutLayoutParams = + (FrameLayout.LayoutParams) maxWidthLayout.getLayoutParams(); + if (mRightGutterEnabled) { + maxWidthLayoutLayoutParams.rightMargin = + getResources().getDimensionPixelSize(R.dimen.car_card_margin); + } + if (!mScrollBarEnabled) { + maxWidthLayoutLayoutParams.setMarginStart(0); + } + maxWidthLayout.setLayoutParams(maxWidthLayoutLayoutParams); } setDayNightStyle(DayNightStyle.AUTO); a.recycle(); } + /** + * Sets the starting and ending padding for each view in the list. + * + * @param start The start padding. + * @param end The end padding. + */ + public void setListViewStartEndPadding(@Px int start, @Px int end) { + int carCardMargin = getResources().getDimensionPixelSize(R.dimen.car_card_margin); + int startGutter = mScrollBarEnabled ? carCardMargin : 0; + int startPadding = Math.max(start - startGutter, 0); + int endGutter = mRightGutterEnabled ? carCardMargin : 0; + int endPadding = Math.max(end - endGutter, 0); + mRecyclerView.setPaddingRelative(startPadding, mRecyclerView.getPaddingTop(), + endPadding, mRecyclerView.getPaddingBottom()); + + // Since we're setting padding we'll need to set the clip to padding to the same + // value as clip children to ensure that the cards fly off the screen. + mRecyclerView.setClipToPadding(mRecyclerView.getClipChildren()); + } + @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); diff --git a/android/support/media/tv/BasePreviewProgram.java b/android/support/media/tv/BasePreviewProgram.java index 39c30140..1423d9d6 100644 --- a/android/support/media/tv/BasePreviewProgram.java +++ b/android/support/media/tv/BasePreviewProgram.java @@ -23,13 +23,14 @@ import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Build; -import android.support.annotation.IntDef; import android.support.annotation.RestrictTo; import android.support.media.tv.TvContractCompat.PreviewProgramColumns; +import android.support.media.tv.TvContractCompat.PreviewProgramColumns.AspectRatio; +import android.support.media.tv.TvContractCompat.PreviewProgramColumns.Availability; +import android.support.media.tv.TvContractCompat.PreviewProgramColumns.InteractionType; +import android.support.media.tv.TvContractCompat.PreviewProgramColumns.Type; import android.support.media.tv.TvContractCompat.PreviewPrograms; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.net.URISyntaxException; import java.text.SimpleDateFormat; import java.util.Date; @@ -54,89 +55,6 @@ public abstract class BasePreviewProgram extends BaseProgram { private static final int IS_LIVE = 1; private static final int IS_BROWSABLE = 1; - /** @hide */ - @IntDef({ - TYPE_UNKNOWN, - PreviewProgramColumns.TYPE_MOVIE, - PreviewProgramColumns.TYPE_TV_SERIES, - PreviewProgramColumns.TYPE_TV_SEASON, - PreviewProgramColumns.TYPE_TV_EPISODE, - PreviewProgramColumns.TYPE_CLIP, - PreviewProgramColumns.TYPE_EVENT, - PreviewProgramColumns.TYPE_CHANNEL, - PreviewProgramColumns.TYPE_TRACK, - PreviewProgramColumns.TYPE_ALBUM, - PreviewProgramColumns.TYPE_ARTIST, - PreviewProgramColumns.TYPE_PLAYLIST, - PreviewProgramColumns.TYPE_STATION, - PreviewProgramColumns.TYPE_GAME - }) - @Retention(RetentionPolicy.SOURCE) - @RestrictTo(LIBRARY_GROUP) - public @interface Type {} - - /** - * The unknown program type. - */ - private static final int TYPE_UNKNOWN = -1; - - /** @hide */ - @IntDef({ - ASPECT_RATIO_UNKNOWN, - PreviewProgramColumns.ASPECT_RATIO_16_9, - PreviewProgramColumns.ASPECT_RATIO_3_2, - PreviewProgramColumns.ASPECT_RATIO_4_3, - PreviewProgramColumns.ASPECT_RATIO_1_1, - PreviewProgramColumns.ASPECT_RATIO_2_3, - PreviewProgramColumns.ASPECT_RATIO_MOVIE_POSTER - }) - @Retention(RetentionPolicy.SOURCE) - @RestrictTo(LIBRARY_GROUP) - public @interface AspectRatio {} - - /** - * The aspect ratio for unknown aspect ratios. - */ - private static final int ASPECT_RATIO_UNKNOWN = -1; - - /** @hide */ - @IntDef({ - AVAILABILITY_UNKNOWN, - PreviewProgramColumns.AVAILABILITY_AVAILABLE, - PreviewProgramColumns.AVAILABILITY_FREE_WITH_SUBSCRIPTION, - PreviewProgramColumns.AVAILABILITY_PAID_CONTENT, - PreviewProgramColumns.AVAILABILITY_PURCHASED, - PreviewProgramColumns.AVAILABILITY_FREE - }) - @Retention(RetentionPolicy.SOURCE) - @RestrictTo(LIBRARY_GROUP) - public @interface Availability {} - - /** - * The unknown availability. - */ - private static final int AVAILABILITY_UNKNOWN = -1; - - /** @hide */ - @IntDef({ - INTERACTION_TYPE_UNKNOWN, - PreviewProgramColumns.INTERACTION_TYPE_VIEWS, - PreviewProgramColumns.INTERACTION_TYPE_LISTENS, - PreviewProgramColumns.INTERACTION_TYPE_FOLLOWERS, - PreviewProgramColumns.INTERACTION_TYPE_FANS, - PreviewProgramColumns.INTERACTION_TYPE_LIKES, - PreviewProgramColumns.INTERACTION_TYPE_THUMBS, - PreviewProgramColumns.INTERACTION_TYPE_VIEWERS, - }) - @Retention(RetentionPolicy.SOURCE) - @RestrictTo(LIBRARY_GROUP) - public @interface InteractionType {} - - /** - * The unknown interaction type. - */ - private static final int INTERACTION_TYPE_UNKNOWN = -1; - BasePreviewProgram(Builder builder) { super(builder); } @@ -209,7 +127,7 @@ public abstract class BasePreviewProgram extends BaseProgram { */ public @Type int getType() { Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_TYPE); - return i == null ? TYPE_UNKNOWN : i; + return i == null ? INVALID_INT_VALUE : i; } /** @@ -219,7 +137,7 @@ public abstract class BasePreviewProgram extends BaseProgram { */ public @AspectRatio int getPosterArtAspectRatio() { Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO); - return i == null ? ASPECT_RATIO_UNKNOWN : i; + return i == null ? INVALID_INT_VALUE : i; } /** @@ -229,7 +147,7 @@ public abstract class BasePreviewProgram extends BaseProgram { */ public @AspectRatio int getThumbnailAspectRatio() { Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO); - return i == null ? ASPECT_RATIO_UNKNOWN : i; + return i == null ? INVALID_INT_VALUE : i; } /** @@ -247,7 +165,7 @@ public abstract class BasePreviewProgram extends BaseProgram { */ public @Availability int getAvailability() { Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_AVAILABILITY); - return i == null ? AVAILABILITY_UNKNOWN : i; + return i == null ? INVALID_INT_VALUE : i; } /** @@ -298,7 +216,7 @@ public abstract class BasePreviewProgram extends BaseProgram { */ public @InteractionType int getInteractionType() { Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_INTERACTION_TYPE); - return i == null ? INTERACTION_TYPE_UNKNOWN : i; + return i == null ? INVALID_INT_VALUE : i; } /** diff --git a/android/support/media/tv/BaseProgram.java b/android/support/media/tv/BaseProgram.java index 23b5cf9c..e4ce9d1f 100644 --- a/android/support/media/tv/BaseProgram.java +++ b/android/support/media/tv/BaseProgram.java @@ -22,16 +22,13 @@ import android.database.Cursor; import android.media.tv.TvContentRating; import android.net.Uri; import android.os.Build; -import android.support.annotation.IntDef; import android.support.annotation.RestrictTo; import android.support.media.tv.TvContractCompat.BaseTvColumns; import android.support.media.tv.TvContractCompat.ProgramColumns; +import android.support.media.tv.TvContractCompat.ProgramColumns.ReviewRatingStyle; import android.support.media.tv.TvContractCompat.Programs; import android.support.media.tv.TvContractCompat.Programs.Genres.Genre; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Base class for derived classes that want to have common fields for programs defined in * {@link TvContractCompat}. @@ -49,22 +46,6 @@ public abstract class BaseProgram { private static final int IS_SEARCHABLE = 1; /** @hide */ - @IntDef({ - REVIEW_RATING_STYLE_UNKNOWN, - ProgramColumns.REVIEW_RATING_STYLE_STARS, - ProgramColumns.REVIEW_RATING_STYLE_THUMBS_UP_DOWN, - ProgramColumns.REVIEW_RATING_STYLE_PERCENTAGE, - }) - @Retention(RetentionPolicy.SOURCE) - @RestrictTo(LIBRARY_GROUP) - @interface ReviewRatingStyle {} - - /** - * The unknown review rating style. - */ - private static final int REVIEW_RATING_STYLE_UNKNOWN = -1; - - /** @hide */ @RestrictTo(LIBRARY_GROUP) protected ContentValues mValues; @@ -273,7 +254,7 @@ public abstract class BaseProgram { */ public @ReviewRatingStyle int getReviewRatingStyle() { Integer i = mValues.getAsInteger(Programs.COLUMN_REVIEW_RATING_STYLE); - return i == null ? REVIEW_RATING_STYLE_UNKNOWN : i; + return i == null ? INVALID_INT_VALUE : i; } /** diff --git a/android/support/media/tv/Program.java b/android/support/media/tv/Program.java index 233f1bab..4e3bd7ac 100644 --- a/android/support/media/tv/Program.java +++ b/android/support/media/tv/Program.java @@ -25,7 +25,6 @@ import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.RestrictTo; import android.support.media.tv.TvContractCompat.Programs; -import android.support.media.tv.TvContractCompat.Programs.Genres.Genre; /** * A convenience class to access {@link TvContractCompat.Programs} entries in the system content @@ -283,7 +282,7 @@ public final class Program extends BaseProgram implements Comparable<Program> { * @return This Builder object to allow for chaining of calls to builder methods. * @see Programs#COLUMN_BROADCAST_GENRE */ - public Builder setBroadcastGenres(@Genre String[] genres) { + public Builder setBroadcastGenres(String[] genres) { mValues.put(Programs.COLUMN_BROADCAST_GENRE, Programs.Genres.encode(genres)); return this; } diff --git a/android/support/media/tv/TvContractCompat.java b/android/support/media/tv/TvContractCompat.java index de4fd04f..5a46e791 100644 --- a/android/support/media/tv/TvContractCompat.java +++ b/android/support/media/tv/TvContractCompat.java @@ -30,6 +30,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.BaseColumns; +import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; @@ -605,6 +606,16 @@ public final class TvContractCompat { */ @RestrictTo(LIBRARY_GROUP) interface ProgramColumns { + /** @hide */ + @IntDef({ + REVIEW_RATING_STYLE_STARS, + REVIEW_RATING_STYLE_THUMBS_UP_DOWN, + REVIEW_RATING_STYLE_PERCENTAGE, + }) + @Retention(RetentionPolicy.SOURCE) + @RestrictTo(LIBRARY_GROUP) + @interface ReviewRatingStyle {} + /** * The review rating style for five star rating. * @@ -923,6 +934,27 @@ public final class TvContractCompat { */ @RestrictTo(LIBRARY_GROUP) public interface PreviewProgramColumns { + + /** @hide */ + @IntDef({ + TYPE_MOVIE, + TYPE_TV_SERIES, + TYPE_TV_SEASON, + TYPE_TV_EPISODE, + TYPE_CLIP, + TYPE_EVENT, + TYPE_CHANNEL, + TYPE_TRACK, + TYPE_ALBUM, + TYPE_ARTIST, + TYPE_PLAYLIST, + TYPE_STATION, + TYPE_GAME + }) + @Retention(RetentionPolicy.SOURCE) + @RestrictTo(LIBRARY_GROUP) + public @interface Type {} + /** * The program type for movie. * @@ -1014,6 +1046,19 @@ public final class TvContractCompat { */ int TYPE_GAME = 12; + /** @hide */ + @IntDef({ + ASPECT_RATIO_16_9, + ASPECT_RATIO_3_2, + ASPECT_RATIO_4_3, + ASPECT_RATIO_1_1, + ASPECT_RATIO_2_3, + ASPECT_RATIO_MOVIE_POSTER, + }) + @Retention(RetentionPolicy.SOURCE) + @RestrictTo(LIBRARY_GROUP) + public @interface AspectRatio {} + /** * The aspect ratio for 16:9. * @@ -1062,6 +1107,18 @@ public final class TvContractCompat { */ int ASPECT_RATIO_MOVIE_POSTER = 5; + /** @hide */ + @IntDef({ + AVAILABILITY_AVAILABLE, + AVAILABILITY_FREE_WITH_SUBSCRIPTION, + AVAILABILITY_PAID_CONTENT, + AVAILABILITY_PURCHASED, + AVAILABILITY_FREE, + }) + @Retention(RetentionPolicy.SOURCE) + @RestrictTo(LIBRARY_GROUP) + public @interface Availability {} + /** * The availability for "available to this user". * @@ -1098,6 +1155,20 @@ public final class TvContractCompat { */ int AVAILABILITY_FREE = 4; + /** @hide */ + @IntDef({ + INTERACTION_TYPE_VIEWS, + INTERACTION_TYPE_LISTENS, + INTERACTION_TYPE_FOLLOWERS, + INTERACTION_TYPE_FANS, + INTERACTION_TYPE_LIKES, + INTERACTION_TYPE_THUMBS, + INTERACTION_TYPE_VIEWERS, + }) + @Retention(RetentionPolicy.SOURCE) + @RestrictTo(LIBRARY_GROUP) + public @interface InteractionType {} + /** * The interaction type for "views". * @@ -2824,6 +2895,17 @@ public final class TvContractCompat { /** The MIME type of a single preview TV program. */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/watch_next_program"; + /** @hide */ + @IntDef({ + WATCH_NEXT_TYPE_CONTINUE, + WATCH_NEXT_TYPE_NEXT, + WATCH_NEXT_TYPE_NEW, + WATCH_NEXT_TYPE_WATCHLIST, + }) + @Retention(RetentionPolicy.SOURCE) + @RestrictTo(LIBRARY_GROUP) + public @interface WatchNextType {} + /** * The watch next type for CONTINUE. Use this type when the user has already watched more * than 1 minute of this content. diff --git a/android/support/media/tv/WatchNextProgram.java b/android/support/media/tv/WatchNextProgram.java index c192745c..f4665846 100644 --- a/android/support/media/tv/WatchNextProgram.java +++ b/android/support/media/tv/WatchNextProgram.java @@ -22,15 +22,12 @@ import android.content.ContentValues; import android.database.Cursor; import android.media.tv.TvContentRating; // For javadoc gen of super class import android.os.Build; -import android.support.annotation.IntDef; import android.support.annotation.RestrictTo; import android.support.media.tv.TvContractCompat.PreviewPrograms; // For javadoc gen of super class import android.support.media.tv.TvContractCompat.Programs; // For javadoc gen of super class import android.support.media.tv.TvContractCompat.Programs.Genres; // For javadoc gen of super class import android.support.media.tv.TvContractCompat.WatchNextPrograms; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +import android.support.media.tv.TvContractCompat.WatchNextPrograms.WatchNextType; /** * A convenience class to access {@link WatchNextPrograms} entries in the system content @@ -90,34 +87,16 @@ public final class WatchNextProgram extends BasePreviewProgram { private static final long INVALID_LONG_VALUE = -1; private static final int INVALID_INT_VALUE = -1; - /** @hide */ - @IntDef({ - WATCH_NEXT_TYPE_UNKNOWN, - WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE, - WatchNextPrograms.WATCH_NEXT_TYPE_NEXT, - WatchNextPrograms.WATCH_NEXT_TYPE_NEW, - WatchNextPrograms.WATCH_NEXT_TYPE_WATCHLIST, - }) - @Retention(RetentionPolicy.SOURCE) - @RestrictTo(LIBRARY_GROUP) - public @interface WatchNextType {} - - /** - * The unknown watch next type. Use this type when the actual type is not known. - */ - public static final int WATCH_NEXT_TYPE_UNKNOWN = -1; - private WatchNextProgram(Builder builder) { super(builder); } /** - * @return The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for the program, - * or {@link #WATCH_NEXT_TYPE_UNKNOWN} if it's unknown. + * @return The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for the program. */ public @WatchNextType int getWatchNextType() { Integer i = mValues.getAsInteger(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE); - return i == null ? WATCH_NEXT_TYPE_UNKNOWN : i; + return i == null ? INVALID_INT_VALUE : i; } /** diff --git a/android/support/mediacompat/testlib/IntentConstants.java b/android/support/mediacompat/testlib/IntentConstants.java index 57db43e7..bc35935e 100644 --- a/android/support/mediacompat/testlib/IntentConstants.java +++ b/android/support/mediacompat/testlib/IntentConstants.java @@ -22,8 +22,6 @@ package android.support.mediacompat.testlib; public class IntentConstants { public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD = "android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD"; - public static final String ACTION_CALL_MEDIA_SESSION_METHOD = - "android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD"; public static final String KEY_METHOD_ID = "method_id"; public static final String KEY_ARGUMENT = "argument"; } diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java deleted file mode 100644 index 82b5c59a..00000000 --- a/android/support/mediacompat/testlib/MediaSessionConstants.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.mediacompat.testlib; - -/** - * Constants for testing the media session and controller. - */ -public class MediaSessionConstants { - - // MediaSessionCompat methods. - public static final int SET_EXTRAS = 101; - public static final int SET_FLAGS = 102; - public static final int SET_METADATA = 103; - public static final int SET_PLAYBACK_STATE = 104; - public static final int SET_QUEUE = 105; - public static final int SET_QUEUE_TITLE = 106; - public static final int SET_SESSION_ACTIVITY = 107; - public static final int SET_CAPTIONING_ENABLED = 108; - public static final int SET_REPEAT_MODE = 109; - public static final int SET_SHUFFLE_MODE = 110; - public static final int SEND_SESSION_EVENT = 112; - public static final int SET_ACTIVE = 113; - public static final int RELEASE = 114; - public static final int SET_PLAYBACK_TO_LOCAL = 115; - public static final int SET_PLAYBACK_TO_REMOTE = 116; - public static final int SET_RATING_TYPE = 117; - - public static final String SERVICE_PACKAGE_NAME = "android.support.mediacompat.service.test"; - public static final String TEST_KEY = "test-key"; - public static final String TEST_VALUE = "test-val"; - public static final String TEST_SESSION_EVENT = "test-session-event"; - public static final int TEST_FLAGS = 5; - public static final int TEST_CURRENT_VOLUME = 10; - public static final int TEST_MAX_VOLUME = 11; - public static final long TEST_QUEUE_ID_1 = 10L; - public static final long TEST_QUEUE_ID_2 = 20L; - public static final String TEST_MEDIA_ID_1 = "media_id_1"; - public static final String TEST_MEDIA_ID_2 = "media_id_2"; - public static final long TEST_ACTION = 55L; - - public static final int TEST_ERROR_CODE = 0x3; - public static final String TEST_ERROR_MSG = "test-error-msg"; -} diff --git a/android/support/transition/AutoTransition.java b/android/support/transition/AutoTransition.java index bf39c3c3..02b49e26 100644 --- a/android/support/transition/AutoTransition.java +++ b/android/support/transition/AutoTransition.java @@ -45,9 +45,9 @@ public class AutoTransition extends TransitionSet { private void init() { setOrdering(ORDERING_SEQUENTIAL); - addTransition(new Fade(Fade.OUT)) - .addTransition(new ChangeBounds()) - .addTransition(new Fade(Fade.IN)); + addTransition(new Fade(Fade.OUT)). + addTransition(new ChangeBounds()). + addTransition(new Fade(Fade.IN)); } } diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java index af37f77a..81431972 100644 --- a/android/support/v17/leanback/widget/GridLayoutManager.java +++ b/android/support/v17/leanback/widget/GridLayoutManager.java @@ -2240,24 +2240,10 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { focusToViewInLayout(hadFocus, scrollToFocus, -deltaPrimary, -deltaSecondary); appendVisibleItems(); prependVisibleItems(); - // b/67370222: do not removeInvisibleViewsAtFront/End() in the loop, otherwise - // loop may bounce between scroll forward and scroll backward forever. Example: - // Assuming there are 19 items, child#18 and child#19 are both in RV, we are - // trying to focus to child#18 and there are 200px remaining scroll distance. - // 1 focusToViewInLayout() tries scroll forward 50 px to align focused child#18 on - // right edge, but there to compensate remaining scroll 200px, also scroll - // backward 200px, 150px pushes last child#19 out side of right edge. - // 2 removeInvisibleViewsAtEnd() remove last child#19, updateScrollLimits() - // invalidates scroll max - // 3 In next iteration, when scroll max/min is unknown, focusToViewInLayout() will - // align focused child#18 at center of screen. - // 4 Because #18 is aligned at center, appendVisibleItems() will fill child#19 to - // the right. - // 5 (back to 1 and loop forever) + removeInvisibleViewsAtFront(); + removeInvisibleViewsAtEnd(); } while (mGrid.getFirstVisibleIndex() != oldFirstVisible || mGrid.getLastVisibleIndex() != oldLastVisible); - removeInvisibleViewsAtFront(); - removeInvisibleViewsAtEnd(); if (state.willRunPredictiveAnimations()) { fillScrapViewsInPostLayout(); diff --git a/android/support/v4/content/res/ResourcesCompat.java b/android/support/v4/content/res/ResourcesCompat.java index 15b8ce9a..4c70ce93 100644 --- a/android/support/v4/content/res/ResourcesCompat.java +++ b/android/support/v4/content/res/ResourcesCompat.java @@ -307,11 +307,11 @@ public final class ResourcesCompat { */ @RestrictTo(LIBRARY_GROUP) public static Typeface getFont(@NonNull Context context, @FontRes int id, TypedValue value, - int style, @Nullable FontCallback fontCallback) throws NotFoundException { + int style) throws NotFoundException { if (context.isRestricted()) { return null; } - return loadFont(context, id, value, style, fontCallback, null /* handler */, + return loadFont(context, id, value, style, null /* callback */, null /* handler */, true /* isXmlRequest */); } diff --git a/android/support/v4/media/RatingCompat.java b/android/support/v4/media/RatingCompat.java index e70243f8..b538cac4 100644 --- a/android/support/v4/media/RatingCompat.java +++ b/android/support/v4/media/RatingCompat.java @@ -18,7 +18,6 @@ package android.support.v4.media; import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; -import android.media.Rating; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -327,25 +326,25 @@ public final class RatingCompat implements Parcelable { */ public static RatingCompat fromRating(Object ratingObj) { if (ratingObj != null && Build.VERSION.SDK_INT >= 19) { - final int ratingStyle = ((Rating) ratingObj).getRatingStyle(); + final int ratingStyle = RatingCompatKitkat.getRatingStyle(ratingObj); final RatingCompat rating; - if (((Rating) ratingObj).isRated()) { + if (RatingCompatKitkat.isRated(ratingObj)) { switch (ratingStyle) { case RATING_HEART: - rating = newHeartRating(((Rating) ratingObj).hasHeart()); + rating = newHeartRating(RatingCompatKitkat.hasHeart(ratingObj)); break; case RATING_THUMB_UP_DOWN: - rating = newThumbRating(((Rating) ratingObj).isThumbUp()); + rating = newThumbRating(RatingCompatKitkat.isThumbUp(ratingObj)); break; case RATING_3_STARS: case RATING_4_STARS: case RATING_5_STARS: rating = newStarRating(ratingStyle, - ((Rating) ratingObj).getStarRating()); + RatingCompatKitkat.getStarRating(ratingObj)); break; case RATING_PERCENTAGE: rating = newPercentageRating( - ((Rating) ratingObj).getPercentRating()); + RatingCompatKitkat.getPercentRating(ratingObj)); break; default: return null; @@ -373,25 +372,25 @@ public final class RatingCompat implements Parcelable { if (isRated()) { switch (mRatingStyle) { case RATING_HEART: - mRatingObj = Rating.newHeartRating(hasHeart()); + mRatingObj = RatingCompatKitkat.newHeartRating(hasHeart()); break; case RATING_THUMB_UP_DOWN: - mRatingObj = Rating.newThumbRating(isThumbUp()); + mRatingObj = RatingCompatKitkat.newThumbRating(isThumbUp()); break; case RATING_3_STARS: case RATING_4_STARS: case RATING_5_STARS: - mRatingObj = Rating.newStarRating(mRatingStyle, + mRatingObj = RatingCompatKitkat.newStarRating(mRatingStyle, getStarRating()); break; case RATING_PERCENTAGE: - mRatingObj = Rating.newPercentageRating(getPercentRating()); + mRatingObj = RatingCompatKitkat.newPercentageRating(getPercentRating()); break; default: return null; } } else { - mRatingObj = Rating.newUnratedRating(mRatingStyle); + mRatingObj = RatingCompatKitkat.newUnratedRating(mRatingStyle); } } return mRatingObj; diff --git a/android/support/v4/media/RatingCompatKitkat.java b/android/support/v4/media/RatingCompatKitkat.java new file mode 100644 index 00000000..1d3fa505 --- /dev/null +++ b/android/support/v4/media/RatingCompatKitkat.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.support.v4.media; + +import android.media.Rating; +import android.support.annotation.RequiresApi; + +@RequiresApi(19) +class RatingCompatKitkat { + public static Object newUnratedRating(int ratingStyle) { + return Rating.newUnratedRating(ratingStyle); + } + + public static Object newHeartRating(boolean hasHeart) { + return Rating.newHeartRating(hasHeart); + } + + public static Object newThumbRating(boolean thumbIsUp) { + return Rating.newThumbRating(thumbIsUp); + } + + public static Object newStarRating(int starRatingStyle, float starRating) { + return Rating.newStarRating(starRatingStyle, starRating); + } + + public static Object newPercentageRating(float percent) { + return Rating.newPercentageRating(percent); + } + + public static boolean isRated(Object ratingObj) { + return ((Rating)ratingObj).isRated(); + } + + public static int getRatingStyle(Object ratingObj) { + return ((Rating)ratingObj).getRatingStyle(); + } + + public static boolean hasHeart(Object ratingObj) { + return ((Rating)ratingObj).hasHeart(); + } + + public static boolean isThumbUp(Object ratingObj) { + return ((Rating)ratingObj).isThumbUp(); + } + + public static float getStarRating(Object ratingObj) { + return ((Rating)ratingObj).getStarRating(); + } + + public static float getPercentRating(Object ratingObj) { + return ((Rating)ratingObj).getPercentRating(); + } +} diff --git a/android/support/v4/provider/FontsContractCompat.java b/android/support/v4/provider/FontsContractCompat.java index 09261869..9ef1b0b0 100644 --- a/android/support/v4/provider/FontsContractCompat.java +++ b/android/support/v4/provider/FontsContractCompat.java @@ -303,9 +303,6 @@ public class FontsContractCompat { final ArrayList<ReplyCallback<TypefaceResult>> replies; synchronized (sLock) { replies = sPendingReplies.get(id); - if (replies == null) { - return; // Nobody requested replies. Do nothing. - } sPendingReplies.remove(id); } for (int i = 0; i < replies.size(); ++i) { diff --git a/android/support/v7/app/MediaRouteButton.java b/android/support/v7/app/MediaRouteButton.java index fdbcf9ad..d3f7020b 100644 --- a/android/support/v7/app/MediaRouteButton.java +++ b/android/support/v7/app/MediaRouteButton.java @@ -121,7 +121,8 @@ public class MediaRouteButton extends View { } public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) { - super(MediaRouterThemeHelper.createThemedButtonContext(context), attrs, defStyleAttr); + super(MediaRouterThemeHelper.createThemedContext(context, defStyleAttr), attrs, + defStyleAttr); context = getContext(); mRouter = MediaRouter.getInstance(context); diff --git a/android/support/v7/app/MediaRouteChooserDialog.java b/android/support/v7/app/MediaRouteChooserDialog.java index 17364efb..0ab2eb11 100644 --- a/android/support/v7/app/MediaRouteChooserDialog.java +++ b/android/support/v7/app/MediaRouteChooserDialog.java @@ -92,8 +92,10 @@ public class MediaRouteChooserDialog extends AppCompatDialog { } public MediaRouteChooserDialog(Context context, int theme) { - super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, false), - MediaRouterThemeHelper.createThemedDialogStyle(context)); + // If we pass theme ID of 0 to AppCompatDialog, it will apply dialogTheme on the context, + // which may override our style settings. Passes our uppermost theme ID to prevent this. + super(MediaRouterThemeHelper.createThemedContext(context, theme), + theme == 0 ? MediaRouterThemeHelper.createThemeForDialog(context, theme) : theme); context = getContext(); mRouter = MediaRouter.getInstance(context); diff --git a/android/support/v7/app/MediaRouteControllerDialog.java b/android/support/v7/app/MediaRouteControllerDialog.java index d89bf21e..4b9a17a3 100644 --- a/android/support/v7/app/MediaRouteControllerDialog.java +++ b/android/support/v7/app/MediaRouteControllerDialog.java @@ -201,8 +201,12 @@ public class MediaRouteControllerDialog extends AlertDialog { } public MediaRouteControllerDialog(Context context, int theme) { - super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, true), - MediaRouterThemeHelper.createThemedDialogStyle(context)); + // If we pass theme ID of 0 to AppCompatDialog, it will apply dialogTheme on the context, + // which may override our style settings. Passes our uppermost theme ID to prevent this. + super(MediaRouterThemeHelper.createThemedContext(context, + MediaRouterThemeHelper.getAlertDialogResolvedTheme(context, theme)), theme == 0 + ? MediaRouterThemeHelper.createThemeForDialog(context, MediaRouterThemeHelper + .getAlertDialogResolvedTheme(context, theme)) : theme); mContext = getContext(); mControllerCallback = new MediaControllerCallback(); diff --git a/android/support/v7/app/MediaRouterThemeHelper.java b/android/support/v7/app/MediaRouterThemeHelper.java index 69e40ac7..9ef218e0 100644 --- a/android/support/v7/app/MediaRouterThemeHelper.java +++ b/android/support/v7/app/MediaRouterThemeHelper.java @@ -42,76 +42,47 @@ final class MediaRouterThemeHelper { private MediaRouterThemeHelper() { } - static Context createThemedButtonContext(Context context) { - // Apply base Media Router theme. - context = new ContextThemeWrapper(context, getRouterThemeId(context)); - - // Apply custom Media Router theme. - int style = getThemeResource(context, R.attr.mediaRouteTheme); - if (style != 0) { - context = new ContextThemeWrapper(context, style); - } - - return context; - } - - /* - * The following two methods are to be used in conjunction. They should be used to prepare - * the context and theme for a super class constructor (the latter method relies on the - * former method to properly prepare the context): - * super(context = createThemedDialogContext(context, theme), - * createThemedDialogStyle(context)); + /** + * Creates a themed context based on the explicit style resource or the parent context's default + * theme. + * <p> + * The theme which will be applied on top of the parent {@code context}'s theme is determined + * by the primary color defined in the given {@code style}, or in the parent {@code context}. * - * It will apply theme in the following order (style lookups will be done in reverse): - * 1) Current theme - * 2) Supplied theme - * 3) Base Media Router theme - * 4) Custom Media Router theme, if provided + * @param context the parent context + * @param style the resource ID of the style against which to inflate this context, or + * {@code 0} to use the parent {@code context}'s default theme. + * @return The themed context. */ - static Context createThemedDialogContext(Context context, int theme, boolean alertDialog) { - // 1) Current theme is already applied to the context - - // 2) If no theme is supplied, look it up from the context (dialogTheme/alertDialogTheme) - if (theme == 0) { - theme = getThemeResource(context, !alertDialog - ? android.support.v7.appcompat.R.attr.dialogTheme - : android.support.v7.appcompat.R.attr.alertDialogTheme); - } - // Apply it - context = new ContextThemeWrapper(context, theme); - - // 3) If a custom Media Router theme is provided then apply the base theme - if (getThemeResource(context, R.attr.mediaRouteTheme) != 0) { - context = new ContextThemeWrapper(context, getRouterThemeId(context)); - } - - return context; + static Context createThemedContext(Context context, int style) { + // First, apply dialog property overlay. + Context themedContext = + new ContextThemeWrapper(context, getStyledRouterThemeId(context, style)); + int customizedThemeId = getThemeResource(context, R.attr.mediaRouteTheme); + return customizedThemeId == 0 ? themedContext + : new ContextThemeWrapper(themedContext, customizedThemeId); } - // This method should be used in conjunction with the previous method. - static int createThemedDialogStyle(Context context) { - // 4) Apply the custom Media Router theme - int theme = getThemeResource(context, R.attr.mediaRouteTheme); - if (theme == 0) { - // 3) No custom MediaRouther theme was provided so apply the base theme instead - theme = getRouterThemeId(context); - } - return theme; + /** + * Creates the theme resource ID intended to be used by dialogs. + */ + static int createThemeForDialog(Context context, int style) { + int customizedThemeId = getThemeResource(context, R.attr.mediaRouteTheme); + return customizedThemeId != 0 ? customizedThemeId : getStyledRouterThemeId(context, style); } - // END. Previous two methods should be used in conjunction. - static int getThemeResource(Context context, int attr) { + public static int getThemeResource(Context context, int attr) { TypedValue value = new TypedValue(); return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0; } - static float getDisabledAlpha(Context context) { + public static float getDisabledAlpha(Context context) { TypedValue value = new TypedValue(); return context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true) ? value.getFloat() : 0.5f; } - static @ControllerColorType int getControllerColor(Context context, int style) { + public static @ControllerColorType int getControllerColor(Context context, int style) { int primaryColor = getThemeColor(context, style, android.support.v7.appcompat.R.attr.colorPrimary); if (ColorUtils.calculateContrast(COLOR_WHITE_ON_DARK_BACKGROUND, primaryColor) @@ -121,7 +92,7 @@ final class MediaRouterThemeHelper { return COLOR_DARK_ON_LIGHT_BACKGROUND; } - static int getButtonTextColor(Context context) { + public static int getButtonTextColor(Context context) { int primaryColor = getThemeColor(context, 0, android.support.v7.appcompat.R.attr.colorPrimary); int backgroundColor = getThemeColor(context, 0, android.R.attr.colorBackground); @@ -133,7 +104,7 @@ final class MediaRouterThemeHelper { return primaryColor; } - static void setMediaControlsBackgroundColor( + public static void setMediaControlsBackgroundColor( Context context, View mainControls, View groupControls, boolean hasGroup) { int primaryColor = getThemeColor(context, 0, android.support.v7.appcompat.R.attr.colorPrimary); @@ -153,7 +124,7 @@ final class MediaRouterThemeHelper { groupControls.setTag(primaryDarkColor); } - static void setVolumeSliderColor( + public static void setVolumeSliderColor( Context context, MediaRouteVolumeSlider volumeSlider, View backgroundView) { int controllerColor = getControllerColor(context, 0); if (Color.alpha(controllerColor) != 0xFF) { @@ -165,10 +136,23 @@ final class MediaRouterThemeHelper { volumeSlider.setColor(controllerColor); } + // This is copied from {@link AlertDialog#resolveDialogTheme} to pre-evaluate theme in advance. + public static int getAlertDialogResolvedTheme(Context context, int themeResId) { + if (themeResId >= 0x01000000) { // start of real resource IDs. + return themeResId; + } else { + TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute( + android.support.v7.appcompat.R.attr.alertDialogTheme, outValue, true); + return outValue.resourceId; + } + } + private static boolean isLightTheme(Context context) { TypedValue value = new TypedValue(); - return context.getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.isLightTheme, - value, true) && value.data != 0; + return context.getTheme().resolveAttribute( + android.support.v7.appcompat.R.attr.isLightTheme, value, true) + && value.data != 0; } private static int getThemeColor(Context context, int style, int attr) { @@ -189,16 +173,16 @@ final class MediaRouterThemeHelper { return value.data; } - private static int getRouterThemeId(Context context) { + private static int getStyledRouterThemeId(Context context, int style) { int themeId; if (isLightTheme(context)) { - if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) { + if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) { themeId = R.style.Theme_MediaRouter_Light; } else { themeId = R.style.Theme_MediaRouter_Light_DarkControlPanel; } } else { - if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) { + if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) { themeId = R.style.Theme_MediaRouter_LightControlPanel; } else { themeId = R.style.Theme_MediaRouter; diff --git a/android/support/v7/util/DiffUtil.java b/android/support/v7/util/DiffUtil.java index ebc33f31..6302666f 100644 --- a/android/support/v7/util/DiffUtil.java +++ b/android/support/v7/util/DiffUtil.java @@ -16,7 +16,6 @@ package android.support.v7.util; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.v7.widget.RecyclerView; @@ -349,72 +348,6 @@ public class DiffUtil { } /** - * Callback for calculating the diff between two non-null items in a list. - * <p> - * {@link Callback} serves two roles - list indexing, and item diffing. ItemCallback handles - * just the second of these, which allows separation of code that indexes into an array or List - * from the presentation-layer and content specific diffing code. - * - * @param <T> Type of items to compare. - */ - public abstract static class ItemCallback<T> { - /** - * Called to check whether two objects represent the same item. - * <p> - * For example, if your items have unique ids, this method should check their id equality. - * - * @param oldItem The item in the old list. - * @param newItem The item in the new list. - * @return True if the two items represent the same object or false if they are different. - * - * @see Callback#areItemsTheSame(int, int) - */ - public abstract boolean areItemsTheSame(@NonNull T oldItem, @NonNull T newItem); - - /** - * Called to check whether two items have the same data. - * <p> - * This information is used to detect if the contents of an item have changed. - * <p> - * This method to check equality instead of {@link Object#equals(Object)} so that you can - * change its behavior depending on your UI. - * <p> - * For example, if you are using DiffUtil with a - * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should - * return whether the items' visual representations are the same. - * <p> - * This method is called only if {@link #areItemsTheSame(T, T)} returns {@code true} for - * these items. - * - * @param oldItem The item in the old list. - * @param newItem The item in the new list. - * @return True if the contents of the items are the same or false if they are different. - * - * @see Callback#areContentsTheSame(int, int) - */ - public abstract boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem); - - /** - * When {@link #areItemsTheSame(T, T)} returns {@code true} for two items and - * {@link #areContentsTheSame(T, T)} returns false for them, this method is called to - * get a payload about the change. - * <p> - * For example, if you are using DiffUtil with {@link RecyclerView}, you can return the - * particular field that changed in the item and your - * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that - * information to run the correct animation. - * <p> - * Default implementation returns {@code null}. - * - * @see Callback#getChangePayload(int, int) - */ - @SuppressWarnings({"WeakerAccess", "unused"}) - public Object getChangePayload(@NonNull T oldItem, @NonNull T newItem) { - return null; - } - } - - /** * Snakes represent a match between two lists. It is optionally prefixed or postfixed with an * add or remove operation. See the Myers' paper for details. */ diff --git a/android/support/v7/widget/AppCompatTextHelper.java b/android/support/v7/widget/AppCompatTextHelper.java index fa6196f5..51510aa2 100644 --- a/android/support/v7/widget/AppCompatTextHelper.java +++ b/android/support/v7/widget/AppCompatTextHelper.java @@ -29,7 +29,6 @@ import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.RequiresApi; import android.support.annotation.RestrictTo; -import android.support.v4.content.res.ResourcesCompat; import android.support.v4.widget.TextViewCompat; import android.support.v7.appcompat.R; import android.text.method.PasswordTransformationMethod; @@ -37,8 +36,6 @@ import android.util.AttributeSet; import android.util.TypedValue; import android.widget.TextView; -import java.lang.ref.WeakReference; - @RequiresApi(9) class AppCompatTextHelper { @@ -66,7 +63,6 @@ class AppCompatTextHelper { private int mStyle = Typeface.NORMAL; private Typeface mFontTypeface; - private boolean mAsyncFontPending; AppCompatTextHelper(TextView view) { mView = view; @@ -217,23 +213,8 @@ class AppCompatTextHelper { ? R.styleable.TextAppearance_android_fontFamily : R.styleable.TextAppearance_fontFamily; if (!context.isRestricted()) { - final WeakReference<TextView> textViewWeak = new WeakReference<>(mView); - ResourcesCompat.FontCallback replyCallback = new ResourcesCompat.FontCallback() { - @Override - public void onFontRetrieved(@NonNull Typeface typeface) { - onAsyncTypefaceReceived(textViewWeak, typeface); - } - - @Override - public void onFontRetrievalFailed(int reason) { - // Do nothing. - } - }; try { - // Note the callback will be triggered on the UI thread. - mFontTypeface = a.getFont(fontFamilyId, mStyle, replyCallback); - // If this call gave us an immediate result, ignore any pending callbacks. - mAsyncFontPending = mFontTypeface == null; + mFontTypeface = a.getFont(fontFamilyId, mStyle); } catch (UnsupportedOperationException | Resources.NotFoundException e) { // Expected if it is not a font resource. } @@ -241,16 +222,12 @@ class AppCompatTextHelper { if (mFontTypeface == null) { // Try with String. This is done by TextView JB+, but fails in ICS String fontFamilyName = a.getString(fontFamilyId); - if (fontFamilyName != null) { - mFontTypeface = Typeface.create(fontFamilyName, mStyle); - } + mFontTypeface = Typeface.create(fontFamilyName, mStyle); } return; } if (a.hasValue(R.styleable.TextAppearance_android_typeface)) { - // Ignore previous pending fonts - mAsyncFontPending = false; int typefaceIndex = a.getInt(R.styleable.TextAppearance_android_typeface, SANS); switch (typefaceIndex) { case SANS: @@ -268,16 +245,6 @@ class AppCompatTextHelper { } } - private void onAsyncTypefaceReceived(WeakReference<TextView> textViewWeak, Typeface typeface) { - if (mAsyncFontPending) { - mFontTypeface = typeface; - final TextView textView = textViewWeak.get(); - if (textView != null) { - textView.setTypeface(typeface, mStyle); - } - } - } - void onSetTextAppearance(Context context, int resId) { final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, resId, R.styleable.TextAppearance); diff --git a/android/support/v7/widget/TintTypedArray.java b/android/support/v7/widget/TintTypedArray.java index 384c4615..22709551 100644 --- a/android/support/v7/widget/TintTypedArray.java +++ b/android/support/v7/widget/TintTypedArray.java @@ -106,8 +106,7 @@ public class TintTypedArray { * not a font resource. */ @Nullable - public Typeface getFont(@StyleableRes int index, int style, - @Nullable ResourcesCompat.FontCallback fontCallback) { + public Typeface getFont(@StyleableRes int index, int style) { final int resourceId = mWrapped.getResourceId(index, 0); if (resourceId == 0) { return null; @@ -115,7 +114,7 @@ public class TintTypedArray { if (mTypedValue == null) { mTypedValue = new TypedValue(); } - return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style, fontCallback); + return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style); } public int length() { diff --git a/android/telephony/CarrierConfigManager.java b/android/telephony/CarrierConfigManager.java index de980b2f..689ce954 100644 --- a/android/telephony/CarrierConfigManager.java +++ b/android/telephony/CarrierConfigManager.java @@ -763,18 +763,6 @@ public class CarrierConfigManager { public static final String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int"; /** - * Some carriers will send call forwarding responses for voicemail in a format that is not 3gpp - * compliant, which causes issues during parsing. This causes the - * {@link com.android.internal.telephony.CallForwardInfo#number} to contain non-numerical - * characters instead of a number. - * - * If true, we will detect the non-numerical characters and replace them with "Voicemail". - * @hide - */ - public static final String KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL = - "call_forwarding_map_non_number_to_voicemail_bool"; - - /** * Determines whether conference calls are supported by a carrier. When {@code true}, * conference calling is supported, {@code false otherwise}. */ @@ -1585,25 +1573,6 @@ public class CarrierConfigManager { public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = "show_ims_registration_status_bool"; - /** - * The flag to disable the popup dialog which warns the user of data charges. - * @hide - */ - public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL = - "disable_charge_indication_bool"; - - /** - * Boolean indicating whether to skip the call forwarding (CF) fail-to-disable dialog. - * The logic used to determine whether we succeeded in disabling is carrier specific, - * so the dialog may not always be accurate. - * {@code false} - show CF fail-to-disable dialog. - * {@code true} - skip showing CF fail-to-disable dialog. - * - * @hide - */ - public static final String KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL = - "skip_cf_fail_to_disable_dialog_bool"; - /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -1734,7 +1703,6 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_GSM_DTMF_TONE_DELAY_INT, 0); sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0); sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100); - sDefaults.putBoolean(KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL, false); sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0); sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true); @@ -1758,7 +1726,6 @@ public class CarrierConfigManager { sDefaults.putString(KEY_CARRIER_NAME_STRING, ""); sDefaults.putBoolean(KEY_SUPPORT_DIRECT_FDN_DIALING_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_DEFAULT_DATA_ROAMING_ENABLED_BOOL, false); - sDefaults.putBoolean(KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL, false); // MMS defaults sDefaults.putBoolean(KEY_MMS_ALIAS_ENABLED_BOOL, false); @@ -1873,7 +1840,6 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null); sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null); sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false); - sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false); } /** diff --git a/android/telephony/MbmsDownloadSession.java b/android/telephony/MbmsDownloadSession.java index 9a9877a8..764b7b22 100644 --- a/android/telephony/MbmsDownloadSession.java +++ b/android/telephony/MbmsDownloadSession.java @@ -77,9 +77,8 @@ public class MbmsDownloadSession implements AutoCloseable { * Integer extra that Android will attach to the intent supplied via * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)} * Indicates the result code of the download. One of - * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED}, - * {@link #RESULT_IO_ERROR}, {@link #RESULT_DOWNLOAD_FAILURE}, {@link #RESULT_OUT_OF_STORAGE}, - * {@link #RESULT_SERVICE_ID_NOT_DEFINED}, or {@link #RESULT_FILE_ROOT_UNREACHABLE}. + * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED}, or + * {@link #RESULT_IO_ERROR}. * * This extra may also be used by the middleware when it is sending intents to the app. */ @@ -143,41 +142,11 @@ public class MbmsDownloadSession implements AutoCloseable { /** * Indicates that the download will not be completed due to an I/O error incurred while - * writing to temp files. - * - * This is likely a transient error and another {@link DownloadRequest} should be sent to try - * the download again. + * writing to temp files. This commonly indicates that the device is out of storage space, + * but may indicate other conditions as well (such as an SD card being removed). */ public static final int RESULT_IO_ERROR = 4; - - /** - * Indicates that the Service ID specified in the {@link DownloadRequest} is incorrect due to - * the Id being incorrect, stale, expired, or similar. - */ - public static final int RESULT_SERVICE_ID_NOT_DEFINED = 5; - - /** - * Indicates that there was an error while processing downloaded files, such as a file repair or - * file decoding error and is not due to a file I/O error. - * - * This is likely a transient error and another {@link DownloadRequest} should be sent to try - * the download again. - */ - public static final int RESULT_DOWNLOAD_FAILURE = 6; - - /** - * Indicates that the file system is full and the {@link DownloadRequest} can not complete. - * Either space must be made on the current file system or the temp file root location must be - * changed to a location that is not full to download the temp files. - */ - public static final int RESULT_OUT_OF_STORAGE = 7; - - /** - * Indicates that the file root that was set is currently unreachable. This can happen if the - * temp files are set to be stored on external storage and the SD card was removed, for example. - * The temp file root should be changed before sending another DownloadRequest. - */ - public static final int RESULT_FILE_ROOT_UNREACHABLE = 8; + // TODO - more results! /** @hide */ @Retention(RetentionPolicy.SOURCE) diff --git a/android/telephony/NetworkScanRequest.java b/android/telephony/NetworkScanRequest.java index 9674c930..d2aef200 100644 --- a/android/telephony/NetworkScanRequest.java +++ b/android/telephony/NetworkScanRequest.java @@ -19,7 +19,6 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; -import java.util.ArrayList; import java.util.Arrays; /** @@ -39,20 +38,6 @@ public final class NetworkScanRequest implements Parcelable { public static final int MAX_BANDS = 8; /** @hide */ public static final int MAX_CHANNELS = 32; - /** @hide */ - public static final int MAX_MCC_MNC_LIST_SIZE = 20; - /** @hide */ - public static final int MIN_SEARCH_PERIODICITY_SEC = 5; - /** @hide */ - public static final int MAX_SEARCH_PERIODICITY_SEC = 300; - /** @hide */ - public static final int MIN_SEARCH_MAX_SEC = 60; - /** @hide */ - public static final int MAX_SEARCH_MAX_SEC = 3600; - /** @hide */ - public static final int MIN_INCREMENTAL_PERIODICITY_SEC = 1; - /** @hide */ - public static final int MAX_INCREMENTAL_PERIODICITY_SEC = 10; /** Performs the scan only once */ public static final int SCAN_TYPE_ONE_SHOT = 0; @@ -61,84 +46,24 @@ public final class NetworkScanRequest implements Parcelable { * * The modem will start new scans periodically, and the interval between two scans is usually * multiple minutes. - */ + * */ public static final int SCAN_TYPE_PERIODIC = 1; /** Defines the type of the scan. */ public int scanType; - /** - * Search periodicity (in seconds). - * Expected range for the input is [5s - 300s] - * This value must be less than or equal to maxSearchTime - */ - public int searchPeriodicity; - - /** - * Maximum duration of the periodic search (in seconds). - * Expected range for the input is [60s - 3600s] - * If the search lasts this long, it will be terminated. - */ - public int maxSearchTime; - - /** - * Indicates whether the modem should report incremental - * results of the network scan to the client. - * FALSE – Incremental results are not reported. - * TRUE (default) – Incremental results are reported - */ - public boolean incrementalResults; - - /** - * Indicates the periodicity with which the modem should - * report incremental results to the client (in seconds). - * Expected range for the input is [1s - 10s] - * This value must be less than or equal to maxSearchTime - */ - public int incrementalResultsPeriodicity; - /** Describes the radio access technologies with bands or channels that need to be scanned. */ public RadioAccessSpecifier[] specifiers; /** - * Describes the List of PLMN ids (MCC-MNC) - * If any PLMN of this list is found, search should end at that point and - * results with all PLMN found till that point should be sent as response. - * If list not sent, search to be completed till end and all PLMNs found to be reported. - * Max size of array is MAX_MCC_MNC_LIST_SIZE - */ - public ArrayList<String> mccMncs; - - /** * Creates a new NetworkScanRequest with scanType and network specifiers * * @param scanType The type of the scan * @param specifiers the radio network with bands / channels to be scanned - * @param searchPeriodicity Search periodicity (in seconds) - * @param maxSearchTime Maximum duration of the periodic search (in seconds) - * @param incrementalResults Indicates whether the modem should report incremental - * results of the network scan to the client - * @param incrementalResultsPeriodicity Indicates the periodicity with which the modem should - * report incremental results to the client (in seconds) - * @param mccMncs Describes the List of PLMN ids (MCC-MNC) */ - public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers, - int searchPeriodicity, - int maxSearchTime, - boolean incrementalResults, - int incrementalResultsPeriodicity, - ArrayList<String> mccMncs) { + public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers) { this.scanType = scanType; this.specifiers = specifiers; - this.searchPeriodicity = searchPeriodicity; - this.maxSearchTime = maxSearchTime; - this.incrementalResults = incrementalResults; - this.incrementalResultsPeriodicity = incrementalResultsPeriodicity; - if (mccMncs != null) { - this.mccMncs = mccMncs; - } else { - this.mccMncs = new ArrayList<>(); - } } @Override @@ -150,11 +75,6 @@ public final class NetworkScanRequest implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(scanType); dest.writeParcelableArray(specifiers, flags); - dest.writeInt(searchPeriodicity); - dest.writeInt(maxSearchTime); - dest.writeBoolean(incrementalResults); - dest.writeInt(incrementalResultsPeriodicity); - dest.writeStringList(mccMncs); } private NetworkScanRequest(Parcel in) { @@ -162,12 +82,6 @@ public final class NetworkScanRequest implements Parcelable { specifiers = (RadioAccessSpecifier[]) in.readParcelableArray( Object.class.getClassLoader(), RadioAccessSpecifier.class); - searchPeriodicity = in.readInt(); - maxSearchTime = in.readInt(); - incrementalResults = in.readBoolean(); - incrementalResultsPeriodicity = in.readInt(); - mccMncs = new ArrayList<>(); - in.readStringList(mccMncs); } @Override @@ -185,24 +99,13 @@ public final class NetworkScanRequest implements Parcelable { } return (scanType == nsr.scanType - && Arrays.equals(specifiers, nsr.specifiers) - && searchPeriodicity == nsr.searchPeriodicity - && maxSearchTime == nsr.maxSearchTime - && incrementalResults == nsr.incrementalResults - && incrementalResultsPeriodicity == nsr.incrementalResultsPeriodicity - && (((mccMncs != null) - && mccMncs.equals(nsr.mccMncs)))); + && Arrays.equals(specifiers, nsr.specifiers)); } @Override public int hashCode () { return ((scanType * 31) - + (Arrays.hashCode(specifiers)) * 37 - + (searchPeriodicity * 41) - + (maxSearchTime * 43) - + ((incrementalResults == true? 1 : 0) * 47) - + (incrementalResultsPeriodicity * 53) - + (mccMncs.hashCode() * 59)); + + (Arrays.hashCode(specifiers)) * 37); } public static final Creator<NetworkScanRequest> CREATOR = diff --git a/android/telephony/ServiceState.java b/android/telephony/ServiceState.java index 116e711e..e448fb2a 100644 --- a/android/telephony/ServiceState.java +++ b/android/telephony/ServiceState.java @@ -1197,6 +1197,15 @@ public class ServiceState implements Parcelable { } } + /** + * @Deprecated to be removed Q3 2013 use {@link #getVoiceNetworkType} + * @hide + */ + public int getNetworkType() { + Rlog.e(LOG_TAG, "ServiceState.getNetworkType() DEPRECATED will be removed *******"); + return rilRadioTechnologyToNetworkType(mRilVoiceRadioTechnology); + } + /** @hide */ public int getDataNetworkType() { return rilRadioTechnologyToNetworkType(mRilDataRadioTechnology); diff --git a/android/telephony/mbms/DownloadRequest.java b/android/telephony/mbms/DownloadRequest.java index f0d60b68..5a57f322 100644 --- a/android/telephony/mbms/DownloadRequest.java +++ b/android/telephony/mbms/DownloadRequest.java @@ -16,7 +16,6 @@ package android.telephony.mbms; -import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.Intent; import android.net.Uri; @@ -27,6 +26,7 @@ import android.util.Log; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -71,19 +71,6 @@ public final class DownloadRequest implements Parcelable { private String appIntent; private int version = CURRENT_VERSION; - - /** - * Builds a new DownloadRequest. - * @param sourceUri the source URI for the DownloadRequest to be built. This URI should - * never be null. - */ - public Builder(@NonNull Uri sourceUri) { - if (sourceUri == null) { - throw new IllegalArgumentException("Source URI must be non-null."); - } - source = sourceUri; - } - /** * Sets the service from which the download request to be built will download from. * @param serviceInfo @@ -105,6 +92,15 @@ public final class DownloadRequest implements Parcelable { } /** + * Sets the source URI for the download request to be built. + * @param source + */ + public Builder setSource(Uri source) { + this.source = source; + return this; + } + + /** * Set the subscription ID on which the file(s) should be downloaded. * @param subscriptionId */ @@ -320,11 +316,9 @@ public final class DownloadRequest implements Parcelable { throw new RuntimeException("Could not get sha256 hash object"); } if (version >= 1) { - // Hash the source URI and the app intent + // Hash the source URI, destination URI, and the app intent digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8)); - if (serializedResultIntentForApp != null) { - digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8)); - } + digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8)); } // Add updates for future versions here return Base64.encodeToString(digest.digest(), Base64.URL_SAFE | Base64.NO_WRAP); diff --git a/android/telephony/mbms/MbmsDownloadReceiver.java b/android/telephony/mbms/MbmsDownloadReceiver.java index 9af1eb9e..fe275372 100644 --- a/android/telephony/mbms/MbmsDownloadReceiver.java +++ b/android/telephony/mbms/MbmsDownloadReceiver.java @@ -287,7 +287,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { return; } - List<Uri> tempFiles = intent.getParcelableArrayListExtra(VendorUtils.EXTRA_TEMP_LIST); + List<Uri> tempFiles = intent.getParcelableExtra(VendorUtils.EXTRA_TEMP_LIST); if (tempFiles == null) { return; } @@ -309,7 +309,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { return; } int fdCount = intent.getIntExtra(VendorUtils.EXTRA_FD_COUNT, 0); - List<Uri> pausedList = intent.getParcelableArrayListExtra(VendorUtils.EXTRA_PAUSED_LIST); + List<Uri> pausedList = intent.getParcelableExtra(VendorUtils.EXTRA_PAUSED_LIST); if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) { Log.i(LOG_TAG, "No temp files actually requested. Ending."); @@ -492,14 +492,9 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } catch (PackageManager.NameNotFoundException e) { throw new RuntimeException("Package manager couldn't find " + context.getPackageName()); } - if (appInfo.metaData == null) { - throw new RuntimeException("App must declare the file provider authority as metadata " + - "in the manifest."); - } String authority = appInfo.metaData.getString(MBMS_FILE_PROVIDER_META_DATA_KEY); if (authority == null) { - throw new RuntimeException("App must declare the file provider authority as metadata " + - "in the manifest."); + throw new RuntimeException("Must declare the file provider authority as meta data"); } return authority; } diff --git a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java index c3b2c482..2f85a1df 100644 --- a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java +++ b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java @@ -113,10 +113,6 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { @Override public final int initialize(final int subscriptionId, final IMbmsDownloadSessionCallback callback) throws RemoteException { - if (callback == null) { - throw new NullPointerException("Callback must not be null"); - } - final int uid = Binder.getCallingUid(); callback.asBinder().linkToDeath(new DeathRecipient() { @Override @@ -244,13 +240,6 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { public final int registerStateCallback(final DownloadRequest downloadRequest, final IDownloadStateCallback callback, int flags) throws RemoteException { final int uid = Binder.getCallingUid(); - if (downloadRequest == null) { - throw new NullPointerException("Download request must not be null"); - } - if (callback == null) { - throw new NullPointerException("Callback must not be null"); - } - DeathRecipient deathRecipient = new DeathRecipient() { @Override public void binderDied() { @@ -303,13 +292,6 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { public final int unregisterStateCallback( final DownloadRequest downloadRequest, final IDownloadStateCallback callback) throws RemoteException { - if (downloadRequest == null) { - throw new NullPointerException("Download request must not be null"); - } - if (callback == null) { - throw new NullPointerException("Callback must not be null"); - } - DeathRecipient deathRecipient = mDownloadCallbackDeathRecipients.remove(callback.asBinder()); if (deathRecipient == null) { diff --git a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java index 65b726df..f8f370a5 100644 --- a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java +++ b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java @@ -65,10 +65,6 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { @Override public final int initialize(final IMbmsStreamingSessionCallback callback, final int subscriptionId) throws RemoteException { - if (callback == null) { - throw new NullPointerException("Callback must not be null"); - } - final int uid = Binder.getCallingUid(); callback.asBinder().linkToDeath(new DeathRecipient() { @Override @@ -156,10 +152,6 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { @Override public int startStreaming(final int subscriptionId, String serviceId, final IStreamingServiceCallback callback) throws RemoteException { - if (callback == null) { - throw new NullPointerException("Callback must not be null"); - } - final int uid = Binder.getCallingUid(); callback.asBinder().linkToDeath(new DeathRecipient() { @Override diff --git a/android/text/BoringLayoutCreateDrawPerfTest.java b/android/text/BoringLayoutCreateDrawPerfTest.java index 586c3852..47dd257b 100644 --- a/android/text/BoringLayoutCreateDrawPerfTest.java +++ b/android/text/BoringLayoutCreateDrawPerfTest.java @@ -46,7 +46,7 @@ public class BoringLayoutCreateDrawPerfTest { private static final float SPACING_ADD = 10f; private static final float SPACING_MULT = 1.5f; - @Parameterized.Parameters(name = "cached={3},{1}chars,{0}") + @Parameterized.Parameters(name = "cached={3},{1} chars,{0}") public static Collection cases() { final List<Object[]> params = new ArrayList<>(); for (int length : new int[]{128}) { diff --git a/android/text/BoringLayoutIsBoringPerfTest.java b/android/text/BoringLayoutIsBoringPerfTest.java index 9d11f295..34de65de 100644 --- a/android/text/BoringLayoutIsBoringPerfTest.java +++ b/android/text/BoringLayoutIsBoringPerfTest.java @@ -40,7 +40,7 @@ public class BoringLayoutIsBoringPerfTest { private static final boolean[] BOOLEANS = new boolean[]{false, true}; - @Parameterized.Parameters(name = "cached={4},{1}chars,{0}") + @Parameterized.Parameters(name = "cached={4},{1} chars,{0}") public static Collection cases() { final List<Object[]> params = new ArrayList<>(); for (int length : new int[]{128}) { diff --git a/android/text/DynamicLayout.java b/android/text/DynamicLayout.java index fba358cf..24260c4f 100644 --- a/android/text/DynamicLayout.java +++ b/android/text/DynamicLayout.java @@ -299,7 +299,7 @@ public class DynamicLayout extends Layout private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); - private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3); + private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3); } /** @@ -440,7 +440,7 @@ public class DynamicLayout extends Layout mEllipsizeAt = null; } - mObjects = new PackedObjectVector<>(1); + mObjects = new PackedObjectVector<Directions>(1); // Initial state is a single line with 0 characters (0 to 0), with top at 0 and bottom at // whatever is natural, and undefined ellipsis. @@ -1050,7 +1050,7 @@ public class DynamicLayout extends Layout private static class ChangeWatcher implements TextWatcher, SpanWatcher { public ChangeWatcher(DynamicLayout layout) { - mLayout = new WeakReference<>(layout); + mLayout = new WeakReference<DynamicLayout>(layout); } private void reflow(CharSequence s, int where, int before, int after) { diff --git a/android/text/Hyphenator.java b/android/text/Hyphenator.java index 4f1488e1..ad26f23a 100644 --- a/android/text/Hyphenator.java +++ b/android/text/Hyphenator.java @@ -16,15 +16,258 @@ package android.text; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.HashMap; +import java.util.Locale; + /** - * Hyphenator just initializes the native implementation of automatic hyphenation, + * Hyphenator is a wrapper class for a native implementation of automatic hyphenation, * in essence finding valid hyphenation opportunities in a word. * * @hide */ public class Hyphenator { - public static void init() { - nInit(); + private static String TAG = "Hyphenator"; + + private final static Object sLock = new Object(); + + @GuardedBy("sLock") + final static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>(); + + private final long mNativePtr; + private final HyphenationData mData; + + private Hyphenator(long nativePtr, HyphenationData data) { + mNativePtr = nativePtr; + mData = data; + } + + public long getNativePtr() { + return mNativePtr; } - private static native void nInit(); + + public static Hyphenator get(@Nullable Locale locale) { + synchronized (sLock) { + Hyphenator result = sMap.get(locale); + if (result != null) { + return result; + } + + // If there's a variant, fall back to language+variant only, if available + final String variant = locale.getVariant(); + if (!variant.isEmpty()) { + final Locale languageAndVariantOnlyLocale = + new Locale(locale.getLanguage(), "", variant); + result = sMap.get(languageAndVariantOnlyLocale); + if (result != null) { + return putAlias(locale, result); + } + } + + // Fall back to language-only, if available + final Locale languageOnlyLocale = new Locale(locale.getLanguage()); + result = sMap.get(languageOnlyLocale); + if (result != null) { + return putAlias(locale, result); + } + + // Fall back to script-only, if available + final String script = locale.getScript(); + if (!script.equals("")) { + final Locale scriptOnlyLocale = new Locale.Builder() + .setLanguage("und") + .setScript(script) + .build(); + result = sMap.get(scriptOnlyLocale); + if (result != null) { + return putAlias(locale, result); + } + } + + return putEmptyAlias(locale); + } + } + + private static class HyphenationData { + private static final String SYSTEM_HYPHENATOR_LOCATION = "/system/usr/hyphen-data"; + + public final int mMinPrefix, mMinSuffix; + public final long mDataAddress; + + // Reasonable enough values for cases where we have no hyphenation patterns but may be able + // to do some automatic hyphenation based on characters. These values would be used very + // rarely. + private static final int DEFAULT_MIN_PREFIX = 2; + private static final int DEFAULT_MIN_SUFFIX = 2; + + public static final HyphenationData sEmptyData = + new HyphenationData(DEFAULT_MIN_PREFIX, DEFAULT_MIN_SUFFIX); + + // Create empty HyphenationData. + private HyphenationData(int minPrefix, int minSuffix) { + mMinPrefix = minPrefix; + mMinSuffix = minSuffix; + mDataAddress = 0; + } + + HyphenationData(String languageTag, int minPrefix, int minSuffix) { + mMinPrefix = minPrefix; + mMinSuffix = minSuffix; + + final String patternFilename = "hyph-" + languageTag.toLowerCase(Locale.US) + ".hyb"; + final File patternFile = new File(SYSTEM_HYPHENATOR_LOCATION, patternFilename); + if (!patternFile.canRead()) { + Log.e(TAG, "hyphenation patterns for " + patternFile + " not found or unreadable"); + mDataAddress = 0; + } else { + long address; + try (RandomAccessFile f = new RandomAccessFile(patternFile, "r")) { + address = Os.mmap(0, f.length(), OsConstants.PROT_READ, + OsConstants.MAP_SHARED, f.getFD(), 0 /* offset */); + } catch (IOException | ErrnoException e) { + Log.e(TAG, "error loading hyphenation " + patternFile, e); + address = 0; + } + mDataAddress = address; + } + } + } + + // Do not call this method outside of init method. + private static Hyphenator putNewHyphenator(Locale loc, HyphenationData data) { + final Hyphenator hyphenator = new Hyphenator(nBuildHyphenator( + data.mDataAddress, loc.getLanguage(), data.mMinPrefix, data.mMinSuffix), data); + sMap.put(loc, hyphenator); + return hyphenator; + } + + // Do not call this method outside of init method. + private static void loadData(String langTag, int minPrefix, int maxPrefix) { + final HyphenationData data = new HyphenationData(langTag, minPrefix, maxPrefix); + putNewHyphenator(Locale.forLanguageTag(langTag), data); + } + + // Caller must acquire sLock before calling this method. + // The Hyphenator for the baseLangTag must exists. + private static Hyphenator addAliasByTag(String langTag, String baseLangTag) { + return putAlias(Locale.forLanguageTag(langTag), + sMap.get(Locale.forLanguageTag(baseLangTag))); + } + + // Caller must acquire sLock before calling this method. + private static Hyphenator putAlias(Locale locale, Hyphenator base) { + return putNewHyphenator(locale, base.mData); + } + + // Caller must acquire sLock before calling this method. + private static Hyphenator putEmptyAlias(Locale locale) { + return putNewHyphenator(locale, HyphenationData.sEmptyData); + } + + // TODO: Confirm that these are the best values. Various sources suggest (1, 1), but + // that appears too small. + private static final int INDIC_MIN_PREFIX = 2; + private static final int INDIC_MIN_SUFFIX = 2; + + /** + * Load hyphenation patterns at initialization time. We want to have patterns + * for all locales loaded and ready to use so we don't have to do any file IO + * on the UI thread when drawing text in different locales. + * + * @hide + */ + public static void init() { + synchronized (sLock) { + sMap.put(null, null); + + loadData("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese + loadData("bg", 2, 2); // Bulgarian + loadData("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Bengali + loadData("cu", 1, 2); // Church Slavonic + loadData("cy", 2, 3); // Welsh + loadData("da", 2, 2); // Danish + loadData("de-1901", 2, 2); // German 1901 orthography + loadData("de-1996", 2, 2); // German 1996 orthography + loadData("de-CH-1901", 2, 2); // Swiss High German 1901 orthography + loadData("en-GB", 2, 3); // British English + loadData("en-US", 2, 3); // American English + loadData("es", 2, 2); // Spanish + loadData("et", 2, 3); // Estonian + loadData("eu", 2, 2); // Basque + loadData("fr", 2, 3); // French + loadData("ga", 2, 3); // Irish + loadData("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati + loadData("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi + loadData("hr", 2, 2); // Croatian + loadData("hu", 2, 2); // Hungarian + // texhyphen sources say Armenian may be (1, 2); but that it needs confirmation. + // Going with a more conservative value of (2, 2) for now. + loadData("hy", 2, 2); // Armenian + loadData("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada + loadData("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam + loadData("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script + loadData("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi + loadData("nb", 2, 2); // Norwegian BokmÃ¥l + loadData("nn", 2, 2); // Norwegian Nynorsk + loadData("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya + loadData("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi + loadData("pt", 2, 3); // Portuguese + loadData("sl", 2, 2); // Slovenian + loadData("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil + loadData("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu + loadData("tk", 2, 2); // Turkmen + loadData("und-Ethi", 1, 1); // Any language in Ethiopic script + + // English locales that fall back to en-US. The data is + // from CLDR. It's all English locales, minus the locales whose + // parent is en-001 (from supplementalData.xml, under <parentLocales>). + // TODO: Figure out how to get this from ICU. + addAliasByTag("en-AS", "en-US"); // English (American Samoa) + addAliasByTag("en-GU", "en-US"); // English (Guam) + addAliasByTag("en-MH", "en-US"); // English (Marshall Islands) + addAliasByTag("en-MP", "en-US"); // English (Northern Mariana Islands) + addAliasByTag("en-PR", "en-US"); // English (Puerto Rico) + addAliasByTag("en-UM", "en-US"); // English (United States Minor Outlying Islands) + addAliasByTag("en-VI", "en-US"); // English (Virgin Islands) + + // All English locales other than those falling back to en-US are mapped to en-GB. + addAliasByTag("en", "en-GB"); + + // For German, we're assuming the 1996 (and later) orthography by default. + addAliasByTag("de", "de-1996"); + // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography. + addAliasByTag("de-LI-1901", "de-CH-1901"); + + // Norwegian is very probably Norwegian BokmÃ¥l. + addAliasByTag("no", "nb"); + + // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl. + addAliasByTag("mn", "mn-Cyrl"); // Mongolian + + // Fall back to Ethiopic script for languages likely to be written in Ethiopic. + // Data is from CLDR's likelySubtags.xml. + // TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags(). + addAliasByTag("am", "und-Ethi"); // Amharic + addAliasByTag("byn", "und-Ethi"); // Blin + addAliasByTag("gez", "und-Ethi"); // GeÊ»ez + addAliasByTag("ti", "und-Ethi"); // Tigrinya + addAliasByTag("wal", "und-Ethi"); // Wolaytta + } + }; + + private static native long nBuildHyphenator(/* non-zero */ long dataAddress, + @NonNull String langTag, @IntRange(from = 1) int minPrefix, + @IntRange(from = 1) int minSuffix); } diff --git a/android/text/Layout.java b/android/text/Layout.java index ac5c2e92..60fff738 100644 --- a/android/text/Layout.java +++ b/android/text/Layout.java @@ -319,6 +319,8 @@ public abstract class Layout { private float getJustifyWidth(int lineNum) { Alignment paraAlign = mAlignment; + TabStops tabStops = null; + boolean tabStopsIsInitialized = false; int left = 0; int right = mWidth; @@ -369,6 +371,10 @@ public abstract class Layout { } } + if (getLineContainsTab(lineNum)) { + tabStops = new TabStops(TAB_INCREMENT, spans); + } + final Alignment align; if (paraAlign == Alignment.ALIGN_LEFT) { align = (dir == DIR_LEFT_TO_RIGHT) ? Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE; @@ -1417,6 +1423,7 @@ public abstract class Layout { float dist = Math.abs(getHorizontal(max, primary) - horiz); if (dist <= bestdist) { + bestdist = dist; best = max; } @@ -1563,7 +1570,7 @@ public abstract class Layout { // XXX: we don't care about tabs tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null); caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft); - TextLine.recycle(tl); + tl = TextLine.recycle(tl); return caret; } @@ -1887,7 +1894,10 @@ public abstract class Layout { int margin = 0; - boolean useFirstLineMargin = lineStart == 0 || spanned.charAt(lineStart - 1) == '\n'; + boolean isFirstParaLine = lineStart == 0 || + spanned.charAt(lineStart - 1) == '\n'; + + boolean useFirstLineMargin = isFirstParaLine; for (int i = 0; i < spans.length; i++) { if (spans[i] instanceof LeadingMarginSpan2) { int spStart = spanned.getSpanStart(spans[i]); diff --git a/android/text/PaintMeasureDrawPerfTest.java b/android/text/PaintMeasureDrawPerfTest.java index 67687985..00b60add 100644 --- a/android/text/PaintMeasureDrawPerfTest.java +++ b/android/text/PaintMeasureDrawPerfTest.java @@ -42,7 +42,7 @@ public class PaintMeasureDrawPerfTest { private static final boolean[] BOOLEANS = new boolean[]{false, true}; - @Parameterized.Parameters(name = "cached={1},{0}chars") + @Parameterized.Parameters(name = "cached={1},{0} chars") public static Collection cases() { final List<Object[]> params = new ArrayList<>(); for (int length : new int[]{128}) { diff --git a/android/text/StaticLayout.java b/android/text/StaticLayout.java index 5c60188d..961cd8ee 100644 --- a/android/text/StaticLayout.java +++ b/android/text/StaticLayout.java @@ -21,18 +21,21 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Paint; +import android.os.LocaleList; import android.text.style.LeadingMarginSpan; import android.text.style.LeadingMarginSpan.LeadingMarginSpan2; import android.text.style.LineHeightSpan; import android.text.style.MetricAffectingSpan; import android.text.style.TabStopSpan; import android.util.Log; +import android.util.Pair; import android.util.Pools.SynchronizedPool; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; import java.util.Arrays; +import java.util.Locale; /** * StaticLayout is a Layout for text that will not be edited after it @@ -98,6 +101,7 @@ public class StaticLayout extends Layout { b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; + b.mLocales = null; b.mMeasuredText = MeasuredText.obtain(); return b; @@ -114,6 +118,7 @@ public class StaticLayout extends Layout { b.mMeasuredText = null; b.mLeftIndents = null; b.mRightIndents = null; + b.mLocales = null; b.mLeftPaddings = null; b.mRightPaddings = null; nFinishBuilder(b.mNativePtr); @@ -404,6 +409,17 @@ public class StaticLayout extends Layout { return this; } + @NonNull + private long[] getHyphenators(@NonNull LocaleList locales) { + final int length = locales.size(); + final long[] result = new long[length]; + for (int i = 0; i < length; i++) { + final Locale locale = locales.get(i); + result[i] = Hyphenator.get(locale).getNativePtr(); + } + return result; + } + /** * Measurement and break iteration is done in native code. The protocol for using * the native code is as follows. @@ -417,17 +433,35 @@ public class StaticLayout extends Layout { * + addStyleRun (a text run, to be measured in native code) * + addReplacementRun (a replacement run, width is given) * + * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis). * Run nComputeLineBreaks() to obtain line breaks for the paragraph. * * After all paragraphs, call finish() to release expensive buffers. */ + private Pair<String, long[]> getLocaleAndHyphenatorIfChanged(TextPaint paint) { + final LocaleList locales = paint.getTextLocales(); + final String languageTags; + long[] hyphenators; + if (!locales.equals(mLocales)) { + mLocales = locales; + return new Pair(locales.toLanguageTags(), getHyphenators(locales)); + } else { + // passing null means keep current locale. + // TODO: move locale change detection to native. + return new Pair(null, null); + } + } + /* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) { - nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl); + Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint); + nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl, locHyph.first, + locHyph.second); } /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) { - nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width); + Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint); + nAddReplacementRun(mNativePtr, start, end, width, locHyph.first, locHyph.second); } /** @@ -485,7 +519,9 @@ public class StaticLayout extends Layout { // This will go away and be subsumed by native builder code private MeasuredText mMeasuredText; - private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3); + private LocaleList mLocales; + + private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3); } public StaticLayout(CharSequence source, TextPaint paint, @@ -774,6 +810,9 @@ public class StaticLayout extends Layout { } } + // TODO: Move locale tracking code to native. + b.mLocales = null; // Reset the locale tracking. + nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart, firstWidth, firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency, @@ -827,9 +866,10 @@ public class StaticLayout extends Layout { spanEndCacheCount++; } + nGetWidths(b.mNativePtr, widths); int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks, lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags, - lineBreaks.breaks.length, widths); + lineBreaks.breaks.length); final int[] breaks = lineBreaks.breaks; final float[] lineWidths = lineBreaks.widths; @@ -907,10 +947,10 @@ public class StaticLayout extends Layout { boolean moreChars = (endPos < bufEnd); final int ascent = fallbackLineSpacing - ? Math.min(fmAscent, Math.round(ascents[breakIndex])) + ? Math.min(fmAscent, (int) Math.round(ascents[breakIndex])) : fmAscent; final int descent = fallbackLineSpacing - ? Math.max(fmDescent, Math.round(descents[breakIndex])) + ? Math.max(fmDescent, (int) Math.round(descents[breakIndex])) : fmDescent; v = out(source, here, endPos, ascent, descent, fmTop, fmBottom, @@ -1137,7 +1177,7 @@ public class StaticLayout extends Layout { mWorkPaint.set(paint); do { final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths, - widthStart, tempAvail, where, line, mWorkPaint, forceEllipsis, dir); + widthStart, tempAvail, where, line, textWidth, mWorkPaint, forceEllipsis, dir); if (ellipsizedWidth <= avail) { lineFits = true; } else { @@ -1167,7 +1207,7 @@ public class StaticLayout extends Layout { // This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it // should not be accessed while the method is running. private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths, - int widthStart, float avail, TextUtils.TruncateAt where, int line, + int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth, TextPaint paint, boolean forceEllipsis, int dir) { final int savedHyphenEdit = paint.getHyphenEdit(); paint.setHyphenEdit(0); @@ -1501,28 +1541,26 @@ public class StaticLayout extends Layout { @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings, @IntRange(from = 0) int indentsOffset); - // TODO: Make this method CriticalNative once native code defers doing layouts. private static native void nAddStyleRun( /* non-zero */ long nativePtr, /* non-zero */ long nativePaint, - @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl); + @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl, + @Nullable String languageTags, @Nullable long[] hyphenators); - // TODO: Make this method CriticalNative once native code defers doing layouts. - private static native void nAddReplacementRun( - /* non-zero */ long nativePtr, /* non-zero */ long nativePaint, + private static native void nAddReplacementRun(/* non-zero */ long nativePtr, @IntRange(from = 0) int start, @IntRange(from = 0) int end, - @FloatRange(from = 0.0f) float width); + @FloatRange(from = 0.0f) float width, @Nullable String languageTags, + @Nullable long[] hyphenators); + + private static native void nGetWidths(long nativePtr, float[] widths); // populates LineBreaks and returns the number of breaks found // // the arrays inside the LineBreaks objects are passed in as well // to reduce the number of JNI calls in the common case where the // arrays do not have to be resized - // The individual character widths will be returned in charWidths. The length of charWidths must - // be at least the length of the text. private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle, int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents, - float[] recycleDescents, int[] recycleFlags, int recycleLength, - float[] charWidths); + float[] recycleDescents, int[] recycleFlags, int recycleLength); private int mLineCount; private int mTopPadding, mBottomPadding; diff --git a/android/text/StaticLayoutCreateDrawPerfTest.java b/android/text/StaticLayoutCreateDrawPerfTest.java index bfdb7589..356e2e0d 100644 --- a/android/text/StaticLayoutCreateDrawPerfTest.java +++ b/android/text/StaticLayoutCreateDrawPerfTest.java @@ -50,7 +50,7 @@ public class StaticLayoutCreateDrawPerfTest { @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - @Parameterized.Parameters(name = "cached={3},{1}chars,{0}") + @Parameterized.Parameters(name = "cached={3},{1} chars,{0}") public static Collection cases() { final List<Object[]> params = new ArrayList<>(); for (int length : new int[]{128}) { diff --git a/android/text/StaticLayout_Delegate.java b/android/text/StaticLayout_Delegate.java index def3c91c..63337f08 100644 --- a/android/text/StaticLayout_Delegate.java +++ b/android/text/StaticLayout_Delegate.java @@ -13,6 +13,7 @@ import android.icu.util.ULocale; import android.text.Primitive.PrimitiveType; import android.text.StaticLayout.LineBreaks; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -71,11 +72,13 @@ public class StaticLayout_Delegate { @LayoutlibDelegate /*package*/ static void nAddStyleRun(long nativeBuilder, long nativePaint, int start, - int end, boolean isRtl) { + int end, boolean isRtl, String languageTags, long[] hyphenators) { Builder builder = sBuilderManager.getDelegate(nativeBuilder); if (builder == null) { return; } + builder.mLocales = languageTags; + builder.mNativeHyphenators = hyphenators; int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR; measureText(nativePaint, builder.mText, start, end - start, builder.mWidths, @@ -83,20 +86,30 @@ public class StaticLayout_Delegate { } @LayoutlibDelegate - /*package*/ static void nAddReplacementRun(long nativeBuilder, long nativePaint, int start, - int end, float width) { + /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width, + String languageTags, long[] hyphenators) { Builder builder = sBuilderManager.getDelegate(nativeBuilder); if (builder == null) { return; } + builder.mLocales = languageTags; + builder.mNativeHyphenators = hyphenators; builder.mWidths[start] = width; Arrays.fill(builder.mWidths, start + 1, end, 0.0f); } @LayoutlibDelegate + /*package*/ static void nGetWidths(long nativeBuilder, float[] floatsArray) { + Builder builder = sBuilderManager.getDelegate(nativeBuilder); + if (builder != null) { + System.arraycopy(builder.mWidths, 0, floatsArray, 0, builder.mWidths.length); + } + } + + @LayoutlibDelegate /*package*/ static int nComputeLineBreaks(long nativeBuilder, LineBreaks recycle, int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents, - float[] recycleDescents, int[] recycleFlags, int recycleLength, float[] charWidths) { + float[] recycleDescents, int[] recycleFlags, int recycleLength) { Builder builder = sBuilderManager.getDelegate(nativeBuilder); if (builder == null) { @@ -105,7 +118,7 @@ public class StaticLayout_Delegate { // compute all possible breakpoints. int length = builder.mWidths.length; - BreakIterator it = BreakIterator.getLineInstance(); + BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocales)); it.setText(new Segment(builder.mText, 0, length)); // average word length in english is 5. So, initialize the possible breaks with a guess. @@ -136,7 +149,6 @@ public class StaticLayout_Delegate { builder.mTabStopCalculator); } builder.mLineBreaker.computeBreaks(recycle); - System.arraycopy(builder.mWidths, 0, charWidths, 0, builder.mWidths.length); return recycle.breaks.length; } @@ -194,9 +206,11 @@ public class StaticLayout_Delegate { * Java representation of the native Builder class. */ private static class Builder { + String mLocales; char[] mText; float[] mWidths; LineBreaker mLineBreaker; + long[] mNativeHyphenators; int mBreakStrategy; LineWidth mLineWidth; TabStops mTabStopCalculator; diff --git a/android/text/TextLine.java b/android/text/TextLine.java index 20c0ed87..2dbff100 100644 --- a/android/text/TextLine.java +++ b/android/text/TextLine.java @@ -73,7 +73,7 @@ class TextLine { new SpanSet<ReplacementSpan>(ReplacementSpan.class); private final DecorationInfo mDecorationInfo = new DecorationInfo(); - private final ArrayList<DecorationInfo> mDecorations = new ArrayList<>(); + private final ArrayList<DecorationInfo> mDecorations = new ArrayList(); private static final TextLine[] sCached = new TextLine[3]; @@ -340,14 +340,14 @@ class TextLine { boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; if (inSegment && advance) { - return h + measureRun(segstart, offset, j, runIsRtl, fmi); + return h += measureRun(segstart, offset, j, runIsRtl, fmi); } float w = measureRun(segstart, j, j, runIsRtl, fmi); h += advance ? w : -w; if (inSegment) { - return h + measureRun(segstart, offset, j, runIsRtl, null); + return h += measureRun(segstart, offset, j, runIsRtl, null); } if (codept == '\t') { @@ -828,14 +828,14 @@ class TextLine { } if (info.isUnderlineText) { final float thickness = - Math.max(wp.getUnderlineThickness(), 1.0f); + Math.max(((Paint) wp).getUnderlineThickness(), 1.0f); drawStroke(wp, c, wp.getColor(), wp.getUnderlinePosition(), thickness, decorationXLeft, decorationXRight, y); } if (info.isStrikeThruText) { final float thickness = - Math.max(wp.getStrikeThruThickness(), 1.0f); + Math.max(((Paint) wp).getStrikeThruThickness(), 1.0f); drawStroke(wp, c, wp.getColor(), wp.getStrikeThruPosition(), thickness, decorationXLeft, decorationXRight, y); } diff --git a/android/text/TextViewSetTextMeasurePerfTest.java b/android/text/TextViewSetTextMeasurePerfTest.java index ff2d57ed..a2bf33e1 100644 --- a/android/text/TextViewSetTextMeasurePerfTest.java +++ b/android/text/TextViewSetTextMeasurePerfTest.java @@ -40,7 +40,7 @@ import java.util.Locale; import java.util.Random; /** - * Performance test for {@link TextView} measure/draw. + * Performance test for multi line, single style {@link StaticLayout} creation/draw. */ @LargeTest @RunWith(Parameterized.class) @@ -51,7 +51,7 @@ public class TextViewSetTextMeasurePerfTest { @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - @Parameterized.Parameters(name = "cached={3},{1}chars,{0}") + @Parameterized.Parameters(name = "cached={3},{1} chars,{0}") public static Collection cases() { final List<Object[]> params = new ArrayList<>(); for (int length : new int[]{128}) { diff --git a/android/util/Log.java b/android/util/Log.java index b94e48b3..02998653 100644 --- a/android/util/Log.java +++ b/android/util/Log.java @@ -16,12 +16,45 @@ package android.util; +import android.os.DeadSystemException; + +import com.android.internal.os.RuntimeInit; +import com.android.internal.util.FastPrintWriter; +import com.android.internal.util.LineBreakBufferedWriter; + import java.io.PrintWriter; import java.io.StringWriter; +import java.io.Writer; import java.net.UnknownHostException; /** - * Mock Log implementation for testing on non android host. + * API for sending log output. + * + * <p>Generally, you should use the {@link #v Log.v()}, {@link #d Log.d()}, + * {@link #i Log.i()}, {@link #w Log.w()}, and {@link #e Log.e()} methods to write logs. + * You can then <a href="{@docRoot}studio/debug/am-logcat.html">view the logs in logcat</a>. + * + * <p>The order in terms of verbosity, from least to most is + * ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled + * into an application except during development. Debug logs are compiled + * in but stripped at runtime. Error, warning and info logs are always kept. + * + * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant + * in your class: + * + * <pre>private static final String TAG = "MyActivity";</pre> + * + * and use that in subsequent calls to the log methods. + * </p> + * + * <p><b>Tip:</b> Don't forget that when you make a call like + * <pre>Log.v(TAG, "index=" + i);</pre> + * that when you're building the string to pass into Log.d, the compiler uses a + * StringBuilder and at least three allocations occur: the StringBuilder + * itself, the buffer, and the String object. Realistically, there is also + * another buffer allocation and copy, and even more pressure on the gc. + * That means that if your log message is filtered out, you might be doing + * significant work and incurring significant overhead. */ public final class Log { @@ -55,6 +88,29 @@ public final class Log { */ public static final int ASSERT = 7; + /** + * Exception class used to capture a stack trace in {@link #wtf}. + * @hide + */ + public static class TerribleFailure extends Exception { + TerribleFailure(String msg, Throwable cause) { super(msg, cause); } + } + + /** + * Interface to handle terrible failures from {@link #wtf}. + * + * @hide + */ + public interface TerribleFailureHandler { + void onTerribleFailure(String tag, TerribleFailure what, boolean system); + } + + private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() { + public void onTerribleFailure(String tag, TerribleFailure what, boolean system) { + RuntimeInit.wtf(tag, what, system); + } + }; + private Log() { } @@ -65,7 +121,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int v(String tag, String msg) { - return println(LOG_ID_MAIN, VERBOSE, tag, msg); + return println_native(LOG_ID_MAIN, VERBOSE, tag, msg); } /** @@ -76,7 +132,7 @@ public final class Log { * @param tr An exception to log */ public static int v(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr); } /** @@ -86,7 +142,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int d(String tag, String msg) { - return println(LOG_ID_MAIN, DEBUG, tag, msg); + return println_native(LOG_ID_MAIN, DEBUG, tag, msg); } /** @@ -97,7 +153,7 @@ public final class Log { * @param tr An exception to log */ public static int d(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr); } /** @@ -107,7 +163,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int i(String tag, String msg) { - return println(LOG_ID_MAIN, INFO, tag, msg); + return println_native(LOG_ID_MAIN, INFO, tag, msg); } /** @@ -118,7 +174,7 @@ public final class Log { * @param tr An exception to log */ public static int i(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, INFO, tag, msg, tr); } /** @@ -128,7 +184,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int w(String tag, String msg) { - return println(LOG_ID_MAIN, WARN, tag, msg); + return println_native(LOG_ID_MAIN, WARN, tag, msg); } /** @@ -139,9 +195,31 @@ public final class Log { * @param tr An exception to log */ public static int w(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, WARN, tag, msg, tr); } + /** + * Checks to see whether or not a log for the specified tag is loggable at the specified level. + * + * The default level of any tag is set to INFO. This means that any level above and including + * INFO will be logged. Before you make any calls to a logging method you should check to see + * if your tag should be logged. You can change the default level by setting a system property: + * 'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>' + * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will + * turn off all logging for your tag. You can also create a local.prop file that with the + * following in it: + * 'log.tag.<YOUR_LOG_TAG>=<LEVEL>' + * and place that in /data/local.prop. + * + * @param tag The tag to check. + * @param level The level to check. + * @return Whether or not that this is allowed to be logged. + * @throws IllegalArgumentException is thrown if the tag.length() > 23 + * for Nougat (7.0) releases (API <= 23) and prior, there is no + * tag limit of concern after this API level. + */ + public static native boolean isLoggable(String tag, int level); + /* * Send a {@link #WARN} log message and log the exception. * @param tag Used to identify the source of a log message. It usually identifies @@ -149,7 +227,7 @@ public final class Log { * @param tr An exception to log */ public static int w(String tag, Throwable tr) { - return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, WARN, tag, "", tr); } /** @@ -159,7 +237,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int e(String tag, String msg) { - return println(LOG_ID_MAIN, ERROR, tag, msg); + return println_native(LOG_ID_MAIN, ERROR, tag, msg); } /** @@ -170,7 +248,82 @@ public final class Log { * @param tr An exception to log */ public static int e(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr); + } + + /** + * What a Terrible Failure: Report a condition that should never happen. + * The error will always be logged at level ASSERT with the call stack. + * Depending on system configuration, a report may be added to the + * {@link android.os.DropBoxManager} and/or the process may be terminated + * immediately with an error dialog. + * @param tag Used to identify the source of a log message. + * @param msg The message you would like logged. + */ + public static int wtf(String tag, String msg) { + return wtf(LOG_ID_MAIN, tag, msg, null, false, false); + } + + /** + * Like {@link #wtf(String, String)}, but also writes to the log the full + * call stack. + * @hide + */ + public static int wtfStack(String tag, String msg) { + return wtf(LOG_ID_MAIN, tag, msg, null, true, false); + } + + /** + * What a Terrible Failure: Report an exception that should never happen. + * Similar to {@link #wtf(String, String)}, with an exception to log. + * @param tag Used to identify the source of a log message. + * @param tr An exception to log. + */ + public static int wtf(String tag, Throwable tr) { + return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false); + } + + /** + * What a Terrible Failure: Report an exception that should never happen. + * Similar to {@link #wtf(String, Throwable)}, with a message as well. + * @param tag Used to identify the source of a log message. + * @param msg The message you would like logged. + * @param tr An exception to log. May be null. + */ + public static int wtf(String tag, String msg, Throwable tr) { + return wtf(LOG_ID_MAIN, tag, msg, tr, false, false); + } + + static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack, + boolean system) { + TerribleFailure what = new TerribleFailure(msg, tr); + // Only mark this as ERROR, do not use ASSERT since that should be + // reserved for cases where the system is guaranteed to abort. + // The onTerribleFailure call does not always cause a crash. + int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr); + sWtfHandler.onTerribleFailure(tag, what, system); + return bytes; + } + + static void wtfQuiet(int logId, String tag, String msg, boolean system) { + TerribleFailure what = new TerribleFailure(msg, null); + sWtfHandler.onTerribleFailure(tag, what, system); + } + + /** + * Sets the terrible failure handler, for testing. + * + * @return the old handler + * + * @hide + */ + public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) { + if (handler == null) { + throw new NullPointerException("handler == null"); + } + TerribleFailureHandler oldHandler = sWtfHandler; + sWtfHandler = handler; + return oldHandler; } /** @@ -193,7 +346,7 @@ public final class Log { } StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); + PrintWriter pw = new FastPrintWriter(sw, false, 256); tr.printStackTrace(pw); pw.flush(); return sw.toString(); @@ -208,7 +361,7 @@ public final class Log { * @return The number of bytes written. */ public static int println(int priority, String tag, String msg) { - return println(LOG_ID_MAIN, priority, tag, msg); + return println_native(LOG_ID_MAIN, priority, tag, msg); } /** @hide */ public static final int LOG_ID_MAIN = 0; @@ -217,9 +370,115 @@ public final class Log { /** @hide */ public static final int LOG_ID_SYSTEM = 3; /** @hide */ public static final int LOG_ID_CRASH = 4; - /** @hide */ @SuppressWarnings("unused") - public static int println(int bufID, - int priority, String tag, String msg) { - return 0; + /** @hide */ public static native int println_native(int bufID, + int priority, String tag, String msg); + + /** + * Return the maximum payload the log daemon accepts without truncation. + * @return LOGGER_ENTRY_MAX_PAYLOAD. + */ + private static native int logger_entry_max_payload_native(); + + /** + * Helper function for long messages. Uses the LineBreakBufferedWriter to break + * up long messages and stacktraces along newlines, but tries to write in large + * chunks. This is to avoid truncation. + * @hide + */ + public static int printlns(int bufID, int priority, String tag, String msg, + Throwable tr) { + ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag); + // Acceptable buffer size. Get the native buffer size, subtract two zero terminators, + // and the length of the tag. + // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It + // is too expensive to compute that ahead of time. + int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base. + - 2 // Two terminators. + - (tag != null ? tag.length() : 0) // Tag length. + - 32; // Some slack. + // At least assume you can print *some* characters (tag is not too large). + bufferSize = Math.max(bufferSize, 100); + + LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize); + + lbbw.println(msg); + + if (tr != null) { + // This is to reduce the amount of log spew that apps do in the non-error + // condition of the network being unavailable. + Throwable t = tr; + while (t != null) { + if (t instanceof UnknownHostException) { + break; + } + if (t instanceof DeadSystemException) { + lbbw.println("DeadSystemException: The system died; " + + "earlier logs will point to the root cause"); + break; + } + t = t.getCause(); + } + if (t == null) { + tr.printStackTrace(lbbw); + } + } + + lbbw.flush(); + + return logWriter.getWritten(); + } + + /** + * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid + * a JNI call during logging. + */ + static class PreloadHolder { + public final static int LOGGER_ENTRY_MAX_PAYLOAD = + logger_entry_max_payload_native(); + } + + /** + * Helper class to write to the logcat. Different from LogWriter, this writes + * the whole given buffer and does not break along newlines. + */ + private static class ImmediateLogWriter extends Writer { + + private int bufID; + private int priority; + private String tag; + + private int written = 0; + + /** + * Create a writer that immediately writes to the log, using the given + * parameters. + */ + public ImmediateLogWriter(int bufID, int priority, String tag) { + this.bufID = bufID; + this.priority = priority; + this.tag = tag; + } + + public int getWritten() { + return written; + } + + @Override + public void write(char[] cbuf, int off, int len) { + // Note: using String here has a bit of overhead as a Java object is created, + // but using the char[] directly is not easier, as it needs to be translated + // to a C char[] for logging. + written += println_native(bufID, priority, tag, new String(cbuf, off, len)); + } + + @Override + public void flush() { + // Ignored. + } + + @Override + public void close() { + // Ignored. + } } } diff --git a/android/util/LruCache.java b/android/util/LruCache.java index 52086065..40154880 100644 --- a/android/util/LruCache.java +++ b/android/util/LruCache.java @@ -20,10 +20,6 @@ import java.util.LinkedHashMap; import java.util.Map; /** - * BEGIN LAYOUTLIB CHANGE - * This is a custom version that doesn't use the non standard LinkedHashMap#eldest. - * END LAYOUTLIB CHANGE - * * A cache that holds strong references to a limited number of values. Each time * a value is accessed, it is moved to the head of a queue. When a value is * added to a full cache, the value at the end of that queue is evicted and may @@ -91,9 +87,8 @@ public class LruCache<K, V> { /** * Sets the size of the cache. - * @param maxSize The new maximum size. * - * @hide + * @param maxSize The new maximum size. */ public void resize(int maxSize) { if (maxSize <= 0) { @@ -190,10 +185,13 @@ public class LruCache<K, V> { } /** + * Remove the eldest entries until the total of remaining entries is at or + * below the requested size. + * * @param maxSize the maximum size of the cache before returning. May be -1 - * to evict even 0-sized elements. + * to evict even 0-sized elements. */ - private void trimToSize(int maxSize) { + public void trimToSize(int maxSize) { while (true) { K key; V value; @@ -207,16 +205,7 @@ public class LruCache<K, V> { break; } - // BEGIN LAYOUTLIB CHANGE - // get the last item in the linked list. - // This is not efficient, the goal here is to minimize the changes - // compared to the platform version. - Map.Entry<K, V> toEvict = null; - for (Map.Entry<K, V> entry : map.entrySet()) { - toEvict = entry; - } - // END LAYOUTLIB CHANGE - + Map.Entry<K, V> toEvict = map.eldest(); if (toEvict == null) { break; } diff --git a/android/util/StatsLog.java b/android/util/StatsLog.java new file mode 100644 index 00000000..0be1a8cf --- /dev/null +++ b/android/util/StatsLog.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +/** + * Logging access for platform metrics. + * + * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})! + * These diagnostic stats are for system integrators, not application authors. + * + * <p>Stats use integer tag codes. + * They carry a payload of one or more int, long, or String values. + * @hide + */ +public class StatsLog { + /** @hide */ public StatsLog() {} + + private static final String TAG = "StatsLog"; + + // We assume that the native methods deal with any concurrency issues. + + /** + * Records an stats log message. + * @param tag The stats type tag code + * @param value A value to log + * @return The number of bytes written + */ + public static native int writeInt(int tag, int value); + + /** + * Records an stats log message. + * @param tag The stats type tag code + * @param value A value to log + * @return The number of bytes written + */ + public static native int writeLong(int tag, long value); + + /** + * Records an stats log message. + * @param tag The stats type tag code + * @param value A value to log + * @return The number of bytes written + */ + public static native int writeFloat(int tag, float value); + + /** + * Records an stats log message. + * @param tag The stats type tag code + * @param str A value to log + * @return The number of bytes written + */ + public static native int writeString(int tag, String str); + + /** + * Records an stats log message. + * @param tag The stats type tag code + * @param list A list of values to log. All values should + * be of type int, long, float or String. + * @return The number of bytes written + */ + public static native int writeArray(int tag, Object... list); +} diff --git a/android/util/StatsLogKey.java b/android/util/StatsLogKey.java new file mode 100644 index 00000000..9ad0a23d --- /dev/null +++ b/android/util/StatsLogKey.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 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. + */ + +// THIS FILE IS AUTO-GENERATED. +// DO NOT MODIFY. + +package android.util; + +/** @hide */ +public class StatsLogKey { + private StatsLogKey() {} + + /** Constants for android.os.statsd.ScreenStateChange. */ + + /** display_state */ + public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE = 1; + + /** Constants for android.os.statsd.ProcessStateChange. */ + + /** state */ + public static final int PROCESS_STATE_CHANGE__STATE = 1; + + /** uid */ + public static final int PROCESS_STATE_CHANGE__UID = 2; + + /** package_name */ + public static final int PROCESS_STATE_CHANGE__PACKAGE_NAME = 1002; + + /** package_version */ + public static final int PROCESS_STATE_CHANGE__PACKAGE_VERSION = 3; + + /** package_version_string */ + public static final int PROCESS_STATE_CHANGE__PACKAGE_VERSION_STRING = 4; + +} diff --git a/android/support/car/drawer/DrawerItemClickListener.java b/android/util/StatsLogTag.java index d707dbd0..5e5a8287 100644 --- a/android/support/car/drawer/DrawerItemClickListener.java +++ b/android/util/StatsLogTag.java @@ -14,16 +14,19 @@ * limitations under the License. */ -package android.support.car.drawer; +// THIS FILE IS AUTO-GENERATED. +// DO NOT MODIFY. + +package android.util; + +/** @hide */ +public class StatsLogTag { + private StatsLogTag() {} + + /** android.os.statsd.ScreenStateChange. */ + public static final int SCREEN_STATE_CHANGE = 2; + + /** android.os.statsd.ProcessStateChange. */ + public static final int PROCESS_STATE_CHANGE = 1112; -/** - * Listener for handling clicks on items/views managed by {@link DrawerItemViewHolder}. - */ -public interface DrawerItemClickListener { - /** - * Callback when item is clicked. - * - * @param position Adapter position of the clicked item. - */ - void onItemClick(int position); } diff --git a/android/util/StatsLogValue.java b/android/util/StatsLogValue.java new file mode 100644 index 00000000..05b9d933 --- /dev/null +++ b/android/util/StatsLogValue.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 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. + */ + +// THIS FILE IS AUTO-GENERATED. +// DO NOT MODIFY. + +package android.util; + +/** @hide */ +public class StatsLogValue { + private StatsLogValue() {} + + /** Constants for android.os.statsd.ScreenStateChange. */ + + /** display_state: STATE_UNKNOWN */ + public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_UNKNOWN = 0; + + /** display_state: STATE_OFF */ + public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF = 1; + + /** display_state: STATE_ON */ + public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON = 2; + + /** display_state: STATE_DOZE */ + public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_DOZE = 3; + + /** display_state: STATE_DOZE_SUSPEND */ + public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_DOZE_SUSPEND = 4; + + /** display_state: STATE_VR */ + public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_VR = 5; + + /** Constants for android.os.statsd.ProcessStateChange. */ + + /** state: START */ + public static final int PROCESS_STATE_CHANGE__STATE__START = 1; + + /** state: CRASH */ + public static final int PROCESS_STATE_CHANGE__STATE__CRASH = 2; + +} diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java index 54825895..31daefff 100644 --- a/android/view/SurfaceControl.java +++ b/android/view/SurfaceControl.java @@ -21,22 +21,15 @@ import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import android.annotation.Size; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; -import android.graphics.Point; -import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.os.Binder; -import android.os.Debug; import android.os.IBinder; import android.util.Log; import android.view.Surface.OutOfResourcesException; import dalvik.system.CloseGuard; -import java.io.Closeable; - -import libcore.util.NativeAllocationRegistry; - /** * SurfaceControl * @hide @@ -61,34 +54,25 @@ public class SurfaceControl { Rect sourceCrop, int width, int height, int minLayer, int maxLayer, boolean allLayers, boolean useIdentityTransform); - private static native long nativeCreateTransaction(); - private static native long nativeGetNativeTransactionFinalizer(); - private static native void nativeApplyTransaction(long transactionObj, boolean sync); - private static native void nativeSetAnimationTransaction(long transactionObj); - - private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder); - private static native void nativeSetRelativeLayer(long transactionObj, long nativeObject, - IBinder relativeTo, int zorder); - private static native void nativeSetPosition(long transactionObj, long nativeObject, - float x, float y); - private static native void nativeSetGeometryAppliesWithResize(long transactionObj, - long nativeObject); - private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h); - private static native void nativeSetTransparentRegionHint(long transactionObj, - long nativeObject, Region region); - private static native void nativeSetAlpha(long transactionObj, long nativeObject, float alpha); - private static native void nativeSetMatrix(long transactionObj, long nativeObject, - float dsdx, float dtdx, + private static native void nativeOpenTransaction(); + private static native void nativeCloseTransaction(boolean sync); + private static native void nativeSetAnimationTransaction(); + + private static native void nativeSetLayer(long nativeObject, int zorder); + private static native void nativeSetRelativeLayer(long nativeObject, IBinder relativeTo, + int zorder); + private static native void nativeSetPosition(long nativeObject, float x, float y); + private static native void nativeSetGeometryAppliesWithResize(long nativeObject); + private static native void nativeSetSize(long nativeObject, int w, int h); + private static native void nativeSetTransparentRegionHint(long nativeObject, Region region); + private static native void nativeSetAlpha(long nativeObject, float alpha); + private static native void nativeSetColor(long nativeObject, float[] color); + private static native void nativeSetMatrix(long nativeObject, float dsdx, float dtdx, float dtdy, float dsdy); - private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color); - private static native void nativeSetFlags(long transactionObj, long nativeObject, - int flags, int mask); - private static native void nativeSetWindowCrop(long transactionObj, long nativeObject, - int l, int t, int r, int b); - private static native void nativeSetFinalCrop(long transactionObj, long nativeObject, - int l, int t, int r, int b); - private static native void nativeSetLayerStack(long transactionObj, long nativeObject, - int layerStack); + private static native void nativeSetFlags(long nativeObject, int flags, int mask); + private static native void nativeSetWindowCrop(long nativeObject, int l, int t, int r, int b); + private static native void nativeSetFinalCrop(long nativeObject, int l, int t, int r, int b); + private static native void nativeSetLayerStack(long nativeObject, int layerStack); private static native boolean nativeClearContentFrameStats(long nativeObject); private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats); @@ -98,16 +82,15 @@ public class SurfaceControl { private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId); private static native IBinder nativeCreateDisplay(String name, boolean secure); private static native void nativeDestroyDisplay(IBinder displayToken); - private static native void nativeSetDisplaySurface(long transactionObj, + private static native void nativeSetDisplaySurface( IBinder displayToken, long nativeSurfaceObject); - private static native void nativeSetDisplayLayerStack(long transactionObj, + private static native void nativeSetDisplayLayerStack( IBinder displayToken, int layerStack); - private static native void nativeSetDisplayProjection(long transactionObj, + private static native void nativeSetDisplayProjection( IBinder displayToken, int orientation, int l, int t, int r, int b, int L, int T, int R, int B); - private static native void nativeSetDisplaySize(long transactionObj, IBinder displayToken, - int width, int height); + private static native void nativeSetDisplaySize(IBinder displayToken, int width, int height); private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs( IBinder displayToken); private static native int nativeGetActiveConfig(IBinder displayToken); @@ -118,17 +101,16 @@ public class SurfaceControl { int colorMode); private static native void nativeSetDisplayPowerMode( IBinder displayToken, int mode); - private static native void nativeDeferTransactionUntil(long transactionObj, long nativeObject, + private static native void nativeDeferTransactionUntil(long nativeObject, IBinder handle, long frame); - private static native void nativeDeferTransactionUntilSurface(long transactionObj, - long nativeObject, + private static native void nativeDeferTransactionUntilSurface(long nativeObject, long surfaceObject, long frame); - private static native void nativeReparentChildren(long transactionObj, long nativeObject, + private static native void nativeReparentChildren(long nativeObject, IBinder handle); - private static native void nativeReparent(long transactionObj, long nativeObject, + private static native void nativeReparent(long nativeObject, IBinder parentHandle); - private static native void nativeSeverChildren(long transactionObj, long nativeObject); - private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject, + private static native void nativeSeverChildren(long nativeObject); + private static native void nativeSetOverrideScalingMode(long nativeObject, int scalingMode); private static native IBinder nativeGetHandle(long nativeObject); private static native boolean nativeGetTransformToDisplayInverse(long nativeObject); @@ -140,9 +122,6 @@ public class SurfaceControl { private final String mName; long mNativeObject; // package visibility only for Surface.java access - static Transaction sGlobalTransaction; - static long sTransactionNestCount = 0; - /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */ /** @@ -398,6 +377,11 @@ public class SurfaceControl { } } + @Override + public String toString() { + return "Surface(name=" + mName + ")"; + } + /** * Release the local reference to the server-side surface. * Always call release() when you're done with a Surface. @@ -445,141 +429,102 @@ public class SurfaceControl { /** start a transaction */ public static void openTransaction() { - synchronized (SurfaceControl.class) { - if (sGlobalTransaction == null) { - sGlobalTransaction = new Transaction(); - } - synchronized(SurfaceControl.class) { - sTransactionNestCount++; - } - } - } - - private static void closeTransaction(boolean sync) { - synchronized(SurfaceControl.class) { - if (sTransactionNestCount == 0) { - Log.e(TAG, "Call to SurfaceControl.closeTransaction without matching openTransaction"); - } else if (--sTransactionNestCount > 0) { - return; - } - sGlobalTransaction.apply(sync); - } + nativeOpenTransaction(); } /** end a transaction */ public static void closeTransaction() { - closeTransaction(false); + nativeCloseTransaction(false); } public static void closeTransactionSync() { - closeTransaction(true); + nativeCloseTransaction(true); } public void deferTransactionUntil(IBinder handle, long frame) { if (frame > 0) { - synchronized(SurfaceControl.class) { - sGlobalTransaction.deferTransactionUntil(this, handle, frame); - } + nativeDeferTransactionUntil(mNativeObject, handle, frame); } } public void deferTransactionUntil(Surface barrier, long frame) { if (frame > 0) { - synchronized(SurfaceControl.class) { - sGlobalTransaction.deferTransactionUntilSurface(this, barrier, frame); - } + nativeDeferTransactionUntilSurface(mNativeObject, barrier.mNativeObject, frame); } } public void reparentChildren(IBinder newParentHandle) { - synchronized(SurfaceControl.class) { - sGlobalTransaction.reparentChildren(this, newParentHandle); - } + nativeReparentChildren(mNativeObject, newParentHandle); } + /** Re-parents this layer to a new parent. */ public void reparent(IBinder newParentHandle) { - synchronized(SurfaceControl.class) { - sGlobalTransaction.reparent(this, newParentHandle); - } + nativeReparent(mNativeObject, newParentHandle); } public void detachChildren() { - synchronized(SurfaceControl.class) { - sGlobalTransaction.detachChildren(this); - } + nativeSeverChildren(mNativeObject); } public void setOverrideScalingMode(int scalingMode) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setOverrideScalingMode(this, scalingMode); - } + nativeSetOverrideScalingMode(mNativeObject, scalingMode); } public IBinder getHandle() { return nativeGetHandle(mNativeObject); } + /** flag the transaction as an animation */ public static void setAnimationTransaction() { - synchronized (SurfaceControl.class) { - sGlobalTransaction.setAnimationTransaction(); - } + nativeSetAnimationTransaction(); } public void setLayer(int zorder) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setLayer(this, zorder); - } + nativeSetLayer(mNativeObject, zorder); } public void setRelativeLayer(IBinder relativeTo, int zorder) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setRelativeLayer(this, relativeTo, zorder); - } + nativeSetRelativeLayer(mNativeObject, relativeTo, zorder); } public void setPosition(float x, float y) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setPosition(this, x, y); - } + nativeSetPosition(mNativeObject, x, y); } + /** + * If the buffer size changes in this transaction, position and crop updates specified + * in this transaction will not complete until a buffer of the new size + * arrives. As transform matrix and size are already frozen in this fashion, + * this enables totally freezing the surface until the resize has completed + * (at which point the geometry influencing aspects of this transaction will then occur) + */ public void setGeometryAppliesWithResize() { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setGeometryAppliesWithResize(this); - } + nativeSetGeometryAppliesWithResize(mNativeObject); } public void setSize(int w, int h) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setSize(this, w, h); - } + nativeSetSize(mNativeObject, w, h); } public void hide() { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.hide(this); - } + nativeSetFlags(mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN); } public void show() { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.show(this); - } + nativeSetFlags(mNativeObject, 0, SURFACE_HIDDEN); } public void setTransparentRegionHint(Region region) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setTransparentRegionHint(this, region); - } + nativeSetTransparentRegionHint(mNativeObject, region); } public boolean clearContentFrameStats() { @@ -600,70 +545,80 @@ public class SurfaceControl { return nativeGetAnimationFrameStats(outStats); } + /** + * Sets an alpha value for the entire Surface. This value is combined with the + * per-pixel alpha. It may be used with opaque Surfaces. + */ public void setAlpha(float alpha) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setAlpha(this, alpha); - } + nativeSetAlpha(mNativeObject, alpha); } + /** + * Sets a color for the Surface. + * @param color A float array with three values to represent r, g, b in range [0..1] + */ public void setColor(@Size(3) float[] color) { checkNotReleased(); - synchronized (SurfaceControl.class) { - sGlobalTransaction.setColor(this, color); - } + nativeSetColor(mNativeObject, color); } public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setMatrix(this, dsdx, dtdx, dtdy, dsdy); - } + nativeSetMatrix(mNativeObject, dsdx, dtdx, dtdy, dsdy); } public void setWindowCrop(Rect crop) { checkNotReleased(); - synchronized (SurfaceControl.class) { - sGlobalTransaction.setWindowCrop(this, crop); + if (crop != null) { + nativeSetWindowCrop(mNativeObject, + crop.left, crop.top, crop.right, crop.bottom); + } else { + nativeSetWindowCrop(mNativeObject, 0, 0, 0, 0); } } public void setFinalCrop(Rect crop) { checkNotReleased(); - synchronized (SurfaceControl.class) { - sGlobalTransaction.setFinalCrop(this, crop); + if (crop != null) { + nativeSetFinalCrop(mNativeObject, + crop.left, crop.top, crop.right, crop.bottom); + } else { + nativeSetFinalCrop(mNativeObject, 0, 0, 0, 0); } } public void setLayerStack(int layerStack) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setLayerStack(this, layerStack); - } + nativeSetLayerStack(mNativeObject, layerStack); } + /** + * Sets the opacity of the surface. Setting the flag is equivalent to creating the + * Surface with the {@link #OPAQUE} flag. + */ public void setOpaque(boolean isOpaque) { checkNotReleased(); - - synchronized (SurfaceControl.class) { - sGlobalTransaction.setOpaque(this, isOpaque); + if (isOpaque) { + nativeSetFlags(mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE); + } else { + nativeSetFlags(mNativeObject, 0, SURFACE_OPAQUE); } } + /** + * Sets the security of the surface. Setting the flag is equivalent to creating the + * Surface with the {@link #SECURE} flag. + */ public void setSecure(boolean isSecure) { checkNotReleased(); - - synchronized (SurfaceControl.class) { - sGlobalTransaction.setSecure(this, isSecure); + if (isSecure) { + nativeSetFlags(mNativeObject, SECURE, SECURE); + } else { + nativeSetFlags(mNativeObject, 0, SECURE); } } - @Override - public String toString() { - return "Surface(name=" + mName + ")/@0x" + - Integer.toHexString(System.identityHashCode(this)); - } - /* * set display parameters. * needs to be inside open/closeTransaction block @@ -786,28 +741,50 @@ public class SurfaceControl { public static void setDisplayProjection(IBinder displayToken, int orientation, Rect layerStackRect, Rect displayRect) { - synchronized (SurfaceControl.class) { - sGlobalTransaction.setDisplayProjection(displayToken, orientation, - layerStackRect, displayRect); + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + if (layerStackRect == null) { + throw new IllegalArgumentException("layerStackRect must not be null"); } + if (displayRect == null) { + throw new IllegalArgumentException("displayRect must not be null"); + } + nativeSetDisplayProjection(displayToken, orientation, + layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom, + displayRect.left, displayRect.top, displayRect.right, displayRect.bottom); } public static void setDisplayLayerStack(IBinder displayToken, int layerStack) { - synchronized (SurfaceControl.class) { - sGlobalTransaction.setDisplayLayerStack(displayToken, layerStack); + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); } + nativeSetDisplayLayerStack(displayToken, layerStack); } public static void setDisplaySurface(IBinder displayToken, Surface surface) { - synchronized (SurfaceControl.class) { - sGlobalTransaction.setDisplaySurface(displayToken, surface); + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + + if (surface != null) { + synchronized (surface.mLock) { + nativeSetDisplaySurface(displayToken, surface.mNativeObject); + } + } else { + nativeSetDisplaySurface(displayToken, 0); } } public static void setDisplaySize(IBinder displayToken, int width, int height) { - synchronized (SurfaceControl.class) { - sGlobalTransaction.setDisplaySize(displayToken, width, height); + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("width and height must be positive"); } + + nativeSetDisplaySize(displayToken, width, height); } public static Display.HdrCapabilities getHdrCapabilities(IBinder displayToken) { @@ -969,261 +946,4 @@ public class SurfaceControl { nativeScreenshot(display, consumer, sourceCrop, width, height, minLayer, maxLayer, allLayers, useIdentityTransform); } - - public static class Transaction implements Closeable { - public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - Transaction.class.getClassLoader(), - nativeGetNativeTransactionFinalizer(), 512); - private long mNativeObject; - - Runnable mFreeNativeResources; - - public Transaction() { - mNativeObject = nativeCreateTransaction(); - mFreeNativeResources - = sRegistry.registerNativeAllocation(this, mNativeObject); - } - - /** - * Apply the transaction, clearing it's state, and making it usable - * as a new transaction. - */ - public void apply() { - apply(false); - } - - /** - * Close the transaction, if the transaction was not already applied this will cancel the - * transaction. - */ - @Override - public void close() { - mFreeNativeResources.run(); - mNativeObject = 0; - } - - /** - * Jankier version of apply. Avoid use (b/28068298). - */ - public void apply(boolean sync) { - nativeApplyTransaction(mNativeObject, sync); - } - - public Transaction show(SurfaceControl sc) { - nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN); - return this; - } - - public Transaction hide(SurfaceControl sc) { - nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN); - return this; - } - - public Transaction setPosition(SurfaceControl sc, float x, float y) { - nativeSetPosition(mNativeObject, sc.mNativeObject, x, y); - return this; - } - - public Transaction setSize(SurfaceControl sc, int w, int h) { - nativeSetSize(mNativeObject, sc.mNativeObject, - w, h); - return this; - } - - public Transaction setLayer(SurfaceControl sc, int z) { - nativeSetLayer(mNativeObject, sc.mNativeObject, z); - return this; - } - - public Transaction setRelativeLayer(SurfaceControl sc, IBinder relativeTo, int z) { - nativeSetRelativeLayer(mNativeObject, sc.mNativeObject, - relativeTo, z); - return this; - } - - public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) { - nativeSetTransparentRegionHint(mNativeObject, - sc.mNativeObject, transparentRegion); - return this; - } - - public Transaction setAlpha(SurfaceControl sc, float alpha) { - nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha); - return this; - } - - public Transaction setMatrix(SurfaceControl sc, - float dsdx, float dtdx, float dtdy, float dsdy) { - nativeSetMatrix(mNativeObject, sc.mNativeObject, - dsdx, dtdx, dtdy, dsdy); - return this; - } - - public Transaction setWindowCrop(SurfaceControl sc, Rect crop) { - if (crop != null) { - nativeSetWindowCrop(mNativeObject, sc.mNativeObject, - crop.left, crop.top, crop.right, crop.bottom); - } else { - nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0); - } - - return this; - } - - public Transaction setFinalCrop(SurfaceControl sc, Rect crop) { - if (crop != null) { - nativeSetFinalCrop(mNativeObject, sc.mNativeObject, - crop.left, crop.top, crop.right, crop.bottom); - } else { - nativeSetFinalCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0); - } - - return this; - } - - public Transaction setLayerStack(SurfaceControl sc, int layerStack) { - nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack); - return this; - } - - public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, long frameNumber) { - nativeDeferTransactionUntil(mNativeObject, sc.mNativeObject, handle, frameNumber); - return this; - } - - public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface, - long frameNumber) { - nativeDeferTransactionUntilSurface(mNativeObject, sc.mNativeObject, - barrierSurface.mNativeObject, frameNumber); - return this; - } - - public Transaction reparentChildren(SurfaceControl sc, IBinder newParentHandle) { - nativeReparentChildren(mNativeObject, sc.mNativeObject, newParentHandle); - return this; - } - - /** Re-parents a specific child layer to a new parent */ - public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) { - nativeReparent(mNativeObject, sc.mNativeObject, - newParentHandle); - return this; - } - - public Transaction detachChildren(SurfaceControl sc) { - nativeSeverChildren(mNativeObject, sc.mNativeObject); - return this; - } - - public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) { - nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject, - overrideScalingMode); - return this; - } - - /** - * Sets a color for the Surface. - * @param color A float array with three values to represent r, g, b in range [0..1] - */ - public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) { - nativeSetColor(mNativeObject, sc.mNativeObject, color); - return this; - } - - /** - * If the buffer size changes in this transaction, position and crop updates specified - * in this transaction will not complete until a buffer of the new size - * arrives. As transform matrix and size are already frozen in this fashion, - * this enables totally freezing the surface until the resize has completed - * (at which point the geometry influencing aspects of this transaction will then occur) - */ - public Transaction setGeometryAppliesWithResize(SurfaceControl sc) { - nativeSetGeometryAppliesWithResize(mNativeObject, sc.mNativeObject); - return this; - } - - /** - * Sets the security of the surface. Setting the flag is equivalent to creating the - * Surface with the {@link #SECURE} flag. - */ - Transaction setSecure(SurfaceControl sc, boolean isSecure) { - if (isSecure) { - nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE); - } else { - nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SECURE); - } - return this; - } - - /** - * Sets the opacity of the surface. Setting the flag is equivalent to creating the - * Surface with the {@link #OPAQUE} flag. - */ - public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) { - if (isOpaque) { - nativeSetFlags(mNativeObject, sc.mNativeObject, OPAQUE, OPAQUE); - } else { - nativeSetFlags(mNativeObject, sc.mNativeObject, 0, OPAQUE); - } - return this; - } - - public Transaction setDisplaySurface(IBinder displayToken, Surface surface) { - if (displayToken == null) { - throw new IllegalArgumentException("displayToken must not be null"); - } - - if (surface != null) { - synchronized (surface.mLock) { - nativeSetDisplaySurface(mNativeObject, displayToken, surface.mNativeObject); - } - } else { - nativeSetDisplaySurface(mNativeObject, displayToken, 0); - } - return this; - } - - public Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) { - if (displayToken == null) { - throw new IllegalArgumentException("displayToken must not be null"); - } - nativeSetDisplayLayerStack(mNativeObject, displayToken, layerStack); - return this; - } - - public Transaction setDisplayProjection(IBinder displayToken, - int orientation, Rect layerStackRect, Rect displayRect) { - if (displayToken == null) { - throw new IllegalArgumentException("displayToken must not be null"); - } - if (layerStackRect == null) { - throw new IllegalArgumentException("layerStackRect must not be null"); - } - if (displayRect == null) { - throw new IllegalArgumentException("displayRect must not be null"); - } - nativeSetDisplayProjection(mNativeObject, displayToken, orientation, - layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom, - displayRect.left, displayRect.top, displayRect.right, displayRect.bottom); - return this; - } - - public Transaction setDisplaySize(IBinder displayToken, int width, int height) { - if (displayToken == null) { - throw new IllegalArgumentException("displayToken must not be null"); - } - if (width <= 0 || height <= 0) { - throw new IllegalArgumentException("width and height must be positive"); - } - - nativeSetDisplaySize(mNativeObject, displayToken, width, height); - return this; - } - - /** flag the transaction as an animation */ - public Transaction setAnimationTransaction() { - nativeSetAnimationTransaction(mNativeObject); - return this; - } - } } diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java index ebb2af45..462dad3f 100644 --- a/android/view/SurfaceView.java +++ b/android/view/SurfaceView.java @@ -16,115 +16,1208 @@ package android.view; -import com.android.layoutlib.bridge.MockView; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; +import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER; +import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER; +import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER; import android.content.Context; +import android.content.res.CompatibilityInfo.Translator; +import android.content.res.Configuration; import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Region; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.SystemClock; import android.util.AttributeSet; +import android.util.Log; + +import com.android.internal.view.SurfaceCallbackHelper; + +import java.util.ArrayList; +import java.util.concurrent.locks.ReentrantLock; /** - * Mock version of the SurfaceView. - * Only non override public methods from the real SurfaceView have been added in there. - * Methods that take an unknown class as parameter or as return object, have been removed for now. + * Provides a dedicated drawing surface embedded inside of a view hierarchy. + * You can control the format of this surface and, if you like, its size; the + * SurfaceView takes care of placing the surface at the correct location on the + * screen + * + * <p>The surface is Z ordered so that it is behind the window holding its + * SurfaceView; the SurfaceView punches a hole in its window to allow its + * surface to be displayed. The view hierarchy will take care of correctly + * compositing with the Surface any siblings of the SurfaceView that would + * normally appear on top of it. This can be used to place overlays such as + * buttons on top of the Surface, though note however that it can have an + * impact on performance since a full alpha-blended composite will be performed + * each time the Surface changes. + * + * <p> The transparent region that makes the surface visible is based on the + * layout positions in the view hierarchy. If the post-layout transform + * properties are used to draw a sibling view on top of the SurfaceView, the + * view may not be properly composited with the surface. * - * TODO: generate automatically. + * <p>Access to the underlying surface is provided via the SurfaceHolder interface, + * which can be retrieved by calling {@link #getHolder}. * + * <p>The Surface will be created for you while the SurfaceView's window is + * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated} + * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the + * Surface is created and destroyed as the window is shown and hidden. + * + * <p>One of the purposes of this class is to provide a surface in which a + * secondary thread can render into the screen. If you are going to use it + * this way, you need to be aware of some threading semantics: + * + * <ul> + * <li> All SurfaceView and + * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called + * from the thread running the SurfaceView's window (typically the main thread + * of the application). They thus need to correctly synchronize with any + * state that is also touched by the drawing thread. + * <li> You must ensure that the drawing thread only touches the underlying + * Surface while it is valid -- between + * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()} + * and + * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}. + * </ul> + * + * <p class="note"><strong>Note:</strong> Starting in platform version + * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is + * updated synchronously with other View rendering. This means that translating + * and scaling a SurfaceView on screen will not cause rendering artifacts. Such + * artifacts may occur on previous versions of the platform when its window is + * positioned asynchronously.</p> */ -public class SurfaceView extends MockView { +public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback { + private static final String TAG = "SurfaceView"; + private static final boolean DEBUG = false; + + final ArrayList<SurfaceHolder.Callback> mCallbacks + = new ArrayList<SurfaceHolder.Callback>(); + + final int[] mLocation = new int[2]; + + final ReentrantLock mSurfaceLock = new ReentrantLock(); + final Surface mSurface = new Surface(); // Current surface in use + boolean mDrawingStopped = true; + // We use this to track if the application has produced a frame + // in to the Surface. Up until that point, we should be careful not to punch + // holes. + boolean mDrawFinished = false; + + final Rect mScreenRect = new Rect(); + SurfaceSession mSurfaceSession; + + SurfaceControl mSurfaceControl; + // In the case of format changes we switch out the surface in-place + // we need to preserve the old one until the new one has drawn. + SurfaceControl mDeferredDestroySurfaceControl; + final Rect mTmpRect = new Rect(); + final Configuration mConfiguration = new Configuration(); + + int mSubLayer = APPLICATION_MEDIA_SUBLAYER; + + boolean mIsCreating = false; + private volatile boolean mRtHandlingPositionUpdates = false; + + private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener + = new ViewTreeObserver.OnScrollChangedListener() { + @Override + public void onScrollChanged() { + updateSurface(); + } + }; + + private final ViewTreeObserver.OnPreDrawListener mDrawListener = + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + // reposition ourselves where the surface is + mHaveFrame = getWidth() > 0 && getHeight() > 0; + updateSurface(); + return true; + } + }; + + boolean mRequestedVisible = false; + boolean mWindowVisibility = false; + boolean mLastWindowVisibility = false; + boolean mViewVisibility = false; + boolean mWindowStopped = false; + + int mRequestedWidth = -1; + int mRequestedHeight = -1; + /* Set SurfaceView's format to 565 by default to maintain backward + * compatibility with applications assuming this format. + */ + int mRequestedFormat = PixelFormat.RGB_565; + + boolean mHaveFrame = false; + boolean mSurfaceCreated = false; + long mLastLockTime = 0; + + boolean mVisible = false; + int mWindowSpaceLeft = -1; + int mWindowSpaceTop = -1; + int mSurfaceWidth = -1; + int mSurfaceHeight = -1; + int mFormat = -1; + final Rect mSurfaceFrame = new Rect(); + int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; + private Translator mTranslator; + + private boolean mGlobalListenersAdded; + private boolean mAttachedToWindow; + + private int mSurfaceFlags = SurfaceControl.HIDDEN; + + private int mPendingReportDraws; public SurfaceView(Context context) { this(context, null); } public SurfaceView(Context context, AttributeSet attrs) { - this(context, attrs , 0); + this(context, attrs, 0); } - public SurfaceView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mRenderNode.requestPositionUpdates(this); + + setWillNotDraw(true); + } + + /** + * Return the SurfaceHolder providing access and control over this + * SurfaceView's underlying surface. + * + * @return SurfaceHolder The holder of the surface. + */ + public SurfaceHolder getHolder() { + return mSurfaceHolder; + } + + private void updateRequestedVisibility() { + mRequestedVisible = mViewVisibility && mWindowVisibility && !mWindowStopped; + } + + /** @hide */ + @Override + public void windowStopped(boolean stopped) { + mWindowStopped = stopped; + updateRequestedVisibility(); + updateSurface(); } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + getViewRootImpl().addWindowStoppedCallback(this); + mWindowStopped = false; + + mViewVisibility = getVisibility() == VISIBLE; + updateRequestedVisibility(); + + mAttachedToWindow = true; + mParent.requestTransparentRegion(SurfaceView.this); + if (!mGlobalListenersAdded) { + ViewTreeObserver observer = getViewTreeObserver(); + observer.addOnScrollChangedListener(mScrollChangedListener); + observer.addOnPreDrawListener(mDrawListener); + mGlobalListenersAdded = true; + } + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + mWindowVisibility = visibility == VISIBLE; + updateRequestedVisibility(); + updateSurface(); + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + mViewVisibility = visibility == VISIBLE; + boolean newRequestedVisible = mWindowVisibility && mViewVisibility && !mWindowStopped; + if (newRequestedVisible != mRequestedVisible) { + // our base class (View) invalidates the layout only when + // we go from/to the GONE state. However, SurfaceView needs + // to request a re-layout when the visibility changes at all. + // This is needed because the transparent region is computed + // as part of the layout phase, and it changes (obviously) when + // the visibility changes. + requestLayout(); + } + mRequestedVisible = newRequestedVisible; + updateSurface(); + } + + private void performDrawFinished() { + if (mPendingReportDraws > 0) { + mDrawFinished = true; + if (mAttachedToWindow) { + notifyDrawFinished(); + invalidate(); + } + } else { + Log.e(TAG, System.identityHashCode(this) + "finished drawing" + + " but no pending report draw (extra call" + + " to draw completion runnable?)"); + } + } + + void notifyDrawFinished() { + ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot != null) { + viewRoot.pendingDrawFinished(); + } + mPendingReportDraws--; + } + + @Override + protected void onDetachedFromWindow() { + ViewRootImpl viewRoot = getViewRootImpl(); + // It's possible to create a SurfaceView using the default constructor and never + // attach it to a view hierarchy, this is a common use case when dealing with + // OpenGL. A developer will probably create a new GLSurfaceView, and let it manage + // the lifecycle. Instead of attaching it to a view, he/she can just pass + // the SurfaceHolder forward, most live wallpapers do it. + if (viewRoot != null) { + viewRoot.removeWindowStoppedCallback(this); + } + + mAttachedToWindow = false; + if (mGlobalListenersAdded) { + ViewTreeObserver observer = getViewTreeObserver(); + observer.removeOnScrollChangedListener(mScrollChangedListener); + observer.removeOnPreDrawListener(mDrawListener); + mGlobalListenersAdded = false; + } + + while (mPendingReportDraws > 0) { + notifyDrawFinished(); + } + + mRequestedVisible = false; + + updateSurface(); + if (mSurfaceControl != null) { + mSurfaceControl.destroy(); + } + mSurfaceControl = null; + + mHaveFrame = false; + + super.onDetachedFromWindow(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = mRequestedWidth >= 0 + ? resolveSizeAndState(mRequestedWidth, widthMeasureSpec, 0) + : getDefaultSize(0, widthMeasureSpec); + int height = mRequestedHeight >= 0 + ? resolveSizeAndState(mRequestedHeight, heightMeasureSpec, 0) + : getDefaultSize(0, heightMeasureSpec); + setMeasuredDimension(width, height); + } + + /** @hide */ + @Override + protected boolean setFrame(int left, int top, int right, int bottom) { + boolean result = super.setFrame(left, top, right, bottom); + updateSurface(); + return result; + } + + @Override public boolean gatherTransparentRegion(Region region) { - return false; + if (isAboveParent() || !mDrawFinished) { + return super.gatherTransparentRegion(region); + } + + boolean opaque = true; + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) { + // this view draws, remove it from the transparent region + opaque = super.gatherTransparentRegion(region); + } else if (region != null) { + int w = getWidth(); + int h = getHeight(); + if (w>0 && h>0) { + getLocationInWindow(mLocation); + // otherwise, punch a hole in the whole hierarchy + int l = mLocation[0]; + int t = mLocation[1]; + region.op(l, t, l+w, t+h, Region.Op.UNION); + } + } + if (PixelFormat.formatHasAlpha(mRequestedFormat)) { + opaque = false; + } + return opaque; } + @Override + public void draw(Canvas canvas) { + if (mDrawFinished && !isAboveParent()) { + // draw() is not called when SKIP_DRAW is set + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) { + // punch a whole in the view-hierarchy below us + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } + } + super.draw(canvas); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + if (mDrawFinished && !isAboveParent()) { + // draw() is not called when SKIP_DRAW is set + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { + // punch a whole in the view-hierarchy below us + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } + } + super.dispatchDraw(canvas); + } + + /** + * Control whether the surface view's surface is placed on top of another + * regular surface view in the window (but still behind the window itself). + * This is typically used to place overlays on top of an underlying media + * surface view. + * + * <p>Note that this must be set before the surface view's containing + * window is attached to the window manager. + * + * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}. + */ public void setZOrderMediaOverlay(boolean isMediaOverlay) { + mSubLayer = isMediaOverlay + ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER; } + /** + * Control whether the surface view's surface is placed on top of its + * window. Normally it is placed behind the window, to allow it to + * (for the most part) appear to composite with the views in the + * hierarchy. By setting this, you cause it to be placed above the + * window. This means that none of the contents of the window this + * SurfaceView is in will be visible on top of its surface. + * + * <p>Note that this must be set before the surface view's containing + * window is attached to the window manager. + * + * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}. + */ public void setZOrderOnTop(boolean onTop) { + if (onTop) { + mSubLayer = APPLICATION_PANEL_SUBLAYER; + } else { + mSubLayer = APPLICATION_MEDIA_SUBLAYER; + } } + /** + * Control whether the surface view's content should be treated as secure, + * preventing it from appearing in screenshots or from being viewed on + * non-secure displays. + * + * <p>Note that this must be set before the surface view's containing + * window is attached to the window manager. + * + * <p>See {@link android.view.Display#FLAG_SECURE} for details. + * + * @param isSecure True if the surface view is secure. + */ public void setSecure(boolean isSecure) { + if (isSecure) { + mSurfaceFlags |= SurfaceControl.SECURE; + } else { + mSurfaceFlags &= ~SurfaceControl.SECURE; + } } - public SurfaceHolder getHolder() { - return mSurfaceHolder; + private void updateOpaqueFlag() { + if (!PixelFormat.formatHasAlpha(mRequestedFormat)) { + mSurfaceFlags |= SurfaceControl.OPAQUE; + } else { + mSurfaceFlags &= ~SurfaceControl.OPAQUE; + } } - private SurfaceHolder mSurfaceHolder = new SurfaceHolder() { + private Rect getParentSurfaceInsets() { + final ViewRootImpl root = getViewRootImpl(); + if (root == null) { + return null; + } else { + return root.mWindowAttributes.surfaceInsets; + } + } + + /** @hide */ + protected void updateSurface() { + if (!mHaveFrame) { + return; + } + ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) { + return; + } + + mTranslator = viewRoot.mTranslator; + if (mTranslator != null) { + mSurface.setCompatibilityTranslator(mTranslator); + } + + int myWidth = mRequestedWidth; + if (myWidth <= 0) myWidth = getWidth(); + int myHeight = mRequestedHeight; + if (myHeight <= 0) myHeight = getHeight(); + + final boolean formatChanged = mFormat != mRequestedFormat; + final boolean visibleChanged = mVisible != mRequestedVisible; + final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged) + && mRequestedVisible; + final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight; + final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility; + boolean redrawNeeded = false; + + if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) { + getLocationInWindow(mLocation); + + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "Changes: creating=" + creating + + " format=" + formatChanged + " size=" + sizeChanged + + " visible=" + visibleChanged + + " left=" + (mWindowSpaceLeft != mLocation[0]) + + " top=" + (mWindowSpaceTop != mLocation[1])); + + try { + final boolean visible = mVisible = mRequestedVisible; + mWindowSpaceLeft = mLocation[0]; + mWindowSpaceTop = mLocation[1]; + mSurfaceWidth = myWidth; + mSurfaceHeight = myHeight; + mFormat = mRequestedFormat; + mLastWindowVisibility = mWindowVisibility; + + mScreenRect.left = mWindowSpaceLeft; + mScreenRect.top = mWindowSpaceTop; + mScreenRect.right = mWindowSpaceLeft + getWidth(); + mScreenRect.bottom = mWindowSpaceTop + getHeight(); + if (mTranslator != null) { + mTranslator.translateRectInAppWindowToScreen(mScreenRect); + } + + final Rect surfaceInsets = getParentSurfaceInsets(); + mScreenRect.offset(surfaceInsets.left, surfaceInsets.top); + + if (creating) { + mSurfaceSession = new SurfaceSession(viewRoot.mSurface); + mDeferredDestroySurfaceControl = mSurfaceControl; + + updateOpaqueFlag(); + mSurfaceControl = new SurfaceControlWithBackground(mSurfaceSession, + "SurfaceView - " + viewRoot.getTitle().toString(), + mSurfaceWidth, mSurfaceHeight, mFormat, + mSurfaceFlags); + } else if (mSurfaceControl == null) { + return; + } + + boolean realSizeChanged = false; + + mSurfaceLock.lock(); + try { + mDrawingStopped = !visible; + + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "Cur surface: " + mSurface); + + SurfaceControl.openTransaction(); + try { + mSurfaceControl.setLayer(mSubLayer); + if (mViewVisibility) { + mSurfaceControl.show(); + } else { + mSurfaceControl.hide(); + } + + // While creating the surface, we will set it's initial + // geometry. Outside of that though, we should generally + // leave it to the RenderThread. + // + // There is one more case when the buffer size changes we aren't yet + // prepared to sync (as even following the transaction applying + // we still need to latch a buffer). + // b/28866173 + if (sizeChanged || creating || !mRtHandlingPositionUpdates) { + mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top); + mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth, + 0.0f, 0.0f, + mScreenRect.height() / (float) mSurfaceHeight); + } + if (sizeChanged) { + mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight); + } + } finally { + SurfaceControl.closeTransaction(); + } + + if (sizeChanged || creating) { + redrawNeeded = true; + } + + mSurfaceFrame.left = 0; + mSurfaceFrame.top = 0; + if (mTranslator == null) { + mSurfaceFrame.right = mSurfaceWidth; + mSurfaceFrame.bottom = mSurfaceHeight; + } else { + float appInvertedScale = mTranslator.applicationInvertedScale; + mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f); + mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f); + } + + final int surfaceWidth = mSurfaceFrame.right; + final int surfaceHeight = mSurfaceFrame.bottom; + realSizeChanged = mLastSurfaceWidth != surfaceWidth + || mLastSurfaceHeight != surfaceHeight; + mLastSurfaceWidth = surfaceWidth; + mLastSurfaceHeight = surfaceHeight; + } finally { + mSurfaceLock.unlock(); + } + + try { + redrawNeeded |= visible && !mDrawFinished; + + SurfaceHolder.Callback callbacks[] = null; + + final boolean surfaceChanged = creating; + if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) { + mSurfaceCreated = false; + if (mSurface.isValid()) { + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "visibleChanged -- surfaceDestroyed"); + callbacks = getSurfaceCallbacks(); + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceDestroyed(mSurfaceHolder); + } + // Since Android N the same surface may be reused and given to us + // again by the system server at a later point. However + // as we didn't do this in previous releases, clients weren't + // necessarily required to clean up properly in + // surfaceDestroyed. This leads to problems for example when + // clients don't destroy their EGL context, and try + // and create a new one on the same surface following reuse. + // Since there is no valid use of the surface in-between + // surfaceDestroyed and surfaceCreated, we force a disconnect, + // so the next connect will always work if we end up reusing + // the surface. + if (mSurface.isValid()) { + mSurface.forceScopedDisconnect(); + } + } + } + + if (creating) { + mSurface.copyFrom(mSurfaceControl); + } + + if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion + < Build.VERSION_CODES.O) { + // Some legacy applications use the underlying native {@link Surface} object + // as a key to whether anything has changed. In these cases, updates to the + // existing {@link Surface} will be ignored when the size changes. + // Therefore, we must explicitly recreate the {@link Surface} in these + // cases. + mSurface.createFrom(mSurfaceControl); + } + + if (visible && mSurface.isValid()) { + if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) { + mSurfaceCreated = true; + mIsCreating = true; + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "visibleChanged -- surfaceCreated"); + if (callbacks == null) { + callbacks = getSurfaceCallbacks(); + } + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceCreated(mSurfaceHolder); + } + } + if (creating || formatChanged || sizeChanged + || visibleChanged || realSizeChanged) { + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "surfaceChanged -- format=" + mFormat + + " w=" + myWidth + " h=" + myHeight); + if (callbacks == null) { + callbacks = getSurfaceCallbacks(); + } + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight); + } + } + if (redrawNeeded) { + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "surfaceRedrawNeeded"); + if (callbacks == null) { + callbacks = getSurfaceCallbacks(); + } + + mPendingReportDraws++; + viewRoot.drawPending(); + SurfaceCallbackHelper sch = + new SurfaceCallbackHelper(this::onDrawFinished); + sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks); + } + } + } finally { + mIsCreating = false; + if (mSurfaceControl != null && !mSurfaceCreated) { + mSurface.release(); + // If we are not in the stopped state, then the destruction of the Surface + // represents a visual change we need to display, and we should go ahead + // and destroy the SurfaceControl. However if we are in the stopped state, + // we can just leave the Surface around so it can be a part of animations, + // and we let the life-time be tied to the parent surface. + if (!mWindowStopped) { + mSurfaceControl.destroy(); + mSurfaceControl = null; + } + } + } + } catch (Exception ex) { + Log.e(TAG, "Exception configuring surface", ex); + } + if (DEBUG) Log.v( + TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top + + " w=" + mScreenRect.width() + " h=" + mScreenRect.height() + + ", frame=" + mSurfaceFrame); + } else { + // Calculate the window position in case RT loses the window + // and we need to fallback to a UI-thread driven position update + getLocationInSurface(mLocation); + final boolean positionChanged = mWindowSpaceLeft != mLocation[0] + || mWindowSpaceTop != mLocation[1]; + final boolean layoutSizeChanged = getWidth() != mScreenRect.width() + || getHeight() != mScreenRect.height(); + if (positionChanged || layoutSizeChanged) { // Only the position has changed + mWindowSpaceLeft = mLocation[0]; + mWindowSpaceTop = mLocation[1]; + // For our size changed check, we keep mScreenRect.width() and mScreenRect.height() + // in view local space. + mLocation[0] = getWidth(); + mLocation[1] = getHeight(); + + mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop, + mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]); + + if (mTranslator != null) { + mTranslator.translateRectInAppWindowToScreen(mScreenRect); + } + + if (mSurfaceControl == null) { + return; + } + + if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) { + try { + if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " + + "postion = [%d, %d, %d, %d]", System.identityHashCode(this), + mScreenRect.left, mScreenRect.top, + mScreenRect.right, mScreenRect.bottom)); + setParentSpaceRectangle(mScreenRect, -1); + } catch (Exception ex) { + Log.e(TAG, "Exception configuring surface", ex); + } + } + } + } + } + + private void onDrawFinished() { + if (DEBUG) { + Log.i(TAG, System.identityHashCode(this) + " " + + "finishedDrawing"); + } + + if (mDeferredDestroySurfaceControl != null) { + mDeferredDestroySurfaceControl.destroy(); + mDeferredDestroySurfaceControl = null; + } + + runOnUiThread(() -> { + performDrawFinished(); + }); + } + + private void setParentSpaceRectangle(Rect position, long frameNumber) { + ViewRootImpl viewRoot = getViewRootImpl(); + + SurfaceControl.openTransaction(); + try { + if (frameNumber > 0) { + mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber); + } + mSurfaceControl.setPosition(position.left, position.top); + mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth, + 0.0f, 0.0f, + position.height() / (float) mSurfaceHeight); + } finally { + SurfaceControl.closeTransaction(); + } + } + + private Rect mRTLastReportedPosition = new Rect(); + + /** + * Called by native by a Rendering Worker thread to update the window position + * @hide + */ + public final void updateSurfacePosition_renderWorker(long frameNumber, + int left, int top, int right, int bottom) { + if (mSurfaceControl == null) { + return; + } + + // TODO: This is teensy bit racey in that a brand new SurfaceView moving on + // its 2nd frame if RenderThread is running slowly could potentially see + // this as false, enter the branch, get pre-empted, then this comes along + // and reports a new position, then the UI thread resumes and reports + // its position. This could therefore be de-sync'd in that interval, but + // the synchronization would violate the rule that RT must never block + // on the UI thread which would open up potential deadlocks. The risk of + // a single-frame desync is therefore preferable for now. + mRtHandlingPositionUpdates = true; + if (mRTLastReportedPosition.left == left + && mRTLastReportedPosition.top == top + && mRTLastReportedPosition.right == right + && mRTLastReportedPosition.bottom == bottom) { + return; + } + try { + if (DEBUG) { + Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " + + "postion = [%d, %d, %d, %d]", System.identityHashCode(this), + frameNumber, left, top, right, bottom)); + } + mRTLastReportedPosition.set(left, top, right, bottom); + setParentSpaceRectangle(mRTLastReportedPosition, frameNumber); + // Now overwrite mRTLastReportedPosition with our values + } catch (Exception ex) { + Log.e(TAG, "Exception from repositionChild", ex); + } + } + + /** + * Called by native on RenderThread to notify that the view is no longer in the + * draw tree. UI thread is blocked at this point. + * @hide + */ + public final void surfacePositionLost_uiRtSync(long frameNumber) { + if (DEBUG) { + Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d", + System.identityHashCode(this), frameNumber)); + } + mRTLastReportedPosition.setEmpty(); + + if (mSurfaceControl == null) { + return; + } + if (mRtHandlingPositionUpdates) { + mRtHandlingPositionUpdates = false; + // This callback will happen while the UI thread is blocked, so we can + // safely access other member variables at this time. + // So do what the UI thread would have done if RT wasn't handling position + // updates. + if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) { + try { + if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " + + "postion = [%d, %d, %d, %d]", System.identityHashCode(this), + mScreenRect.left, mScreenRect.top, + mScreenRect.right, mScreenRect.bottom)); + setParentSpaceRectangle(mScreenRect, frameNumber); + } catch (Exception ex) { + Log.e(TAG, "Exception configuring surface", ex); + } + } + } + } + + private SurfaceHolder.Callback[] getSurfaceCallbacks() { + SurfaceHolder.Callback callbacks[]; + synchronized (mCallbacks) { + callbacks = new SurfaceHolder.Callback[mCallbacks.size()]; + mCallbacks.toArray(callbacks); + } + return callbacks; + } + + /** + * This method still exists only for compatibility reasons because some applications have relied + * on this method via reflection. See Issue 36345857 for details. + * + * @deprecated No platform code is using this method anymore. + * @hide + */ + @Deprecated + public void setWindowType(int type) { + if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) { + throw new UnsupportedOperationException( + "SurfaceView#setWindowType() has never been a public API."); + } + + if (type == TYPE_APPLICATION_PANEL) { + Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) " + + "just to make the SurfaceView to be placed on top of its window, you must " + + "call setZOrderOnTop(true) instead.", new Throwable()); + setZOrderOnTop(true); + return; + } + Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. " + + "type=" + type, new Throwable()); + } + + private void runOnUiThread(Runnable runnable) { + Handler handler = getHandler(); + if (handler != null && handler.getLooper() != Looper.myLooper()) { + handler.post(runnable); + } else { + runnable.run(); + } + } + + /** + * Check to see if the surface has fixed size dimensions or if the surface's + * dimensions are dimensions are dependent on its current layout. + * + * @return true if the surface has dimensions that are fixed in size + * @hide + */ + public boolean isFixedSize() { + return (mRequestedWidth != -1 || mRequestedHeight != -1); + } + + private boolean isAboveParent() { + return mSubLayer >= 0; + } + + private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() { + private static final String LOG_TAG = "SurfaceHolder"; @Override public boolean isCreating() { - return false; + return mIsCreating; } @Override public void addCallback(Callback callback) { + synchronized (mCallbacks) { + // This is a linear search, but in practice we'll + // have only a couple callbacks, so it doesn't matter. + if (mCallbacks.contains(callback) == false) { + mCallbacks.add(callback); + } + } } @Override public void removeCallback(Callback callback) { + synchronized (mCallbacks) { + mCallbacks.remove(callback); + } } @Override public void setFixedSize(int width, int height) { + if (mRequestedWidth != width || mRequestedHeight != height) { + mRequestedWidth = width; + mRequestedHeight = height; + requestLayout(); + } } @Override public void setSizeFromLayout() { + if (mRequestedWidth != -1 || mRequestedHeight != -1) { + mRequestedWidth = mRequestedHeight = -1; + requestLayout(); + } } @Override public void setFormat(int format) { + // for backward compatibility reason, OPAQUE always + // means 565 for SurfaceView + if (format == PixelFormat.OPAQUE) + format = PixelFormat.RGB_565; + + mRequestedFormat = format; + if (mSurfaceControl != null) { + updateSurface(); + } } + /** + * @deprecated setType is now ignored. + */ @Override - public void setType(int type) { - } + @Deprecated + public void setType(int type) { } @Override public void setKeepScreenOn(boolean screenOn) { + runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn)); } + /** + * Gets a {@link Canvas} for drawing into the SurfaceView's Surface + * + * After drawing into the provided {@link Canvas}, the caller must + * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface. + * + * The caller must redraw the entire surface. + * @return A canvas for drawing into the surface. + */ @Override public Canvas lockCanvas() { - return null; + return internalLockCanvas(null, false); } + /** + * Gets a {@link Canvas} for drawing into the SurfaceView's Surface + * + * After drawing into the provided {@link Canvas}, the caller must + * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface. + * + * @param inOutDirty A rectangle that represents the dirty region that the caller wants + * to redraw. This function may choose to expand the dirty rectangle if for example + * the surface has been resized or if the previous contents of the surface were + * not available. The caller must redraw the entire dirty region as represented + * by the contents of the inOutDirty rectangle upon return from this function. + * The caller may also pass <code>null</code> instead, in the case where the + * entire surface should be redrawn. + * @return A canvas for drawing into the surface. + */ @Override - public Canvas lockCanvas(Rect dirty) { + public Canvas lockCanvas(Rect inOutDirty) { + return internalLockCanvas(inOutDirty, false); + } + + @Override + public Canvas lockHardwareCanvas() { + return internalLockCanvas(null, true); + } + + private Canvas internalLockCanvas(Rect dirty, boolean hardware) { + mSurfaceLock.lock(); + + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped=" + + mDrawingStopped + ", surfaceControl=" + mSurfaceControl); + + Canvas c = null; + if (!mDrawingStopped && mSurfaceControl != null) { + try { + if (hardware) { + c = mSurface.lockHardwareCanvas(); + } else { + c = mSurface.lockCanvas(dirty); + } + } catch (Exception e) { + Log.e(LOG_TAG, "Exception locking surface", e); + } + } + + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c); + if (c != null) { + mLastLockTime = SystemClock.uptimeMillis(); + return c; + } + + // If the Surface is not ready to be drawn, then return null, + // but throttle calls to this function so it isn't called more + // than every 100ms. + long now = SystemClock.uptimeMillis(); + long nextTime = mLastLockTime + 100; + if (nextTime > now) { + try { + Thread.sleep(nextTime-now); + } catch (InterruptedException e) { + } + now = SystemClock.uptimeMillis(); + } + mLastLockTime = now; + mSurfaceLock.unlock(); + return null; } + /** + * Posts the new contents of the {@link Canvas} to the surface and + * releases the {@link Canvas}. + * + * @param canvas The canvas previously obtained from {@link #lockCanvas}. + */ @Override public void unlockCanvasAndPost(Canvas canvas) { + mSurface.unlockCanvasAndPost(canvas); + mSurfaceLock.unlock(); } @Override public Surface getSurface() { - return null; + return mSurface; } @Override public Rect getSurfaceFrame() { - return null; + return mSurfaceFrame; } }; -} + class SurfaceControlWithBackground extends SurfaceControl { + private SurfaceControl mBackgroundControl; + private boolean mOpaque = true; + public boolean mVisible = false; + + public SurfaceControlWithBackground(SurfaceSession s, + String name, int w, int h, int format, int flags) + throws Exception { + super(s, name, w, h, format, flags); + mBackgroundControl = new SurfaceControl(s, "Background for - " + name, w, h, + PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM); + mOpaque = (flags & SurfaceControl.OPAQUE) != 0; + } + + @Override + public void setAlpha(float alpha) { + super.setAlpha(alpha); + mBackgroundControl.setAlpha(alpha); + } + + @Override + public void setLayer(int zorder) { + super.setLayer(zorder); + // -3 is below all other child layers as SurfaceView never goes below -2 + mBackgroundControl.setLayer(-3); + } + + @Override + public void setPosition(float x, float y) { + super.setPosition(x, y); + mBackgroundControl.setPosition(x, y); + } + + @Override + public void setSize(int w, int h) { + super.setSize(w, h); + mBackgroundControl.setSize(w, h); + } + + @Override + public void setWindowCrop(Rect crop) { + super.setWindowCrop(crop); + mBackgroundControl.setWindowCrop(crop); + } + + @Override + public void setFinalCrop(Rect crop) { + super.setFinalCrop(crop); + mBackgroundControl.setFinalCrop(crop); + } + + @Override + public void setLayerStack(int layerStack) { + super.setLayerStack(layerStack); + mBackgroundControl.setLayerStack(layerStack); + } + + @Override + public void setOpaque(boolean isOpaque) { + super.setOpaque(isOpaque); + mOpaque = isOpaque; + updateBackgroundVisibility(); + } + + @Override + public void setSecure(boolean isSecure) { + super.setSecure(isSecure); + } + + @Override + public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { + super.setMatrix(dsdx, dtdx, dsdy, dtdy); + mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy); + } + + @Override + public void hide() { + super.hide(); + mVisible = false; + updateBackgroundVisibility(); + } + + @Override + public void show() { + super.show(); + mVisible = true; + updateBackgroundVisibility(); + } + + @Override + public void destroy() { + super.destroy(); + mBackgroundControl.destroy(); + } + + @Override + public void release() { + super.release(); + mBackgroundControl.release(); + } + + @Override + public void setTransparentRegionHint(Region region) { + super.setTransparentRegionHint(region); + mBackgroundControl.setTransparentRegionHint(region); + } + + @Override + public void deferTransactionUntil(IBinder handle, long frame) { + super.deferTransactionUntil(handle, frame); + mBackgroundControl.deferTransactionUntil(handle, frame); + } + + @Override + public void deferTransactionUntil(Surface barrier, long frame) { + super.deferTransactionUntil(barrier, frame); + mBackgroundControl.deferTransactionUntil(barrier, frame); + } + + void updateBackgroundVisibility() { + if (mOpaque && mVisible) { + mBackgroundControl.show(); + } else { + mBackgroundControl.hide(); + } + } + } +} diff --git a/android/view/View.java b/android/view/View.java index c043dcac..b6be2961 100644 --- a/android/view/View.java +++ b/android/view/View.java @@ -1448,59 +1448,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * <p>Enables low quality mode for the drawing cache.</p> - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public static final int DRAWING_CACHE_QUALITY_LOW = 0x00080000; /** * <p>Enables high quality mode for the drawing cache.</p> - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public static final int DRAWING_CACHE_QUALITY_HIGH = 0x00100000; /** * <p>Enables automatic quality mode for the drawing cache.</p> - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public static final int DRAWING_CACHE_QUALITY_AUTO = 0x00000000; private static final int[] DRAWING_CACHE_QUALITY_FLAGS = { @@ -2342,9 +2300,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static final int PFLAG_HOVERED = 0x10000000; /** - * Flag set by {@link AutofillManager} if it needs to be notified when this view is clicked. + * no longer needed, should be reused */ - private static final int PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK = 0x20000000; + private static final int PFLAG_DOES_NOTHING_REUSE_PLEASE = 0x20000000; /** {@hide} */ static final int PFLAG_ACTIVATED = 0x40000000; @@ -6397,42 +6355,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return null; } - /** @hide */ - public void setNotifyAutofillManagerOnClick(boolean notify) { - if (notify) { - mPrivateFlags |= PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK; - } else { - mPrivateFlags &= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK; - } - } - - private void notifyAutofillManagerOnClick() { - if ((mPrivateFlags & PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK) != 0) { - try { - getAutofillManager().notifyViewClicked(this); - } finally { - // Set it to already called so it's not called twice when called by - // performClickInternal() - mPrivateFlags |= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK; - } - } - } - - /** - * Entry point for {@link #performClick()} - other methods on View should call it instead of - * {@code performClick()} directly to make sure the autofill manager is notified when - * necessary (as subclasses could extend {@code performClick()} without calling the parent's - * method). - */ - private boolean performClickInternal() { - // Must notify autofill manager before performing the click actions to avoid scenarios where - // the app has a click listener that changes the state of views the autofill service might - // be interested on. - notifyAutofillManagerOnClick(); - - return performClick(); - } - /** * Call this view's OnClickListener, if it is defined. Performs all normal * actions associated with clicking: reporting accessibility event, playing @@ -6441,14 +6363,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return True there was an assigned OnClickListener that was called, false * otherwise is returned. */ - // NOTE: other methods on View should not call this method directly, but performClickInternal() - // instead, to guarantee that the autofill manager is notified when necessary (as subclasses - // could extend this method without calling super.performClick()). public boolean performClick() { - // We still need to call this method to handle the cases where performClick() was called - // externally, instead of through performClickInternal() - notifyAutofillManagerOnClick(); - final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { @@ -8992,21 +8907,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #isDrawingCacheEnabled() * * @attr ref android.R.styleable#View_drawingCacheQuality - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated @DrawingCacheQuality public int getDrawingCacheQuality() { return mViewFlags & DRAWING_CACHE_QUALITY_MASK; @@ -9024,21 +8925,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #isDrawingCacheEnabled() * * @attr ref android.R.styleable#View_drawingCacheQuality - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public void setDrawingCacheQuality(@DrawingCacheQuality int quality) { setFlags(quality, DRAWING_CACHE_QUALITY_MASK); } @@ -11546,7 +11433,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: { if (isClickable()) { - performClickInternal(); + performClick(); return true; } } break; @@ -12658,7 +12545,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // This is a tap, so remove the longpress check removeLongPressCallback(); if (!event.isCanceled()) { - return performClickInternal(); + return performClick(); } } } @@ -13230,7 +13117,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { - performClickInternal(); + performClick(); } } } @@ -18216,21 +18103,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #getDrawingCache() * @see #buildDrawingCache() * @see #setLayerType(int, android.graphics.Paint) - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public void setDrawingCacheEnabled(boolean enabled) { mCachingFailed = false; setFlags(enabled ? DRAWING_CACHE_ENABLED : 0, DRAWING_CACHE_ENABLED); @@ -18243,21 +18116,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #setDrawingCacheEnabled(boolean) * @see #getDrawingCache() - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated @ViewDebug.ExportedProperty(category = "drawing") public boolean isDrawingCacheEnabled() { return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED; @@ -18271,11 +18130,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @SuppressWarnings({"UnusedDeclaration"}) public void outputDirtyFlags(String indent, boolean clear, int clearMask) { - Log.d(VIEW_LOG_TAG, indent + this + " DIRTY(" - + (mPrivateFlags & View.PFLAG_DIRTY_MASK) - + ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID(" - + (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID) - + ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")"); + Log.d("View", indent + this + " DIRTY(" + (mPrivateFlags & View.PFLAG_DIRTY_MASK) + + ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID(" + + (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID) + + ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")"); if (clear) { mPrivateFlags &= clearMask; } @@ -18399,21 +18257,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return A non-scaled bitmap representing this view or null if cache is disabled. * * @see #getDrawingCache(boolean) - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public Bitmap getDrawingCache() { return getDrawingCache(false); } @@ -18444,21 +18288,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #isDrawingCacheEnabled() * @see #buildDrawingCache(boolean) * @see #destroyDrawingCache() - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public Bitmap getDrawingCache(boolean autoScale) { if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) { return null; @@ -18478,21 +18308,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #setDrawingCacheEnabled(boolean) * @see #buildDrawingCache() * @see #getDrawingCache() - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public void destroyDrawingCache() { if (mDrawingCache != null) { mDrawingCache.recycle(); @@ -18514,21 +18330,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #setDrawingCacheEnabled(boolean) * @see #buildDrawingCache() * @see #getDrawingCache() - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public void setDrawingCacheBackgroundColor(@ColorInt int color) { if (color != mDrawingCacheBackgroundColor) { mDrawingCacheBackgroundColor = color; @@ -18540,21 +18342,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #setDrawingCacheBackgroundColor(int) * * @return The background color to used for the drawing cache's bitmap - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated @ColorInt public int getDrawingCacheBackgroundColor() { return mDrawingCacheBackgroundColor; @@ -18564,21 +18352,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <p>Calling this method is equivalent to calling <code>buildDrawingCache(false)</code>.</p> * * @see #buildDrawingCache(boolean) - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public void buildDrawingCache() { buildDrawingCache(false); } @@ -18605,21 +18379,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #getDrawingCache() * @see #destroyDrawingCache() - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public void buildDrawingCache(boolean autoScale) { if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ? mDrawingCache == null : mUnscaledDrawingCache == null)) { @@ -20052,7 +19812,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, boolean changed = false; if (DBG) { - Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + "," + Log.d("View", this + " View.setFrame(" + left + "," + top + "," + right + "," + bottom + ")"); } @@ -25098,7 +24858,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private final class PerformClick implements Runnable { @Override public void run() { - performClickInternal(); + performClick(); } } diff --git a/android/view/ViewDebug.java b/android/view/ViewDebug.java index afa94131..3426485e 100644 --- a/android/view/ViewDebug.java +++ b/android/view/ViewDebug.java @@ -528,23 +528,84 @@ public class ViewDebug { /** @hide */ public static void profileViewAndChildren(final View view, BufferedWriter out) throws IOException { - RenderNode node = RenderNode.create("ViewDebug", null); - profileViewAndChildren(view, node, out, true); - node.destroy(); + profileViewAndChildren(view, out, true); } - private static void profileViewAndChildren(View view, RenderNode node, BufferedWriter out, - boolean root) throws IOException { + private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root) + throws IOException { + long durationMeasure = (root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0) - ? profileViewMeasure(view) : 0; + ? profileViewOperation(view, new ViewOperation<Void>() { + public Void[] pre() { + forceLayout(view); + return null; + } + + private void forceLayout(View view) { + view.forceLayout(); + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + final int count = group.getChildCount(); + for (int i = 0; i < count; i++) { + forceLayout(group.getChildAt(i)); + } + } + } + + public void run(Void... data) { + view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec); + } + + public void post(Void... data) { + } + }) + : 0; long durationLayout = (root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0) - ? profileViewLayout(view) : 0; + ? profileViewOperation(view, new ViewOperation<Void>() { + public Void[] pre() { + return null; + } + + public void run(Void... data) { + view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom); + } + + public void post(Void... data) { + } + }) : 0; long durationDraw = (root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0) - ? profileViewDraw(view, node) : 0; + ? profileViewOperation(view, new ViewOperation<Object>() { + public Object[] pre() { + final DisplayMetrics metrics = + (view != null && view.getResources() != null) ? + view.getResources().getDisplayMetrics() : null; + final Bitmap bitmap = metrics != null ? + Bitmap.createBitmap(metrics, metrics.widthPixels, + metrics.heightPixels, Bitmap.Config.RGB_565) : null; + final Canvas canvas = bitmap != null ? new Canvas(bitmap) : null; + return new Object[] { + bitmap, canvas + }; + } + public void run(Object... data) { + if (data[1] != null) { + view.draw((Canvas) data[1]); + } + } + + public void post(Object... data) { + if (data[1] != null) { + ((Canvas) data[1]).setBitmap(null); + } + if (data[0] != null) { + ((Bitmap) data[0]).recycle(); + } + } + }) : 0; out.write(String.valueOf(durationMeasure)); out.write(' '); out.write(String.valueOf(durationLayout)); @@ -555,86 +616,34 @@ public class ViewDebug { ViewGroup group = (ViewGroup) view; final int count = group.getChildCount(); for (int i = 0; i < count; i++) { - profileViewAndChildren(group.getChildAt(i), node, out, false); - } - } - } - - private static long profileViewMeasure(final View view) { - return profileViewOperation(view, new ViewOperation() { - @Override - public void pre() { - forceLayout(view); - } - - private void forceLayout(View view) { - view.forceLayout(); - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - final int count = group.getChildCount(); - for (int i = 0; i < count; i++) { - forceLayout(group.getChildAt(i)); - } - } - } - - @Override - public void run() { - view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec); - } - }); - } - - private static long profileViewLayout(View view) { - return profileViewOperation(view, - () -> view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom)); - } - - private static long profileViewDraw(View view, RenderNode node) { - DisplayMetrics dm = view.getResources().getDisplayMetrics(); - if (dm == null) { - return 0; - } - - if (view.isHardwareAccelerated()) { - DisplayListCanvas canvas = node.start(dm.widthPixels, dm.heightPixels); - try { - return profileViewOperation(view, () -> view.draw(canvas)); - } finally { - node.end(canvas); - } - } else { - Bitmap bitmap = Bitmap.createBitmap( - dm, dm.widthPixels, dm.heightPixels, Bitmap.Config.RGB_565); - Canvas canvas = new Canvas(bitmap); - try { - return profileViewOperation(view, () -> view.draw(canvas)); - } finally { - canvas.setBitmap(null); - bitmap.recycle(); + profileViewAndChildren(group.getChildAt(i), out, false); } } } - interface ViewOperation { - default void pre() {} - - void run(); + interface ViewOperation<T> { + T[] pre(); + void run(T... data); + void post(T... data); } - private static long profileViewOperation(View view, final ViewOperation operation) { + private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) { final CountDownLatch latch = new CountDownLatch(1); final long[] duration = new long[1]; - view.post(() -> { - try { - operation.pre(); - long start = Debug.threadCpuTimeNanos(); - //noinspection unchecked - operation.run(); - duration[0] = Debug.threadCpuTimeNanos() - start; - } finally { - latch.countDown(); + view.post(new Runnable() { + public void run() { + try { + T[] data = operation.pre(); + long start = Debug.threadCpuTimeNanos(); + //noinspection unchecked + operation.run(data); + duration[0] = Debug.threadCpuTimeNanos() - start; + //noinspection unchecked + operation.post(data); + } finally { + latch.countDown(); + } } }); diff --git a/android/view/ViewGroup.java b/android/view/ViewGroup.java index 929beaea..b2e5a163 100644 --- a/android/view/ViewGroup.java +++ b/android/view/ViewGroup.java @@ -421,78 +421,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Used to indicate that no drawing cache should be kept in memory. - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public static final int PERSISTENT_NO_CACHE = 0x0; /** * Used to indicate that the animation drawing cache should be kept in memory. - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public static final int PERSISTENT_ANIMATION_CACHE = 0x1; /** * Used to indicate that the scrolling drawing cache should be kept in memory. - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public static final int PERSISTENT_SCROLLING_CACHE = 0x2; /** * Used to indicate that all drawing caches should be kept in memory. - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public static final int PERSISTENT_ALL_CACHES = 0x3; // Layout Modes @@ -3825,21 +3769,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * Enables or disables the drawing cache for each child of this view group. * * @param enabled true to enable the cache, false to dispose of it - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated protected void setChildrenDrawingCacheEnabled(boolean enabled) { if (enabled || (mPersistentDrawingCache & PERSISTENT_ALL_CACHES) != PERSISTENT_ALL_CACHES) { final View[] children = mChildren; @@ -6401,21 +6331,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @return one or a combination of {@link #PERSISTENT_NO_CACHE}, * {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE} * and {@link #PERSISTENT_ALL_CACHES} - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated @ViewDebug.ExportedProperty(category = "drawing", mapping = { @ViewDebug.IntToString(from = PERSISTENT_NO_CACHE, to = "NONE"), @ViewDebug.IntToString(from = PERSISTENT_ANIMATION_CACHE, to = "ANIMATION"), @@ -6436,21 +6352,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @param drawingCacheToKeep one or a combination of {@link #PERSISTENT_NO_CACHE}, * {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE} * and {@link #PERSISTENT_ALL_CACHES} - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public void setPersistentDrawingCache(int drawingCacheToKeep) { mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES; } diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java index 99438d87..71106ada 100644 --- a/android/view/ViewRootImpl.java +++ b/android/view/ViewRootImpl.java @@ -72,7 +72,6 @@ import android.util.DisplayMetrics; import android.util.Log; import android.util.MergedConfiguration; import android.util.Slog; -import android.util.SparseArray; import android.util.TimeUtils; import android.util.TypedValue; import android.view.Surface.OutOfResourcesException; @@ -1669,6 +1668,8 @@ public final class ViewRootImpl implements ViewParent, host.dispatchAttachedToWindow(mAttachInfo, 0); mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); dispatchApplyInsets(host); + //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn); + } else { desiredWindowWidth = frame.width(); desiredWindowHeight = frame.height(); @@ -2826,7 +2827,7 @@ public final class ViewRootImpl implements ViewParent, try { mWindowDrawCountDown.await(); } catch (InterruptedException e) { - Log.e(mTag, "Window redraw count down interrupted!"); + Log.e(mTag, "Window redraw count down interruped!"); } mWindowDrawCountDown = null; } @@ -2896,6 +2897,8 @@ public final class ViewRootImpl implements ViewParent, final float appScale = mAttachInfo.mApplicationScale; final boolean scalingRequired = mAttachInfo.mScalingRequired; + int resizeAlpha = 0; + final Rect dirty = mDirty; if (mSurfaceHolder != null) { // The app owns the surface, we won't draw. @@ -3466,7 +3469,6 @@ public final class ViewRootImpl implements ViewParent, } void dispatchDetachedFromWindow() { - mFirstInputStage.onDetachedFromWindow(); if (mView != null && mView.mAttachInfo != null) { mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false); mView.dispatchDetachedFromWindow(); @@ -3729,273 +3731,266 @@ public final class ViewRootImpl implements ViewParent, @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_INVALIDATE: - ((View) msg.obj).invalidate(); - break; - case MSG_INVALIDATE_RECT: - final View.AttachInfo.InvalidateInfo info = - (View.AttachInfo.InvalidateInfo) msg.obj; - info.target.invalidate(info.left, info.top, info.right, info.bottom); - info.recycle(); - break; - case MSG_PROCESS_INPUT_EVENTS: - mProcessInputEventsScheduled = false; - doProcessInputEvents(); - break; - case MSG_DISPATCH_APP_VISIBILITY: - handleAppVisibility(msg.arg1 != 0); - break; - case MSG_DISPATCH_GET_NEW_SURFACE: - handleGetNewSurface(); + case MSG_INVALIDATE: + ((View) msg.obj).invalidate(); + break; + case MSG_INVALIDATE_RECT: + final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj; + info.target.invalidate(info.left, info.top, info.right, info.bottom); + info.recycle(); + break; + case MSG_PROCESS_INPUT_EVENTS: + mProcessInputEventsScheduled = false; + doProcessInputEvents(); + break; + case MSG_DISPATCH_APP_VISIBILITY: + handleAppVisibility(msg.arg1 != 0); + break; + case MSG_DISPATCH_GET_NEW_SURFACE: + handleGetNewSurface(); + break; + case MSG_RESIZED: { + // Recycled in the fall through... + SomeArgs args = (SomeArgs) msg.obj; + if (mWinFrame.equals(args.arg1) + && mPendingOverscanInsets.equals(args.arg5) + && mPendingContentInsets.equals(args.arg2) + && mPendingStableInsets.equals(args.arg6) + && mPendingVisibleInsets.equals(args.arg3) + && mPendingOutsets.equals(args.arg7) + && mPendingBackDropFrame.equals(args.arg8) + && args.arg4 == null + && args.argi1 == 0 + && mDisplay.getDisplayId() == args.argi3) { break; - case MSG_RESIZED: { - // Recycled in the fall through... + } + } // fall through... + case MSG_RESIZED_REPORT: + if (mAdded) { SomeArgs args = (SomeArgs) msg.obj; - if (mWinFrame.equals(args.arg1) - && mPendingOverscanInsets.equals(args.arg5) - && mPendingContentInsets.equals(args.arg2) - && mPendingStableInsets.equals(args.arg6) - && mPendingVisibleInsets.equals(args.arg3) - && mPendingOutsets.equals(args.arg7) - && mPendingBackDropFrame.equals(args.arg8) - && args.arg4 == null - && args.argi1 == 0 - && mDisplay.getDisplayId() == args.argi3) { - break; + + final int displayId = args.argi3; + MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4; + final boolean displayChanged = mDisplay.getDisplayId() != displayId; + + if (!mLastReportedMergedConfiguration.equals(mergedConfiguration)) { + // If configuration changed - notify about that and, maybe, about move to + // display. + performConfigurationChange(mergedConfiguration, false /* force */, + displayChanged ? displayId : INVALID_DISPLAY /* same display */); + } else if (displayChanged) { + // Moved to display without config change - report last applied one. + onMovedToDisplay(displayId, mLastConfigurationFromResources); } - } // fall through... - case MSG_RESIZED_REPORT: - if (mAdded) { - SomeArgs args = (SomeArgs) msg.obj; - - final int displayId = args.argi3; - MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4; - final boolean displayChanged = mDisplay.getDisplayId() != displayId; - - if (!mLastReportedMergedConfiguration.equals(mergedConfiguration)) { - // If configuration changed - notify about that and, maybe, - // about move to display. - performConfigurationChange(mergedConfiguration, false /* force */, - displayChanged - ? displayId : INVALID_DISPLAY /* same display */); - } else if (displayChanged) { - // Moved to display without config change - report last applied one. - onMovedToDisplay(displayId, mLastConfigurationFromResources); - } - final boolean framesChanged = !mWinFrame.equals(args.arg1) - || !mPendingOverscanInsets.equals(args.arg5) - || !mPendingContentInsets.equals(args.arg2) - || !mPendingStableInsets.equals(args.arg6) - || !mPendingVisibleInsets.equals(args.arg3) - || !mPendingOutsets.equals(args.arg7); - - mWinFrame.set((Rect) args.arg1); - mPendingOverscanInsets.set((Rect) args.arg5); - mPendingContentInsets.set((Rect) args.arg2); - mPendingStableInsets.set((Rect) args.arg6); - mPendingVisibleInsets.set((Rect) args.arg3); - mPendingOutsets.set((Rect) args.arg7); - mPendingBackDropFrame.set((Rect) args.arg8); - mForceNextWindowRelayout = args.argi1 != 0; - mPendingAlwaysConsumeNavBar = args.argi2 != 0; - - args.recycle(); - - if (msg.what == MSG_RESIZED_REPORT) { - reportNextDraw(); - } + final boolean framesChanged = !mWinFrame.equals(args.arg1) + || !mPendingOverscanInsets.equals(args.arg5) + || !mPendingContentInsets.equals(args.arg2) + || !mPendingStableInsets.equals(args.arg6) + || !mPendingVisibleInsets.equals(args.arg3) + || !mPendingOutsets.equals(args.arg7); + + mWinFrame.set((Rect) args.arg1); + mPendingOverscanInsets.set((Rect) args.arg5); + mPendingContentInsets.set((Rect) args.arg2); + mPendingStableInsets.set((Rect) args.arg6); + mPendingVisibleInsets.set((Rect) args.arg3); + mPendingOutsets.set((Rect) args.arg7); + mPendingBackDropFrame.set((Rect) args.arg8); + mForceNextWindowRelayout = args.argi1 != 0; + mPendingAlwaysConsumeNavBar = args.argi2 != 0; - if (mView != null && framesChanged) { - forceLayout(mView); - } - requestLayout(); + args.recycle(); + + if (msg.what == MSG_RESIZED_REPORT) { + reportNextDraw(); } - break; - case MSG_WINDOW_MOVED: - if (mAdded) { - final int w = mWinFrame.width(); - final int h = mWinFrame.height(); - final int l = msg.arg1; - final int t = msg.arg2; - mWinFrame.left = l; - mWinFrame.right = l + w; - mWinFrame.top = t; - mWinFrame.bottom = t + h; - - mPendingBackDropFrame.set(mWinFrame); - maybeHandleWindowMove(mWinFrame); + + if (mView != null && framesChanged) { + forceLayout(mView); } - break; - case MSG_WINDOW_FOCUS_CHANGED: { - final boolean hasWindowFocus = msg.arg1 != 0; - if (mAdded) { - mAttachInfo.mHasWindowFocus = hasWindowFocus; - - profileRendering(hasWindowFocus); - - if (hasWindowFocus) { - boolean inTouchMode = msg.arg2 != 0; - ensureTouchModeLocally(inTouchMode); - if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) { - mFullRedrawNeeded = true; + requestLayout(); + } + break; + case MSG_WINDOW_MOVED: + if (mAdded) { + final int w = mWinFrame.width(); + final int h = mWinFrame.height(); + final int l = msg.arg1; + final int t = msg.arg2; + mWinFrame.left = l; + mWinFrame.right = l + w; + mWinFrame.top = t; + mWinFrame.bottom = t + h; + + mPendingBackDropFrame.set(mWinFrame); + maybeHandleWindowMove(mWinFrame); + } + break; + case MSG_WINDOW_FOCUS_CHANGED: { + if (mAdded) { + boolean hasWindowFocus = msg.arg1 != 0; + mAttachInfo.mHasWindowFocus = hasWindowFocus; + + profileRendering(hasWindowFocus); + + if (hasWindowFocus) { + boolean inTouchMode = msg.arg2 != 0; + ensureTouchModeLocally(inTouchMode); + + if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()){ + mFullRedrawNeeded = true; + try { + final WindowManager.LayoutParams lp = mWindowAttributes; + final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null; + mAttachInfo.mThreadedRenderer.initializeIfNeeded( + mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets); + } catch (OutOfResourcesException e) { + Log.e(mTag, "OutOfResourcesException locking surface", e); try { - final WindowManager.LayoutParams lp = mWindowAttributes; - final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null; - mAttachInfo.mThreadedRenderer.initializeIfNeeded( - mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets); - } catch (OutOfResourcesException e) { - Log.e(mTag, "OutOfResourcesException locking surface", e); - try { - if (!mWindowSession.outOfMemory(mWindow)) { - Slog.w(mTag, "No processes killed for memory;" - + " killing self"); - Process.killProcess(Process.myPid()); - } - } catch (RemoteException ex) { + if (!mWindowSession.outOfMemory(mWindow)) { + Slog.w(mTag, "No processes killed for memory; killing self"); + Process.killProcess(Process.myPid()); } - // Retry in a bit. - sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), - 500); - return; + } catch (RemoteException ex) { } + // Retry in a bit. + sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), 500); + return; } } + } - mLastWasImTarget = WindowManager.LayoutParams - .mayUseInputMethod(mWindowAttributes.flags); + mLastWasImTarget = WindowManager.LayoutParams + .mayUseInputMethod(mWindowAttributes.flags); - InputMethodManager imm = InputMethodManager.peekInstance(); - if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { - imm.onPreWindowFocus(mView, hasWindowFocus); - } - if (mView != null) { - mAttachInfo.mKeyDispatchState.reset(); - mView.dispatchWindowFocusChanged(hasWindowFocus); - mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { + imm.onPreWindowFocus(mView, hasWindowFocus); + } + if (mView != null) { + mAttachInfo.mKeyDispatchState.reset(); + mView.dispatchWindowFocusChanged(hasWindowFocus); + mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); - if (mAttachInfo.mTooltipHost != null) { - mAttachInfo.mTooltipHost.hideTooltip(); - } + if (mAttachInfo.mTooltipHost != null) { + mAttachInfo.mTooltipHost.hideTooltip(); } + } - // Note: must be done after the focus change callbacks, - // so all of the view state is set up correctly. - if (hasWindowFocus) { - if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { - imm.onPostWindowFocus(mView, mView.findFocus(), - mWindowAttributes.softInputMode, - !mHasHadWindowFocus, mWindowAttributes.flags); - } - // Clear the forward bit. We can just do this directly, since - // the window manager doesn't care about it. - mWindowAttributes.softInputMode &= + // Note: must be done after the focus change callbacks, + // so all of the view state is set up correctly. + if (hasWindowFocus) { + if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { + imm.onPostWindowFocus(mView, mView.findFocus(), + mWindowAttributes.softInputMode, + !mHasHadWindowFocus, mWindowAttributes.flags); + } + // Clear the forward bit. We can just do this directly, since + // the window manager doesn't care about it. + mWindowAttributes.softInputMode &= + ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; + ((WindowManager.LayoutParams)mView.getLayoutParams()) + .softInputMode &= ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; - ((WindowManager.LayoutParams) mView.getLayoutParams()) - .softInputMode &= - ~WindowManager.LayoutParams - .SOFT_INPUT_IS_FORWARD_NAVIGATION; - mHasHadWindowFocus = true; - } else { - if (mPointerCapture) { - handlePointerCaptureChanged(false); - } + mHasHadWindowFocus = true; + } else { + if (mPointerCapture) { + handlePointerCaptureChanged(false); } } - mFirstInputStage.onWindowFocusChanged(hasWindowFocus); - } break; - case MSG_DIE: - doDie(); - break; - case MSG_DISPATCH_INPUT_EVENT: { - SomeArgs args = (SomeArgs) msg.obj; - InputEvent event = (InputEvent) args.arg1; - InputEventReceiver receiver = (InputEventReceiver) args.arg2; - enqueueInputEvent(event, receiver, 0, true); - args.recycle(); - } break; - case MSG_SYNTHESIZE_INPUT_EVENT: { - InputEvent event = (InputEvent) msg.obj; - enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true); - } break; - case MSG_DISPATCH_KEY_FROM_IME: { - if (LOCAL_LOGV) { - Log.v(TAG, "Dispatching key " + msg.obj + " from IME to " + mView); - } - KeyEvent event = (KeyEvent) msg.obj; - if ((event.getFlags() & KeyEvent.FLAG_FROM_SYSTEM) != 0) { - // The IME is trying to say this event is from the - // system! Bad bad bad! - //noinspection UnusedAssignment - event = KeyEvent.changeFlags(event, - event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM); - } - enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true); - } break; - case MSG_CHECK_FOCUS: { - InputMethodManager imm = InputMethodManager.peekInstance(); - if (imm != null) { - imm.checkFocus(); - } - } break; - case MSG_CLOSE_SYSTEM_DIALOGS: { - if (mView != null) { - mView.onCloseSystemDialogs((String) msg.obj); - } - } break; - case MSG_DISPATCH_DRAG_EVENT: { - } // fall through - case MSG_DISPATCH_DRAG_LOCATION_EVENT: { - DragEvent event = (DragEvent) msg.obj; - // only present when this app called startDrag() - event.mLocalState = mLocalDragState; - handleDragEvent(event); - } break; - case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: { - handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj); - } break; - case MSG_UPDATE_CONFIGURATION: { - Configuration config = (Configuration) msg.obj; - if (config.isOtherSeqNewer( - mLastReportedMergedConfiguration.getMergedConfiguration())) { - // If we already have a newer merged config applied - use its global part. - config = mLastReportedMergedConfiguration.getGlobalConfiguration(); - } + } + } break; + case MSG_DIE: + doDie(); + break; + case MSG_DISPATCH_INPUT_EVENT: { + SomeArgs args = (SomeArgs)msg.obj; + InputEvent event = (InputEvent)args.arg1; + InputEventReceiver receiver = (InputEventReceiver)args.arg2; + enqueueInputEvent(event, receiver, 0, true); + args.recycle(); + } break; + case MSG_SYNTHESIZE_INPUT_EVENT: { + InputEvent event = (InputEvent)msg.obj; + enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true); + } break; + case MSG_DISPATCH_KEY_FROM_IME: { + if (LOCAL_LOGV) Log.v( + TAG, "Dispatching key " + + msg.obj + " from IME to " + mView); + KeyEvent event = (KeyEvent)msg.obj; + if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) { + // The IME is trying to say this event is from the + // system! Bad bad bad! + //noinspection UnusedAssignment + event = KeyEvent.changeFlags(event, event.getFlags() & + ~KeyEvent.FLAG_FROM_SYSTEM); + } + enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true); + } break; + case MSG_CHECK_FOCUS: { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + imm.checkFocus(); + } + } break; + case MSG_CLOSE_SYSTEM_DIALOGS: { + if (mView != null) { + mView.onCloseSystemDialogs((String)msg.obj); + } + } break; + case MSG_DISPATCH_DRAG_EVENT: + case MSG_DISPATCH_DRAG_LOCATION_EVENT: { + DragEvent event = (DragEvent)msg.obj; + event.mLocalState = mLocalDragState; // only present when this app called startDrag() + handleDragEvent(event); + } break; + case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: { + handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj); + } break; + case MSG_UPDATE_CONFIGURATION: { + Configuration config = (Configuration) msg.obj; + if (config.isOtherSeqNewer( + mLastReportedMergedConfiguration.getMergedConfiguration())) { + // If we already have a newer merged config applied - use its global part. + config = mLastReportedMergedConfiguration.getGlobalConfiguration(); + } - // Use the newer global config and last reported override config. - mPendingMergedConfiguration.setConfiguration(config, - mLastReportedMergedConfiguration.getOverrideConfiguration()); + // Use the newer global config and last reported override config. + mPendingMergedConfiguration.setConfiguration(config, + mLastReportedMergedConfiguration.getOverrideConfiguration()); - performConfigurationChange(mPendingMergedConfiguration, false /* force */, - INVALID_DISPLAY /* same display */); - } break; - case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: { - setAccessibilityFocus(null, null); - } break; - case MSG_INVALIDATE_WORLD: { - if (mView != null) { - invalidateWorld(mView); - } - } break; - case MSG_DISPATCH_WINDOW_SHOWN: { - handleDispatchWindowShown(); - } break; - case MSG_REQUEST_KEYBOARD_SHORTCUTS: { - final IResultReceiver receiver = (IResultReceiver) msg.obj; - final int deviceId = msg.arg1; - handleRequestKeyboardShortcuts(receiver, deviceId); - } break; - case MSG_UPDATE_POINTER_ICON: { - MotionEvent event = (MotionEvent) msg.obj; - resetPointerIcon(event); - } break; - case MSG_POINTER_CAPTURE_CHANGED: { - final boolean hasCapture = msg.arg1 != 0; - handlePointerCaptureChanged(hasCapture); - } break; - case MSG_DRAW_FINISHED: { - pendingDrawFinished(); - } break; + performConfigurationChange(mPendingMergedConfiguration, false /* force */, + INVALID_DISPLAY /* same display */); + } break; + case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: { + setAccessibilityFocus(null, null); + } break; + case MSG_INVALIDATE_WORLD: { + if (mView != null) { + invalidateWorld(mView); + } + } break; + case MSG_DISPATCH_WINDOW_SHOWN: { + handleDispatchWindowShown(); + } break; + case MSG_REQUEST_KEYBOARD_SHORTCUTS: { + final IResultReceiver receiver = (IResultReceiver) msg.obj; + final int deviceId = msg.arg1; + handleRequestKeyboardShortcuts(receiver, deviceId); + } break; + case MSG_UPDATE_POINTER_ICON: { + MotionEvent event = (MotionEvent) msg.obj; + resetPointerIcon(event); + } break; + case MSG_POINTER_CAPTURE_CHANGED: { + final boolean hasCapture = msg.arg1 != 0; + handlePointerCaptureChanged(hasCapture); + } break; + case MSG_DRAW_FINISHED: { + pendingDrawFinished(); + } break; } } } @@ -4208,18 +4203,6 @@ public final class ViewRootImpl implements ViewParent, } } - protected void onWindowFocusChanged(boolean hasWindowFocus) { - if (mNext != null) { - mNext.onWindowFocusChanged(hasWindowFocus); - } - } - - protected void onDetachedFromWindow() { - if (mNext != null) { - mNext.onDetachedFromWindow(); - } - } - protected boolean shouldDropInputEvent(QueuedInputEvent q) { if (mView == null || !mAdded) { Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent); @@ -4973,9 +4956,9 @@ public final class ViewRootImpl implements ViewParent, final MotionEvent event = (MotionEvent)q.mEvent; final int source = event.getSource(); if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { - mTrackball.cancel(); + mTrackball.cancel(event); } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { - mJoystick.cancel(); + mJoystick.cancel(event); } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION) == InputDevice.SOURCE_TOUCH_NAVIGATION) { mTouchNavigation.cancel(event); @@ -4984,18 +4967,6 @@ public final class ViewRootImpl implements ViewParent, } super.onDeliverToNext(q); } - - @Override - protected void onWindowFocusChanged(boolean hasWindowFocus) { - if (!hasWindowFocus) { - mJoystick.cancel(); - } - } - - @Override - protected void onDetachedFromWindow() { - mJoystick.cancel(); - } } /** @@ -5108,7 +5079,7 @@ public final class ViewRootImpl implements ViewParent, } } - public void cancel() { + public void cancel(MotionEvent event) { mLastTime = Integer.MIN_VALUE; // If we reach this, we consumed a trackball event. @@ -5292,11 +5263,14 @@ public final class ViewRootImpl implements ViewParent, * Creates dpad events from unhandled joystick movements. */ final class SyntheticJoystickHandler extends Handler { + private final static String TAG = "SyntheticJoystickHandler"; private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 1; private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 2; - private final JoystickAxesState mJoystickAxesState = new JoystickAxesState(); - private final SparseArray<KeyEvent> mDeviceKeyEvents = new SparseArray<>(); + private int mLastXDirection; + private int mLastYDirection; + private int mLastXKeyCode; + private int mLastYKeyCode; public SyntheticJoystickHandler() { super(true); @@ -5307,10 +5281,11 @@ public final class ViewRootImpl implements ViewParent, switch (msg.what) { case MSG_ENQUEUE_X_AXIS_KEY_REPEAT: case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: { + KeyEvent oldEvent = (KeyEvent)msg.obj; + KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent, + SystemClock.uptimeMillis(), + oldEvent.getRepeatCount() + 1); if (mAttachInfo.mHasWindowFocus) { - KeyEvent oldEvent = (KeyEvent) msg.obj; - KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent, - SystemClock.uptimeMillis(), oldEvent.getRepeatCount() + 1); enqueueInputEvent(e); Message m = obtainMessage(msg.what, e); m.setAsynchronous(true); @@ -5322,176 +5297,97 @@ public final class ViewRootImpl implements ViewParent, public void process(MotionEvent event) { switch(event.getActionMasked()) { - case MotionEvent.ACTION_CANCEL: - cancel(); - break; - case MotionEvent.ACTION_MOVE: - update(event); - break; - default: - Log.w(mTag, "Unexpected action: " + event.getActionMasked()); + case MotionEvent.ACTION_CANCEL: + cancel(event); + break; + case MotionEvent.ACTION_MOVE: + update(event, true); + break; + default: + Log.w(mTag, "Unexpected action: " + event.getActionMasked()); } } - private void cancel() { + private void cancel(MotionEvent event) { removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT); removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT); - for (int i = 0; i < mDeviceKeyEvents.size(); i++) { - final KeyEvent keyEvent = mDeviceKeyEvents.valueAt(i); - if (keyEvent != null) { - enqueueInputEvent(KeyEvent.changeTimeRepeat(keyEvent, - SystemClock.uptimeMillis(), 0)); - } - } - mDeviceKeyEvents.clear(); - mJoystickAxesState.resetState(); - } - - private void update(MotionEvent event) { - final int historySize = event.getHistorySize(); - for (int h = 0; h < historySize; h++) { - final long time = event.getHistoricalEventTime(h); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_X, - event.getHistoricalAxisValue(MotionEvent.AXIS_X, 0, h)); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_Y, - event.getHistoricalAxisValue(MotionEvent.AXIS_Y, 0, h)); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_X, - event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, 0, h)); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y, - event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, 0, h)); - } - final long time = event.getEventTime(); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_X, - event.getAxisValue(MotionEvent.AXIS_X)); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_Y, - event.getAxisValue(MotionEvent.AXIS_Y)); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_X, - event.getAxisValue(MotionEvent.AXIS_HAT_X)); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y, - event.getAxisValue(MotionEvent.AXIS_HAT_Y)); + update(event, false); } - final class JoystickAxesState { - // State machine: from neutral state (no button press) can go into - // button STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state, emitting an ACTION_DOWN event. - // From STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state can go into neutral state, - // emitting an ACTION_UP event. - private static final int STATE_UP_OR_LEFT = -1; - private static final int STATE_NEUTRAL = 0; - private static final int STATE_DOWN_OR_RIGHT = 1; - - final int[] mAxisStatesHat = {STATE_NEUTRAL, STATE_NEUTRAL}; // {AXIS_HAT_X, AXIS_HAT_Y} - final int[] mAxisStatesStick = {STATE_NEUTRAL, STATE_NEUTRAL}; // {AXIS_X, AXIS_Y} - - void resetState() { - mAxisStatesHat[0] = STATE_NEUTRAL; - mAxisStatesHat[1] = STATE_NEUTRAL; - mAxisStatesStick[0] = STATE_NEUTRAL; - mAxisStatesStick[1] = STATE_NEUTRAL; - } - - void updateStateForAxis(MotionEvent event, long time, int axis, float value) { - // Emit KeyEvent if necessary - // axis can be AXIS_X, AXIS_Y, AXIS_HAT_X, AXIS_HAT_Y - final int axisStateIndex; - final int repeatMessage; - if (isXAxis(axis)) { - axisStateIndex = 0; - repeatMessage = MSG_ENQUEUE_X_AXIS_KEY_REPEAT; - } else if (isYAxis(axis)) { - axisStateIndex = 1; - repeatMessage = MSG_ENQUEUE_Y_AXIS_KEY_REPEAT; - } else { - Log.e(mTag, "Unexpected axis " + axis + " in updateStateForAxis!"); - return; - } - final int newState = joystickAxisValueToState(value); + private void update(MotionEvent event, boolean synthesizeNewKeys) { + final long time = event.getEventTime(); + final int metaState = event.getMetaState(); + final int deviceId = event.getDeviceId(); + final int source = event.getSource(); - final int currentState; - if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) { - currentState = mAxisStatesStick[axisStateIndex]; - } else { - currentState = mAxisStatesHat[axisStateIndex]; - } + int xDirection = joystickAxisValueToDirection( + event.getAxisValue(MotionEvent.AXIS_HAT_X)); + if (xDirection == 0) { + xDirection = joystickAxisValueToDirection(event.getX()); + } - if (currentState == newState) { - return; + int yDirection = joystickAxisValueToDirection( + event.getAxisValue(MotionEvent.AXIS_HAT_Y)); + if (yDirection == 0) { + yDirection = joystickAxisValueToDirection(event.getY()); + } + + if (xDirection != mLastXDirection) { + if (mLastXKeyCode != 0) { + removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT); + enqueueInputEvent(new KeyEvent(time, time, + KeyEvent.ACTION_UP, mLastXKeyCode, 0, metaState, + deviceId, 0, KeyEvent.FLAG_FALLBACK, source)); + mLastXKeyCode = 0; } - final int metaState = event.getMetaState(); - final int deviceId = event.getDeviceId(); - final int source = event.getSource(); + mLastXDirection = xDirection; - if (currentState == STATE_DOWN_OR_RIGHT || currentState == STATE_UP_OR_LEFT) { - // send a button release event - final int keyCode = joystickAxisAndStateToKeycode(axis, currentState); - if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { - enqueueInputEvent(new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode, - 0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source)); - // remove the corresponding pending UP event if focus lost/view detached - mDeviceKeyEvents.put(deviceId, null); - } - removeMessages(repeatMessage); + if (xDirection != 0 && synthesizeNewKeys) { + mLastXKeyCode = xDirection > 0 + ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT; + final KeyEvent e = new KeyEvent(time, time, + KeyEvent.ACTION_DOWN, mLastXKeyCode, 0, metaState, + deviceId, 0, KeyEvent.FLAG_FALLBACK, source); + enqueueInputEvent(e); + Message m = obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e); + m.setAsynchronous(true); + sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout()); } + } - if (newState == STATE_DOWN_OR_RIGHT || newState == STATE_UP_OR_LEFT) { - // send a button down event - final int keyCode = joystickAxisAndStateToKeycode(axis, newState); - if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { - KeyEvent keyEvent = new KeyEvent(time, time, KeyEvent.ACTION_DOWN, keyCode, - 0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source); - enqueueInputEvent(keyEvent); - Message m = obtainMessage(repeatMessage, keyEvent); - m.setAsynchronous(true); - sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout()); - // store the corresponding ACTION_UP event so that it can be sent - // if focus is lost or root view is removed - mDeviceKeyEvents.put(deviceId, - new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode, - 0, metaState, deviceId, 0, - KeyEvent.FLAG_FALLBACK | KeyEvent.FLAG_CANCELED, - source)); - } - } - if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) { - mAxisStatesStick[axisStateIndex] = newState; - } else { - mAxisStatesHat[axisStateIndex] = newState; + if (yDirection != mLastYDirection) { + if (mLastYKeyCode != 0) { + removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT); + enqueueInputEvent(new KeyEvent(time, time, + KeyEvent.ACTION_UP, mLastYKeyCode, 0, metaState, + deviceId, 0, KeyEvent.FLAG_FALLBACK, source)); + mLastYKeyCode = 0; } - } - private boolean isXAxis(int axis) { - return axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_HAT_X; - } - private boolean isYAxis(int axis) { - return axis == MotionEvent.AXIS_Y || axis == MotionEvent.AXIS_HAT_Y; - } + mLastYDirection = yDirection; - private int joystickAxisAndStateToKeycode(int axis, int state) { - if (isXAxis(axis) && state == STATE_UP_OR_LEFT) { - return KeyEvent.KEYCODE_DPAD_LEFT; - } - if (isXAxis(axis) && state == STATE_DOWN_OR_RIGHT) { - return KeyEvent.KEYCODE_DPAD_RIGHT; - } - if (isYAxis(axis) && state == STATE_UP_OR_LEFT) { - return KeyEvent.KEYCODE_DPAD_UP; + if (yDirection != 0 && synthesizeNewKeys) { + mLastYKeyCode = yDirection > 0 + ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP; + final KeyEvent e = new KeyEvent(time, time, + KeyEvent.ACTION_DOWN, mLastYKeyCode, 0, metaState, + deviceId, 0, KeyEvent.FLAG_FALLBACK, source); + enqueueInputEvent(e); + Message m = obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e); + m.setAsynchronous(true); + sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout()); } - if (isYAxis(axis) && state == STATE_DOWN_OR_RIGHT) { - return KeyEvent.KEYCODE_DPAD_DOWN; - } - Log.e(mTag, "Unknown axis " + axis + " or direction " + state); - return KeyEvent.KEYCODE_UNKNOWN; // should never happen } + } - private int joystickAxisValueToState(float value) { - if (value >= 0.5f) { - return STATE_DOWN_OR_RIGHT; - } else if (value <= -0.5f) { - return STATE_UP_OR_LEFT; - } else { - return STATE_NEUTRAL; - } + private int joystickAxisValueToDirection(float value) { + if (value >= 0.5f) { + return 1; + } else if (value <= -0.5f) { + return -1; + } else { + return 0; } } } @@ -6212,6 +6108,7 @@ public final class ViewRootImpl implements ViewParent, if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params); } + //Log.d(mTag, ">>>>>> CALLING relayout"); if (params != null && mOrigWindowType != params.type) { // For compatibility with old apps, don't crash here. if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { @@ -6232,6 +6129,7 @@ public final class ViewRootImpl implements ViewParent, mPendingAlwaysConsumeNavBar = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR) != 0; + //Log.d(mTag, "<<<<<< BACK FROM relayout"); if (restore) { params.restore(); } diff --git a/android/view/ViewStructure.java b/android/view/ViewStructure.java index 309366c6..f671c349 100644 --- a/android/view/ViewStructure.java +++ b/android/view/ViewStructure.java @@ -365,30 +365,6 @@ public abstract class ViewStructure { public abstract void setDataIsSensitive(boolean sensitive); /** - * Sets the minimum width in ems of the text associated with this view, when supported. - * - * <p>Should only be set when the node is used for autofill purposes - it will be ignored - * when used for Assist. - */ - public abstract void setMinTextEms(int minEms); - - /** - * Sets the maximum width in ems of the text associated with this view, when supported. - * - * <p>Should only be set when the node is used for autofill purposes - it will be ignored - * when used for Assist. - */ - public abstract void setMaxTextEms(int maxEms); - - /** - * Sets the maximum length of the text associated with this view, when supported. - * - * <p>Should only be set when the node is used for autofill purposes - it will be ignored - * when used for Assist. - */ - public abstract void setMaxTextLength(int maxLength); - - /** * Call when done populating a {@link ViewStructure} returned by * {@link #asyncNewChild}. */ diff --git a/android/view/WindowManagerInternal.java b/android/view/WindowManagerInternal.java index 69cc1002..98f8dc8e 100644 --- a/android/view/WindowManagerInternal.java +++ b/android/view/WindowManagerInternal.java @@ -335,8 +335,8 @@ public abstract class WindowManagerInternal { public abstract void setOnHardKeyboardStatusChangeListener( OnHardKeyboardStatusChangeListener listener); - /** Returns true if a stack in the windowing mode is currently visible. */ - public abstract boolean isStackVisible(int windowingMode); + /** Returns true if the stack with the input Id is currently visible. */ + public abstract boolean isStackVisible(int stackId); /** * @return True if and only if the docked divider is currently in resize mode. diff --git a/android/view/WindowManagerPolicy.java b/android/view/WindowManagerPolicy.java index 137e551d..da72535d 100644 --- a/android/view/WindowManagerPolicy.java +++ b/android/view/WindowManagerPolicy.java @@ -467,8 +467,11 @@ public interface WindowManagerPolicy { */ public boolean isDimming(); - /** @return the current windowing mode of this window. */ - int getWindowingMode(); + /** + * @return the stack id this windows belongs to, or {@link StackId#INVALID_STACK_ID} if + * not attached to any stack. + */ + int getStackId(); /** * Returns true if the window is current in multi-windowing mode. i.e. it shares the diff --git a/android/view/accessibility/AccessibilityCache.java b/android/view/accessibility/AccessibilityCache.java index d7851171..0f21c5c8 100644 --- a/android/view/accessibility/AccessibilityCache.java +++ b/android/view/accessibility/AccessibilityCache.java @@ -329,6 +329,8 @@ public final class AccessibilityCache { final long oldParentId = oldInfo.getParentNodeId(); if (info.getParentNodeId() != oldParentId) { clearSubTreeLocked(windowId, oldParentId); + } else { + oldInfo.recycle(); } } diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java index 11cb046a..0b9bc576 100644 --- a/android/view/accessibility/AccessibilityManager.java +++ b/android/view/accessibility/AccessibilityManager.java @@ -16,46 +16,152 @@ package android.view.accessibility; +import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME; + +import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SystemService; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.content.res.Resources; +import android.os.Binder; import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.Log; +import android.util.SparseArray; import android.view.IWindow; import android.view.View; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IntPair; + +import java.util.ArrayList; import java.util.Collections; import java.util.List; /** - * System level service that serves as an event dispatch for {@link AccessibilityEvent}s. - * Such events are generated when something notable happens in the user interface, + * System level service that serves as an event dispatch for {@link AccessibilityEvent}s, + * and provides facilities for querying the accessibility state of the system. + * Accessibility events are generated when something notable happens in the user interface, * for example an {@link android.app.Activity} starts, the focus or selection of a * {@link android.view.View} changes etc. Parties interested in handling accessibility * events implement and register an accessibility service which extends - * {@code android.accessibilityservice.AccessibilityService}. + * {@link android.accessibilityservice.AccessibilityService}. * * @see AccessibilityEvent - * @see android.content.Context#getSystemService + * @see AccessibilityNodeInfo + * @see android.accessibilityservice.AccessibilityService + * @see Context#getSystemService + * @see Context#ACCESSIBILITY_SERVICE */ -@SuppressWarnings("UnusedDeclaration") +@SystemService(Context.ACCESSIBILITY_SERVICE) public final class AccessibilityManager { + private static final boolean DEBUG = false; + + private static final String LOG_TAG = "AccessibilityManager"; + + /** @hide */ + public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001; + + /** @hide */ + public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002; + + /** @hide */ + public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004; + + /** @hide */ + public static final int DALTONIZER_DISABLED = -1; + + /** @hide */ + public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0; + + /** @hide */ + public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12; + + /** @hide */ + public static final int AUTOCLICK_DELAY_DEFAULT = 600; + + /** + * Activity action: Launch UI to manage which accessibility service or feature is assigned + * to the navigation bar Accessibility button. + * <p> + * Input: Nothing. + * </p> + * <p> + * Output: Nothing. + * </p> + * + * @hide + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON = + "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON"; + + static final Object sInstanceSync = new Object(); + + private static AccessibilityManager sInstance; + + private final Object mLock = new Object(); + + private IAccessibilityManager mService; + + final int mUserId; + + final Handler mHandler; + + final Handler.Callback mCallback; + + boolean mIsEnabled; - private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0); + int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK; + boolean mIsTouchExplorationEnabled; + + boolean mIsHighTextContrastEnabled; + + private final ArrayMap<AccessibilityStateChangeListener, Handler> + mAccessibilityStateChangeListeners = new ArrayMap<>(); + + private final ArrayMap<TouchExplorationStateChangeListener, Handler> + mTouchExplorationStateChangeListeners = new ArrayMap<>(); + + private final ArrayMap<HighTextContrastChangeListener, Handler> + mHighTextContrastStateChangeListeners = new ArrayMap<>(); + + private final ArrayMap<AccessibilityServicesStateChangeListener, Handler> + mServicesStateChangeListeners = new ArrayMap<>(); + + /** + * Map from a view's accessibility id to the list of request preparers set for that view + */ + private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists; /** - * Listener for the accessibility state. + * Listener for the system accessibility state. To listen for changes to the + * accessibility state on the device, implement this interface and register + * it with the system by calling {@link #addAccessibilityStateChangeListener}. */ public interface AccessibilityStateChangeListener { /** - * Called back on change in the accessibility state. + * Called when the accessibility enabled state changes. * * @param enabled Whether accessibility is enabled. */ - public void onAccessibilityStateChanged(boolean enabled); + void onAccessibilityStateChanged(boolean enabled); } /** @@ -71,7 +177,24 @@ public final class AccessibilityManager { * * @param enabled Whether touch exploration is enabled. */ - public void onTouchExplorationStateChanged(boolean enabled); + void onTouchExplorationStateChanged(boolean enabled); + } + + /** + * Listener for changes to the state of accessibility services. Changes include services being + * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service. + * {@see #addAccessibilityServicesStateChangeListener}. + * + * @hide + */ + public interface AccessibilityServicesStateChangeListener { + + /** + * Called when the state of accessibility services changes. + * + * @param manager The manager that is calling back + */ + void onAccessibilityServicesStateChanged(AccessibilityManager manager); } /** @@ -79,6 +202,8 @@ public final class AccessibilityManager { * the high text contrast state on the device, implement this interface and * register it with the system by calling * {@link #addHighTextContrastStateChangeListener}. + * + * @hide */ public interface HighTextContrastChangeListener { @@ -87,26 +212,72 @@ public final class AccessibilityManager { * * @param enabled Whether high text contrast is enabled. */ - public void onHighTextContrastStateChanged(boolean enabled); + void onHighTextContrastStateChanged(boolean enabled); } private final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() { - public void setState(int state) { - } + @Override + public void setState(int state) { + // We do not want to change this immediately as the application may + // have already checked that accessibility is on and fired an event, + // that is now propagating up the view tree, Hence, if accessibility + // is now off an exception will be thrown. We want to have the exception + // enforcement to guard against apps that fire unnecessary accessibility + // events when accessibility is off. + mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget(); + } - public void notifyServicesStateChanged() { + @Override + public void notifyServicesStateChanged() { + final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners; + synchronized (mLock) { + if (mServicesStateChangeListeners.isEmpty()) { + return; } + listeners = new ArrayMap<>(mServicesStateChangeListeners); + } - public void setRelevantEventTypes(int eventTypes) { - } - }; + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + final AccessibilityServicesStateChangeListener listener = + mServicesStateChangeListeners.keyAt(i); + mServicesStateChangeListeners.valueAt(i).post(() -> listener + .onAccessibilityServicesStateChanged(AccessibilityManager.this)); + } + } + + @Override + public void setRelevantEventTypes(int eventTypes) { + mRelevantEventTypes = eventTypes; + } + }; /** * Get an AccessibilityManager instance (create one if necessary). * + * @param context Context in which this manager operates. + * + * @hide */ public static AccessibilityManager getInstance(Context context) { + synchronized (sInstanceSync) { + if (sInstance == null) { + final int userId; + if (Binder.getCallingUid() == Process.SYSTEM_UID + || context.checkCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS) + == PackageManager.PERMISSION_GRANTED + || context.checkCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL) + == PackageManager.PERMISSION_GRANTED) { + userId = UserHandle.USER_CURRENT; + } else { + userId = UserHandle.myUserId(); + } + sInstance = new AccessibilityManager(context, null, userId); + } + } return sInstance; } @@ -114,21 +285,68 @@ public final class AccessibilityManager { * Create an instance. * * @param context A {@link Context}. + * @param service An interface to the backing service. + * @param userId User id under which to run. + * + * @hide */ public AccessibilityManager(Context context, IAccessibilityManager service, int userId) { + // Constructor can't be chained because we can't create an instance of an inner class + // before calling another constructor. + mCallback = new MyCallback(); + mHandler = new Handler(context.getMainLooper(), mCallback); + mUserId = userId; + synchronized (mLock) { + tryConnectToServiceLocked(service); + } + } + + /** + * Create an instance. + * + * @param handler The handler to use + * @param service An interface to the backing service. + * @param userId User id under which to run. + * + * @hide + */ + public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) { + mCallback = new MyCallback(); + mHandler = handler; + mUserId = userId; + synchronized (mLock) { + tryConnectToServiceLocked(service); + } } + /** + * @hide + */ public IAccessibilityManagerClient getClient() { return mClient; } /** - * Returns if the {@link AccessibilityManager} is enabled. + * @hide + */ + @VisibleForTesting + public Handler.Callback getCallback() { + return mCallback; + } + + /** + * Returns if the accessibility in the system is enabled. * - * @return True if this {@link AccessibilityManager} is enabled, false otherwise. + * @return True if accessibility is enabled, false otherwise. */ public boolean isEnabled() { - return false; + synchronized (mLock) { + IAccessibilityManager service = getServiceLocked(); + if (service == null) { + return false; + } + return mIsEnabled; + } } /** @@ -137,7 +355,13 @@ public final class AccessibilityManager { * @return True if touch exploration is enabled, false otherwise. */ public boolean isTouchExplorationEnabled() { - return true; + synchronized (mLock) { + IAccessibilityManager service = getServiceLocked(); + if (service == null) { + return false; + } + return mIsTouchExplorationEnabled; + } } /** @@ -147,35 +371,169 @@ public final class AccessibilityManager { * doing its own rendering and does not rely on the platform rendering pipeline. * </p> * + * @return True if high text contrast is enabled, false otherwise. + * + * @hide */ public boolean isHighTextContrastEnabled() { - return false; + synchronized (mLock) { + IAccessibilityManager service = getServiceLocked(); + if (service == null) { + return false; + } + return mIsHighTextContrastEnabled; + } } /** * Sends an {@link AccessibilityEvent}. + * + * @param event The event to send. + * + * @throws IllegalStateException if accessibility is not enabled. + * + * <strong>Note:</strong> The preferred mechanism for sending custom accessibility + * events is through calling + * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)} + * instead of this method to allow predecessors to augment/filter events sent by + * their descendants. */ public void sendAccessibilityEvent(AccessibilityEvent event) { + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + if (!mIsEnabled) { + Looper myLooper = Looper.myLooper(); + if (myLooper == Looper.getMainLooper()) { + throw new IllegalStateException( + "Accessibility off. Did you forget to check that?"); + } else { + // If we're not running on the thread with the main looper, it's possible for + // the state of accessibility to change between checking isEnabled and + // calling this method. So just log the error rather than throwing the + // exception. + Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled"); + return; + } + } + if ((event.getEventType() & mRelevantEventTypes) == 0) { + if (DEBUG) { + Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event + + " that is not among " + + AccessibilityEvent.eventTypeToString(mRelevantEventTypes)); + } + return; + } + userId = mUserId; + } + try { + event.setEventTime(SystemClock.uptimeMillis()); + // it is possible that this manager is in the same process as the service but + // client using it is called through Binder from another process. Example: MMS + // app adds a SMS notification and the NotificationManagerService calls this method + long identityToken = Binder.clearCallingIdentity(); + service.sendAccessibilityEvent(event, userId); + Binder.restoreCallingIdentity(identityToken); + if (DEBUG) { + Log.i(LOG_TAG, event + " sent"); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error during sending " + event + " ", re); + } finally { + event.recycle(); + } } /** - * Requests interruption of the accessibility feedback from all accessibility services. + * Requests feedback interruption from all accessibility services. */ public void interrupt() { + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + if (!mIsEnabled) { + Looper myLooper = Looper.myLooper(); + if (myLooper == Looper.getMainLooper()) { + throw new IllegalStateException( + "Accessibility off. Did you forget to check that?"); + } else { + // If we're not running on the thread with the main looper, it's possible for + // the state of accessibility to change between checking isEnabled and + // calling this method. So just log the error rather than throwing the + // exception. + Log.e(LOG_TAG, "Interrupt called with accessibility disabled"); + return; + } + } + userId = mUserId; + } + try { + service.interrupt(userId); + if (DEBUG) { + Log.i(LOG_TAG, "Requested interrupt from all services"); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re); + } } /** * Returns the {@link ServiceInfo}s of the installed accessibility services. * * @return An unmodifiable list with {@link ServiceInfo}s. + * + * @deprecated Use {@link #getInstalledAccessibilityServiceList()} */ @Deprecated public List<ServiceInfo> getAccessibilityServiceList() { - return Collections.emptyList(); + List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList(); + List<ServiceInfo> services = new ArrayList<>(); + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + AccessibilityServiceInfo info = infos.get(i); + services.add(info.getResolveInfo().serviceInfo); + } + return Collections.unmodifiableList(services); } + /** + * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services. + * + * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. + */ public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { - return Collections.emptyList(); + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return Collections.emptyList(); + } + userId = mUserId; + } + + List<AccessibilityServiceInfo> services = null; + try { + services = service.getInstalledAccessibilityServiceList(userId); + if (DEBUG) { + Log.i(LOG_TAG, "Installed AccessibilityServices " + services); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); + } + if (services != null) { + return Collections.unmodifiableList(services); + } else { + return Collections.emptyList(); + } } /** @@ -190,21 +548,48 @@ public final class AccessibilityManager { * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN * @see AccessibilityServiceInfo#FEEDBACK_VISUAL + * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE */ public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( int feedbackTypeFlags) { - return Collections.emptyList(); + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return Collections.emptyList(); + } + userId = mUserId; + } + + List<AccessibilityServiceInfo> services = null; + try { + services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId); + if (DEBUG) { + Log.i(LOG_TAG, "Installed AccessibilityServices " + services); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); + } + if (services != null) { + return Collections.unmodifiableList(services); + } else { + return Collections.emptyList(); + } } /** * Registers an {@link AccessibilityStateChangeListener} for changes in - * the global accessibility state of the system. + * the global accessibility state of the system. Equivalent to calling + * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)} + * with a null handler. * * @param listener The listener. - * @return True if successfully registered. + * @return Always returns {@code true}. */ public boolean addAccessibilityStateChangeListener( - AccessibilityStateChangeListener listener) { + @NonNull AccessibilityStateChangeListener listener) { + addAccessibilityStateChangeListener(listener, null); return true; } @@ -218,22 +603,40 @@ public final class AccessibilityManager { * for a callback on the process's main handler. */ public void addAccessibilityStateChangeListener( - @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {} + @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) { + synchronized (mLock) { + mAccessibilityStateChangeListeners + .put(listener, (handler == null) ? mHandler : handler); + } + } + /** + * Unregisters an {@link AccessibilityStateChangeListener}. + * + * @param listener The listener. + * @return True if the listener was previously registered. + */ public boolean removeAccessibilityStateChangeListener( - AccessibilityStateChangeListener listener) { - return true; + @NonNull AccessibilityStateChangeListener listener) { + synchronized (mLock) { + int index = mAccessibilityStateChangeListeners.indexOfKey(listener); + mAccessibilityStateChangeListeners.remove(listener); + return (index >= 0); + } } /** * Registers a {@link TouchExplorationStateChangeListener} for changes in - * the global touch exploration state of the system. + * the global touch exploration state of the system. Equivalent to calling + * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)} + * with a null handler. * * @param listener The listener. - * @return True if successfully registered. + * @return Always returns {@code true}. */ public boolean addTouchExplorationStateChangeListener( @NonNull TouchExplorationStateChangeListener listener) { + addTouchExplorationStateChangeListener(listener, null); return true; } @@ -247,17 +650,103 @@ public final class AccessibilityManager { * for a callback on the process's main handler. */ public void addTouchExplorationStateChangeListener( - @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {} + @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) { + synchronized (mLock) { + mTouchExplorationStateChangeListeners + .put(listener, (handler == null) ? mHandler : handler); + } + } /** * Unregisters a {@link TouchExplorationStateChangeListener}. * * @param listener The listener. - * @return True if successfully unregistered. + * @return True if listener was previously registered. */ public boolean removeTouchExplorationStateChangeListener( @NonNull TouchExplorationStateChangeListener listener) { - return true; + synchronized (mLock) { + int index = mTouchExplorationStateChangeListeners.indexOfKey(listener); + mTouchExplorationStateChangeListeners.remove(listener); + return (index >= 0); + } + } + + /** + * Registers a {@link AccessibilityServicesStateChangeListener}. + * + * @param listener The listener. + * @param handler The handler on which the listener should be called back, or {@code null} + * for a callback on the process's main handler. + * @hide + */ + public void addAccessibilityServicesStateChangeListener( + @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) { + synchronized (mLock) { + mServicesStateChangeListeners + .put(listener, (handler == null) ? mHandler : handler); + } + } + + /** + * Unregisters a {@link AccessibilityServicesStateChangeListener}. + * + * @param listener The listener. + * + * @hide + */ + public void removeAccessibilityServicesStateChangeListener( + @NonNull AccessibilityServicesStateChangeListener listener) { + // Final CopyOnWriteArrayList - no lock needed. + mServicesStateChangeListeners.remove(listener); + } + + /** + * Registers a {@link AccessibilityRequestPreparer}. + */ + public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) { + if (mRequestPreparerLists == null) { + mRequestPreparerLists = new SparseArray<>(1); + } + int id = preparer.getView().getAccessibilityViewId(); + List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id); + if (requestPreparerList == null) { + requestPreparerList = new ArrayList<>(1); + mRequestPreparerLists.put(id, requestPreparerList); + } + requestPreparerList.add(preparer); + } + + /** + * Unregisters a {@link AccessibilityRequestPreparer}. + */ + public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) { + if (mRequestPreparerLists == null) { + return; + } + int viewId = preparer.getView().getAccessibilityViewId(); + List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId); + if (requestPreparerList != null) { + requestPreparerList.remove(preparer); + if (requestPreparerList.isEmpty()) { + mRequestPreparerLists.remove(viewId); + } + } + } + + /** + * Get the preparers that are registered for an accessibility ID + * + * @param id The ID of interest + * @return The list of preparers, or {@code null} if there are none. + * + * @hide + */ + public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) { + if (mRequestPreparerLists == null) { + return null; + } + return mRequestPreparerLists.get(id); } /** @@ -269,7 +758,12 @@ public final class AccessibilityManager { * @hide */ public void addHighTextContrastStateChangeListener( - @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {} + @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) { + synchronized (mLock) { + mHighTextContrastStateChangeListeners + .put(listener, (handler == null) ? mHandler : handler); + } + } /** * Unregisters a {@link HighTextContrastChangeListener}. @@ -279,7 +773,51 @@ public final class AccessibilityManager { * @hide */ public void removeHighTextContrastStateChangeListener( - @NonNull HighTextContrastChangeListener listener) {} + @NonNull HighTextContrastChangeListener listener) { + synchronized (mLock) { + mHighTextContrastStateChangeListeners.remove(listener); + } + } + + /** + * Check if the accessibility volume stream is active. + * + * @return True if accessibility volume is active (i.e. some service has requested it). False + * otherwise. + * @hide + */ + public boolean isAccessibilityVolumeStreamActive() { + List<AccessibilityServiceInfo> serviceInfos = + getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK); + for (int i = 0; i < serviceInfos.size(); i++) { + if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) { + return true; + } + } + return false; + } + + /** + * Report a fingerprint gesture to accessibility. Only available for the system process. + * + * @param keyCode The key code of the gesture + * @return {@code true} if accessibility consumes the event. {@code false} if not. + * @hide + */ + public boolean sendFingerprintGesture(int keyCode) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return false; + } + } + try { + return service.sendFingerprintGesture(keyCode); + } catch (RemoteException e) { + return false; + } + } /** * Sets the current state and notifies listeners, if necessary. @@ -287,14 +825,314 @@ public final class AccessibilityManager { * @param stateFlags The state flags. */ private void setStateLocked(int stateFlags) { + final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; + final boolean touchExplorationEnabled = + (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0; + final boolean highTextContrastEnabled = + (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0; + + final boolean wasEnabled = mIsEnabled; + final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled; + final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled; + + // Ensure listeners get current state from isZzzEnabled() calls. + mIsEnabled = enabled; + mIsTouchExplorationEnabled = touchExplorationEnabled; + mIsHighTextContrastEnabled = highTextContrastEnabled; + + if (wasEnabled != enabled) { + notifyAccessibilityStateChanged(); + } + + if (wasTouchExplorationEnabled != touchExplorationEnabled) { + notifyTouchExplorationStateChanged(); + } + + if (wasHighTextContrastEnabled != highTextContrastEnabled) { + notifyHighTextContrastStateChanged(); + } } + /** + * Find an installed service with the specified {@link ComponentName}. + * + * @param componentName The name to match to the service. + * + * @return The info corresponding to the installed service, or {@code null} if no such service + * is installed. + * @hide + */ + public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName( + ComponentName componentName) { + final List<AccessibilityServiceInfo> installedServiceInfos = + getInstalledAccessibilityServiceList(); + if ((installedServiceInfos == null) || (componentName == null)) { + return null; + } + for (int i = 0; i < installedServiceInfos.size(); i++) { + if (componentName.equals(installedServiceInfos.get(i).getComponentName())) { + return installedServiceInfos.get(i); + } + } + return null; + } + + /** + * Adds an accessibility interaction connection interface for a given window. + * @param windowToken The window token to which a connection is added. + * @param connection The connection. + * + * @hide + */ public int addAccessibilityInteractionConnection(IWindow windowToken, IAccessibilityInteractionConnection connection) { + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return View.NO_ID; + } + userId = mUserId; + } + try { + return service.addAccessibilityInteractionConnection(windowToken, connection, userId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); + } return View.NO_ID; } + /** + * Removed an accessibility interaction connection interface for a given window. + * @param windowToken The window token to which a connection is removed. + * + * @hide + */ public void removeAccessibilityInteractionConnection(IWindow windowToken) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.removeAccessibilityInteractionConnection(windowToken); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re); + } + } + + /** + * Perform the accessibility shortcut if the caller has permission. + * + * @hide + */ + public void performAccessibilityShortcut() { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.performAccessibilityShortcut(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re); + } + } + + /** + * Notifies that the accessibility button in the system's navigation area has been clicked + * + * @hide + */ + public void notifyAccessibilityButtonClicked() { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.notifyAccessibilityButtonClicked(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while dispatching accessibility button click", re); + } + } + + /** + * Notifies that the visibility of the accessibility button in the system's navigation area + * has changed. + * + * @param shown {@code true} if the accessibility button is visible within the system + * navigation area, {@code false} otherwise + * @hide + */ + public void notifyAccessibilityButtonVisibilityChanged(boolean shown) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.notifyAccessibilityButtonVisibilityChanged(shown); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re); + } + } + + /** + * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture + * window. Intended for use by the System UI only. + * + * @param connection The connection to handle the actions. Set to {@code null} to avoid + * affecting the actions. + * + * @hide + */ + public void setPictureInPictureActionReplacingConnection( + @Nullable IAccessibilityInteractionConnection connection) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.setPictureInPictureActionReplacingConnection(connection); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error setting picture in picture action replacement", re); + } } + private IAccessibilityManager getServiceLocked() { + if (mService == null) { + tryConnectToServiceLocked(null); + } + return mService; + } + + private void tryConnectToServiceLocked(IAccessibilityManager service) { + if (service == null) { + IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); + if (iBinder == null) { + return; + } + service = IAccessibilityManager.Stub.asInterface(iBinder); + } + + try { + final long userStateAndRelevantEvents = service.addClient(mClient, mUserId); + setStateLocked(IntPair.first(userStateAndRelevantEvents)); + mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents); + mService = service; + } catch (RemoteException re) { + Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); + } + } + + /** + * Notifies the registered {@link AccessibilityStateChangeListener}s. + */ + private void notifyAccessibilityStateChanged() { + final boolean isEnabled; + final ArrayMap<AccessibilityStateChangeListener, Handler> listeners; + synchronized (mLock) { + if (mAccessibilityStateChangeListeners.isEmpty()) { + return; + } + isEnabled = mIsEnabled; + listeners = new ArrayMap<>(mAccessibilityStateChangeListeners); + } + + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + final AccessibilityStateChangeListener listener = + mAccessibilityStateChangeListeners.keyAt(i); + mAccessibilityStateChangeListeners.valueAt(i) + .post(() -> listener.onAccessibilityStateChanged(isEnabled)); + } + } + + /** + * Notifies the registered {@link TouchExplorationStateChangeListener}s. + */ + private void notifyTouchExplorationStateChanged() { + final boolean isTouchExplorationEnabled; + final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners; + synchronized (mLock) { + if (mTouchExplorationStateChangeListeners.isEmpty()) { + return; + } + isTouchExplorationEnabled = mIsTouchExplorationEnabled; + listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners); + } + + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + final TouchExplorationStateChangeListener listener = + mTouchExplorationStateChangeListeners.keyAt(i); + mTouchExplorationStateChangeListeners.valueAt(i) + .post(() -> listener.onTouchExplorationStateChanged(isTouchExplorationEnabled)); + } + } + + /** + * Notifies the registered {@link HighTextContrastChangeListener}s. + */ + private void notifyHighTextContrastStateChanged() { + final boolean isHighTextContrastEnabled; + final ArrayMap<HighTextContrastChangeListener, Handler> listeners; + synchronized (mLock) { + if (mHighTextContrastStateChangeListeners.isEmpty()) { + return; + } + isHighTextContrastEnabled = mIsHighTextContrastEnabled; + listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners); + } + + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + final HighTextContrastChangeListener listener = + mHighTextContrastStateChangeListeners.keyAt(i); + mHighTextContrastStateChangeListeners.valueAt(i) + .post(() -> listener.onHighTextContrastStateChanged(isHighTextContrastEnabled)); + } + } + + /** + * Determines if the accessibility button within the system navigation area is supported. + * + * @return {@code true} if the accessibility button is supported on this device, + * {@code false} otherwise + */ + public static boolean isAccessibilityButtonSupported() { + final Resources res = Resources.getSystem(); + return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar); + } + + private final class MyCallback implements Handler.Callback { + public static final int MSG_SET_STATE = 1; + + @Override + public boolean handleMessage(Message message) { + switch (message.what) { + case MSG_SET_STATE: { + // See comment at mClient + final int state = message.arg1; + synchronized (mLock) { + setStateLocked(state); + } + } break; + } + return true; + } + } } diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java index e564fa34..4fb2a99a 100644 --- a/android/view/autofill/AutofillManager.java +++ b/android/view/autofill/AutofillManager.java @@ -91,10 +91,10 @@ import java.util.Objects; * </ul> * * <p>When the service returns datasets, the Android System displays an autofill dataset picker - * UI associated with the view, when the view is focused on and is part of a dataset. - * The application can be notified when the UI is shown by registering an + * UI affordance associated with the view, when the view is focused on and is part of a dataset. + * The application can be notified when the affordance is shown by registering an * {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user - * selects a dataset from the UI, all views present in the dataset are autofilled, through + * selects a dataset from the affordance, all views present in the dataset are autofilled, through * calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}. * * <p>When the service returns ids of savable views, the Android System keeps track of changes @@ -108,7 +108,7 @@ import java.util.Objects; * </ul> * * <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System - * shows an autofill save UI if the value of savable views have changed. If the user selects the + * shows a save UI affordance if the value of savable views have changed. If the user selects the * option to Save, the current value of the views is then sent to the autofill service. * * <p>It is safe to call into its methods from any thread. @@ -150,12 +150,6 @@ public final class AutofillManager { * service authentication will contain the Bundle set by * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra. * - * <p>On Android {@link android.os.Build.VERSION_CODES#P} and higher, the autofill service - * can also add this bundle to the {@link Intent} set as the - * {@link android.app.Activity#setResult(int, Intent) result} for an authentication request, - * so the bundle can be recovered later on - * {@link android.service.autofill.SaveRequest#getClientState()}. - * * <p> * Type: {@link android.os.Bundle} */ @@ -317,14 +311,6 @@ public final class AutofillManager { @GuardedBy("mLock") @Nullable private ArraySet<AutofillId> mFillableIds; - /** If set, session is commited when the field is clicked. */ - @GuardedBy("mLock") - @Nullable private AutofillId mSaveTriggerId; - - /** If set, session is commited when the activity is finished; otherwise session is canceled. */ - @GuardedBy("mLock") - private boolean mSaveOnFinish; - /** @hide */ public interface AutofillClient { /** @@ -848,46 +834,6 @@ public final class AutofillManager { } } - - /** - * Called when a {@link View} is clicked. Currently only used by views that should trigger save. - * - * @hide - */ - public void notifyViewClicked(View view) { - final AutofillId id = view.getAutofillId(); - - if (sVerbose) Log.v(TAG, "notifyViewClicked(): id=" + id + ", trigger=" + mSaveTriggerId); - - synchronized (mLock) { - if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) { - if (sDebug) Log.d(TAG, "triggering commit by click of " + id); - commitLocked(); - mMetricsLogger.action(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED, - mContext.getPackageName()); - } - } - } - - /** - * Called by {@link android.app.Activity} to commit or cancel the session on finish. - * - * @hide - */ - public void onActivityFinished() { - if (!hasAutofillFeature()) { - return; - } - synchronized (mLock) { - if (mSaveOnFinish) { - commitLocked(); - } else { - if (sDebug) Log.d(TAG, "Cancelling session on finish() as requested by service"); - cancelLocked(); - } - } - } - /** * Called to indicate the current autofill context should be commited. * @@ -904,15 +850,12 @@ public final class AutofillManager { return; } synchronized (mLock) { - commitLocked(); - } - } + if (!mEnabled && !isActiveLocked()) { + return; + } - private void commitLocked() { - if (!mEnabled && !isActiveLocked()) { - return; + finishSessionLocked(); } - finishSessionLocked(); } /** @@ -931,15 +874,12 @@ public final class AutofillManager { return; } synchronized (mLock) { - cancelLocked(); - } - } + if (!mEnabled && !isActiveLocked()) { + return; + } - private void cancelLocked() { - if (!mEnabled && !isActiveLocked()) { - return; + cancelSessionLocked(); } - cancelSessionLocked(); } /** @hide */ @@ -997,12 +937,7 @@ public final class AutofillManager { } private AutofillClient getClientLocked() { - final AutofillClient client = mContext.getAutofillClient(); - if (client == null && sDebug) { - Log.d(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context " - + mContext); - } - return client; + return mContext.getAutofillClient(); } /** @hide */ @@ -1024,10 +959,6 @@ public final class AutofillManager { final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT); final Bundle responseData = new Bundle(); responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result); - final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE); - if (newClientState != null) { - responseData.putBundle(EXTRA_CLIENT_STATE, newClientState); - } try { mService.setAuthenticationResult(responseData, mSessionId, authenticationId, mContext.getUserId()); @@ -1107,7 +1038,6 @@ public final class AutofillManager { mState = STATE_UNKNOWN; mTrackedViews = null; mFillableIds = null; - mSaveTriggerId = null; } private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action, @@ -1359,15 +1289,12 @@ public final class AutofillManager { /** * Set the tracked views. * - * @param trackedIds The views to be tracked. + * @param trackedIds The views to be tracked * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible. - * @param saveOnFinish Finish the session once the activity is finished. * @param fillableIds Views that might anchor FillUI. - * @param saveTriggerId View that when clicked triggers commit(). */ private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds, - boolean saveOnAllViewsInvisible, boolean saveOnFinish, - @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) { + boolean saveOnAllViewsInvisible, @Nullable AutofillId[] fillableIds) { synchronized (mLock) { if (mEnabled && mSessionId == sessionId) { if (saveOnAllViewsInvisible) { @@ -1375,7 +1302,6 @@ public final class AutofillManager { } else { mTrackedViews = null; } - mSaveOnFinish = saveOnFinish; if (fillableIds != null) { if (mFillableIds == null) { mFillableIds = new ArraySet<>(fillableIds.length); @@ -1388,30 +1314,10 @@ public final class AutofillManager { + ", mFillableIds" + mFillableIds); } } - - if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) { - // Turn off trigger on previous view id. - setNotifyOnClickLocked(mSaveTriggerId, false); - } - - if (saveTriggerId != null && !saveTriggerId.equals(mSaveTriggerId)) { - // Turn on trigger on new view id. - mSaveTriggerId = saveTriggerId; - setNotifyOnClickLocked(mSaveTriggerId, true); - } } } } - private void setNotifyOnClickLocked(@NonNull AutofillId id, boolean notify) { - final View view = findView(id); - if (view == null) { - Log.w(TAG, "setNotifyOnClick(): invalid id: " + id); - return; - } - view.setNotifyAutofillManagerOnClick(notify); - } - private void setSaveUiState(int sessionId, boolean shown) { if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown); synchronized (mLock) { @@ -1584,7 +1490,6 @@ public final class AutofillManager { final String pfx = outerPrefix + " "; pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId); pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked()); - pw.print(pfx); pw.print("context: "); pw.println(mContext); pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled); pw.print(pfx); pw.print("hasService: "); pw.println(mService != null); pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null); @@ -1599,8 +1504,6 @@ public final class AutofillManager { pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds); } pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds); - pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId); - pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish); } private String getStateAsStringLocked() { @@ -1849,7 +1752,7 @@ public final class AutofillManager { * Callback for autofill related events. * * <p>Typically used for applications that display their own "auto-complete" views, so they can - * enable / disable such views when the autofill UI is shown / hidden. + * enable / disable such views when the autofill UI affordance is shown / hidden. */ public abstract static class AutofillCallback { @@ -1859,26 +1762,26 @@ public final class AutofillManager { public @interface AutofillEventType {} /** - * The autofill input UI associated with the view was shown. + * The autofill input UI affordance associated with the view was shown. * - * <p>If the view provides its own auto-complete UI and its currently shown, it + * <p>If the view provides its own auto-complete UI affordance and its currently shown, it * should be hidden upon receiving this event. */ public static final int EVENT_INPUT_SHOWN = 1; /** - * The autofill input UI associated with the view was hidden. + * The autofill input UI affordance associated with the view was hidden. * - * <p>If the view provides its own auto-complete UI that was hidden upon a + * <p>If the view provides its own auto-complete UI affordance that was hidden upon a * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now. */ public static final int EVENT_INPUT_HIDDEN = 2; /** - * The autofill input UI associated with the view isn't shown because + * The autofill input UI affordance associated with the view isn't shown because * autofill is not available. * - * <p>If the view provides its own auto-complete UI but was not displaying it + * <p>If the view provides its own auto-complete UI affordance but was not displaying it * to avoid flickering, it could shown it upon receiving this event. */ public static final int EVENT_INPUT_UNAVAILABLE = 3; @@ -1980,12 +1883,12 @@ public final class AutofillManager { @Override public void setTrackedViews(int sessionId, AutofillId[] ids, - boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds, - AutofillId saveTriggerId) { + boolean saveOnAllViewsInvisible, AutofillId[] fillableIds) { final AutofillManager afm = mAfm.get(); if (afm != null) { - afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, - saveOnFinish, fillableIds, saveTriggerId)); + afm.post(() -> + afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, fillableIds) + ); } } diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java index c3601d9d..bb1e693f 100644 --- a/android/view/textclassifier/TextClassifier.java +++ b/android/view/textclassifier/TextClassifier.java @@ -152,12 +152,4 @@ public interface TextClassifier { */ @WorkerThread default void logEvent(String source, String event) {} - - /** - * Returns this TextClassifier's settings. - * @hide - */ - default TextClassifierConstants getSettings() { - return TextClassifierConstants.DEFAULT; - } } diff --git a/android/view/textclassifier/TextClassifierConstants.java b/android/view/textclassifier/TextClassifierConstants.java deleted file mode 100644 index 51e6168e..00000000 --- a/android/view/textclassifier/TextClassifierConstants.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.textclassifier; - -import android.annotation.Nullable; -import android.util.KeyValueListParser; -import android.util.Slog; - -/** - * TextClassifier specific settings. - * This is encoded as a key=value list, separated by commas. Ex: - * - * <pre> - * smart_selection_dark_launch (boolean) - * smart_selection_enabled_for_edit_text (boolean) - * </pre> - * - * <p> - * Type: string - * see also android.provider.Settings.Global.TEXT_CLASSIFIER_CONSTANTS - * - * Example of setting the values for testing. - * adb shell settings put global text_classifier_constants smart_selection_dark_launch=true,smart_selection_enabled_for_edit_text=true - * @hide - */ -public final class TextClassifierConstants { - - private static final String LOG_TAG = "TextClassifierConstants"; - - private static final String SMART_SELECTION_DARK_LAUNCH = - "smart_selection_dark_launch"; - private static final String SMART_SELECTION_ENABLED_FOR_EDIT_TEXT = - "smart_selection_enabled_for_edit_text"; - - private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false; - private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true; - - /** Default settings. */ - static final TextClassifierConstants DEFAULT = new TextClassifierConstants(); - - private final boolean mDarkLaunch; - private final boolean mSuggestSelectionEnabledForEditableText; - - private TextClassifierConstants() { - mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT; - mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT; - } - - private TextClassifierConstants(@Nullable String settings) { - final KeyValueListParser parser = new KeyValueListParser(','); - try { - parser.setString(settings); - } catch (IllegalArgumentException e) { - // Failed to parse the settings string, log this and move on with defaults. - Slog.e(LOG_TAG, "Bad TextClassifier settings: " + settings); - } - mDarkLaunch = parser.getBoolean( - SMART_SELECTION_DARK_LAUNCH, - SMART_SELECTION_DARK_LAUNCH_DEFAULT); - mSuggestSelectionEnabledForEditableText = parser.getBoolean( - SMART_SELECTION_ENABLED_FOR_EDIT_TEXT, - SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT); - } - - static TextClassifierConstants loadFromString(String settings) { - return new TextClassifierConstants(settings); - } - - public boolean isDarkLaunch() { - return mDarkLaunch; - } - - public boolean isSuggestSelectionEnabledForEditableText() { - return mSuggestSelectionEnabledForEditableText; - } -} diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java index ef087472..2aa81a2c 100644 --- a/android/view/textclassifier/TextClassifierImpl.java +++ b/android/view/textclassifier/TextClassifierImpl.java @@ -24,12 +24,12 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; +import android.icu.text.BreakIterator; import android.net.Uri; import android.os.LocaleList; import android.os.ParcelFileDescriptor; import android.provider.Browser; import android.provider.ContactsContract; -import android.provider.Settings; import android.text.Spannable; import android.text.TextUtils; import android.text.method.WordIterator; @@ -47,7 +47,6 @@ import com.android.internal.util.Preconditions; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.text.BreakIterator; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -92,8 +91,6 @@ final class TextClassifierImpl implements TextClassifier { @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. private SmartSelection mSmartSelection; - private TextClassifierConstants mSettings; - TextClassifierImpl(Context context) { mContext = Preconditions.checkNotNull(context); } @@ -192,15 +189,6 @@ final class TextClassifierImpl implements TextClassifier { } } - @Override - public TextClassifierConstants getSettings() { - if (mSettings == null) { - mSettings = TextClassifierConstants.loadFromString(Settings.Global.getString( - mContext.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS)); - } - return mSettings; - } - private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException { synchronized (mSmartSelectionLock) { localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList; diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java index 8e1f2183..f368c74a 100644 --- a/android/view/textservice/TextServicesManager.java +++ b/android/view/textservice/TextServicesManager.java @@ -1,58 +1,213 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2011 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 + * 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 + * 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. + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. */ package android.view.textservice; +import android.annotation.SystemService; +import android.content.Context; import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; +import android.util.Log; import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener; +import com.android.internal.textservice.ITextServicesManager; + import java.util.Locale; /** - * A stub class of TextServicesManager for Layout-Lib. + * System API to the overall text services, which arbitrates interaction between applications + * and text services. + * + * The user can change the current text services in Settings. And also applications can specify + * the target text services. + * + * <h3>Architecture Overview</h3> + * + * <p>There are three primary parties involved in the text services + * framework (TSF) architecture:</p> + * + * <ul> + * <li> The <strong>text services manager</strong> as expressed by this class + * is the central point of the system that manages interaction between all + * other parts. It is expressed as the client-side API here which exists + * in each application context and communicates with a global system service + * that manages the interaction across all processes. + * <li> A <strong>text service</strong> implements a particular + * interaction model allowing the client application to retrieve information of text. + * The system binds to the current text service that is in use, causing it to be created and run. + * <li> Multiple <strong>client applications</strong> arbitrate with the text service + * manager for connections to text services. + * </ul> + * + * <h3>Text services sessions</h3> + * <ul> + * <li>The <strong>spell checker session</strong> is one of the text services. + * {@link android.view.textservice.SpellCheckerSession}</li> + * </ul> + * */ +@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE) public final class TextServicesManager { - private static final TextServicesManager sInstance = new TextServicesManager(); - private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0]; + private static final String TAG = TextServicesManager.class.getSimpleName(); + private static final boolean DBG = false; + + private static TextServicesManager sInstance; + + private final ITextServicesManager mService; + + private TextServicesManager() throws ServiceNotFoundException { + mService = ITextServicesManager.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE)); + } /** * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist. * @hide */ public static TextServicesManager getInstance() { - return sInstance; + synchronized (TextServicesManager.class) { + if (sInstance == null) { + try { + sInstance = new TextServicesManager(); + } catch (ServiceNotFoundException e) { + throw new IllegalStateException(e); + } + } + return sInstance; + } + } + + /** + * Returns the language component of a given locale string. + */ + private static String parseLanguageFromLocaleString(String locale) { + final int idx = locale.indexOf('_'); + if (idx < 0) { + return locale; + } else { + return locale.substring(0, idx); + } } + /** + * Get a spell checker session for the specified spell checker + * @param locale the locale for the spell checker. If {@code locale} is null and + * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be + * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true, + * the locale specified in Settings will be returned only when it is same as {@code locale}. + * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is + * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be + * selected. + * @param listener a spell checker session lister for getting results from a spell checker. + * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled + * languages in settings will be returned. + * @return the spell checker session of the spell checker + */ public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale, SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) { - return null; + if (listener == null) { + throw new NullPointerException(); + } + if (!referToSpellCheckerLanguageSettings && locale == null) { + throw new IllegalArgumentException("Locale should not be null if you don't refer" + + " settings."); + } + + if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) { + return null; + } + + final SpellCheckerInfo sci; + try { + sci = mService.getCurrentSpellChecker(null); + } catch (RemoteException e) { + return null; + } + if (sci == null) { + return null; + } + SpellCheckerSubtype subtypeInUse = null; + if (referToSpellCheckerLanguageSettings) { + subtypeInUse = getCurrentSpellCheckerSubtype(true); + if (subtypeInUse == null) { + return null; + } + if (locale != null) { + final String subtypeLocale = subtypeInUse.getLocale(); + final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale); + if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) { + return null; + } + } + } else { + final String localeStr = locale.toString(); + for (int i = 0; i < sci.getSubtypeCount(); ++i) { + final SpellCheckerSubtype subtype = sci.getSubtypeAt(i); + final String tempSubtypeLocale = subtype.getLocale(); + final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale); + if (tempSubtypeLocale.equals(localeStr)) { + subtypeInUse = subtype; + break; + } else if (tempSubtypeLanguage.length() >= 2 && + locale.getLanguage().equals(tempSubtypeLanguage)) { + subtypeInUse = subtype; + } + } + } + if (subtypeInUse == null) { + return null; + } + final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener); + try { + mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(), + session.getTextServicesSessionListener(), + session.getSpellCheckerSessionListener(), bundle); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return session; } /** * @hide */ public SpellCheckerInfo[] getEnabledSpellCheckers() { - return EMPTY_SPELL_CHECKER_INFO; + try { + final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers(); + if (DBG) { + Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null")); + } + return retval; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** * @hide */ public SpellCheckerInfo getCurrentSpellChecker() { - return null; + try { + // Passing null as a locale for ICS + return mService.getCurrentSpellChecker(null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -60,13 +215,22 @@ public final class TextServicesManager { */ public SpellCheckerSubtype getCurrentSpellCheckerSubtype( boolean allowImplicitlySelectedSubtype) { - return null; + try { + // Passing null as a locale until we support multiple enabled spell checker subtypes. + return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** * @hide */ public boolean isSpellCheckerEnabled() { - return false; + try { + return mService.isSpellCheckerEnabled(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } } diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java index 202f2046..dfc81b2b 100644 --- a/android/webkit/WebView.java +++ b/android/webkit/WebView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2006 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. @@ -16,223 +16,3001 @@ package android.webkit; -import com.android.layoutlib.bridge.MockView; - +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.Widget; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.res.Configuration; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.Picture; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.net.http.SslCertificate; +import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.os.Message; +import android.os.RemoteException; +import android.os.StrictMode; +import android.print.PrintDocumentAdapter; +import android.security.KeyChain; import android.util.AttributeSet; +import android.util.Log; +import android.util.SparseArray; +import android.view.DragEvent; +import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.View; +import android.view.ViewDebug; +import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; +import android.view.ViewStructure; +import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeProvider; +import android.view.autofill.AutofillValue; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.textclassifier.TextClassifier; +import android.widget.AbsoluteLayout; + +import java.io.BufferedWriter; +import java.io.File; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.Map; /** - * Mock version of the WebView. - * Only non override public methods from the real WebView have been added in there. - * Methods that take an unknown class as parameter or as return object, have been removed for now. - * - * TODO: generate automatically. + * <p>A View that displays web pages. This class is the basis upon which you + * can roll your own web browser or simply display some online content within your Activity. + * It uses the WebKit rendering engine to display + * web pages and includes methods to navigate forward and backward + * through a history, zoom in and out, perform text searches and more. + * + * <p>Note that, in order for your Activity to access the Internet and load web pages + * in a WebView, you must add the {@code INTERNET} permissions to your + * Android Manifest file: + * + * <pre> + * {@code <uses-permission android:name="android.permission.INTERNET" />} + * </pre> + * + * <p>This must be a child of the <a + * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a> + * element. + * + * <p>For more information, read + * <a href="{@docRoot}guide/webapps/webview.html">Building Web Apps in WebView</a>. + * + * <h3>Basic usage</h3> + * + * <p>By default, a WebView provides no browser-like widgets, does not + * enable JavaScript and web page errors are ignored. If your goal is only + * to display some HTML as a part of your UI, this is probably fine; + * the user won't need to interact with the web page beyond reading + * it, and the web page won't need to interact with the user. If you + * actually want a full-blown web browser, then you probably want to + * invoke the Browser application with a URL Intent rather than show it + * with a WebView. For example: + * <pre> + * Uri uri = Uri.parse("https://www.example.com"); + * Intent intent = new Intent(Intent.ACTION_VIEW, uri); + * startActivity(intent); + * </pre> + * <p>See {@link android.content.Intent} for more information. + * + * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout, + * or set the entire Activity window as a WebView during {@link + * android.app.Activity#onCreate(Bundle) onCreate()}: + * + * <pre class="prettyprint"> + * WebView webview = new WebView(this); + * setContentView(webview); + * </pre> + * + * <p>Then load the desired web page: + * + * <pre> + * // Simplest usage: note that an exception will NOT be thrown + * // if there is an error loading this page (see below). + * webview.loadUrl("https://example.com/"); + * + * // OR, you can also load from an HTML string: + * String summary = "<html><body>You scored <b>192</b> points.</body></html>"; + * webview.loadData(summary, "text/html", null); + * // ... although note that there are restrictions on what this HTML can do. + * // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link + * #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info. + * </pre> + * + * <p>A WebView has several customization points where you can add your + * own behavior. These are: + * + * <ul> + * <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass. + * This class is called when something that might impact a + * browser UI happens, for instance, progress updates and + * JavaScript alerts are sent here (see <a + * href="{@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging Tasks</a>). + * </li> + * <li>Creating and setting a {@link android.webkit.WebViewClient} subclass. + * It will be called when things happen that impact the + * rendering of the content, eg, errors or form submissions. You + * can also intercept URL loading here (via {@link + * android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String) + * shouldOverrideUrlLoading()}).</li> + * <li>Modifying the {@link android.webkit.WebSettings}, such as + * enabling JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean) + * setJavaScriptEnabled()}. </li> + * <li>Injecting Java objects into the WebView using the + * {@link android.webkit.WebView#addJavascriptInterface} method. This + * method allows you to inject Java objects into a page's JavaScript + * context, so that they can be accessed by JavaScript in the page.</li> + * </ul> + * + * <p>Here's a more complicated example, showing error handling, + * settings, and progress notification: + * + * <pre class="prettyprint"> + * // Let's display the progress in the activity title bar, like the + * // browser app does. + * getWindow().requestFeature(Window.FEATURE_PROGRESS); + * + * webview.getSettings().setJavaScriptEnabled(true); + * + * final Activity activity = this; + * webview.setWebChromeClient(new WebChromeClient() { + * public void onProgressChanged(WebView view, int progress) { + * // Activities and WebViews measure progress with different scales. + * // The progress meter will automatically disappear when we reach 100% + * activity.setProgress(progress * 1000); + * } + * }); + * webview.setWebViewClient(new WebViewClient() { + * public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + * Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show(); + * } + * }); + * + * webview.loadUrl("https://developer.android.com/"); + * </pre> + * + * <h3>Zoom</h3> + * + * <p>To enable the built-in zoom, set + * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)} + * (introduced in API level {@link android.os.Build.VERSION_CODES#CUPCAKE}). + * + * <p>NOTE: Using zoom if either the height or width is set to + * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} may lead to undefined behavior + * and should be avoided. + * + * <h3>Cookie and window management</h3> + * + * <p>For obvious security reasons, your application has its own + * cache, cookie store etc.—it does not share the Browser + * application's data. + * + * <p>By default, requests by the HTML to open new windows are + * ignored. This is {@code true} whether they be opened by JavaScript or by + * the target attribute on a link. You can customize your + * {@link WebChromeClient} to provide your own behavior for opening multiple windows, + * and render them in whatever manner you want. + * + * <p>The standard behavior for an Activity is to be destroyed and + * recreated when the device orientation or any other configuration changes. This will cause + * the WebView to reload the current page. If you don't want that, you + * can set your Activity to handle the {@code orientation} and {@code keyboardHidden} + * changes, and then just leave the WebView alone. It'll automatically + * re-orient itself as appropriate. Read <a + * href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a> for + * more information about how to handle configuration changes during runtime. + * + * + * <h3>Building web pages to support different screen densities</h3> + * + * <p>The screen density of a device is based on the screen resolution. A screen with low density + * has fewer available pixels per inch, where a screen with high density + * has more — sometimes significantly more — pixels per inch. The density of a + * screen is important because, other things being equal, a UI element (such as a button) whose + * height and width are defined in terms of screen pixels will appear larger on the lower density + * screen and smaller on the higher density screen. + * For simplicity, Android collapses all actual screen densities into three generalized densities: + * high, medium, and low. + * <p>By default, WebView scales a web page so that it is drawn at a size that matches the default + * appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen + * (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels + * are bigger). + * Starting with API level {@link android.os.Build.VERSION_CODES#ECLAIR}, WebView supports DOM, CSS, + * and meta tag features to help you (as a web developer) target screens with different screen + * densities. + * <p>Here's a summary of the features you can use to handle different screen densities: + * <ul> + * <li>The {@code window.devicePixelRatio} DOM property. The value of this property specifies the + * default scaling factor used for the current device. For example, if the value of {@code + * window.devicePixelRatio} is "1.0", then the device is considered a medium density (mdpi) device + * and default scaling is not applied to the web page; if the value is "1.5", then the device is + * considered a high density device (hdpi) and the page content is scaled 1.5x; if the + * value is "0.75", then the device is considered a low density device (ldpi) and the content is + * scaled 0.75x.</li> + * <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen + * densities for which this style sheet is to be used. The corresponding value should be either + * "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium + * density, or high density screens, respectively. For example: + * <pre> + * <link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" /></pre> + * <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5, + * which is the high density pixel ratio. + * </li> + * </ul> + * + * <h3>HTML5 Video support</h3> + * + * <p>In order to support inline HTML5 video in your application you need to have hardware + * acceleration turned on. + * + * <h3>Full screen support</h3> + * + * <p>In order to support full screen — for video or other HTML content — you need to set a + * {@link android.webkit.WebChromeClient} and implement both + * {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)} + * and {@link WebChromeClient#onHideCustomView()}. If the implementation of either of these two methods is + * missing then the web contents will not be allowed to enter full screen. Optionally you can implement + * {@link WebChromeClient#getVideoLoadingProgressView()} to customize the View displayed whilst a video + * is loading. + * + * <h3>HTML5 Geolocation API support</h3> + * + * <p>For applications targeting Android N and later releases + * (API level > {@link android.os.Build.VERSION_CODES#M}) the geolocation api is only supported on + * secure origins such as https. For such applications requests to geolocation api on non-secure + * origins are automatically denied without invoking the corresponding + * {@link WebChromeClient#onGeolocationPermissionsShowPrompt(String, GeolocationPermissions.Callback)} + * method. + * + * <h3>Layout size</h3> + * <p> + * It is recommended to set the WebView layout height to a fixed value or to + * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} instead of using + * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}. + * When using {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} + * for the height none of the WebView's parents should use a + * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} layout height since that could result in + * incorrect sizing of the views. + * + * <p>Setting the WebView's height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} + * enables the following behaviors: + * <ul> + * <li>The HTML body layout height is set to a fixed value. This means that elements with a height + * relative to the HTML body may not be sized correctly. </li> + * <li>For applications targeting {@link android.os.Build.VERSION_CODES#KITKAT} and earlier SDKs the + * HTML viewport meta tag will be ignored in order to preserve backwards compatibility. </li> + * </ul> + * + * <p> + * Using a layout width of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} is not + * supported. If such a width is used the WebView will attempt to use the width of the parent + * instead. + * + * <h3>Metrics</h3> + * + * <p> + * WebView may upload anonymous diagnostic data to Google when the user has consented. This data + * helps Google improve WebView. Data is collected on a per-app basis for each app which has + * instantiated a WebView. An individual app can opt out of this feature by putting the following + * tag in its manifest: + * <pre> + * <meta-data android:name="android.webkit.WebView.MetricsOptOut" + * android:value="true" /> + * </pre> + * <p> + * Data will only be uploaded for a given app if the user has consented AND the app has not opted + * out. + * + * <h3>Safe Browsing</h3> + * + * <p> + * If Safe Browsing is enabled, WebView will block malicious URLs and present a warning UI to the + * user to allow them to navigate back safely or proceed to the malicious page. + * <p> + * The recommended way for apps to enable the feature is putting the following tag in the manifest: + * <p> + * <pre> + * <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing" + * android:value="true" /> + * </pre> * */ -public class WebView extends MockView { +// Implementation notes. +// The WebView is a thin API class that delegates its public API to a backend WebViewProvider +// class instance. WebView extends {@link AbsoluteLayout} for backward compatibility reasons. +// Methods are delegated to the provider implementation: all public API methods introduced in this +// file are fully delegated, whereas public and protected methods from the View base classes are +// only delegated where a specific need exists for them to do so. +@Widget +public class WebView extends AbsoluteLayout + implements ViewTreeObserver.OnGlobalFocusChangeListener, + ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler { + + private static final String LOGTAG = "WebView"; + + // Throwing an exception for incorrect thread usage if the + // build target is JB MR2 or newer. Defaults to false, and is + // set in the WebView constructor. + private static volatile boolean sEnforceThreadChecking = false; + + /** + * Transportation object for returning WebView across thread boundaries. + */ + public class WebViewTransport { + private WebView mWebview; + /** + * Sets the WebView to the transportation object. + * + * @param webview the WebView to transport + */ + public synchronized void setWebView(WebView webview) { + mWebview = webview; + } + + /** + * Gets the WebView object. + * + * @return the transported WebView object + */ + public synchronized WebView getWebView() { + return mWebview; + } + } + + /** + * URI scheme for telephone number. + */ + public static final String SCHEME_TEL = "tel:"; /** - * Construct a new WebView with a Context object. - * @param context A Context object used to access application assets. + * URI scheme for email address. + */ + public static final String SCHEME_MAILTO = "mailto:"; + /** + * URI scheme for map address. + */ + public static final String SCHEME_GEO = "geo:0,0?q="; + + /** + * Interface to listen for find results. + */ + public interface FindListener { + /** + * Notifies the listener about progress made by a find operation. + * + * @param activeMatchOrdinal the zero-based ordinal of the currently selected match + * @param numberOfMatches how many matches have been found + * @param isDoneCounting whether the find operation has actually completed. The listener + * may be notified multiple times while the + * operation is underway, and the numberOfMatches + * value should not be considered final unless + * isDoneCounting is {@code true}. + */ + public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, + boolean isDoneCounting); + } + + /** + * Callback interface supplied to {@link #postVisualStateCallback} for receiving + * notifications about the visual state. + */ + public static abstract class VisualStateCallback { + /** + * Invoked when the visual state is ready to be drawn in the next {@link #onDraw}. + * + * @param requestId The identifier passed to {@link #postVisualStateCallback} when this + * callback was posted. + */ + public abstract void onComplete(long requestId); + } + + /** + * Interface to listen for new pictures as they change. + * + * @deprecated This interface is now obsolete. + */ + @Deprecated + public interface PictureListener { + /** + * Used to provide notification that the WebView's picture has changed. + * See {@link WebView#capturePicture} for details of the picture. + * + * @param view the WebView that owns the picture + * @param picture the new picture. Applications targeting + * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} or above + * will always receive a {@code null} Picture. + * @deprecated Deprecated due to internal changes. + */ + @Deprecated + void onNewPicture(WebView view, @Nullable Picture picture); + } + + public static class HitTestResult { + /** + * Default HitTestResult, where the target is unknown. + */ + public static final int UNKNOWN_TYPE = 0; + /** + * @deprecated This type is no longer used. + */ + @Deprecated + public static final int ANCHOR_TYPE = 1; + /** + * HitTestResult for hitting a phone number. + */ + public static final int PHONE_TYPE = 2; + /** + * HitTestResult for hitting a map address. + */ + public static final int GEO_TYPE = 3; + /** + * HitTestResult for hitting an email address. + */ + public static final int EMAIL_TYPE = 4; + /** + * HitTestResult for hitting an HTML::img tag. + */ + public static final int IMAGE_TYPE = 5; + /** + * @deprecated This type is no longer used. + */ + @Deprecated + public static final int IMAGE_ANCHOR_TYPE = 6; + /** + * HitTestResult for hitting a HTML::a tag with src=http. + */ + public static final int SRC_ANCHOR_TYPE = 7; + /** + * HitTestResult for hitting a HTML::a tag with src=http + HTML::img. + */ + public static final int SRC_IMAGE_ANCHOR_TYPE = 8; + /** + * HitTestResult for hitting an edit text area. + */ + public static final int EDIT_TEXT_TYPE = 9; + + private int mType; + private String mExtra; + + /** + * @hide Only for use by WebViewProvider implementations + */ + @SystemApi + public HitTestResult() { + mType = UNKNOWN_TYPE; + } + + /** + * @hide Only for use by WebViewProvider implementations + */ + @SystemApi + public void setType(int type) { + mType = type; + } + + /** + * @hide Only for use by WebViewProvider implementations + */ + @SystemApi + public void setExtra(String extra) { + mExtra = extra; + } + + /** + * Gets the type of the hit test result. See the XXX_TYPE constants + * defined in this class. + * + * @return the type of the hit test result + */ + public int getType() { + return mType; + } + + /** + * Gets additional type-dependant information about the result. See + * {@link WebView#getHitTestResult()} for details. May either be {@code null} + * or contain extra information about this result. + * + * @return additional type-dependant information about the result + */ + @Nullable + public String getExtra() { + return mExtra; + } + } + + /** + * Constructs a new WebView with a Context object. + * + * @param context a Context object used to access application assets */ public WebView(Context context) { this(context, null); } /** - * Construct a new WebView with layout parameters. - * @param context A Context object used to access application assets. - * @param attrs An AttributeSet passed to our parent. + * Constructs a new WebView with layout parameters. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent */ public WebView(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.webViewStyle); } /** - * Construct a new WebView with layout parameters and a default style. - * @param context A Context object used to access application assets. - * @param attrs An AttributeSet passed to our parent. - * @param defStyle The default style resource ID. + * Constructs a new WebView with layout parameters and a default style. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. */ - public WebView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public WebView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } - - // START FAKE PUBLIC METHODS - + + /** + * Constructs a new WebView with layout parameters and a default style. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param defStyleRes a resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. + */ + public WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + this(context, attrs, defStyleAttr, defStyleRes, null, false); + } + + /** + * Constructs a new WebView with layout parameters and a default style. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param privateBrowsing whether this WebView will be initialized in + * private mode + * + * @deprecated Private browsing is no longer supported directly via + * WebView and will be removed in a future release. Prefer using + * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager} + * and {@link WebStorage} for fine-grained control of privacy data. + */ + @Deprecated + public WebView(Context context, AttributeSet attrs, int defStyleAttr, + boolean privateBrowsing) { + this(context, attrs, defStyleAttr, 0, null, privateBrowsing); + } + + /** + * Constructs a new WebView with layout parameters, a default style and a set + * of custom JavaScript interfaces to be added to this WebView at initialization + * time. This guarantees that these interfaces will be available when the JS + * context is initialized. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param javaScriptInterfaces a Map of interface names, as keys, and + * object implementing those interfaces, as + * values + * @param privateBrowsing whether this WebView will be initialized in + * private mode + * @hide This is used internally by dumprendertree, as it requires the JavaScript interfaces to + * be added synchronously, before a subsequent loadUrl call takes effect. + */ + protected WebView(Context context, AttributeSet attrs, int defStyleAttr, + Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) { + this(context, attrs, defStyleAttr, 0, javaScriptInterfaces, privateBrowsing); + } + + /** + * @hide + */ + @SuppressWarnings("deprecation") // for super() call into deprecated base class constructor. + protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, + Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) { + super(context, attrs, defStyleAttr, defStyleRes); + + // WebView is important by default, unless app developer overrode attribute. + if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { + setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); + } + + if (context == null) { + throw new IllegalArgumentException("Invalid context argument"); + } + sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >= + Build.VERSION_CODES.JELLY_BEAN_MR2; + checkThread(); + + ensureProviderCreated(); + mProvider.init(javaScriptInterfaces, privateBrowsing); + // Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed. + CookieSyncManager.setGetInstanceIsAllowed(); + } + + /** + * Specifies whether the horizontal scrollbar has overlay style. + * + * @deprecated This method has no effect. + * @param overlay {@code true} if horizontal scrollbar should have overlay style + */ + @Deprecated public void setHorizontalScrollbarOverlay(boolean overlay) { } + /** + * Specifies whether the vertical scrollbar has overlay style. + * + * @deprecated This method has no effect. + * @param overlay {@code true} if vertical scrollbar should have overlay style + */ + @Deprecated public void setVerticalScrollbarOverlay(boolean overlay) { } + /** + * Gets whether horizontal scrollbar has overlay style. + * + * @deprecated This method is now obsolete. + * @return {@code true} + */ + @Deprecated public boolean overlayHorizontalScrollbar() { - return false; + // The old implementation defaulted to true, so return true for consistency + return true; } + /** + * Gets whether vertical scrollbar has overlay style. + * + * @deprecated This method is now obsolete. + * @return {@code false} + */ + @Deprecated public boolean overlayVerticalScrollbar() { + // The old implementation defaulted to false, so return false for consistency return false; } + /** + * Gets the visible height (in pixels) of the embedded title bar (if any). + * + * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ + @Deprecated + public int getVisibleTitleHeight() { + checkThread(); + return mProvider.getVisibleTitleHeight(); + } + + /** + * Gets the SSL certificate for the main top-level page or {@code null} if there is + * no certificate (the site is not secure). + * + * @return the SSL certificate for the main top-level page + */ + @Nullable + public SslCertificate getCertificate() { + checkThread(); + return mProvider.getCertificate(); + } + + /** + * Sets the SSL certificate for the main top-level page. + * + * @deprecated Calling this function has no useful effect, and will be + * ignored in future releases. + */ + @Deprecated + public void setCertificate(SslCertificate certificate) { + checkThread(); + mProvider.setCertificate(certificate); + } + + //------------------------------------------------------------------------- + // Methods called by activity + //------------------------------------------------------------------------- + + /** + * Sets a username and password pair for the specified host. This data is + * used by the WebView to autocomplete username and password fields in web + * forms. Note that this is unrelated to the credentials used for HTTP + * authentication. + * + * @param host the host that required the credentials + * @param username the username for the given host + * @param password the password for the given host + * @see WebViewDatabase#clearUsernamePassword + * @see WebViewDatabase#hasUsernamePassword + * @deprecated Saving passwords in WebView will not be supported in future versions. + */ + @Deprecated public void savePassword(String host, String username, String password) { + checkThread(); + mProvider.savePassword(host, username, password); } + /** + * Stores HTTP authentication credentials for a given host and realm to the {@link WebViewDatabase} + * instance. + * + * @param host the host to which the credentials apply + * @param realm the realm to which the credentials apply + * @param username the username + * @param password the password + * @deprecated Use {@link WebViewDatabase#setHttpAuthUsernamePassword} instead + */ + @Deprecated public void setHttpAuthUsernamePassword(String host, String realm, String username, String password) { + checkThread(); + mProvider.setHttpAuthUsernamePassword(host, realm, username, password); } + /** + * Retrieves HTTP authentication credentials for a given host and realm from the {@link + * WebViewDatabase} instance. + * @param host the host to which the credentials apply + * @param realm the realm to which the credentials apply + * @return the credentials as a String array, if found. The first element + * is the username and the second element is the password. {@code null} if + * no credentials are found. + * @deprecated Use {@link WebViewDatabase#getHttpAuthUsernamePassword} instead + */ + @Deprecated + @Nullable public String[] getHttpAuthUsernamePassword(String host, String realm) { - return null; + checkThread(); + return mProvider.getHttpAuthUsernamePassword(host, realm); } + /** + * Destroys the internal state of this WebView. This method should be called + * after this WebView has been removed from the view system. No other + * methods may be called on this WebView after destroy. + */ public void destroy() { + checkThread(); + mProvider.destroy(); } + /** + * Enables platform notifications of data state and proxy changes. + * Notifications are enabled by default. + * + * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ + @Deprecated public static void enablePlatformNotifications() { + // noop } + /** + * Disables platform notifications of data state and proxy changes. + * Notifications are enabled by default. + * + * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ + @Deprecated public static void disablePlatformNotifications() { + // noop } + /** + * Used only by internal tests to free up memory. + * + * @hide + */ + public static void freeMemoryForTests() { + getFactory().getStatics().freeMemoryForTests(); + } + + /** + * Informs WebView of the network state. This is used to set + * the JavaScript property window.navigator.isOnline and + * generates the online/offline event as specified in HTML5, sec. 5.7.7 + * + * @param networkUp a boolean indicating if network is available + */ + public void setNetworkAvailable(boolean networkUp) { + checkThread(); + mProvider.setNetworkAvailable(networkUp); + } + + /** + * Saves the state of this WebView used in + * {@link android.app.Activity#onSaveInstanceState}. Please note that this + * method no longer stores the display data for this WebView. The previous + * behavior could potentially leak files if {@link #restoreState} was never + * called. + * + * @param outState the Bundle to store this WebView's state + * @return the same copy of the back/forward list used to save the state, {@code null} if the + * method fails. + */ + @Nullable + public WebBackForwardList saveState(Bundle outState) { + checkThread(); + return mProvider.saveState(outState); + } + + /** + * Saves the current display data to the Bundle given. Used in conjunction + * with {@link #saveState}. + * @param b a Bundle to store the display data + * @param dest the file to store the serialized picture data. Will be + * overwritten with this WebView's picture data. + * @return {@code true} if the picture was successfully saved + * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ + @Deprecated + public boolean savePicture(Bundle b, final File dest) { + checkThread(); + return mProvider.savePicture(b, dest); + } + + /** + * Restores the display data that was saved in {@link #savePicture}. Used in + * conjunction with {@link #restoreState}. Note that this will not work if + * this WebView is hardware accelerated. + * + * @param b a Bundle containing the saved display data + * @param src the file where the picture data was stored + * @return {@code true} if the picture was successfully restored + * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ + @Deprecated + public boolean restorePicture(Bundle b, File src) { + checkThread(); + return mProvider.restorePicture(b, src); + } + + /** + * Restores the state of this WebView from the given Bundle. This method is + * intended for use in {@link android.app.Activity#onRestoreInstanceState} + * and should be called to restore the state of this WebView. If + * it is called after this WebView has had a chance to build state (load + * pages, create a back/forward list, etc.) there may be undesirable + * side-effects. Please note that this method no longer restores the + * display data for this WebView. + * + * @param inState the incoming Bundle of state + * @return the restored back/forward list or {@code null} if restoreState failed + */ + @Nullable + public WebBackForwardList restoreState(Bundle inState) { + checkThread(); + return mProvider.restoreState(inState); + } + + /** + * Loads the given URL with the specified additional HTTP headers. + * <p> + * Also see compatibility note on {@link #evaluateJavascript}. + * + * @param url the URL of the resource to load + * @param additionalHttpHeaders the additional headers to be used in the + * HTTP request for this URL, specified as a map from name to + * value. Note that if this map contains any of the headers + * that are set by default by this WebView, such as those + * controlling caching, accept types or the User-Agent, their + * values may be overridden by this WebView's defaults. + */ + public void loadUrl(String url, Map<String, String> additionalHttpHeaders) { + checkThread(); + mProvider.loadUrl(url, additionalHttpHeaders); + } + + /** + * Loads the given URL. + * <p> + * Also see compatibility note on {@link #evaluateJavascript}. + * + * @param url the URL of the resource to load + */ public void loadUrl(String url) { + checkThread(); + mProvider.loadUrl(url); + } + + /** + * Loads the URL with postData using "POST" method into this WebView. If url + * is not a network URL, it will be loaded with {@link #loadUrl(String)} + * instead, ignoring the postData param. + * + * @param url the URL of the resource to load + * @param postData the data will be passed to "POST" request, which must be + * be "application/x-www-form-urlencoded" encoded. + */ + public void postUrl(String url, byte[] postData) { + checkThread(); + if (URLUtil.isNetworkUrl(url)) { + mProvider.postUrl(url, postData); + } else { + mProvider.loadUrl(url); + } } - public void loadData(String data, String mimeType, String encoding) { + /** + * Loads the given data into this WebView using a 'data' scheme URL. + * <p> + * Note that JavaScript's same origin policy means that script running in a + * page loaded using this method will be unable to access content loaded + * using any scheme other than 'data', including 'http(s)'. To avoid this + * restriction, use {@link + * #loadDataWithBaseURL(String,String,String,String,String) + * loadDataWithBaseURL()} with an appropriate base URL. + * <p> + * The encoding parameter specifies whether the data is base64 or URL + * encoded. If the data is base64 encoded, the value of the encoding + * parameter must be 'base64'. For all other values of the parameter, + * including {@code null}, it is assumed that the data uses ASCII encoding for + * octets inside the range of safe URL characters and use the standard %xx + * hex encoding of URLs for octets outside that range. For example, '#', + * '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively. + * <p> + * The 'data' scheme URL formed by this method uses the default US-ASCII + * charset. If you need need to set a different charset, you should form a + * 'data' scheme URL which explicitly specifies a charset parameter in the + * mediatype portion of the URL and call {@link #loadUrl(String)} instead. + * Note that the charset obtained from the mediatype portion of a data URL + * always overrides that specified in the HTML or XML document itself. + * + * @param data a String of data in the given encoding + * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null}, + * defaults to 'text/html'. + * @param encoding the encoding of the data + */ + public void loadData(String data, @Nullable String mimeType, @Nullable String encoding) { + checkThread(); + mProvider.loadData(data, mimeType, encoding); } - public void loadDataWithBaseURL(String baseUrl, String data, - String mimeType, String encoding, String failUrl) { + /** + * Loads the given data into this WebView, using baseUrl as the base URL for + * the content. The base URL is used both to resolve relative URLs and when + * applying JavaScript's same origin policy. The historyUrl is used for the + * history entry. + * <p> + * Note that content specified in this way can access local device files + * (via 'file' scheme URLs) only if baseUrl specifies a scheme other than + * 'http', 'https', 'ftp', 'ftps', 'about' or 'javascript'. + * <p> + * If the base URL uses the data scheme, this method is equivalent to + * calling {@link #loadData(String,String,String) loadData()} and the + * historyUrl is ignored, and the data will be treated as part of a data: URL. + * If the base URL uses any other scheme, then the data will be loaded into + * the WebView as a plain string (i.e. not part of a data URL) and any URL-encoded + * entities in the string will not be decoded. + * <p> + * Note that the baseUrl is sent in the 'Referer' HTTP header when + * requesting subresources (images, etc.) of the page loaded using this method. + * + * @param baseUrl the URL to use as the page's base URL. If {@code null} defaults to + * 'about:blank'. + * @param data a String of data in the given encoding + * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null}, + * defaults to 'text/html'. + * @param encoding the encoding of the data + * @param historyUrl the URL to use as the history entry. If {@code null} defaults + * to 'about:blank'. If non-null, this must be a valid URL. + */ + public void loadDataWithBaseURL(@Nullable String baseUrl, String data, + @Nullable String mimeType, @Nullable String encoding, @Nullable String historyUrl) { + checkThread(); + mProvider.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); } + /** + * Asynchronously evaluates JavaScript in the context of the currently displayed page. + * If non-null, |resultCallback| will be invoked with any result returned from that + * execution. This method must be called on the UI thread and the callback will + * be made on the UI thread. + * <p> + * Compatibility note. Applications targeting {@link android.os.Build.VERSION_CODES#N} or + * later, JavaScript state from an empty WebView is no longer persisted across navigations like + * {@link #loadUrl(String)}. For example, global variables and functions defined before calling + * {@link #loadUrl(String)} will not exist in the loaded page. Applications should use + * {@link #addJavascriptInterface} instead to persist JavaScript objects across navigations. + * + * @param script the JavaScript to execute. + * @param resultCallback A callback to be invoked when the script execution + * completes with the result of the execution (if any). + * May be {@code null} if no notification of the result is required. + */ + public void evaluateJavascript(String script, @Nullable ValueCallback<String> resultCallback) { + checkThread(); + mProvider.evaluateJavaScript(script, resultCallback); + } + + /** + * Saves the current view as a web archive. + * + * @param filename the filename where the archive should be placed + */ + public void saveWebArchive(String filename) { + checkThread(); + mProvider.saveWebArchive(filename); + } + + /** + * Saves the current view as a web archive. + * + * @param basename the filename where the archive should be placed + * @param autoname if {@code false}, takes basename to be a file. If {@code true}, basename + * is assumed to be a directory in which a filename will be + * chosen according to the URL of the current page. + * @param callback called after the web archive has been saved. The + * parameter for onReceiveValue will either be the filename + * under which the file was saved, or {@code null} if saving the + * file failed. + */ + public void saveWebArchive(String basename, boolean autoname, @Nullable ValueCallback<String> + callback) { + checkThread(); + mProvider.saveWebArchive(basename, autoname, callback); + } + + /** + * Stops the current load. + */ public void stopLoading() { + checkThread(); + mProvider.stopLoading(); } + /** + * Reloads the current URL. + */ public void reload() { + checkThread(); + mProvider.reload(); } + /** + * Gets whether this WebView has a back history item. + * + * @return {@code true} iff this WebView has a back history item + */ public boolean canGoBack() { - return false; + checkThread(); + return mProvider.canGoBack(); } + /** + * Goes back in the history of this WebView. + */ public void goBack() { + checkThread(); + mProvider.goBack(); } + /** + * Gets whether this WebView has a forward history item. + * + * @return {@code true} iff this WebView has a forward history item + */ public boolean canGoForward() { - return false; + checkThread(); + return mProvider.canGoForward(); } + /** + * Goes forward in the history of this WebView. + */ public void goForward() { + checkThread(); + mProvider.goForward(); } + /** + * Gets whether the page can go back or forward the given + * number of steps. + * + * @param steps the negative or positive number of steps to move the + * history + */ public boolean canGoBackOrForward(int steps) { - return false; + checkThread(); + return mProvider.canGoBackOrForward(steps); } + /** + * Goes to the history item that is the number of steps away from + * the current item. Steps is negative if backward and positive + * if forward. + * + * @param steps the number of steps to take back or forward in the back + * forward list + */ public void goBackOrForward(int steps) { + checkThread(); + mProvider.goBackOrForward(steps); + } + + /** + * Gets whether private browsing is enabled in this WebView. + */ + public boolean isPrivateBrowsingEnabled() { + checkThread(); + return mProvider.isPrivateBrowsingEnabled(); } + /** + * Scrolls the contents of this WebView up by half the view size. + * + * @param top {@code true} to jump to the top of the page + * @return {@code true} if the page was scrolled + */ public boolean pageUp(boolean top) { - return false; + checkThread(); + return mProvider.pageUp(top); } - + + /** + * Scrolls the contents of this WebView down by half the page size. + * + * @param bottom {@code true} to jump to bottom of page + * @return {@code true} if the page was scrolled + */ public boolean pageDown(boolean bottom) { - return false; + checkThread(); + return mProvider.pageDown(bottom); } + /** + * Posts a {@link VisualStateCallback}, which will be called when + * the current state of the WebView is ready to be drawn. + * + * <p>Because updates to the DOM are processed asynchronously, updates to the DOM may not + * immediately be reflected visually by subsequent {@link WebView#onDraw} invocations. The + * {@link VisualStateCallback} provides a mechanism to notify the caller when the contents of + * the DOM at the current time are ready to be drawn the next time the {@link WebView} + * draws. + * + * <p>The next draw after the callback completes is guaranteed to reflect all the updates to the + * DOM up to the point at which the {@link VisualStateCallback} was posted, but it may also + * contain updates applied after the callback was posted. + * + * <p>The state of the DOM covered by this API includes the following: + * <ul> + * <li>primitive HTML elements (div, img, span, etc..)</li> + * <li>images</li> + * <li>CSS animations</li> + * <li>WebGL</li> + * <li>canvas</li> + * </ul> + * It does not include the state of: + * <ul> + * <li>the video tag</li> + * </ul> + * + * <p>To guarantee that the {@link WebView} will successfully render the first frame + * after the {@link VisualStateCallback#onComplete} method has been called a set of conditions + * must be met: + * <ul> + * <li>If the {@link WebView}'s visibility is set to {@link View#VISIBLE VISIBLE} then + * the {@link WebView} must be attached to the view hierarchy.</li> + * <li>If the {@link WebView}'s visibility is set to {@link View#INVISIBLE INVISIBLE} + * then the {@link WebView} must be attached to the view hierarchy and must be made + * {@link View#VISIBLE VISIBLE} from the {@link VisualStateCallback#onComplete} method.</li> + * <li>If the {@link WebView}'s visibility is set to {@link View#GONE GONE} then the + * {@link WebView} must be attached to the view hierarchy and its + * {@link AbsoluteLayout.LayoutParams LayoutParams}'s width and height need to be set to fixed + * values and must be made {@link View#VISIBLE VISIBLE} from the + * {@link VisualStateCallback#onComplete} method.</li> + * </ul> + * + * <p>When using this API it is also recommended to enable pre-rasterization if the {@link + * WebView} is off screen to avoid flickering. See {@link WebSettings#setOffscreenPreRaster} for + * more details and do consider its caveats. + * + * @param requestId An id that will be returned in the callback to allow callers to match + * requests with callbacks. + * @param callback The callback to be invoked. + */ + public void postVisualStateCallback(long requestId, VisualStateCallback callback) { + checkThread(); + mProvider.insertVisualStateCallback(requestId, callback); + } + + /** + * Clears this WebView so that onDraw() will draw nothing but white background, + * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY. + * @deprecated Use WebView.loadUrl("about:blank") to reliably reset the view state + * and release page resources (including any running JavaScript). + */ + @Deprecated public void clearView() { + checkThread(); + mProvider.clearView(); } - + + /** + * Gets a new picture that captures the current contents of this WebView. + * The picture is of the entire document being displayed, and is not + * limited to the area currently displayed by this WebView. Also, the + * picture is a static copy and is unaffected by later changes to the + * content being displayed. + * <p> + * Note that due to internal changes, for API levels between + * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and + * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} inclusive, the + * picture does not include fixed position elements or scrollable divs. + * <p> + * Note that from {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} the returned picture + * should only be drawn into bitmap-backed Canvas - using any other type of Canvas will involve + * additional conversion at a cost in memory and performance. Also the + * {@link android.graphics.Picture#createFromStream} and + * {@link android.graphics.Picture#writeToStream} methods are not supported on the + * returned object. + * + * @deprecated Use {@link #onDraw} to obtain a bitmap snapshot of the WebView, or + * {@link #saveWebArchive} to save the content to a file. + * + * @return a picture that captures the current contents of this WebView + */ + @Deprecated public Picture capturePicture() { - return null; + checkThread(); + return mProvider.capturePicture(); + } + + /** + * @deprecated Use {@link #createPrintDocumentAdapter(String)} which requires user + * to provide a print document name. + */ + @Deprecated + public PrintDocumentAdapter createPrintDocumentAdapter() { + checkThread(); + return mProvider.createPrintDocumentAdapter("default"); } + /** + * Creates a PrintDocumentAdapter that provides the content of this WebView for printing. + * + * The adapter works by converting the WebView contents to a PDF stream. The WebView cannot + * be drawn during the conversion process - any such draws are undefined. It is recommended + * to use a dedicated off screen WebView for the printing. If necessary, an application may + * temporarily hide a visible WebView by using a custom PrintDocumentAdapter instance + * wrapped around the object returned and observing the onStart and onFinish methods. See + * {@link android.print.PrintDocumentAdapter} for more information. + * + * @param documentName The user-facing name of the printed document. See + * {@link android.print.PrintDocumentInfo} + */ + public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) { + checkThread(); + return mProvider.createPrintDocumentAdapter(documentName); + } + + /** + * Gets the current scale of this WebView. + * + * @return the current scale + * + * @deprecated This method is prone to inaccuracy due to race conditions + * between the web rendering and UI threads; prefer + * {@link WebViewClient#onScaleChanged}. + */ + @Deprecated + @ViewDebug.ExportedProperty(category = "webview") public float getScale() { - return 0; + checkThread(); + return mProvider.getScale(); } + /** + * Sets the initial scale for this WebView. 0 means default. + * The behavior for the default scale depends on the state of + * {@link WebSettings#getUseWideViewPort()} and + * {@link WebSettings#getLoadWithOverviewMode()}. + * If the content fits into the WebView control by width, then + * the zoom is set to 100%. For wide content, the behavior + * depends on the state of {@link WebSettings#getLoadWithOverviewMode()}. + * If its value is {@code true}, the content will be zoomed out to be fit + * by width into the WebView control, otherwise not. + * + * If initial scale is greater than 0, WebView starts with this value + * as initial scale. + * Please note that unlike the scale properties in the viewport meta tag, + * this method doesn't take the screen density into account. + * + * @param scaleInPercent the initial scale in percent + */ public void setInitialScale(int scaleInPercent) { + checkThread(); + mProvider.setInitialScale(scaleInPercent); } + /** + * Invokes the graphical zoom picker widget for this WebView. This will + * result in the zoom widget appearing on the screen to control the zoom + * level of this WebView. + */ public void invokeZoomPicker() { + checkThread(); + mProvider.invokeZoomPicker(); + } + + /** + * Gets a HitTestResult based on the current cursor node. If a HTML::a + * tag is found and the anchor has a non-JavaScript URL, the HitTestResult + * type is set to SRC_ANCHOR_TYPE and the URL is set in the "extra" field. + * If the anchor does not have a URL or if it is a JavaScript URL, the type + * will be UNKNOWN_TYPE and the URL has to be retrieved through + * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is + * found, the HitTestResult type is set to IMAGE_TYPE and the URL is set in + * the "extra" field. A type of + * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a URL that has an image as + * a child node. If a phone number is found, the HitTestResult type is set + * to PHONE_TYPE and the phone number is set in the "extra" field of + * HitTestResult. If a map address is found, the HitTestResult type is set + * to GEO_TYPE and the address is set in the "extra" field of HitTestResult. + * If an email address is found, the HitTestResult type is set to EMAIL_TYPE + * and the email is set in the "extra" field of HitTestResult. Otherwise, + * HitTestResult type is set to UNKNOWN_TYPE. + */ + public HitTestResult getHitTestResult() { + checkThread(); + return mProvider.getHitTestResult(); } - public void requestFocusNodeHref(Message hrefMsg) { + /** + * Requests the anchor or image element URL at the last tapped point. + * If hrefMsg is {@code null}, this method returns immediately and does not + * dispatch hrefMsg to its target. If the tapped point hits an image, + * an anchor, or an image in an anchor, the message associates + * strings in named keys in its data. The value paired with the key + * may be an empty string. + * + * @param hrefMsg the message to be dispatched with the result of the + * request. The message data contains three keys. "url" + * returns the anchor's href attribute. "title" returns the + * anchor's text. "src" returns the image's src attribute. + */ + public void requestFocusNodeHref(@Nullable Message hrefMsg) { + checkThread(); + mProvider.requestFocusNodeHref(hrefMsg); } + /** + * Requests the URL of the image last touched by the user. msg will be sent + * to its target with a String representing the URL as its object. + * + * @param msg the message to be dispatched with the result of the request + * as the data member with "url" as key. The result can be {@code null}. + */ public void requestImageRef(Message msg) { + checkThread(); + mProvider.requestImageRef(msg); } + /** + * Gets the URL for the current page. This is not always the same as the URL + * passed to WebViewClient.onPageStarted because although the load for + * that URL has begun, the current page may not have changed. + * + * @return the URL for the current page + */ + @ViewDebug.ExportedProperty(category = "webview") public String getUrl() { - return null; + checkThread(); + return mProvider.getUrl(); } + /** + * Gets the original URL for the current page. This is not always the same + * as the URL passed to WebViewClient.onPageStarted because although the + * load for that URL has begun, the current page may not have changed. + * Also, there may have been redirects resulting in a different URL to that + * originally requested. + * + * @return the URL that was originally requested for the current page + */ + @ViewDebug.ExportedProperty(category = "webview") + public String getOriginalUrl() { + checkThread(); + return mProvider.getOriginalUrl(); + } + + /** + * Gets the title for the current page. This is the title of the current page + * until WebViewClient.onReceivedTitle is called. + * + * @return the title for the current page + */ + @ViewDebug.ExportedProperty(category = "webview") public String getTitle() { - return null; + checkThread(); + return mProvider.getTitle(); } + /** + * Gets the favicon for the current page. This is the favicon of the current + * page until WebViewClient.onReceivedIcon is called. + * + * @return the favicon for the current page + */ public Bitmap getFavicon() { - return null; + checkThread(); + return mProvider.getFavicon(); } + /** + * Gets the touch icon URL for the apple-touch-icon <link> element, or + * a URL on this site's server pointing to the standard location of a + * touch icon. + * + * @hide + */ + public String getTouchIconUrl() { + return mProvider.getTouchIconUrl(); + } + + /** + * Gets the progress for the current page. + * + * @return the progress for the current page between 0 and 100 + */ public int getProgress() { - return 0; + checkThread(); + return mProvider.getProgress(); } - + + /** + * Gets the height of the HTML content. + * + * @return the height of the HTML content + */ + @ViewDebug.ExportedProperty(category = "webview") public int getContentHeight() { - return 0; + checkThread(); + return mProvider.getContentHeight(); + } + + /** + * Gets the width of the HTML content. + * + * @return the width of the HTML content + * @hide + */ + @ViewDebug.ExportedProperty(category = "webview") + public int getContentWidth() { + return mProvider.getContentWidth(); } + /** + * Pauses all layout, parsing, and JavaScript timers for all WebViews. This + * is a global requests, not restricted to just this WebView. This can be + * useful if the application has been paused. + */ public void pauseTimers() { + checkThread(); + mProvider.pauseTimers(); } + /** + * Resumes all layout, parsing, and JavaScript timers for all WebViews. + * This will resume dispatching all timers. + */ public void resumeTimers() { + checkThread(); + mProvider.resumeTimers(); + } + + /** + * Does a best-effort attempt to pause any processing that can be paused + * safely, such as animations and geolocation. Note that this call + * does not pause JavaScript. To pause JavaScript globally, use + * {@link #pauseTimers}. + * + * To resume WebView, call {@link #onResume}. + */ + public void onPause() { + checkThread(); + mProvider.onPause(); + } + + /** + * Resumes a WebView after a previous call to {@link #onPause}. + */ + public void onResume() { + checkThread(); + mProvider.onResume(); + } + + /** + * Gets whether this WebView is paused, meaning onPause() was called. + * Calling onResume() sets the paused state back to {@code false}. + * + * @hide + */ + public boolean isPaused() { + return mProvider.isPaused(); + } + + /** + * Informs this WebView that memory is low so that it can free any available + * memory. + * @deprecated Memory caches are automatically dropped when no longer needed, and in response + * to system memory pressure. + */ + @Deprecated + public void freeMemory() { + checkThread(); + mProvider.freeMemory(); } - public void clearCache() { + /** + * Clears the resource cache. Note that the cache is per-application, so + * this will clear the cache for all WebViews used. + * + * @param includeDiskFiles if {@code false}, only the RAM cache is cleared + */ + public void clearCache(boolean includeDiskFiles) { + checkThread(); + mProvider.clearCache(includeDiskFiles); } + /** + * Removes the autocomplete popup from the currently focused form field, if + * present. Note this only affects the display of the autocomplete popup, + * it does not remove any saved form data from this WebView's store. To do + * that, use {@link WebViewDatabase#clearFormData}. + */ public void clearFormData() { + checkThread(); + mProvider.clearFormData(); } + /** + * Tells this WebView to clear its internal back/forward list. + */ public void clearHistory() { + checkThread(); + mProvider.clearHistory(); } + /** + * Clears the SSL preferences table stored in response to proceeding with + * SSL certificate errors. + */ public void clearSslPreferences() { + checkThread(); + mProvider.clearSslPreferences(); + } + + /** + * Clears the client certificate preferences stored in response + * to proceeding/cancelling client cert requests. Note that WebView + * automatically clears these preferences when it receives a + * {@link KeyChain#ACTION_STORAGE_CHANGED} intent. The preferences are + * shared by all the WebViews that are created by the embedder application. + * + * @param onCleared A runnable to be invoked when client certs are cleared. + * The runnable will be called in UI thread. + */ + public static void clearClientCertPreferences(@Nullable Runnable onCleared) { + getFactory().getStatics().clearClientCertPreferences(onCleared); + } + + /** + * Starts Safe Browsing initialization. + * <p> + * URL loads are not guaranteed to be protected by Safe Browsing until after {@code callback} is + * invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those + * devices {@code callback} will receive {@code false}. + * <p> + * This does not enable the Safe Browsing feature itself, and should only be called if Safe + * Browsing is enabled by the manifest tag or {@link WebSettings#setSafeBrowsingEnabled}. This + * prepares resources used for Safe Browsing. + * <p> + * This should be called with the Application Context (and will always use the Application + * context to do its work regardless). + * + * @param context Application Context. + * @param callback will be called on the UI thread with {@code true} if initialization is + * successful, {@code false} otherwise. + */ + public static void startSafeBrowsing(Context context, + @Nullable ValueCallback<Boolean> callback) { + getFactory().getStatics().initSafeBrowsing(context, callback); + } + + /** + * Sets the list of domains that are exempt from SafeBrowsing checks. The list is + * global for all the WebViews. + * <p> + * Each rule should take one of these: + * <table> + * <tr><th> Rule </th> <th> Example </th> <th> Matches Subdomain</th> </tr> + * <tr><td> HOSTNAME </td> <td> example.com </td> <td> Yes </td> </tr> + * <tr><td> .HOSTNAME </td> <td> .example.com </td> <td> No </td> </tr> + * <tr><td> IPV4_LITERAL </td> <td> 192.168.1.1 </td> <td> No </td></tr> + * <tr><td> IPV6_LITERAL_WITH_BRACKETS </td><td>[10:20:30:40:50:60:70:80]</td><td>No</td></tr> + * </table> + * <p> + * All other rules, including wildcards, are invalid. + * + * @param urls the list of URLs + * @param callback will be called with {@code true} if URLs are successfully added to the + * whitelist. It will be called with {@code false} if any URLs are malformed. The callback will + * be run on the UI thread + */ + public static void setSafeBrowsingWhitelist(@NonNull List<String> urls, + @Nullable ValueCallback<Boolean> callback) { + getFactory().getStatics().setSafeBrowsingWhitelist(urls, callback); + } + + /** + * Returns a URL pointing to the privacy policy for Safe Browsing reporting. + * + * @return the url pointing to a privacy policy document which can be displayed to users. + */ + @NonNull + public static Uri getSafeBrowsingPrivacyPolicyUrl() { + return getFactory().getStatics().getSafeBrowsingPrivacyPolicyUrl(); + } + + /** + * Gets the WebBackForwardList for this WebView. This contains the + * back/forward list for use in querying each item in the history stack. + * This is a copy of the private WebBackForwardList so it contains only a + * snapshot of the current state. Multiple calls to this method may return + * different objects. The object returned from this method will not be + * updated to reflect any new state. + */ + public WebBackForwardList copyBackForwardList() { + checkThread(); + return mProvider.copyBackForwardList(); + + } + + /** + * Registers the listener to be notified as find-on-page operations + * progress. This will replace the current listener. + * + * @param listener an implementation of {@link FindListener} + */ + public void setFindListener(FindListener listener) { + checkThread(); + setupFindListenerIfNeeded(); + mFindListener.mUserFindListener = listener; + } + + /** + * Highlights and scrolls to the next match found by + * {@link #findAllAsync}, wrapping around page boundaries as necessary. + * Notifies any registered {@link FindListener}. If {@link #findAllAsync(String)} + * has not been called yet, or if {@link #clearMatches} has been called since the + * last find operation, this function does nothing. + * + * @param forward the direction to search + * @see #setFindListener + */ + public void findNext(boolean forward) { + checkThread(); + mProvider.findNext(forward); + } + + /** + * Finds all instances of find on the page and highlights them. + * Notifies any registered {@link FindListener}. + * + * @param find the string to find + * @return the number of occurrences of the String "find" that were found + * @deprecated {@link #findAllAsync} is preferred. + * @see #setFindListener + */ + @Deprecated + public int findAll(String find) { + checkThread(); + StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync"); + return mProvider.findAll(find); + } + + /** + * Finds all instances of find on the page and highlights them, + * asynchronously. Notifies any registered {@link FindListener}. + * Successive calls to this will cancel any pending searches. + * + * @param find the string to find. + * @see #setFindListener + */ + public void findAllAsync(String find) { + checkThread(); + mProvider.findAllAsync(find); + } + + /** + * Starts an ActionMode for finding text in this WebView. Only works if this + * WebView is attached to the view system. + * + * @param text if non-null, will be the initial text to search for. + * Otherwise, the last String searched for in this WebView will + * be used to start. + * @param showIme if {@code true}, show the IME, assuming the user will begin typing. + * If {@code false} and text is non-null, perform a find all. + * @return {@code true} if the find dialog is shown, {@code false} otherwise + * @deprecated This method does not work reliably on all Android versions; + * implementing a custom find dialog using WebView.findAllAsync() + * provides a more robust solution. + */ + @Deprecated + public boolean showFindDialog(@Nullable String text, boolean showIme) { + checkThread(); + return mProvider.showFindDialog(text, showIme); } + /** + * Gets the first substring consisting of the address of a physical + * location. Currently, only addresses in the United States are detected, + * and consist of: + * <ul> + * <li>a house number</li> + * <li>a street name</li> + * <li>a street type (Road, Circle, etc), either spelled out or + * abbreviated</li> + * <li>a city name</li> + * <li>a state or territory, either spelled out or two-letter abbr</li> + * <li>an optional 5 digit or 9 digit zip code</li> + * </ul> + * All names must be correctly capitalized, and the zip code, if present, + * must be valid for the state. The street type must be a standard USPS + * spelling or abbreviation. The state or territory must also be spelled + * or abbreviated using USPS standards. The house number may not exceed + * five digits. + * + * @param addr the string to search for addresses + * @return the address, or if no address is found, {@code null} + */ + @Nullable public static String findAddress(String addr) { - return null; + // TODO: Rewrite this in Java so it is not needed to start up chromium + // Could also be deprecated + return getFactory().getStatics().findAddress(addr); + } + + /** + * For apps targeting the L release, WebView has a new default behavior that reduces + * memory footprint and increases performance by intelligently choosing + * the portion of the HTML document that needs to be drawn. These + * optimizations are transparent to the developers. However, under certain + * circumstances, an App developer may want to disable them: + * <ol> + * <li>When an app uses {@link #onDraw} to do own drawing and accesses portions + * of the page that is way outside the visible portion of the page.</li> + * <li>When an app uses {@link #capturePicture} to capture a very large HTML document. + * Note that capturePicture is a deprecated API.</li> + * </ol> + * Enabling drawing the entire HTML document has a significant performance + * cost. This method should be called before any WebViews are created. + */ + public static void enableSlowWholeDocumentDraw() { + getFactory().getStatics().enableSlowWholeDocumentDraw(); } + /** + * Clears the highlighting surrounding text matches created by + * {@link #findAllAsync}. + */ + public void clearMatches() { + checkThread(); + mProvider.clearMatches(); + } + + /** + * Queries the document to see if it contains any image references. The + * message object will be dispatched with arg1 being set to 1 if images + * were found and 0 if the document does not reference any images. + * + * @param response the message that will be dispatched with the result + */ public void documentHasImages(Message response) { + checkThread(); + mProvider.documentHasImages(response); } + /** + * Sets the WebViewClient that will receive various notifications and + * requests. This will replace the current handler. + * + * @param client an implementation of WebViewClient + * @see #getWebViewClient + */ public void setWebViewClient(WebViewClient client) { + checkThread(); + mProvider.setWebViewClient(client); + } + + /** + * Gets the WebViewClient. + * + * @return the WebViewClient, or a default client if not yet set + * @see #setWebViewClient + */ + public WebViewClient getWebViewClient() { + checkThread(); + return mProvider.getWebViewClient(); } + /** + * Registers the interface to be used when content can not be handled by + * the rendering engine, and should be downloaded instead. This will replace + * the current handler. + * + * @param listener an implementation of DownloadListener + */ public void setDownloadListener(DownloadListener listener) { + checkThread(); + mProvider.setDownloadListener(listener); } + /** + * Sets the chrome handler. This is an implementation of WebChromeClient for + * use in handling JavaScript dialogs, favicons, titles, and the progress. + * This will replace the current handler. + * + * @param client an implementation of WebChromeClient + * @see #getWebChromeClient + */ public void setWebChromeClient(WebChromeClient client) { + checkThread(); + mProvider.setWebChromeClient(client); } - public void addJavascriptInterface(Object obj, String interfaceName) { + /** + * Gets the chrome handler. + * + * @return the WebChromeClient, or {@code null} if not yet set + * @see #setWebChromeClient + */ + @Nullable + public WebChromeClient getWebChromeClient() { + checkThread(); + return mProvider.getWebChromeClient(); + } + + /** + * Sets the Picture listener. This is an interface used to receive + * notifications of a new Picture. + * + * @param listener an implementation of WebView.PictureListener + * @deprecated This method is now obsolete. + */ + @Deprecated + public void setPictureListener(PictureListener listener) { + checkThread(); + mProvider.setPictureListener(listener); + } + + /** + * Injects the supplied Java object into this WebView. The object is + * injected into the JavaScript context of the main frame, using the + * supplied name. This allows the Java object's methods to be + * accessed from JavaScript. For applications targeted to API + * level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + * and above, only public methods that are annotated with + * {@link android.webkit.JavascriptInterface} can be accessed from JavaScript. + * For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below, + * all public methods (including the inherited ones) can be accessed, see the + * important security note below for implications. + * <p> Note that injected objects will not appear in JavaScript until the page is next + * (re)loaded. JavaScript should be enabled before injecting the object. For example: + * <pre> + * class JsObject { + * {@literal @}JavascriptInterface + * public String toString() { return "injectedObject"; } + * } + * webview.getSettings().setJavaScriptEnabled(true); + * webView.addJavascriptInterface(new JsObject(), "injectedObject"); + * webView.loadData("<!DOCTYPE html><title></title>", "text/html", null); + * webView.loadUrl("javascript:alert(injectedObject.toString())");</pre> + * <p> + * <strong>IMPORTANT:</strong> + * <ul> + * <li> This method can be used to allow JavaScript to control the host + * application. This is a powerful feature, but also presents a security + * risk for apps targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or earlier. + * Apps that target a version later than {@link android.os.Build.VERSION_CODES#JELLY_BEAN} + * are still vulnerable if the app runs on a device running Android earlier than 4.2. + * The most secure way to use this method is to target {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + * and to ensure the method is called only when running on Android 4.2 or later. + * With these older versions, JavaScript could use reflection to access an + * injected object's public fields. Use of this method in a WebView + * containing untrusted content could allow an attacker to manipulate the + * host application in unintended ways, executing Java code with the + * permissions of the host application. Use extreme care when using this + * method in a WebView which could contain untrusted content.</li> + * <li> JavaScript interacts with Java object on a private, background + * thread of this WebView. Care is therefore required to maintain thread + * safety. + * </li> + * <li> The Java object's fields are not accessible.</li> + * <li> For applications targeted to API level {@link android.os.Build.VERSION_CODES#LOLLIPOP} + * and above, methods of injected Java objects are enumerable from + * JavaScript.</li> + * </ul> + * + * @param object the Java object to inject into this WebView's JavaScript + * context. {@code null} values are ignored. + * @param name the name used to expose the object in JavaScript + */ + public void addJavascriptInterface(Object object, String name) { + checkThread(); + mProvider.addJavascriptInterface(object, name); + } + + /** + * Removes a previously injected Java object from this WebView. Note that + * the removal will not be reflected in JavaScript until the page is next + * (re)loaded. See {@link #addJavascriptInterface}. + * + * @param name the name used to expose the object in JavaScript + */ + public void removeJavascriptInterface(@NonNull String name) { + checkThread(); + mProvider.removeJavascriptInterface(name); + } + + /** + * Creates a message channel to communicate with JS and returns the message + * ports that represent the endpoints of this message channel. The HTML5 message + * channel functionality is described + * <a href="https://html.spec.whatwg.org/multipage/comms.html#messagechannel">here + * </a> + * + * <p>The returned message channels are entangled and already in started state. + * + * @return the two message ports that form the message channel. + */ + public WebMessagePort[] createWebMessageChannel() { + checkThread(); + return mProvider.createWebMessageChannel(); + } + + /** + * Post a message to main frame. The embedded application can restrict the + * messages to a certain target origin. See + * <a href="https://html.spec.whatwg.org/multipage/comms.html#posting-messages"> + * HTML5 spec</a> for how target origin can be used. + * <p> + * A target origin can be set as a wildcard ("*"). However this is not recommended. + * See the page above for security issues. + * + * @param message the WebMessage + * @param targetOrigin the target origin. + */ + public void postWebMessage(WebMessage message, Uri targetOrigin) { + checkThread(); + mProvider.postMessageToMainFrame(message, targetOrigin); + } + + /** + * Gets the WebSettings object used to control the settings for this + * WebView. + * + * @return a WebSettings object that can be used to control this WebView's + * settings + */ + public WebSettings getSettings() { + checkThread(); + return mProvider.getSettings(); + } + + /** + * Enables debugging of web contents (HTML / CSS / JavaScript) + * loaded into any WebViews of this application. This flag can be enabled + * in order to facilitate debugging of web layouts and JavaScript + * code running inside WebViews. Please refer to WebView documentation + * for the debugging guide. + * + * The default is {@code false}. + * + * @param enabled whether to enable web contents debugging + */ + public static void setWebContentsDebuggingEnabled(boolean enabled) { + getFactory().getStatics().setWebContentsDebuggingEnabled(enabled); + } + + /** + * Gets the list of currently loaded plugins. + * + * @return the list of currently loaded plugins + * @deprecated This was used for Gears, which has been deprecated. + * @hide + */ + @Deprecated + public static synchronized PluginList getPluginList() { + return new PluginList(); + } + + /** + * @deprecated This was used for Gears, which has been deprecated. + * @hide + */ + @Deprecated + public void refreshPlugins(boolean reloadOpenPages) { + checkThread(); + } + + /** + * Puts this WebView into text selection mode. Do not rely on this + * functionality; it will be deprecated in the future. + * + * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ + @Deprecated + public void emulateShiftHeld() { + checkThread(); + } + + /** + * @deprecated WebView no longer needs to implement + * ViewGroup.OnHierarchyChangeListener. This method does nothing now. + */ + @Override + // Cannot add @hide as this can always be accessed via the interface. + @Deprecated + public void onChildViewAdded(View parent, View child) {} + + /** + * @deprecated WebView no longer needs to implement + * ViewGroup.OnHierarchyChangeListener. This method does nothing now. + */ + @Override + // Cannot add @hide as this can always be accessed via the interface. + @Deprecated + public void onChildViewRemoved(View p, View child) {} + + /** + * @deprecated WebView should not have implemented + * ViewTreeObserver.OnGlobalFocusChangeListener. This method does nothing now. + */ + @Override + // Cannot add @hide as this can always be accessed via the interface. + @Deprecated + public void onGlobalFocusChanged(View oldFocus, View newFocus) { + } + + /** + * @deprecated Only the default case, {@code true}, will be supported in a future version. + */ + @Deprecated + public void setMapTrackballToArrowKeys(boolean setMap) { + checkThread(); + mProvider.setMapTrackballToArrowKeys(setMap); } + + public void flingScroll(int vx, int vy) { + checkThread(); + mProvider.flingScroll(vx, vy); + } + + /** + * Gets the zoom controls for this WebView, as a separate View. The caller + * is responsible for inserting this View into the layout hierarchy. + * <p/> + * API level {@link android.os.Build.VERSION_CODES#CUPCAKE} introduced + * built-in zoom mechanisms for the WebView, as opposed to these separate + * zoom controls. The built-in mechanisms are preferred and can be enabled + * using {@link WebSettings#setBuiltInZoomControls}. + * + * @deprecated the built-in zoom mechanisms are preferred + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} + */ + @Deprecated public View getZoomControls() { - return null; + checkThread(); + return mProvider.getZoomControls(); } + /** + * Gets whether this WebView can be zoomed in. + * + * @return {@code true} if this WebView can be zoomed in + * + * @deprecated This method is prone to inaccuracy due to race conditions + * between the web rendering and UI threads; prefer + * {@link WebViewClient#onScaleChanged}. + */ + @Deprecated + public boolean canZoomIn() { + checkThread(); + return mProvider.canZoomIn(); + } + + /** + * Gets whether this WebView can be zoomed out. + * + * @return {@code true} if this WebView can be zoomed out + * + * @deprecated This method is prone to inaccuracy due to race conditions + * between the web rendering and UI threads; prefer + * {@link WebViewClient#onScaleChanged}. + */ + @Deprecated + public boolean canZoomOut() { + checkThread(); + return mProvider.canZoomOut(); + } + + /** + * Performs a zoom operation in this WebView. + * + * @param zoomFactor the zoom factor to apply. The zoom factor will be clamped to the WebView's + * zoom limits. This value must be in the range 0.01 to 100.0 inclusive. + */ + public void zoomBy(float zoomFactor) { + checkThread(); + if (zoomFactor < 0.01) + throw new IllegalArgumentException("zoomFactor must be greater than 0.01."); + if (zoomFactor > 100.0) + throw new IllegalArgumentException("zoomFactor must be less than 100."); + mProvider.zoomBy(zoomFactor); + } + + /** + * Performs zoom in in this WebView. + * + * @return {@code true} if zoom in succeeds, {@code false} if no zoom changes + */ public boolean zoomIn() { - return false; + checkThread(); + return mProvider.zoomIn(); } + /** + * Performs zoom out in this WebView. + * + * @return {@code true} if zoom out succeeds, {@code false} if no zoom changes + */ public boolean zoomOut() { - return false; + checkThread(); + return mProvider.zoomOut(); + } + + /** + * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ + @Deprecated + public void debugDump() { + checkThread(); + } + + /** + * See {@link ViewDebug.HierarchyHandler#dumpViewHierarchyWithProperties(BufferedWriter, int)} + * @hide + */ + @Override + public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) { + mProvider.dumpViewHierarchyWithProperties(out, level); + } + + /** + * See {@link ViewDebug.HierarchyHandler#findHierarchyView(String, int)} + * @hide + */ + @Override + public View findHierarchyView(String className, int hashCode) { + return mProvider.findHierarchyView(className, hashCode); + } + + /** @hide */ + @IntDef({ + RENDERER_PRIORITY_WAIVED, + RENDERER_PRIORITY_BOUND, + RENDERER_PRIORITY_IMPORTANT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RendererPriority {} + + /** + * The renderer associated with this WebView is bound with + * {@link Context#BIND_WAIVE_PRIORITY}. At this priority level + * {@link WebView} renderers will be strong targets for out of memory + * killing. + * + * Use with {@link #setRendererPriorityPolicy}. + */ + public static final int RENDERER_PRIORITY_WAIVED = 0; + /** + * The renderer associated with this WebView is bound with + * the default priority for services. + * + * Use with {@link #setRendererPriorityPolicy}. + */ + public static final int RENDERER_PRIORITY_BOUND = 1; + /** + * The renderer associated with this WebView is bound with + * {@link Context#BIND_IMPORTANT}. + * + * Use with {@link #setRendererPriorityPolicy}. + */ + public static final int RENDERER_PRIORITY_IMPORTANT = 2; + + /** + * Set the renderer priority policy for this {@link WebView}. The + * priority policy will be used to determine whether an out of + * process renderer should be considered to be a target for OOM + * killing. + * + * Because a renderer can be associated with more than one + * WebView, the final priority it is computed as the maximum of + * any attached WebViews. When a WebView is destroyed it will + * cease to be considerered when calculating the renderer + * priority. Once no WebViews remain associated with the renderer, + * the priority of the renderer will be reduced to + * {@link #RENDERER_PRIORITY_WAIVED}. + * + * The default policy is to set the priority to + * {@link #RENDERER_PRIORITY_IMPORTANT} regardless of visibility, + * and this should not be changed unless the caller also handles + * renderer crashes with + * {@link WebViewClient#onRenderProcessGone}. Any other setting + * will result in WebView renderers being killed by the system + * more aggressively than the application. + * + * @param rendererRequestedPriority the minimum priority at which + * this WebView desires the renderer process to be bound. + * @param waivedWhenNotVisible if {@code true}, this flag specifies that + * when this WebView is not visible, it will be treated as + * if it had requested a priority of + * {@link #RENDERER_PRIORITY_WAIVED}. + */ + public void setRendererPriorityPolicy( + @RendererPriority int rendererRequestedPriority, + boolean waivedWhenNotVisible) { + mProvider.setRendererPriorityPolicy(rendererRequestedPriority, waivedWhenNotVisible); + } + + /** + * Get the requested renderer priority for this WebView. + * + * @return the requested renderer priority policy. + */ + @RendererPriority + public int getRendererRequestedPriority() { + return mProvider.getRendererRequestedPriority(); + } + + /** + * Return whether this WebView requests a priority of + * {@link #RENDERER_PRIORITY_WAIVED} when not visible. + * + * @return whether this WebView requests a priority of + * {@link #RENDERER_PRIORITY_WAIVED} when not visible. + */ + public boolean getRendererPriorityWaivedWhenNotVisible() { + return mProvider.getRendererPriorityWaivedWhenNotVisible(); + } + + /** + * Sets the {@link TextClassifier} for this WebView. + */ + public void setTextClassifier(@Nullable TextClassifier textClassifier) { + mProvider.setTextClassifier(textClassifier); + } + + /** + * Returns the {@link TextClassifier} used by this WebView. + * If no TextClassifier has been set, this WebView uses the default set by the system. + */ + @NonNull + public TextClassifier getTextClassifier() { + return mProvider.getTextClassifier(); + } + + //------------------------------------------------------------------------- + // Interface for WebView providers + //------------------------------------------------------------------------- + + /** + * Gets the WebViewProvider. Used by providers to obtain the underlying + * implementation, e.g. when the application responds to + * WebViewClient.onCreateWindow() request. + * + * @hide WebViewProvider is not public API. + */ + @SystemApi + public WebViewProvider getWebViewProvider() { + return mProvider; + } + + /** + * Callback interface, allows the provider implementation to access non-public methods + * and fields, and make super-class calls in this WebView instance. + * @hide Only for use by WebViewProvider implementations + */ + @SystemApi + public class PrivateAccess { + // ---- Access to super-class methods ---- + public int super_getScrollBarStyle() { + return WebView.super.getScrollBarStyle(); + } + + public void super_scrollTo(int scrollX, int scrollY) { + WebView.super.scrollTo(scrollX, scrollY); + } + + public void super_computeScroll() { + WebView.super.computeScroll(); + } + + public boolean super_onHoverEvent(MotionEvent event) { + return WebView.super.onHoverEvent(event); + } + + public boolean super_performAccessibilityAction(int action, Bundle arguments) { + return WebView.super.performAccessibilityActionInternal(action, arguments); + } + + public boolean super_performLongClick() { + return WebView.super.performLongClick(); + } + + public boolean super_setFrame(int left, int top, int right, int bottom) { + return WebView.super.setFrame(left, top, right, bottom); + } + + public boolean super_dispatchKeyEvent(KeyEvent event) { + return WebView.super.dispatchKeyEvent(event); + } + + public boolean super_onGenericMotionEvent(MotionEvent event) { + return WebView.super.onGenericMotionEvent(event); + } + + public boolean super_requestFocus(int direction, Rect previouslyFocusedRect) { + return WebView.super.requestFocus(direction, previouslyFocusedRect); + } + + public void super_setLayoutParams(ViewGroup.LayoutParams params) { + WebView.super.setLayoutParams(params); + } + + public void super_startActivityForResult(Intent intent, int requestCode) { + WebView.super.startActivityForResult(intent, requestCode); + } + + // ---- Access to non-public methods ---- + public void overScrollBy(int deltaX, int deltaY, + int scrollX, int scrollY, + int scrollRangeX, int scrollRangeY, + int maxOverScrollX, int maxOverScrollY, + boolean isTouchEvent) { + WebView.this.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, + maxOverScrollX, maxOverScrollY, isTouchEvent); + } + + public void awakenScrollBars(int duration) { + WebView.this.awakenScrollBars(duration); + } + + public void awakenScrollBars(int duration, boolean invalidate) { + WebView.this.awakenScrollBars(duration, invalidate); + } + + public float getVerticalScrollFactor() { + return WebView.this.getVerticalScrollFactor(); + } + + public float getHorizontalScrollFactor() { + return WebView.this.getHorizontalScrollFactor(); + } + + public void setMeasuredDimension(int measuredWidth, int measuredHeight) { + WebView.this.setMeasuredDimension(measuredWidth, measuredHeight); + } + + public void onScrollChanged(int l, int t, int oldl, int oldt) { + WebView.this.onScrollChanged(l, t, oldl, oldt); + } + + public int getHorizontalScrollbarHeight() { + return WebView.this.getHorizontalScrollbarHeight(); + } + + public void super_onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, + int l, int t, int r, int b) { + WebView.super.onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b); + } + + // ---- Access to (non-public) fields ---- + /** Raw setter for the scroll X value, without invoking onScrollChanged handlers etc. */ + public void setScrollXRaw(int scrollX) { + WebView.this.mScrollX = scrollX; + } + + /** Raw setter for the scroll Y value, without invoking onScrollChanged handlers etc. */ + public void setScrollYRaw(int scrollY) { + WebView.this.mScrollY = scrollY; + } + + } + + //------------------------------------------------------------------------- + // Package-private internal stuff + //------------------------------------------------------------------------- + + // Only used by android.webkit.FindActionModeCallback. + void setFindDialogFindListener(FindListener listener) { + checkThread(); + setupFindListenerIfNeeded(); + mFindListener.mFindDialogFindListener = listener; + } + + // Only used by android.webkit.FindActionModeCallback. + void notifyFindDialogDismissed() { + checkThread(); + mProvider.notifyFindDialogDismissed(); + } + + //------------------------------------------------------------------------- + // Private internal stuff + //------------------------------------------------------------------------- + + private WebViewProvider mProvider; + + /** + * In addition to the FindListener that the user may set via the WebView.setFindListener + * API, FindActionModeCallback will register it's own FindListener. We keep them separate + * via this class so that the two FindListeners can potentially exist at once. + */ + private class FindListenerDistributor implements FindListener { + private FindListener mFindDialogFindListener; + private FindListener mUserFindListener; + + @Override + public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, + boolean isDoneCounting) { + if (mFindDialogFindListener != null) { + mFindDialogFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches, + isDoneCounting); + } + + if (mUserFindListener != null) { + mUserFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches, + isDoneCounting); + } + } + } + private FindListenerDistributor mFindListener; + + private void setupFindListenerIfNeeded() { + if (mFindListener == null) { + mFindListener = new FindListenerDistributor(); + mProvider.setFindListener(mFindListener); + } + } + + private void ensureProviderCreated() { + checkThread(); + if (mProvider == null) { + // As this can get called during the base class constructor chain, pass the minimum + // number of dependencies here; the rest are deferred to init(). + mProvider = getFactory().createWebView(this, new PrivateAccess()); + } + } + + private static WebViewFactoryProvider getFactory() { + return WebViewFactory.getProvider(); + } + + private final Looper mWebViewThread = Looper.myLooper(); + + private void checkThread() { + // Ignore mWebViewThread == null because this can be called during in the super class + // constructor, before this class's own constructor has even started. + if (mWebViewThread != null && Looper.myLooper() != mWebViewThread) { + Throwable throwable = new Throwable( + "A WebView method was called on thread '" + + Thread.currentThread().getName() + "'. " + + "All WebView methods must be called on the same thread. " + + "(Expected Looper " + mWebViewThread + " called on " + Looper.myLooper() + + ", FYI main Looper is " + Looper.getMainLooper() + ")"); + Log.w(LOGTAG, Log.getStackTraceString(throwable)); + StrictMode.onWebViewMethodCalledOnWrongThread(throwable); + + if (sEnforceThreadChecking) { + throw new RuntimeException(throwable); + } + } + } + + //------------------------------------------------------------------------- + // Override View methods + //------------------------------------------------------------------------- + + // TODO: Add a test that enumerates all methods in ViewDelegte & ScrollDelegate, and ensures + // there's a corresponding override (or better, caller) for each of them in here. + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mProvider.getViewDelegate().onAttachedToWindow(); + } + + /** @hide */ + @Override + protected void onDetachedFromWindowInternal() { + mProvider.getViewDelegate().onDetachedFromWindow(); + super.onDetachedFromWindowInternal(); + } + + /** @hide */ + @Override + public void onMovedToDisplay(int displayId, Configuration config) { + mProvider.getViewDelegate().onMovedToDisplay(displayId, config); + } + + @Override + public void setLayoutParams(ViewGroup.LayoutParams params) { + mProvider.getViewDelegate().setLayoutParams(params); + } + + @Override + public void setOverScrollMode(int mode) { + super.setOverScrollMode(mode); + // This method may be called in the constructor chain, before the WebView provider is + // created. + ensureProviderCreated(); + mProvider.getViewDelegate().setOverScrollMode(mode); + } + + @Override + public void setScrollBarStyle(int style) { + mProvider.getViewDelegate().setScrollBarStyle(style); + super.setScrollBarStyle(style); + } + + @Override + protected int computeHorizontalScrollRange() { + return mProvider.getScrollDelegate().computeHorizontalScrollRange(); + } + + @Override + protected int computeHorizontalScrollOffset() { + return mProvider.getScrollDelegate().computeHorizontalScrollOffset(); + } + + @Override + protected int computeVerticalScrollRange() { + return mProvider.getScrollDelegate().computeVerticalScrollRange(); + } + + @Override + protected int computeVerticalScrollOffset() { + return mProvider.getScrollDelegate().computeVerticalScrollOffset(); + } + + @Override + protected int computeVerticalScrollExtent() { + return mProvider.getScrollDelegate().computeVerticalScrollExtent(); + } + + @Override + public void computeScroll() { + mProvider.getScrollDelegate().computeScroll(); + } + + @Override + public boolean onHoverEvent(MotionEvent event) { + return mProvider.getViewDelegate().onHoverEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return mProvider.getViewDelegate().onTouchEvent(event); + } + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + return mProvider.getViewDelegate().onGenericMotionEvent(event); + } + + @Override + public boolean onTrackballEvent(MotionEvent event) { + return mProvider.getViewDelegate().onTrackballEvent(event); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return mProvider.getViewDelegate().onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return mProvider.getViewDelegate().onKeyUp(keyCode, event); + } + + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + return mProvider.getViewDelegate().onKeyMultiple(keyCode, repeatCount, event); + } + + /* + TODO: These are not currently implemented in WebViewClassic, but it seems inconsistent not + to be delegating them too. + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + return mProvider.getViewDelegate().onKeyPreIme(keyCode, event); + } + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return mProvider.getViewDelegate().onKeyLongPress(keyCode, event); + } + @Override + public boolean onKeyShortcut(int keyCode, KeyEvent event) { + return mProvider.getViewDelegate().onKeyShortcut(keyCode, event); + } + */ + + @Override + public AccessibilityNodeProvider getAccessibilityNodeProvider() { + AccessibilityNodeProvider provider = + mProvider.getViewDelegate().getAccessibilityNodeProvider(); + return provider == null ? super.getAccessibilityNodeProvider() : provider; + } + + @Deprecated + @Override + public boolean shouldDelayChildPressedState() { + return mProvider.getViewDelegate().shouldDelayChildPressedState(); + } + + @Override + public CharSequence getAccessibilityClassName() { + return WebView.class.getName(); + } + + @Override + public void onProvideVirtualStructure(ViewStructure structure) { + mProvider.getViewDelegate().onProvideVirtualStructure(structure); + } + + /** + * {@inheritDoc} + * + * <p>The {@link ViewStructure} traditionally represents a {@link View}, while for web pages + * it represent HTML nodes. Hence, it's necessary to "map" the HTML properties in a way that is + * understood by the {@link android.service.autofill.AutofillService} implementations: + * + * <ol> + * <li>Only the HTML nodes inside a {@code FORM} are generated. + * <li>The source of the HTML is set using {@link ViewStructure#setWebDomain(String)} in the + * node representing the WebView. + * <li>If a web page has multiple {@code FORM}s, only the data for the current form is + * represented—if the user taps a field from another form, then the current autofill + * context is canceled (by calling {@link android.view.autofill.AutofillManager#cancel()} and + * a new context is created for that {@code FORM}. + * <li>Similarly, if the page has {@code IFRAME} nodes, they are not initially represented in + * the view structure until the user taps a field from a {@code FORM} inside the + * {@code IFRAME}, in which case it would be treated the same way as multiple forms described + * above, except that the {@link ViewStructure#setWebDomain(String) web domain} of the + * {@code FORM} contains the {@code src} attribute from the {@code IFRAME} node. + * <li>The W3C autofill field ({@code autocomplete} tag attribute) maps to + * {@link ViewStructure#setAutofillHints(String[])}. + * <li>If the view is editable, the {@link ViewStructure#setAutofillType(int)} and + * {@link ViewStructure#setAutofillValue(AutofillValue)} must be set. + * <li>The {@code placeholder} attribute maps to {@link ViewStructure#setHint(CharSequence)}. + * <li>Other HTML attributes can be represented through + * {@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}. + * </ol> + * + * <p>If the WebView implementation can determine that the value of a field was set statically + * (for example, not through Javascript), it should also call + * {@code structure.setDataIsSensitive(false)}. + * + * <p>For example, an HTML form with 2 fields for username and password: + * + * <pre class="prettyprint"> + * <input type="text" name="username" id="user" value="Type your username" autocomplete="username" placeholder="Email or username"> + * <input type="password" name="password" id="pass" autocomplete="current-password" placeholder="Password"> + * </pre> + * + * <p>Would map to: + * + * <pre class="prettyprint"> + * int index = structure.addChildCount(2); + * ViewStructure username = structure.newChild(index); + * username.setAutofillId(structure.getAutofillId(), 1); // id 1 - first child + * username.setAutofillHints("username"); + * username.setHtmlInfo(username.newHtmlInfoBuilder("input") + * .addAttribute("type", "text") + * .addAttribute("name", "username") + * .build()); + * username.setHint("Email or username"); + * username.setAutofillType(View.AUTOFILL_TYPE_TEXT); + * username.setAutofillValue(AutofillValue.forText("Type your username")); + * // Value of the field is not sensitive because it was created statically and not changed. + * username.setDataIsSensitive(false); + * + * ViewStructure password = structure.newChild(index + 1); + * username.setAutofillId(structure, 2); // id 2 - second child + * password.setAutofillHints("current-password"); + * password.setHtmlInfo(password.newHtmlInfoBuilder("input") + * .addAttribute("type", "password") + * .addAttribute("name", "password") + * .build()); + * password.setHint("Password"); + * password.setAutofillType(View.AUTOFILL_TYPE_TEXT); + * </pre> + */ + @Override + public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) { + mProvider.getViewDelegate().onProvideAutofillVirtualStructure(structure, flags); + } + + @Override + public void autofill(SparseArray<AutofillValue>values) { + mProvider.getViewDelegate().autofill(values); + } + + /** @hide */ + @Override + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); + mProvider.getViewDelegate().onInitializeAccessibilityNodeInfo(info); + } + + /** @hide */ + @Override + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); + mProvider.getViewDelegate().onInitializeAccessibilityEvent(event); + } + + /** @hide */ + @Override + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + return mProvider.getViewDelegate().performAccessibilityAction(action, arguments); + } + + /** @hide */ + @Override + protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, + int l, int t, int r, int b) { + mProvider.getViewDelegate().onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b); + } + + @Override + protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { + mProvider.getViewDelegate().onOverScrolled(scrollX, scrollY, clampedX, clampedY); + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + mProvider.getViewDelegate().onWindowVisibilityChanged(visibility); + } + + @Override + protected void onDraw(Canvas canvas) { + mProvider.getViewDelegate().onDraw(canvas); + } + + @Override + public boolean performLongClick() { + return mProvider.getViewDelegate().performLongClick(); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + mProvider.getViewDelegate().onConfigurationChanged(newConfig); + } + + /** + * Creates a new InputConnection for an InputMethod to interact with the WebView. + * This is similar to {@link View#onCreateInputConnection} but note that WebView + * calls InputConnection methods on a thread other than the UI thread. + * If these methods are overridden, then the overriding methods should respect + * thread restrictions when calling View methods or accessing data. + */ + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + return mProvider.getViewDelegate().onCreateInputConnection(outAttrs); + } + + @Override + public boolean onDragEvent(DragEvent event) { + return mProvider.getViewDelegate().onDragEvent(event); + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + // This method may be called in the constructor chain, before the WebView provider is + // created. + ensureProviderCreated(); + mProvider.getViewDelegate().onVisibilityChanged(changedView, visibility); + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + mProvider.getViewDelegate().onWindowFocusChanged(hasWindowFocus); + super.onWindowFocusChanged(hasWindowFocus); + } + + @Override + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + mProvider.getViewDelegate().onFocusChanged(focused, direction, previouslyFocusedRect); + super.onFocusChanged(focused, direction, previouslyFocusedRect); + } + + /** @hide */ + @Override + protected boolean setFrame(int left, int top, int right, int bottom) { + return mProvider.getViewDelegate().setFrame(left, top, right, bottom); + } + + @Override + protected void onSizeChanged(int w, int h, int ow, int oh) { + super.onSizeChanged(w, h, ow, oh); + mProvider.getViewDelegate().onSizeChanged(w, h, ow, oh); + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + mProvider.getViewDelegate().onScrollChanged(l, t, oldl, oldt); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return mProvider.getViewDelegate().dispatchKeyEvent(event); + } + + @Override + public boolean requestFocus(int direction, Rect previouslyFocusedRect) { + return mProvider.getViewDelegate().requestFocus(direction, previouslyFocusedRect); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + mProvider.getViewDelegate().onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { + return mProvider.getViewDelegate().requestChildRectangleOnScreen(child, rect, immediate); + } + + @Override + public void setBackgroundColor(int color) { + mProvider.getViewDelegate().setBackgroundColor(color); + } + + @Override + public void setLayerType(int layerType, Paint paint) { + super.setLayerType(layerType, paint); + mProvider.getViewDelegate().setLayerType(layerType, paint); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + mProvider.getViewDelegate().preDispatchDraw(canvas); + super.dispatchDraw(canvas); + } + + @Override + public void onStartTemporaryDetach() { + super.onStartTemporaryDetach(); + mProvider.getViewDelegate().onStartTemporaryDetach(); + } + + @Override + public void onFinishTemporaryDetach() { + super.onFinishTemporaryDetach(); + mProvider.getViewDelegate().onFinishTemporaryDetach(); + } + + @Override + public Handler getHandler() { + return mProvider.getViewDelegate().getHandler(super.getHandler()); + } + + @Override + public View findFocus() { + return mProvider.getViewDelegate().findFocus(super.findFocus()); + } + + /** + * If WebView has already been loaded into the current process this method will return the + * package that was used to load it. Otherwise, the package that would be used if the WebView + * was loaded right now will be returned; this does not cause WebView to be loaded, so this + * information may become outdated at any time. + * The WebView package changes either when the current WebView package is updated, disabled, or + * uninstalled. It can also be changed through a Developer Setting. + * If the WebView package changes, any app process that has loaded WebView will be killed. The + * next time the app starts and loads WebView it will use the new WebView package instead. + * @return the current WebView package, or {@code null} if there is none. + */ + @Nullable + public static PackageInfo getCurrentWebViewPackage() { + PackageInfo webviewPackage = WebViewFactory.getLoadedPackageInfo(); + if (webviewPackage != null) { + return webviewPackage; + } + + IWebViewUpdateService service = WebViewFactory.getUpdateService(); + if (service == null) { + return null; + } + try { + return service.getCurrentWebViewPackage(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}. + * + * @param requestCode The integer request code originally supplied to + * startActivityForResult(), allowing you to identify who this + * result came from. + * @param resultCode The integer result code returned by the child activity + * through its setResult(). + * @param data An Intent, which can return result data to the caller + * (various data can be attached to Intent "extras"). + * @hide + */ + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + mProvider.getViewDelegate().onActivityResult(requestCode, resultCode, data); + } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + + checkThread(); + encoder.addProperty("webview:contentHeight", mProvider.getContentHeight()); + encoder.addProperty("webview:contentWidth", mProvider.getContentWidth()); + encoder.addProperty("webview:scale", mProvider.getScale()); + encoder.addProperty("webview:title", mProvider.getTitle()); + encoder.addProperty("webview:url", mProvider.getUrl()); + encoder.addProperty("webview:originalUrl", mProvider.getOriginalUrl()); } } diff --git a/android/widget/Editor.java b/android/widget/Editor.java index d4be7e57..afd11881 100644 --- a/android/widget/Editor.java +++ b/android/widget/Editor.java @@ -165,7 +165,7 @@ public class Editor { private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11; private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100; - private static final float MAGNIFIER_ZOOM = 1.25f; + private static final float MAGNIFIER_ZOOM = 1.5f; @IntDef({MagnifierHandleTrigger.SELECTION_START, MagnifierHandleTrigger.SELECTION_END, MagnifierHandleTrigger.INSERTION}) @@ -476,17 +476,6 @@ public class Editor { stopTextActionModeWithPreservingSelection(); } - void invalidateMagnifier() { - final DisplayMetrics dm = mTextView.getResources().getDisplayMetrics(); - invalidateMagnifier(0, 0, dm.widthPixels, dm.heightPixels); - } - - void invalidateMagnifier(final float l, final float t, final float r, final float b) { - if (mMagnifier != null) { - mTextView.post(() -> mMagnifier.invalidate(new RectF(l, t, r, b))); - } - } - private void discardTextDisplayLists() { if (mTextRenderNodes != null) { for (int i = 0; i < mTextRenderNodes.length; i++) { @@ -4556,17 +4545,17 @@ public class Editor { + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f; final int[] coordinatesOnScreen = new int[2]; mTextView.getLocationOnScreen(coordinatesOnScreen); - final float centerXOnScreen = mTextView.convertViewToScreenCoord(xPosInView, true); - final float centerYOnScreen = mTextView.convertViewToScreenCoord(yPosInView, false); + final float centerXOnScreen = xPosInView + mTextView.getTotalPaddingLeft() + - mTextView.getScrollX() + coordinatesOnScreen[0]; + final float centerYOnScreen = yPosInView + mTextView.getTotalPaddingTop() + - mTextView.getScrollY() + coordinatesOnScreen[1]; - suspendBlink(); mMagnifier.show(centerXOnScreen, centerYOnScreen, MAGNIFIER_ZOOM); } protected final void dismissMagnifier() { if (mMagnifier != null) { mMagnifier.dismiss(); - resumeBlink(); } } diff --git a/android/widget/RemoteViews.java b/android/widget/RemoteViews.java index 199b596a..1b26f8e2 100644 --- a/android/widget/RemoteViews.java +++ b/android/widget/RemoteViews.java @@ -2653,11 +2653,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Equivalent to calling * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} - * to launch the provided {@link PendingIntent}. The source bounds - * ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the clicked - * view in screen space. - * Note that any activity options associated with the pendingIntent may get overridden - * before starting the intent. + * to launch the provided {@link PendingIntent}. * * When setting the on-click action of items within collections (eg. {@link ListView}, * {@link StackView} etc.), this method will not work. Instead, use {@link diff --git a/android/widget/SelectionActionModeHelper.java b/android/widget/SelectionActionModeHelper.java index 5e22650a..3be42a5b 100644 --- a/android/widget/SelectionActionModeHelper.java +++ b/android/widget/SelectionActionModeHelper.java @@ -95,15 +95,11 @@ public final class SelectionActionModeHelper { } public void startActionModeAsync(boolean adjustSelection) { - // Check if the smart selection should run for editable text. - adjustSelection &= !mTextView.isTextEditable() - || mTextView.getTextClassifier().getSettings() - .isSuggestSelectionEnabledForEditableText(); - mSelectionTracker.onOriginalSelection( getText(mTextView), mTextView.getSelectionStart(), - mTextView.getSelectionEnd()); + mTextView.getSelectionEnd(), + mTextView.isTextEditable()); cancelAsyncTask(); if (skipTextClassification()) { startActionMode(null); @@ -200,10 +196,7 @@ public final class SelectionActionModeHelper { private void startActionMode(@Nullable SelectionResult result) { final CharSequence text = getText(mTextView); if (result != null && text instanceof Spannable) { - // Do not change the selection if TextClassifier should be dark launched. - if (!mTextView.getTextClassifier().getSettings().isDarkLaunch()) { - Selection.setSelection((Spannable) text, result.mStart, result.mEnd); - } + Selection.setSelection((Spannable) text, result.mStart, result.mEnd); mTextClassification = result.mClassification; } else { mTextClassification = null; @@ -384,7 +377,7 @@ public final class SelectionActionModeHelper { } private void resetTextClassificationHelper() { - mTextClassificationHelper.init( + mTextClassificationHelper.reset( mTextView.getTextClassifier(), getText(mTextView), mTextView.getSelectionStart(), mTextView.getSelectionEnd(), @@ -422,7 +415,8 @@ public final class SelectionActionModeHelper { /** * Called when the original selection happens, before smart selection is triggered. */ - public void onOriginalSelection(CharSequence text, int selectionStart, int selectionEnd) { + public void onOriginalSelection( + CharSequence text, int selectionStart, int selectionEnd, boolean editableText) { // If we abandoned a selection and created a new one very shortly after, we may still // have a pending request to log ABANDON, which we flush here. mDelayedLogAbandon.flush(); @@ -818,11 +812,11 @@ public final class SelectionActionModeHelper { TextClassificationHelper(TextClassifier textClassifier, CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) { - init(textClassifier, text, selectionStart, selectionEnd, locales); + reset(textClassifier, text, selectionStart, selectionEnd, locales); } @UiThread - public void init(TextClassifier textClassifier, + public void reset(TextClassifier textClassifier, CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) { mTextClassifier = Preconditions.checkNotNull(textClassifier); mText = Preconditions.checkNotNull(text).toString(); @@ -845,12 +839,8 @@ public final class SelectionActionModeHelper { trimText(); final TextSelection selection = mTextClassifier.suggestSelection( mTrimmedText, mRelativeStart, mRelativeEnd, mLocales); - // Do not classify new selection boundaries if TextClassifier should be dark launched. - if (!mTextClassifier.getSettings().isDarkLaunch()) { - mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart); - mSelectionEnd = Math.min( - mText.length(), selection.getSelectionEndIndex() + mTrimStart); - } + mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart); + mSelectionEnd = Math.min(mText.length(), selection.getSelectionEndIndex() + mTrimStart); return performClassification(selection); } diff --git a/android/widget/TextView.java b/android/widget/TextView.java index ce805526..24ae03c3 100644 --- a/android/widget/TextView.java +++ b/android/widget/TextView.java @@ -9219,36 +9219,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - @Override - public void invalidate() { - super.invalidate(); - - if (mEditor != null) { - mEditor.invalidateMagnifier(); - } - } - - @Override - public void invalidate(int l, int t, int r, int b) { - super.invalidate(l, t, r, b); - - if (mEditor != null) { - mEditor.invalidateMagnifier( - convertViewToScreenCoord(l, true /* isHorizontal */), - convertViewToScreenCoord(t, false /* isHorizontal */), - convertViewToScreenCoord(r, true /* isHorizontal */), - convertViewToScreenCoord(b, false /* isHorizontal */)); - } - } - - float convertViewToScreenCoord(float viewCoord, boolean isHorizontal) { - final int[] coordinatesOnScreen = new int[2]; - getLocationOnScreen(coordinatesOnScreen); - return isHorizontal - ? viewCoord + getTotalPaddingLeft() - getScrollX() + coordinatesOnScreen[0] - : viewCoord + getTotalPaddingTop() - getScrollY() + coordinatesOnScreen[1]; - } - /** * @return whether or not the cursor is visible (assuming this TextView is editable) * @@ -10368,17 +10338,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. structure.setTextStyle(getTextSize(), getCurrentTextColor(), AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); - } else { - structure.setMinTextEms(getMinEms()); - structure.setMaxTextEms(getMaxEms()); - int maxLength = -1; - for (InputFilter filter: getFilters()) { - if (filter instanceof InputFilter.LengthFilter) { - maxLength = ((InputFilter.LengthFilter) filter).getMax(); - break; - } - } - structure.setMaxTextLength(maxLength); } } structure.setHint(getHint()); diff --git a/com/android/commands/pm/Pm.java b/com/android/commands/pm/Pm.java index 60ec8a96..c5c38f53 100644 --- a/com/android/commands/pm/Pm.java +++ b/com/android/commands/pm/Pm.java @@ -1570,19 +1570,11 @@ public final class Pm { private static int showUsage() { System.err.println("usage: pm path [--user USER_ID] PACKAGE"); System.err.println(" pm dump PACKAGE"); - System.err.println(" pm install [-lrtsfdg] [-i PACKAGE] [--user USER_ID]"); - System.err.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]"); - System.err.println(" [--originating-uri URI] [---referrer URI]"); - System.err.println(" [--abi ABI_NAME] [--force-sdk]"); - System.err.println(" [--preload] [--instantapp] [--full] [--dont-kill]"); - System.err.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]"); - System.err.println(" pm install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID]"); - System.err.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]"); - System.err.println(" [--originating-uri URI] [---referrer URI]"); - System.err.println(" [--abi ABI_NAME] [--force-sdk]"); - System.err.println(" [--preload] [--instantapp] [--full] [--dont-kill]"); - System.err.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]"); - System.err.println(" pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]"); + System.err.println(" pm install [-lrtsfd] [-i PACKAGE] [--user USER_ID] [PATH]"); + System.err.println(" pm install-create [-lrtsfdp] [-i PACKAGE] [-S BYTES]"); + System.err.println(" [--install-location 0/1/2]"); + System.err.println(" [--force-uuid internal|UUID]"); + System.err.println(" pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH]"); System.err.println(" pm install-commit SESSION_ID"); System.err.println(" pm install-abandon SESSION_ID"); System.err.println(" pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE"); @@ -1621,27 +1613,15 @@ public final class Pm { System.err.println("pm install: install a single legacy package"); System.err.println("pm install-create: create an install session"); System.err.println(" -l: forward lock application"); - System.err.println(" -r: allow replacement of existing application"); + System.err.println(" -r: replace existing application"); System.err.println(" -t: allow test packages"); - System.err.println(" -i: specify package name of installer owning the app"); + System.err.println(" -i: specify the installer package name"); System.err.println(" -s: install application on sdcard"); System.err.println(" -f: install application on internal flash"); System.err.println(" -d: allow version code downgrade (debuggable packages only)"); - System.err.println(" -p: partial application install (new split on top of existing pkg)"); + System.err.println(" -p: partial application install"); System.err.println(" -g: grant all runtime permissions"); System.err.println(" -S: size in bytes of entire session"); - System.err.println(" --dont-kill: installing a new feature split, don't kill running app"); - System.err.println(" --originating-uri: set URI where app was downloaded from"); - System.err.println(" --referrer: set URI that instigated the install of the app"); - System.err.println(" --pkg: specify expected package name of app being installed"); - System.err.println(" --abi: override the default ABI of the platform"); - System.err.println(" --instantapp: cause the app to be installed as an ephemeral install app"); - System.err.println(" --full: cause the app to be installed as a non-ephemeral full app"); - System.err.println(" --install-location: force the install location:"); - System.err.println(" 0=auto, 1=internal only, 2=prefer external"); - System.err.println(" --force-uuid: force install on to disk volume with given UUID"); - System.err.println(" --force-sdk: allow install even when existing app targets platform"); - System.err.println(" codename but new one targets a final API level"); System.err.println(""); System.err.println("pm install-write: write a package into existing session; path may"); System.err.println(" be '-' to read from stdin"); diff --git a/com/android/ex/photo/ActionBarWrapper.java b/com/android/ex/photo/ActionBarWrapper.java index 6d4d4d20..ae621979 100644 --- a/com/android/ex/photo/ActionBarWrapper.java +++ b/com/android/ex/photo/ActionBarWrapper.java @@ -1,7 +1,8 @@ package com.android.ex.photo; -import android.app.ActionBar; + import android.graphics.drawable.Drawable; +import android.support.v7.app.ActionBar; /** * Wrapper around {@link ActionBar}. diff --git a/com/android/ex/photo/PhotoViewActivity.java b/com/android/ex/photo/PhotoViewActivity.java index 7b53918f..a5c4a438 100644 --- a/com/android/ex/photo/PhotoViewActivity.java +++ b/com/android/ex/photo/PhotoViewActivity.java @@ -21,14 +21,14 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; -import android.support.v4.app.FragmentActivity; +import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; /** * Activity to view the contents of an album. */ -public class PhotoViewActivity extends FragmentActivity +public class PhotoViewActivity extends AppCompatActivity implements PhotoViewController.ActivityInterface { private PhotoViewController mController; @@ -41,7 +41,7 @@ public class PhotoViewActivity extends FragmentActivity mController.onCreate(savedInstanceState); } - public PhotoViewController createController() { + protected PhotoViewController createController() { return new PhotoViewController(this); } @@ -122,7 +122,7 @@ public class PhotoViewActivity extends FragmentActivity @Override public ActionBarInterface getActionBarInterface() { if (mActionBar == null) { - mActionBar = new ActionBarWrapper(getActionBar()); + mActionBar = new ActionBarWrapper(getSupportActionBar()); } return mActionBar; } diff --git a/com/android/internal/alsa/AlsaCardsParser.java b/com/android/internal/alsa/AlsaCardsParser.java index bb75bf6e..5b92a173 100644 --- a/com/android/internal/alsa/AlsaCardsParser.java +++ b/com/android/internal/alsa/AlsaCardsParser.java @@ -37,12 +37,6 @@ public class AlsaCardsParser { private ArrayList<AlsaCardRecord> mCardRecords = new ArrayList<AlsaCardRecord>(); - public static final int SCANSTATUS_NOTSCANNED = -1; - public static final int SCANSTATUS_SUCCESS = 0; - public static final int SCANSTATUS_FAIL = 1; - public static final int SCANSTATUS_EMPTY = 2; - private int mScanStatus = SCANSTATUS_NOTSCANNED; - public class AlsaCardRecord { private static final String TAG = "AlsaCardRecord"; private static final String kUsbCardKeyStr = "at usb-"; @@ -110,11 +104,10 @@ public class AlsaCardsParser { public AlsaCardsParser() {} - public int scan() { + public void scan() { if (DEBUG) { - Slog.i(TAG, "AlsaCardsParser.scan()...."); + Slog.i(TAG, "AlsaCardsParser.scan()"); } - mCardRecords = new ArrayList<AlsaCardRecord>(); File cardsFile = new File(kCardsFilePath); @@ -141,26 +134,11 @@ public class AlsaCardsParser { mCardRecords.add(cardRecord); } reader.close(); - if (mCardRecords.size() > 0) { - mScanStatus = SCANSTATUS_SUCCESS; - } else { - mScanStatus = SCANSTATUS_EMPTY; - } } catch (FileNotFoundException e) { e.printStackTrace(); - mScanStatus = SCANSTATUS_FAIL; } catch (IOException e) { e.printStackTrace(); - mScanStatus = SCANSTATUS_FAIL; - } - if (DEBUG) { - Slog.i(TAG, " status:" + mScanStatus); } - return mScanStatus; - } - - public int getScanStatus() { - return mScanStatus; } public ArrayList<AlsaCardRecord> getScanRecords() { @@ -204,11 +182,7 @@ public class AlsaCardsParser { } // get the new list of devices - if (scan() != SCANSTATUS_SUCCESS) { - Slog.e(TAG, "Error scanning Alsa cards file."); - return -1; - } - + scan(); if (DEBUG) { LogDevices("Current Devices:", mCardRecords); } diff --git a/com/android/internal/alsa/AlsaDevicesParser.java b/com/android/internal/alsa/AlsaDevicesParser.java index 15261baf..6e3d5966 100644 --- a/com/android/internal/alsa/AlsaDevicesParser.java +++ b/com/android/internal/alsa/AlsaDevicesParser.java @@ -46,12 +46,6 @@ public class AlsaDevicesParser { private boolean mHasPlaybackDevices = false; private boolean mHasMIDIDevices = false; - public static final int SCANSTATUS_NOTSCANNED = -1; - public static final int SCANSTATUS_SUCCESS = 0; - public static final int SCANSTATUS_FAIL = 1; - public static final int SCANSTATUS_EMPTY = 2; - private int mScanStatus = SCANSTATUS_NOTSCANNED; - public class AlsaDeviceRecord { public static final int kDeviceType_Unknown = -1; public static final int kDeviceType_Audio = 0; @@ -264,11 +258,7 @@ public class AlsaDevicesParser { return line.charAt(kIndex_CardDeviceField) == '['; } - public int scan() { - if (DEBUG) { - Slog.i(TAG, "AlsaDevicesParser.scan()...."); - } - + public boolean scan() { mDeviceRecords.clear(); File devicesFile = new File(kDevicesFilePath); @@ -284,27 +274,13 @@ public class AlsaDevicesParser { } } reader.close(); - // success if we add at least 1 record - if (mDeviceRecords.size() > 0) { - mScanStatus = SCANSTATUS_SUCCESS; - } else { - mScanStatus = SCANSTATUS_EMPTY; - } + return true; } catch (FileNotFoundException e) { e.printStackTrace(); - mScanStatus = SCANSTATUS_FAIL; } catch (IOException e) { e.printStackTrace(); - mScanStatus = SCANSTATUS_FAIL; } - if (DEBUG) { - Slog.i(TAG, " status:" + mScanStatus); - } - return mScanStatus; - } - - public int getScanStatus() { - return mScanStatus; + return false; } // diff --git a/com/android/internal/notification/SystemNotificationChannels.java b/com/android/internal/notification/SystemNotificationChannels.java index 4a181b27..d64c9a1d 100644 --- a/com/android/internal/notification/SystemNotificationChannels.java +++ b/com/android/internal/notification/SystemNotificationChannels.java @@ -20,7 +20,6 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.content.pm.ParceledListSlice; -import android.media.AudioAttributes; import android.os.RemoteException; import android.provider.Settings; @@ -48,7 +47,6 @@ public class SystemNotificationChannels { public static String RETAIL_MODE = "RETAIL_MODE"; public static String USB = "USB"; public static String FOREGROUND_SERVICE = "FOREGROUND_SERVICE"; - public static String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP"; public static void createAll(Context context) { final NotificationManager nm = context.getSystemService(NotificationManager.class); @@ -141,17 +139,6 @@ public class SystemNotificationChannels { foregroundChannel.setBlockableSystem(true); channelsList.add(foregroundChannel); - NotificationChannel heavyWeightChannel = new NotificationChannel( - HEAVY_WEIGHT_APP, - context.getString(R.string.notification_channel_heavy_weight_app), - NotificationManager.IMPORTANCE_DEFAULT); - heavyWeightChannel.setShowBadge(false); - heavyWeightChannel.setSound(null, new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT) - .build()); - channelsList.add(heavyWeightChannel); - nm.createNotificationChannels(channelsList); } diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java index 5c310b15..36fd991c 100644 --- a/com/android/internal/os/BatteryStatsImpl.java +++ b/com/android/internal/os/BatteryStatsImpl.java @@ -119,7 +119,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 168 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 167 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS; @@ -681,17 +681,17 @@ public class BatteryStatsImpl extends BatteryStats { } @Override - public long getUahDischarge(int which) { + public long getMahDischarge(int which) { return mDischargeCounter.getCountLocked(which); } @Override - public long getUahDischargeScreenOff(int which) { + public long getMahDischargeScreenOff(int which) { return mDischargeScreenOffCounter.getCountLocked(which); } @Override - public long getUahDischargeScreenDoze(int which) { + public long getMahDischargeScreenDoze(int which) { return mDischargeScreenDozeCounter.getCountLocked(which); } @@ -3588,7 +3588,7 @@ public class BatteryStatsImpl extends BatteryStats { public void updateTimeBasesLocked(boolean unplugged, int screenState, long uptime, long realtime) { - final boolean screenOff = !isScreenOn(screenState); + final boolean screenOff = isScreenOff(screenState) || isScreenDoze(screenState); final boolean updateOnBatteryTimeBase = unplugged != mOnBatteryTimeBase.isRunning(); final boolean updateOnBatteryScreenOffTimeBase = (unplugged && screenOff) != mOnBatteryScreenOffTimeBase.isRunning(); @@ -5427,10 +5427,6 @@ public class BatteryStatsImpl extends BatteryStats { elapsedRealtimeUs, which); } - @Override public Timer getScreenBrightnessTimer(int brightnessBin) { - return mScreenBrightnessTimer[brightnessBin]; - } - @Override public long getInteractiveTime(long elapsedRealtimeUs, int which) { return mInteractiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @@ -5524,18 +5520,10 @@ public class BatteryStatsImpl extends BatteryStats { elapsedRealtimeUs, which); } - @Override public Timer getPhoneSignalScanningTimer() { - return mPhoneSignalScanningTimer; - } - @Override public int getPhoneSignalStrengthCount(int strengthBin, int which) { return mPhoneSignalStrengthsTimer[strengthBin].getCountLocked(which); } - @Override public Timer getPhoneSignalStrengthTimer(int strengthBin) { - return mPhoneSignalStrengthsTimer[strengthBin]; - } - @Override public long getPhoneDataConnectionTime(int dataType, long elapsedRealtimeUs, int which) { return mPhoneDataConnectionsTimer[dataType].getTotalTimeLocked( @@ -5546,10 +5534,6 @@ public class BatteryStatsImpl extends BatteryStats { return mPhoneDataConnectionsTimer[dataType].getCountLocked(which); } - @Override public Timer getPhoneDataConnectionTimer(int dataType) { - return mPhoneDataConnectionsTimer[dataType]; - } - @Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) { return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @@ -5588,10 +5572,6 @@ public class BatteryStatsImpl extends BatteryStats { return mWifiStateTimer[wifiState].getCountLocked(which); } - @Override public Timer getWifiStateTimer(int wifiState) { - return mWifiStateTimer[wifiState]; - } - @Override public long getWifiSupplStateTime(int state, long elapsedRealtimeUs, int which) { return mWifiSupplStateTimer[state].getTotalTimeLocked( @@ -5602,10 +5582,6 @@ public class BatteryStatsImpl extends BatteryStats { return mWifiSupplStateTimer[state].getCountLocked(which); } - @Override public Timer getWifiSupplStateTimer(int state) { - return mWifiSupplStateTimer[state]; - } - @Override public long getWifiSignalStrengthTime(int strengthBin, long elapsedRealtimeUs, int which) { return mWifiSignalStrengthsTimer[strengthBin].getTotalTimeLocked( @@ -5616,10 +5592,6 @@ public class BatteryStatsImpl extends BatteryStats { return mWifiSignalStrengthsTimer[strengthBin].getCountLocked(which); } - @Override public Timer getWifiSignalStrengthTimer(int strengthBin) { - return mWifiSignalStrengthsTimer[strengthBin]; - } - @Override public ControllerActivityCounter getBluetoothControllerActivity() { return mBluetoothActivity; @@ -9491,7 +9463,7 @@ public class BatteryStatsImpl extends BatteryStats { } public boolean isScreenOn(int state) { - return state == Display.STATE_ON || state == Display.STATE_VR; + return state == Display.STATE_ON; } public boolean isScreenOff(int state) { @@ -12819,7 +12791,7 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; mMobileRadioActiveTimer = new StopwatchTimer(mClocks, null, -400, null, mOnBatteryTimeBase, in); - mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null, + mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null, mOnBatteryTimeBase, in); mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase, in); mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in); @@ -13118,7 +13090,7 @@ public class BatteryStatsImpl extends BatteryStats { } } } else { - out.writeInt(0); + // TODO: There should be two 0's printed here, not just one. out.writeInt(0); } diff --git a/com/android/internal/os/LoggingPrintStream.java b/com/android/internal/os/LoggingPrintStream.java index d27874cd..f14394ad 100644 --- a/com/android/internal/os/LoggingPrintStream.java +++ b/com/android/internal/os/LoggingPrintStream.java @@ -28,15 +28,12 @@ import java.nio.charset.CodingErrorAction; import java.util.Formatter; import java.util.Locale; -import com.android.internal.annotations.VisibleForTesting; - /** * A print stream which logs output line by line. * * {@hide} */ -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) -public abstract class LoggingPrintStream extends PrintStream { +abstract class LoggingPrintStream extends PrintStream { private final StringBuilder builder = new StringBuilder(); diff --git a/com/android/internal/os/ZygoteInit.java b/com/android/internal/os/ZygoteInit.java index 2be6212b..4abab283 100644 --- a/com/android/internal/os/ZygoteInit.java +++ b/com/android/internal/os/ZygoteInit.java @@ -549,7 +549,7 @@ public class ZygoteInit { try { dexoptNeeded = DexFile.getDexOptNeeded( classPathElement, instructionSet, systemServerFilter, - null /* classLoaderContext */, false /* newProfile */, false /* downgrade */); + false /* newProfile */, false /* downgrade */); } catch (FileNotFoundException ignored) { // Do not add to the classpath. Log.w(TAG, "Missing classpath element for system server: " + classPathElement); diff --git a/com/android/internal/telephony/CarrierKeyDownloadManager.java b/com/android/internal/telephony/CarrierKeyDownloadManager.java index 66bc5291..606f7ffd 100644 --- a/com/android/internal/telephony/CarrierKeyDownloadManager.java +++ b/com/android/internal/telephony/CarrierKeyDownloadManager.java @@ -16,10 +16,6 @@ package com.android.internal.telephony; -import static android.preference.PreferenceManager.getDefaultSharedPreferences; - -import static java.nio.charset.StandardCharsets.UTF_8; - import android.app.AlarmManager; import android.app.DownloadManager; import android.app.PendingIntent; @@ -57,7 +53,8 @@ import java.security.PublicKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Date; -import java.util.zip.GZIPInputStream; + +import static android.preference.PreferenceManager.getDefaultSharedPreferences; /** * This class contains logic to get Certificates and keep them current. @@ -85,7 +82,7 @@ public class CarrierKeyDownloadManager { private static final String SEPARATOR = ":"; private static final String JSON_CERTIFICATE = "certificate"; - // This is a hack to accommodate certain Carriers who insists on using the public-key + // This is a hack to accomodate Verizon. Verizon insists on using the public-key // field to store the certificate. We'll just use which-ever is not null. private static final String JSON_CERTIFICATE_ALTERNATE = "public-key"; private static final String JSON_TYPE = "key-type"; @@ -299,7 +296,6 @@ public class CarrierKeyDownloadManager { DownloadManager.Query query = new DownloadManager.Query(); query.setFilterById(carrierKeyDownloadIdentifier); Cursor cursor = mDownloadManager.query(query); - InputStream source = null; if (cursor == null) { return; @@ -308,7 +304,7 @@ public class CarrierKeyDownloadManager { int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); if (DownloadManager.STATUS_SUCCESSFUL == cursor.getInt(columnIndex)) { try { - source = new FileInputStream( + final InputStream source = new FileInputStream( mDownloadManager.openDownloadedFile(carrierKeyDownloadIdentifier) .getFileDescriptor()); jsonStr = convertToString(source); @@ -318,11 +314,6 @@ public class CarrierKeyDownloadManager { + ". " + e); } finally { mDownloadManager.remove(carrierKeyDownloadIdentifier); - try { - source.close(); - } catch (IOException e) { - e.printStackTrace(); - } } } Log.d(LOG_TAG, "Completed downloading keys"); @@ -362,23 +353,24 @@ public class CarrierKeyDownloadManager { } private static String convertToString(InputStream is) { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + + String line; try { - // The current implementation at certain Carriers has the data gzipped, which requires - // us to unzip the contents. Longer term, we want to add a flag in carrier config which - // determines if the data needs to be zipped or not. - GZIPInputStream gunzip = new GZIPInputStream(is); - BufferedReader reader = new BufferedReader(new InputStreamReader(gunzip, UTF_8)); - StringBuilder sb = new StringBuilder(); - - String line; while ((line = reader.readLine()) != null) { sb.append(line).append('\n'); } - return sb.toString(); } catch (IOException e) { e.printStackTrace(); + } finally { + try { + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } } - return null; + return sb.toString(); } /** @@ -409,7 +401,7 @@ public class CarrierKeyDownloadManager { JSONArray keys = jsonObj.getJSONArray(JSON_CARRIER_KEYS); for (int i = 0; i < keys.length(); i++) { JSONObject key = keys.getJSONObject(i); - // This is a hack to accommodate certain carriers who insist on using the public-key + // This is a hack to accomodate Verizon. Verizon insists on using the public-key // field to store the certificate. We'll just use which-ever is not null. String cert = null; if (key.has(JSON_CERTIFICATE)) { diff --git a/com/android/internal/telephony/NetworkScanRequestTracker.java b/com/android/internal/telephony/NetworkScanRequestTracker.java index 46b1eef9..14c6810c 100644 --- a/com/android/internal/telephony/NetworkScanRequestTracker.java +++ b/com/android/internal/telephony/NetworkScanRequestTracker.java @@ -125,34 +125,6 @@ public final class NetworkScanRequestTracker { return false; } } - - if ((nsri.mRequest.searchPeriodicity < NetworkScanRequest.MIN_SEARCH_PERIODICITY_SEC) - || (nsri.mRequest.searchPeriodicity - > NetworkScanRequest.MAX_SEARCH_PERIODICITY_SEC)) { - return false; - } - - if ((nsri.mRequest.maxSearchTime < NetworkScanRequest.MIN_SEARCH_MAX_SEC) - || (nsri.mRequest.maxSearchTime > NetworkScanRequest.MAX_SEARCH_MAX_SEC)) { - return false; - } - - if ((nsri.mRequest.incrementalResultsPeriodicity - < NetworkScanRequest.MIN_INCREMENTAL_PERIODICITY_SEC) - || (nsri.mRequest.incrementalResultsPeriodicity - > NetworkScanRequest.MAX_INCREMENTAL_PERIODICITY_SEC)) { - return false; - } - - if ((nsri.mRequest.searchPeriodicity > nsri.mRequest.maxSearchTime) - || (nsri.mRequest.incrementalResultsPeriodicity > nsri.mRequest.maxSearchTime)) { - return false; - } - - if ((nsri.mRequest.mccMncs != null) - && (nsri.mRequest.mccMncs.size() > NetworkScanRequest.MAX_MCC_MNC_LIST_SIZE)) { - return false; - } return true; } diff --git a/com/android/internal/telephony/Phone.java b/com/android/internal/telephony/Phone.java index 16f816f4..6acc8743 100644 --- a/com/android/internal/telephony/Phone.java +++ b/com/android/internal/telephony/Phone.java @@ -3319,9 +3319,7 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { mRadioCapability.set(rc); if (SubscriptionManager.isValidSubscriptionId(getSubId())) { - boolean restoreSelection = !mContext.getResources().getBoolean( - com.android.internal.R.bool.skip_restoring_network_selection); - sendSubscriptionSettings(restoreSelection); + sendSubscriptionSettings(true); } } diff --git a/com/android/internal/telephony/RIL.java b/com/android/internal/telephony/RIL.java index 9007f14d..84c2b659 100644 --- a/com/android/internal/telephony/RIL.java +++ b/com/android/internal/telephony/RIL.java @@ -1655,69 +1655,56 @@ public class RIL extends BaseCommands implements CommandsInterface { } } - private android.hardware.radio.V1_1.RadioAccessSpecifier convertRadioAccessSpecifierToRadioHAL( - RadioAccessSpecifier ras) { - android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat = - new android.hardware.radio.V1_1.RadioAccessSpecifier(); - rasInHalFormat.radioAccessNetwork = ras.radioAccessNetwork; - List<Integer> bands = null; - switch (ras.radioAccessNetwork) { - case RadioAccessNetworks.GERAN: - bands = rasInHalFormat.geranBands; - break; - case RadioAccessNetworks.UTRAN: - bands = rasInHalFormat.utranBands; - break; - case RadioAccessNetworks.EUTRAN: - bands = rasInHalFormat.eutranBands; - break; - default: - Log.wtf(RILJ_LOG_TAG, "radioAccessNetwork " + ras.radioAccessNetwork - + " not supported!"); - return null; - } - - if (ras.bands != null) { - for (int band : ras.bands) { - bands.add(band); - } - } - if (ras.channels != null) { - for (int channel : ras.channels) { - rasInHalFormat.channels.add(channel); - } - } - - return rasInHalFormat; - } - @Override public void startNetworkScan(NetworkScanRequest nsr, Message result) { IRadio radioProxy = getRadioProxy(result); if (radioProxy != null) { - android.hardware.radio.V1_2.IRadio radioProxy12 = - android.hardware.radio.V1_2.IRadio.castFrom(radioProxy); - if (radioProxy12 != null) { - android.hardware.radio.V1_2.NetworkScanRequest request = - new android.hardware.radio.V1_2.NetworkScanRequest(); + android.hardware.radio.V1_1.IRadio radioProxy11 = + android.hardware.radio.V1_1.IRadio.castFrom(radioProxy); + if (radioProxy11 == null) { + if (result != null) { + AsyncResult.forMessage(result, null, + CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); + result.sendToTarget(); + } + } else { + android.hardware.radio.V1_1.NetworkScanRequest request = + new android.hardware.radio.V1_1.NetworkScanRequest(); request.type = nsr.scanType; - request.interval = nsr.searchPeriodicity; - request.maxSearchTime = nsr.maxSearchTime; - request.incrementalResultsPeriodicity = nsr.incrementalResultsPeriodicity; - request.incrementalResults = nsr.incrementalResults; - + request.interval = 60; for (RadioAccessSpecifier ras : nsr.specifiers) { - - android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat = - convertRadioAccessSpecifierToRadioHAL(ras); - if (rasInHalFormat == null) { - return; + android.hardware.radio.V1_1.RadioAccessSpecifier s = + new android.hardware.radio.V1_1.RadioAccessSpecifier(); + s.radioAccessNetwork = ras.radioAccessNetwork; + List<Integer> bands = null; + switch (ras.radioAccessNetwork) { + case RadioAccessNetworks.GERAN: + bands = s.geranBands; + break; + case RadioAccessNetworks.UTRAN: + bands = s.utranBands; + break; + case RadioAccessNetworks.EUTRAN: + bands = s.eutranBands; + break; + default: + Log.wtf(RILJ_LOG_TAG, "radioAccessNetwork " + ras.radioAccessNetwork + + " not supported!"); + return; } - - request.specifiers.add(rasInHalFormat); + if (ras.bands != null) { + for (int band : ras.bands) { + bands.add(band); + } + } + if (ras.channels != null) { + for (int channel : ras.channels) { + s.channels.add(channel); + } + } + request.specifiers.add(s); } - request.mccMncs.addAll(nsr.mccMncs); RILRequest rr = obtainRequest(RIL_REQUEST_START_NETWORK_SCAN, result, mRILDefaultWorkSource); @@ -1726,47 +1713,10 @@ public class RIL extends BaseCommands implements CommandsInterface { } try { - radioProxy12.startNetworkScan_1_2(rr.mSerial, request); + radioProxy11.startNetworkScan(rr.mSerial, request); } catch (RemoteException | RuntimeException e) { handleRadioProxyExceptionForRR(rr, "startNetworkScan", e); } - } else { - android.hardware.radio.V1_1.IRadio radioProxy11 = - android.hardware.radio.V1_1.IRadio.castFrom(radioProxy); - if (radioProxy11 == null) { - if (result != null) { - AsyncResult.forMessage(result, null, - CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED)); - result.sendToTarget(); - } - } else { - android.hardware.radio.V1_1.NetworkScanRequest request = - new android.hardware.radio.V1_1.NetworkScanRequest(); - request.type = nsr.scanType; - request.interval = nsr.searchPeriodicity; - for (RadioAccessSpecifier ras : nsr.specifiers) { - android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat = - convertRadioAccessSpecifierToRadioHAL(ras); - if (rasInHalFormat == null) { - return; - } - - request.specifiers.add(rasInHalFormat); - } - - RILRequest rr = obtainRequest(RIL_REQUEST_START_NETWORK_SCAN, result, - mRILDefaultWorkSource); - - if (RILJ_LOGD) { - riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)); - } - - try { - radioProxy11.startNetworkScan(rr.mSerial, request); - } catch (RemoteException | RuntimeException e) { - handleRadioProxyExceptionForRR(rr, "startNetworkScan", e); - } - } } } } diff --git a/com/android/internal/telephony/cat/CatService.java b/com/android/internal/telephony/cat/CatService.java index 802944d8..cd7a7561 100644 --- a/com/android/internal/telephony/cat/CatService.java +++ b/com/android/internal/telephony/cat/CatService.java @@ -1071,13 +1071,6 @@ public class CatService extends Handler implements AppInterface { } break; case NO_RESPONSE_FROM_USER: - // No need to send terminal response for SET UP CALL on user timeout, - // instead use dedicated API - if (type == CommandType.SET_UP_CALL) { - mCmdIf.handleCallSetupRequestFromSim(false, null); - mCurrntCmd = null; - return; - } case UICC_SESSION_TERM_BY_USER: resp = null; break; diff --git a/com/android/internal/telephony/uicc/UiccCardApplication.java b/com/android/internal/telephony/uicc/UiccCardApplication.java index fa6bc3a6..e2904dfd 100644 --- a/com/android/internal/telephony/uicc/UiccCardApplication.java +++ b/com/android/internal/telephony/uicc/UiccCardApplication.java @@ -382,8 +382,11 @@ public class UiccCardApplication { case EVENT_CHANGE_PIN2_DONE: // a PIN/PUK/PIN2/PUK2 complete // request has completed. ar.userObj is the response Message + int attemptsRemaining = -1; ar = (AsyncResult)msg.obj; - int attemptsRemaining = parsePinPukErrorResult(ar); + if ((ar.exception != null) && (ar.result != null)) { + attemptsRemaining = parsePinPukErrorResult(ar); + } Message response = (Message)ar.userObj; AsyncResult.forMessage(response).exception = ar.exception; response.arg1 = attemptsRemaining; diff --git a/com/android/internal/util/MemInfoReader.java b/com/android/internal/util/MemInfoReader.java index 8d716667..b71fa067 100644 --- a/com/android/internal/util/MemInfoReader.java +++ b/com/android/internal/util/MemInfoReader.java @@ -82,7 +82,7 @@ public final class MemInfoReader { * that are mapped in to processes. */ public long getCachedSizeKb() { - return mInfos[Debug.MEMINFO_BUFFERS] + mInfos[Debug.MEMINFO_SLAB_RECLAIMABLE] + return mInfos[Debug.MEMINFO_BUFFERS] + mInfos[Debug.MEMINFO_CACHED] - mInfos[Debug.MEMINFO_MAPPED]; } @@ -90,7 +90,7 @@ public final class MemInfoReader { * Amount of RAM that is in use by the kernel for actual allocations. */ public long getKernelUsedSizeKb() { - return mInfos[Debug.MEMINFO_SHMEM] + mInfos[Debug.MEMINFO_SLAB_UNRECLAIMABLE] + return mInfos[Debug.MEMINFO_SHMEM] + mInfos[Debug.MEMINFO_SLAB] + mInfos[Debug.MEMINFO_VM_ALLOC_USED] + mInfos[Debug.MEMINFO_PAGE_TABLES] + mInfos[Debug.MEMINFO_KERNEL_STACK]; } diff --git a/com/android/internal/view/menu/ListMenuItemView.java b/com/android/internal/view/menu/ListMenuItemView.java index 8f80bfe3..f76c7247 100644 --- a/com/android/internal/view/menu/ListMenuItemView.java +++ b/com/android/internal/view/menu/ListMenuItemView.java @@ -319,15 +319,13 @@ public class ListMenuItemView extends LinearLayout public void setGroupDividerEnabled(boolean groupDividerEnabled) { // If mHasListDivider is true, disabling the groupDivider. // Otherwise, checking enbling it according to groupDividerEnabled flag. - if (mGroupDivider != null) { - mGroupDivider.setVisibility(!mHasListDivider - && groupDividerEnabled ? View.VISIBLE : View.GONE); - } + mGroupDivider.setVisibility(!mHasListDivider + && groupDividerEnabled ? View.VISIBLE : View.GONE); } @Override public void adjustListItemSelectionBounds(Rect rect) { - if (mGroupDivider != null && mGroupDivider.getVisibility() == View.VISIBLE) { + if (mGroupDivider.getVisibility() == View.VISIBLE) { // groupDivider is a part of MenuItemListView. // If ListMenuItem with divider enabled is hovered/clicked, divider also gets selected. // Clipping the selector bounds from the top divider portion when divider is enabled, diff --git a/com/android/internal/widget/Magnifier.java b/com/android/internal/widget/Magnifier.java index 9bc0778d..86e7b38a 100644 --- a/com/android/internal/widget/Magnifier.java +++ b/com/android/internal/widget/Magnifier.java @@ -22,9 +22,7 @@ import android.annotation.UiThread; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Point; -import android.graphics.PointF; import android.graphics.Rect; -import android.graphics.RectF; import android.os.Handler; import android.util.Log; import android.view.Gravity; @@ -43,8 +41,6 @@ import com.android.internal.util.Preconditions; */ public final class Magnifier { private static final String LOG_TAG = "magnifier"; - // Use this to specify that a previous configuration value does not exist. - private static final int INEXISTENT_PREVIOUS_CONFIG_VALUE = -1; // The view for which this magnifier is attached. private final View mView; // The window containing the magnifier. @@ -63,15 +59,6 @@ public final class Magnifier { // the copy is finished. private final Handler mPixelCopyHandler = Handler.getMain(); - private RectF mTmpRectF; - - // Variables holding previous states, used for detecting redundant calls and invalidation. - private Point mPrevStartCoordsOnScreen = new Point( - INEXISTENT_PREVIOUS_CONFIG_VALUE, INEXISTENT_PREVIOUS_CONFIG_VALUE); - private PointF mPrevCenterCoordsOnScreen = new PointF( - INEXISTENT_PREVIOUS_CONFIG_VALUE, INEXISTENT_PREVIOUS_CONFIG_VALUE); - private float mPrevScale = INEXISTENT_PREVIOUS_CONFIG_VALUE; - /** * Initializes a magnifier. * @@ -101,45 +88,16 @@ public final class Magnifier { /** * Shows the magnifier on the screen. * - * @param centerXOnScreen horizontal coordinate of the center point of the magnifier source. The - * lower end is clamped to 0 - * @param centerYOnScreen vertical coordinate of the center point of the magnifier source. The - * lower end is clamped to 0 - * @param scale the scale at which the magnifier zooms on the source content. The - * lower end is clamped to 1 and the higher end to 4 + * @param centerXOnScreen horizontal coordinate of the center point of the magnifier source + * @param centerYOnScreen vertical coordinate of the center point of the magnifier source + * @param scale the scale at which the magnifier zooms on the source content */ public void show(@FloatRange(from=0) float centerXOnScreen, @FloatRange(from=0) float centerYOnScreen, - @FloatRange(from=1, to=4) float scale) { - if (scale > 4) { - scale = 4; - } - - if (scale < 1) { - scale = 1; - } - - if (centerXOnScreen < 0) { - centerXOnScreen = 0; - } - - if (centerYOnScreen < 0) { - centerYOnScreen = 0; - } - - showInternal(centerXOnScreen, centerYOnScreen, scale, false); - } - - private void showInternal(@FloatRange(from=0) float centerXOnScreen, - @FloatRange(from=0) float centerYOnScreen, - @FloatRange(from=1, to=4) float scale, - boolean forceShow) { - if (mPrevScale != scale) { - resizeBitmap(scale); - mPrevScale = scale; - } + @FloatRange(from=1, to=10) float scale) { + maybeResizeBitmap(scale); configureCoordinates(centerXOnScreen, centerYOnScreen); - maybePerformPixelCopy(scale, forceShow); + performPixelCopy(); if (mWindow.isShowing()) { mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(), @@ -148,9 +106,6 @@ public final class Magnifier { mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY, mWindowCoords.x, mWindowCoords.y); } - - mPrevCenterCoordsOnScreen.x = centerXOnScreen; - mPrevCenterCoordsOnScreen.y = centerYOnScreen; } /** @@ -158,38 +113,6 @@ public final class Magnifier { */ public void dismiss() { mWindow.dismiss(); - - mPrevStartCoordsOnScreen.x = INEXISTENT_PREVIOUS_CONFIG_VALUE; - mPrevStartCoordsOnScreen.y = INEXISTENT_PREVIOUS_CONFIG_VALUE; - mPrevCenterCoordsOnScreen.x = INEXISTENT_PREVIOUS_CONFIG_VALUE; - mPrevCenterCoordsOnScreen.y = INEXISTENT_PREVIOUS_CONFIG_VALUE; - mPrevScale = INEXISTENT_PREVIOUS_CONFIG_VALUE; - } - - /** - * Forces the magnifier to update content by taking and showing a new snapshot using the - * previous coordinates. It does this only if the magnifier is showing and the dirty rectangle - * intersects the rectangle which holds the content to be magnified. - * - * @param dirtyRectOnScreen the rectangle representing the screen bounds of the dirty region - */ - public void invalidate(RectF dirtyRectOnScreen) { - if (mWindow.isShowing() && mPrevCenterCoordsOnScreen.x != INEXISTENT_PREVIOUS_CONFIG_VALUE - && mPrevCenterCoordsOnScreen.y != INEXISTENT_PREVIOUS_CONFIG_VALUE - && mPrevScale != INEXISTENT_PREVIOUS_CONFIG_VALUE) { - // Update the current showing RectF. - mTmpRectF = new RectF(mPrevStartCoordsOnScreen.x, - mPrevStartCoordsOnScreen.y, - mPrevStartCoordsOnScreen.x + mBitmap.getWidth(), - mPrevStartCoordsOnScreen.y + mBitmap.getHeight()); - - // Update only if we are currently showing content that has been declared as invalid. - if (RectF.intersects(dirtyRectOnScreen, mTmpRectF)) { - // Update the contents shown in the magnifier. - showInternal(mPrevCenterCoordsOnScreen.x, mPrevCenterCoordsOnScreen.y, mPrevScale, - true /* forceShow */); - } - } } /** @@ -206,11 +129,13 @@ public final class Magnifier { return mWindowWidth; } - private void resizeBitmap(float scale) { + private void maybeResizeBitmap(float scale) { final int bitmapWidth = (int) (mWindowWidth / scale); final int bitmapHeight = (int) (mWindowHeight / scale); - mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); - getImageView().setImageBitmap(mBitmap); + if (mBitmap.getWidth() != bitmapWidth || mBitmap.getHeight() != bitmapHeight) { + mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); + getImageView().setImageBitmap(mBitmap); + } } private void configureCoordinates(float posXOnScreen, float posYOnScreen) { @@ -219,29 +144,24 @@ public final class Magnifier { final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize( R.dimen.magnifier_offset); + final int availableTopSpace = (mCenterZoomCoords.y - mWindowHeight / 2) + - verticalMagnifierOffset - (mBitmap.getHeight() / 2); + mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2; - mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset; + mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 + + verticalMagnifierOffset * (availableTopSpace > 0 ? -1 : 1); } - private void maybePerformPixelCopy(final float scale, final boolean forceShow) { - final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2; - int rawStartX = mCenterZoomCoords.x - mBitmap.getWidth() / 2; - + private void performPixelCopy() { + int startX = mCenterZoomCoords.x - mBitmap.getWidth() / 2; // Clamp startX value to avoid distorting the rendering of the magnifier content. - if (rawStartX < 0) { - rawStartX = 0; - } else if (rawStartX + mBitmap.getWidth() > mView.getWidth()) { - rawStartX = mView.getWidth() - mBitmap.getWidth(); + if (startX < 0) { + startX = 0; + } else if (startX + mBitmap.getWidth() > mView.getWidth()) { + startX = mView.getWidth() - mBitmap.getWidth(); } - if (!forceShow && rawStartX == mPrevStartCoordsOnScreen.x - && startY == mPrevStartCoordsOnScreen.y - && scale == mPrevScale) { - // Skip, we are already showing the desired content. - return; - } - - final int startX = rawStartX; + final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2; final ViewRootImpl viewRootImpl = mView.getViewRootImpl(); if (viewRootImpl != null && viewRootImpl.mSurface != null @@ -251,11 +171,7 @@ public final class Magnifier { new Rect(startX, startY, startX + mBitmap.getWidth(), startY + mBitmap.getHeight()), mBitmap, - result -> { - getImageView().invalidate(); - mPrevStartCoordsOnScreen.x = startX; - mPrevStartCoordsOnScreen.y = startY; - }, + result -> getImageView().invalidate(), mPixelCopyHandler); } else { Log.d(LOG_TAG, "Could not perform PixelCopy request"); diff --git a/com/android/keyguard/CarrierText.java b/com/android/keyguard/CarrierText.java index 13c48d0d..159ac4cc 100644 --- a/com/android/keyguard/CarrierText.java +++ b/com/android/keyguard/CarrierText.java @@ -39,7 +39,6 @@ import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.IccCardConstants.State; import com.android.internal.telephony.TelephonyIntents; import com.android.settingslib.WirelessUtils; -import android.telephony.TelephonyManager; public class CarrierText extends TextView { private static final boolean DEBUG = KeyguardConstants.DEBUG; @@ -53,8 +52,6 @@ public class CarrierText extends TextView { private WifiManager mWifiManager; - private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()]; - private KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() { @Override public void onRefreshCarrierInfo() { @@ -68,22 +65,6 @@ public class CarrierText extends TextView { public void onStartedWakingUp() { setSelected(true); }; - - public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) { - if (slotId < 0) { - Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId); - return; - } - - if (DEBUG) Log.d(TAG,"onSimStateChanged: " + getStatusForIccState(simState)); - if (getStatusForIccState(simState) == StatusMode.SimIoError) { - mSimErrorState[slotId] = true; - updateCarrierText(); - } else if (mSimErrorState[slotId]) { - mSimErrorState[slotId] = false; - updateCarrierText(); - } - }; }; /** * The status of this lock screen. Primarily used for widgets on LockScreen. @@ -96,8 +77,7 @@ public class CarrierText extends TextView { SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times SimLocked, // SIM card is currently locked SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure - SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM. - SimIoError; // SIM card is faulty + SimNotReady; // SIM is not ready yet. May never be on devices w/o a SIM. } public CarrierText(Context context) { @@ -121,35 +101,6 @@ public class CarrierText extends TextView { mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); } - /** - * Checks if there are faulty cards. Adds the text depending on the slot of the card - * @param text: current carrier text based on the sim state - * @param noSims: whether a valid sim card is inserted - * @return text - */ - private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) { - final CharSequence carrier = ""; - CharSequence carrierTextForSimIOError = getCarrierTextForSimState( - IccCardConstants.State.CARD_IO_ERROR, carrier); - for (int index = 0; index < mSimErrorState.length; index++) { - if (mSimErrorState[index]) { - // In the case when no sim cards are detected but a faulty card is inserted - // overwrite the text and only show "Invalid card" - if (noSims) { - return concatenate(carrierTextForSimIOError, - getContext().getText(com.android.internal.R.string.emergency_calls_only)); - } else if (index == 0) { - // prepend "Invalid card" when faulty card is inserted in slot 0 - text = concatenate(carrierTextForSimIOError, text); - } else { - // concatenate "Invalid card" when faulty card is inserted in slot 1 - text = concatenate(text, carrierTextForSimIOError); - } - } - } - return text; - } - protected void updateCarrierText() { boolean allSimsMissing = true; boolean anySimReadyAndInService = false; @@ -228,7 +179,6 @@ public class CarrierText extends TextView { } } - displayText = updateCarrierTextWithSimIoError(displayText, allSimsMissing); // APM (airplane mode) != no carrier state. There are carrier services // (e.g. WFC = Wi-Fi calling) which may operate in APM. if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) { @@ -320,11 +270,6 @@ public class CarrierText extends TextView { getContext().getText(R.string.keyguard_sim_puk_locked_message), text); break; - case SimIoError: - carrierText = makeCarrierStringOnEmergencyCapable( - getContext().getText(R.string.keyguard_sim_error_message_short), - text); - break; } return carrierText; @@ -374,8 +319,6 @@ public class CarrierText extends TextView { return StatusMode.SimPermDisabled; case UNKNOWN: return StatusMode.SimMissing; - case CARD_IO_ERROR: - return StatusMode.SimIoError; } return StatusMode.SimMissing; } diff --git a/com/android/keyguard/KeyguardSecurityModel.java b/com/android/keyguard/KeyguardSecurityModel.java index 0cb64230..7baa57e7 100644 --- a/com/android/keyguard/KeyguardSecurityModel.java +++ b/com/android/keyguard/KeyguardSecurityModel.java @@ -57,16 +57,16 @@ public class KeyguardSecurityModel { SecurityMode getSecurityMode(int userId) { KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); - if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId( - monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))) { - return SecurityMode.SimPuk; - } - if (SubscriptionManager.isValidSubscriptionId( monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) { return SecurityMode.SimPin; } + if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId( + monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))) { + return SecurityMode.SimPuk; + } + final int security = mLockPatternUtils.getActivePasswordQuality(userId); switch (security) { case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: diff --git a/com/android/keyguard/KeyguardUpdateMonitor.java b/com/android/keyguard/KeyguardUpdateMonitor.java index 2bb992c4..d83a6c60 100644 --- a/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/com/android/keyguard/KeyguardUpdateMonitor.java @@ -52,6 +52,7 @@ import android.media.AudioManager; import android.os.BatteryManager; import android.os.CancellationSignal; import android.os.Handler; +import android.os.IBinder; import android.os.IRemoteCallback; import android.os.Message; import android.os.RemoteException; @@ -77,7 +78,7 @@ import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.TaskStackChangeListener; +import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; import com.google.android.collect.Lists; @@ -901,8 +902,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } } else if (IccCardConstants.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) { state = IccCardConstants.State.NETWORK_LOCKED; - } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) { - state = IccCardConstants.State.CARD_IO_ERROR; } else if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(stateExtra) || IccCardConstants.INTENT_VALUE_ICC_IMSI.equals(stateExtra)) { // This is required because telephony doesn't return to "READY" after @@ -1772,7 +1771,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } } - private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { + private final TaskStackListener mTaskStackListener = new TaskStackListener() { @Override public void onTaskStackChangedBackground() { try { diff --git a/com/android/layoutlib/bridge/Bridge.java b/com/android/layoutlib/bridge/Bridge.java index 5dca8e7f..0cfc1811 100644 --- a/com/android/layoutlib/bridge/Bridge.java +++ b/com/android/layoutlib/bridge/Bridge.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2016 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. @@ -14,659 +14,62 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; - -import com.android.ide.common.rendering.api.Capability; -import com.android.ide.common.rendering.api.DrawableParams; -import com.android.ide.common.rendering.api.Features; -import com.android.ide.common.rendering.api.LayoutLog; -import com.android.ide.common.rendering.api.RenderSession; +package com.android.layoutlib.bridge;import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.Result.Status; import com.android.ide.common.rendering.api.SessionParams; -import com.android.layoutlib.bridge.android.RenderParamsFlags; -import com.android.layoutlib.bridge.impl.RenderDrawable; -import com.android.layoutlib.bridge.impl.RenderSessionImpl; -import com.android.layoutlib.bridge.util.DynamicIdMap; -import com.android.ninepatch.NinePatchChunk; -import com.android.resources.ResourceType; -import com.android.tools.layoutlib.create.MethodAdapter; -import com.android.tools.layoutlib.create.OverrideMethod; -import com.android.util.Pair; - -import android.annotation.NonNull; -import android.content.res.BridgeAssetManager; -import android.graphics.Bitmap; -import android.graphics.FontFamily_Delegate; -import android.graphics.Typeface; -import android.graphics.Typeface_Delegate; -import android.icu.util.ULocale; -import android.os.Looper; -import android.os.Looper_Accessor; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; - -import java.io.File; -import java.lang.ref.SoftReference; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Map; -import java.util.WeakHashMap; -import java.util.concurrent.locks.ReentrantLock; -import libcore.io.MemoryMappedFile_Delegate; - -import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; /** - * Main entry point of the LayoutLib Bridge. - * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call - * {@link #createSession(SessionParams)} + * Legacy Bridge used in the SDK version of layoutlib */ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { + private static final String SDK_NOT_SUPPORTED = "The SDK layoutlib version is not supported"; + private static final Result NOT_SUPPORTED_RESULT = + Status.NOT_IMPLEMENTED.createResult(SDK_NOT_SUPPORTED); + private static BufferedImage sImage; - private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left"; + private static class BridgeRenderSession extends RenderSession { - public static class StaticMethodNotImplementedException extends RuntimeException { - private static final long serialVersionUID = 1L; + @Override + public synchronized BufferedImage getImage() { + if (sImage == null) { + sImage = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = sImage.createGraphics(); + g.clearRect(0, 0, 500, 500); + g.drawString(SDK_NOT_SUPPORTED, 20, 20); + g.dispose(); + } - public StaticMethodNotImplementedException(String msg) { - super(msg); + return sImage; } - } - - /** - * Lock to ensure only one rendering/inflating happens at a time. - * This is due to some singleton in the Android framework. - */ - private final static ReentrantLock sLock = new ReentrantLock(); - - /** - * Maps from id to resource type/name. This is for com.android.internal.R - */ - @SuppressWarnings("deprecation") - private final static Map<Integer, Pair<ResourceType, String>> sRMap = new HashMap<>(); - /** - * Reverse map compared to sRMap, resource type -> (resource name -> id). - * This is for com.android.internal.R. - */ - private final static Map<ResourceType, Map<String, Integer>> sRevRMap = new EnumMap<>(ResourceType.class); - - // framework resources are defined as 0x01XX#### where XX is the resource type (layout, - // drawable, etc...). Using FF as the type allows for 255 resource types before we get a - // collision which should be fine. - private final static int DYNAMIC_ID_SEED_START = 0x01ff0000; - private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START); - - private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache = - new WeakHashMap<>(); - private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache = - new WeakHashMap<>(); - - private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = new HashMap<>(); - private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache = - new HashMap<>(); - - private static Map<String, Map<String, Integer>> sEnumValueMap; - private static Map<String, String> sPlatformProperties; - - /** - * A default log than prints to stdout/stderr. - */ - private final static LayoutLog sDefaultLog = new LayoutLog() { @Override - public void error(String tag, String message, Object data) { - System.err.println(message); + public Result render(long timeout, boolean forceMeasure) { + return NOT_SUPPORTED_RESULT; } @Override - public void error(String tag, String message, Throwable throwable, Object data) { - System.err.println(message); + public Result measure(long timeout) { + return NOT_SUPPORTED_RESULT; } @Override - public void warning(String tag, String message, Object data) { - System.out.println(message); - } - }; - - /** - * Current log. - */ - private static LayoutLog sCurrentLog = sDefaultLog; - - private static final int LAST_SUPPORTED_FEATURE = Features.THEME_PREVIEW_NAVIGATION_BAR; - - @Override - public int getApiLevel() { - return com.android.ide.common.rendering.api.Bridge.API_CURRENT; - } - - @SuppressWarnings("deprecation") - @Override - @Deprecated - public EnumSet<Capability> getCapabilities() { - // The Capability class is deprecated and frozen. All Capabilities enumerated there are - // supported by this version of LayoutLibrary. So, it's safe to use EnumSet.allOf() - return EnumSet.allOf(Capability.class); - } - - @Override - public boolean supports(int feature) { - return feature <= LAST_SUPPORTED_FEATURE; - } - - @Override - public boolean init(Map<String,String> platformProperties, - File fontLocation, - Map<String, Map<String, Integer>> enumValueMap, - LayoutLog log) { - sPlatformProperties = platformProperties; - sEnumValueMap = enumValueMap; - - BridgeAssetManager.initSystem(); - - // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener - // on static (native) methods which prints the signature on the console and - // throws an exception. - // This is useful when testing the rendering in ADT to identify static native - // methods that are ignored -- layoutlib_create makes them returns 0/false/null - // which is generally OK yet might be a problem, so this is how you'd find out. - // - // Currently layoutlib_create only overrides static native method. - // Static non-natives are not overridden and thus do not get here. - final String debug = System.getenv("DEBUG_LAYOUT"); - if (debug != null && !debug.equals("0") && !debug.equals("false")) { - - OverrideMethod.setDefaultListener(new MethodAdapter() { - @Override - public void onInvokeV(String signature, boolean isNative, Object caller) { - sDefaultLog.error(null, "Missing Stub: " + signature + - (isNative ? " (native)" : ""), null /*data*/); - - if (debug.equalsIgnoreCase("throw")) { - // Throwing this exception doesn't seem that useful. It breaks - // the layout editor yet doesn't display anything meaningful to the - // user. Having the error in the console is just as useful. We'll - // throw it only if the environment variable is "throw" or "THROW". - throw new StaticMethodNotImplementedException(signature); - } - } - }); - } - - // load the fonts. - FontFamily_Delegate.setFontLocation(fontLocation.getAbsolutePath()); - MemoryMappedFile_Delegate.setDataDir(fontLocation.getAbsoluteFile().getParentFile()); - - // now parse com.android.internal.R (and only this one as android.R is a subset of - // the internal version), and put the content in the maps. - try { - Class<?> r = com.android.internal.R.class; - // Parse the styleable class first, since it may contribute to attr values. - parseStyleable(); - - for (Class<?> inner : r.getDeclaredClasses()) { - if (inner == com.android.internal.R.styleable.class) { - // Already handled the styleable case. Not skipping attr, as there may be attrs - // that are not referenced from styleables. - continue; - } - String resTypeName = inner.getSimpleName(); - ResourceType resType = ResourceType.getEnum(resTypeName); - if (resType != null) { - Map<String, Integer> fullMap = null; - switch (resType) { - case ATTR: - fullMap = sRevRMap.get(ResourceType.ATTR); - break; - case STRING: - case STYLE: - // Slightly less than thousand entries in each. - fullMap = new HashMap<>(1280); - // no break. - default: - if (fullMap == null) { - fullMap = new HashMap<>(); - } - sRevRMap.put(resType, fullMap); - } - - for (Field f : inner.getDeclaredFields()) { - // only process static final fields. Since the final attribute may have - // been altered by layoutlib_create, we only check static - if (!isValidRField(f)) { - continue; - } - Class<?> type = f.getType(); - if (!type.isArray()) { - Integer value = (Integer) f.get(null); - //noinspection deprecation - sRMap.put(value, Pair.of(resType, f.getName())); - fullMap.put(f.getName(), value); - } - } - } - } - } catch (Exception throwable) { - if (log != null) { - log.error(LayoutLog.TAG_BROKEN, - "Failed to load com.android.internal.R from the layout library jar", - throwable, null); - } - return false; + public Result getResult() { + return NOT_SUPPORTED_RESULT; } - - return true; - } - - /** - * Tests if the field is pubic, static and one of int or int[]. - */ - private static boolean isValidRField(Field field) { - int modifiers = field.getModifiers(); - boolean isAcceptable = Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers); - Class<?> type = field.getType(); - return isAcceptable && type == int.class || - (type.isArray() && type.getComponentType() == int.class); - } - private static void parseStyleable() throws Exception { - // R.attr doesn't contain all the needed values. There are too many resources in the - // framework for all to be in the R class. Only the ones specified manually in - // res/values/symbols.xml are put in R class. Since, we need to create a map of all attr - // values, we try and find them from the styleables. - - // There were 1500 elements in this map at M timeframe. - Map<String, Integer> revRAttrMap = new HashMap<>(2048); - sRevRMap.put(ResourceType.ATTR, revRAttrMap); - // There were 2000 elements in this map at M timeframe. - Map<String, Integer> revRStyleableMap = new HashMap<>(3072); - sRevRMap.put(ResourceType.STYLEABLE, revRStyleableMap); - Class<?> c = com.android.internal.R.styleable.class; - Field[] fields = c.getDeclaredFields(); - // Sort the fields to bring all arrays to the beginning, so that indices into the array are - // able to refer back to the arrays (i.e. no forward references). - Arrays.sort(fields, (o1, o2) -> { - if (o1 == o2) { - return 0; - } - Class<?> t1 = o1.getType(); - Class<?> t2 = o2.getType(); - if (t1.isArray() && !t2.isArray()) { - return -1; - } else if (t2.isArray() && !t1.isArray()) { - return 1; - } - return o1.getName().compareTo(o2.getName()); - }); - Map<String, int[]> styleables = new HashMap<>(); - for (Field field : fields) { - if (!isValidRField(field)) { - // Only consider public static fields that are int or int[]. - // Don't check the final flag as it may have been modified by layoutlib_create. - continue; - } - String name = field.getName(); - if (field.getType().isArray()) { - int[] styleableValue = (int[]) field.get(null); - styleables.put(name, styleableValue); - continue; - } - // Not an array. - String arrayName = name; - int[] arrayValue = null; - int index; - while ((index = arrayName.lastIndexOf('_')) >= 0) { - // Find the name of the corresponding styleable. - // Search in reverse order so that attrs like LinearLayout_Layout_layout_gravity - // are mapped to LinearLayout_Layout and not to LinearLayout. - arrayName = arrayName.substring(0, index); - arrayValue = styleables.get(arrayName); - if (arrayValue != null) { - break; - } - } - index = (Integer) field.get(null); - if (arrayValue != null) { - String attrName = name.substring(arrayName.length() + 1); - int attrValue = arrayValue[index]; - //noinspection deprecation - sRMap.put(attrValue, Pair.of(ResourceType.ATTR, attrName)); - revRAttrMap.put(attrName, attrValue); - } - //noinspection deprecation - sRMap.put(index, Pair.of(ResourceType.STYLEABLE, name)); - revRStyleableMap.put(name, index); - } - } @Override - public boolean dispose() { - BridgeAssetManager.clearSystem(); - - // dispose of the default typeface. - Typeface_Delegate.resetDefaults(); - Typeface.sDynamicTypefaceCache.evictAll(); - sProject9PatchCache.clear(); - sProjectBitmapCache.clear(); - - return true; - } - - /** - * Starts a layout session by inflating and rendering it. The method returns a - * {@link RenderSession} on which further actions can be taken. - * <p/> - * If {@link SessionParams} includes the {@link RenderParamsFlags#FLAG_DO_NOT_RENDER_ON_CREATE}, - * this method will only inflate the layout but will NOT render it. - * @param params the {@link SessionParams} object with all the information necessary to create - * the scene. - * @return a new {@link RenderSession} object that contains the result of the layout. - * @since 5 - */ - @Override public RenderSession createSession(SessionParams params) { - try { - Result lastResult; - RenderSessionImpl scene = new RenderSessionImpl(params); - try { - prepareThread(); - lastResult = scene.init(params.getTimeout()); - if (lastResult.isSuccess()) { - lastResult = scene.inflate(); - - boolean doNotRenderOnCreate = Boolean.TRUE.equals( - params.getFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE)); - if (lastResult.isSuccess() && !doNotRenderOnCreate) { - lastResult = scene.render(true /*freshRender*/); - } - } - } finally { - scene.release(); - cleanupThread(); - } - - return new BridgeRenderSession(scene, lastResult); - } catch (Throwable t) { - // get the real cause of the exception. - Throwable t2 = t; - while (t2.getCause() != null) { - t2 = t2.getCause(); - } - return new BridgeRenderSession(null, - ERROR_UNKNOWN.createResult(t2.getMessage(), t)); - } - } - - @Override - public Result renderDrawable(DrawableParams params) { - try { - Result lastResult; - RenderDrawable action = new RenderDrawable(params); - try { - prepareThread(); - lastResult = action.init(params.getTimeout()); - if (lastResult.isSuccess()) { - lastResult = action.render(); - } - } finally { - action.release(); - cleanupThread(); - } - - return lastResult; - } catch (Throwable t) { - // get the real cause of the exception. - Throwable t2 = t; - while (t2.getCause() != null) { - t2 = t.getCause(); - } - return ERROR_UNKNOWN.createResult(t2.getMessage(), t); - } + return new BridgeRenderSession(); } @Override - public void clearCaches(Object projectKey) { - if (projectKey != null) { - sProjectBitmapCache.remove(projectKey); - sProject9PatchCache.remove(projectKey); - } - } - - @Override - public Result getViewParent(Object viewObject) { - if (viewObject instanceof View) { - return Status.SUCCESS.createResult(((View)viewObject).getParent()); - } - - throw new IllegalArgumentException("viewObject is not a View"); - } - - @Override - public Result getViewIndex(Object viewObject) { - if (viewObject instanceof View) { - View view = (View) viewObject; - ViewParent parentView = view.getParent(); - - if (parentView instanceof ViewGroup) { - Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view)); - } - - return Status.SUCCESS.createResult(); - } - - throw new IllegalArgumentException("viewObject is not a View"); - } - - @Override - public boolean isRtl(String locale) { - return isLocaleRtl(locale); - } - - public static boolean isLocaleRtl(String locale) { - if (locale == null) { - locale = ""; - } - ULocale uLocale = new ULocale(locale); - return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL); - } - - /** - * Returns the lock for the bridge - */ - public static ReentrantLock getLock() { - return sLock; - } - - /** - * Prepares the current thread for rendering. - * - * Note that while this can be called several time, the first call to {@link #cleanupThread()} - * will do the clean-up, and make the thread unable to do further scene actions. - */ - public synchronized static void prepareThread() { - // we need to make sure the Looper has been initialized for this thread. - // this is required for View that creates Handler objects. - if (Looper.myLooper() == null) { - Looper.prepareMainLooper(); - } - } - - /** - * Cleans up thread-specific data. After this, the thread cannot be used for scene actions. - * <p> - * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single - * call to this will prevent the thread from doing further scene actions - */ - public synchronized static void cleanupThread() { - // clean up the looper - Looper_Accessor.cleanupThread(); - } - - public static LayoutLog getLog() { - return sCurrentLog; - } - - public static void setLog(LayoutLog log) { - // check only the thread currently owning the lock can do this. - if (!sLock.isHeldByCurrentThread()) { - throw new IllegalStateException("scene must be acquired first. see #acquire(long)"); - } - - if (log != null) { - sCurrentLog = log; - } else { - sCurrentLog = sDefaultLog; - } - } - - /** - * Returns details of a framework resource from its integer value. - * @param value the integer value - * @return a Pair containing the resource type and name, or null if the id - * does not match any resource. - */ - @SuppressWarnings("deprecation") - public static Pair<ResourceType, String> resolveResourceId(int value) { - Pair<ResourceType, String> pair = sRMap.get(value); - if (pair == null) { - pair = sDynamicIds.resolveId(value); - } - return pair; - } - - /** - * Returns the integer id of a framework resource, from a given resource type and resource name. - * <p/> - * If no resource is found, it creates a dynamic id for the resource. - * - * @param type the type of the resource - * @param name the name of the resource. - * - * @return an {@link Integer} containing the resource id. - */ - @NonNull - public static Integer getResourceId(ResourceType type, String name) { - Map<String, Integer> map = sRevRMap.get(type); - Integer value = null; - if (map != null) { - value = map.get(name); - } - - return value == null ? sDynamicIds.getId(type, name) : value; - - } - - /** - * Returns the list of possible enums for a given attribute name. - */ - public static Map<String, Integer> getEnumValues(String attributeName) { - if (sEnumValueMap != null) { - return sEnumValueMap.get(attributeName); - } - - return null; - } - - /** - * Returns the platform build properties. - */ - public static Map<String, String> getPlatformProperties() { - return sPlatformProperties; - } - - /** - * Returns the bitmap for a specific path, from a specific project cache, or from the - * framework cache. - * @param value the path of the bitmap - * @param projectKey the key of the project, or null to query the framework cache. - * @return the cached Bitmap or null if not found. - */ - public static Bitmap getCachedBitmap(String value, Object projectKey) { - if (projectKey != null) { - Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); - if (map != null) { - SoftReference<Bitmap> ref = map.get(value); - if (ref != null) { - return ref.get(); - } - } - } else { - SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value); - if (ref != null) { - return ref.get(); - } - } - - return null; - } - - /** - * Sets a bitmap in a project cache or in the framework cache. - * @param value the path of the bitmap - * @param bmp the Bitmap object - * @param projectKey the key of the project, or null to put the bitmap in the framework cache. - */ - public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) { - if (projectKey != null) { - Map<String, SoftReference<Bitmap>> map = - sProjectBitmapCache.computeIfAbsent(projectKey, k -> new HashMap<>()); - - map.put(value, new SoftReference<>(bmp)); - } else { - sFrameworkBitmapCache.put(value, new SoftReference<>(bmp)); - } - } - - /** - * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the - * framework cache. - * @param value the path of the 9 patch - * @param projectKey the key of the project, or null to query the framework cache. - * @return the cached 9 patch or null if not found. - */ - public static NinePatchChunk getCached9Patch(String value, Object projectKey) { - if (projectKey != null) { - Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey); - - if (map != null) { - SoftReference<NinePatchChunk> ref = map.get(value); - if (ref != null) { - return ref.get(); - } - } - } else { - SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value); - if (ref != null) { - return ref.get(); - } - } - - return null; - } - - /** - * Sets a 9 patch chunk in a project cache or in the framework cache. - * @param value the path of the 9 patch - * @param ninePatch the 9 patch object - * @param projectKey the key of the project, or null to put the bitmap in the framework cache. - */ - public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) { - if (projectKey != null) { - Map<String, SoftReference<NinePatchChunk>> map = - sProject9PatchCache.computeIfAbsent(projectKey, k -> new HashMap<>()); - - map.put(value, new SoftReference<>(ninePatch)); - } else { - sFramework9PatchCache.put(value, new SoftReference<>(ninePatch)); - } + public int getApiLevel() { + return 0; } } diff --git a/com/android/server/AppOpsService.java b/com/android/server/AppOpsService.java index 4ffa5f1f..50b8df2a 100644 --- a/com/android/server/AppOpsService.java +++ b/com/android/server/AppOpsService.java @@ -491,8 +491,7 @@ public class AppOpsService extends IAppOpsService.Stub { return Collections.emptyList(); } synchronized (this) { - Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false /* edit */, - false /* uidMismatchExpected */); + Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false); if (pkgOps == null) { return null; } @@ -531,8 +530,7 @@ public class AppOpsService extends IAppOpsService.Stub { private void pruneOp(Op op, int uid, String packageName) { if (op.time == 0 && op.rejectTime == 0) { - Ops ops = getOpsRawLocked(uid, packageName, false /* edit */, - false /* uidMismatchExpected */); + Ops ops = getOpsRawLocked(uid, packageName, false); if (ops != null) { ops.remove(op.op); if (ops.size() <= 0) { @@ -1048,9 +1046,7 @@ public class AppOpsService extends IAppOpsService.Stub { public int checkPackage(int uid, String packageName) { Preconditions.checkNotNull(packageName); synchronized (this) { - Ops ops = getOpsRawLocked(uid, packageName, true /* edit */, - true /* uidMismatchExpected */); - if (ops != null) { + if (getOpsRawLocked(uid, packageName, true) != null) { return AppOpsManager.MODE_ALLOWED; } else { return AppOpsManager.MODE_ERRORED; @@ -1094,8 +1090,7 @@ public class AppOpsService extends IAppOpsService.Stub { private int noteOperationUnchecked(int code, int uid, String packageName, int proxyUid, String proxyPackageName) { synchronized (this) { - Ops ops = getOpsRawLocked(uid, packageName, true /* edit */, - false /* uidMismatchExpected */); + Ops ops = getOpsRawLocked(uid, packageName, true); if (ops == null) { if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid + " package " + packageName); @@ -1153,8 +1148,7 @@ public class AppOpsService extends IAppOpsService.Stub { } ClientState client = (ClientState)token; synchronized (this) { - Ops ops = getOpsRawLocked(uid, resolvedPackageName, true /* edit */, - false /* uidMismatchExpected */); + Ops ops = getOpsRawLocked(uid, resolvedPackageName, true); if (ops == null) { if (DEBUG) Log.d(TAG, "startOperation: no op for code " + code + " uid " + uid + " package " + resolvedPackageName); @@ -1280,8 +1274,7 @@ public class AppOpsService extends IAppOpsService.Stub { return uidState; } - private Ops getOpsRawLocked(int uid, String packageName, boolean edit, - boolean uidMismatchExpected) { + private Ops getOpsRawLocked(int uid, String packageName, boolean edit) { UidState uidState = getUidStateLocked(uid, edit); if (uidState == null) { return null; @@ -1333,12 +1326,10 @@ public class AppOpsService extends IAppOpsService.Stub { if (pkgUid != uid) { // Oops! The package name is not valid for the uid they are calling // under. Abort. - if (!uidMismatchExpected) { - RuntimeException ex = new RuntimeException("here"); - ex.fillInStackTrace(); - Slog.w(TAG, "Bad call: specified package " + packageName - + " under uid " + uid + " but it is really " + pkgUid, ex); - } + RuntimeException ex = new RuntimeException("here"); + ex.fillInStackTrace(); + Slog.w(TAG, "Bad call: specified package " + packageName + + " under uid " + uid + " but it is really " + pkgUid, ex); return null; } } finally { @@ -1368,8 +1359,7 @@ public class AppOpsService extends IAppOpsService.Stub { } private Op getOpLocked(int code, int uid, String packageName, boolean edit) { - Ops ops = getOpsRawLocked(uid, packageName, edit, - false /* uidMismatchExpected */); + Ops ops = getOpsRawLocked(uid, packageName, edit); if (ops == null) { return null; } @@ -1403,8 +1393,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (AppOpsManager.opAllowSystemBypassRestriction(code)) { // If we are the system, bypass user restrictions for certain codes synchronized (this) { - Ops ops = getOpsRawLocked(uid, packageName, true /* edit */, - false /* uidMismatchExpected */); + Ops ops = getOpsRawLocked(uid, packageName, true); if ((ops != null) && ops.isPrivileged) { return false; } @@ -1724,8 +1713,7 @@ public class AppOpsService extends IAppOpsService.Stub { out.startTag(null, "uid"); out.attribute(null, "n", Integer.toString(pkg.getUid())); synchronized (this) { - Ops ops = getOpsRawLocked(pkg.getUid(), pkg.getPackageName(), - false /* edit */, false /* uidMismatchExpected */); + Ops ops = getOpsRawLocked(pkg.getUid(), pkg.getPackageName(), false); // Should always be present as the list of PackageOps is generated // from Ops. if (ops != null) { diff --git a/com/android/server/BatteryService.java b/com/android/server/BatteryService.java index 47be0a70..5106c8d7 100644 --- a/com/android/server/BatteryService.java +++ b/com/android/server/BatteryService.java @@ -24,7 +24,6 @@ import android.os.PowerManager; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.ShellCommand; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.util.DumpUtils; import com.android.server.am.BatteryStatsService; @@ -36,10 +35,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.hidl.manager.V1_0.IServiceManager; -import android.hidl.manager.V1_0.IServiceNotification; -import android.hardware.health.V2_0.HealthInfo; -import android.hardware.health.V2_0.IHealth; import android.os.BatteryManager; import android.os.BatteryManagerInternal; import android.os.BatteryProperties; @@ -67,9 +62,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.util.Arrays; -import java.util.List; -import java.util.NoSuchElementException; /** * <p>BatteryService monitors the charging status, and charge level of the device @@ -126,8 +118,8 @@ public final class BatteryService extends SystemService { private final Object mLock = new Object(); - private HealthInfo mHealthInfo; - private final HealthInfo mLastHealthInfo = new HealthInfo(); + private BatteryProperties mBatteryProps; + private final BatteryProperties mLastBatteryProps = new BatteryProperties(); private boolean mBatteryLevelCritical; private int mLastBatteryStatus; private int mLastBatteryHealth; @@ -259,16 +251,16 @@ public final class BatteryService extends SystemService { private boolean isPoweredLocked(int plugTypeSet) { // assume we are powered if battery state is unknown so // the "stay on while plugged in" option will work. - if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) { + if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) { return true; } - if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.legacy.chargerAcOnline) { + if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mBatteryProps.chargerAcOnline) { return true; } - if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.legacy.chargerUsbOnline) { + if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mBatteryProps.chargerUsbOnline) { return true; } - if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.legacy.chargerWirelessOnline) { + if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mBatteryProps.chargerWirelessOnline) { return true; } return false; @@ -285,15 +277,15 @@ public final class BatteryService extends SystemService { * (becomes <= mLowBatteryWarningLevel). */ return !plugged - && mHealthInfo.legacy.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN - && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel + && mBatteryProps.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN + && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel); } private void shutdownIfNoPowerLocked() { // shut down gracefully if our battery is critically low and we are not powered. // wait until the system has booted before attempting to display the shutdown dialog. - if (mHealthInfo.legacy.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) { + if (mBatteryProps.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) { mHandler.post(new Runnable() { @Override public void run() { @@ -314,7 +306,7 @@ public final class BatteryService extends SystemService { // shut down gracefully if temperature is too high (> 68.0C by default) // wait until the system has booted before attempting to display the // shutdown dialog. - if (mHealthInfo.legacy.batteryTemperature > mShutdownBatteryTemperature) { + if (mBatteryProps.batteryTemperature > mShutdownBatteryTemperature) { mHandler.post(new Runnable() { @Override public void run() { @@ -334,66 +326,25 @@ public final class BatteryService extends SystemService { private void update(BatteryProperties props) { synchronized (mLock) { if (!mUpdatesStopped) { - mHealthInfo = new HealthInfo(); - copy(mHealthInfo, props); + mBatteryProps = props; // Process the new values. processValuesLocked(false); } else { - copy(mLastHealthInfo, props); + mLastBatteryProps.set(props); } } } - private static void copy(HealthInfo dst, HealthInfo src) { - dst.legacy.chargerAcOnline = src.legacy.chargerAcOnline; - dst.legacy.chargerUsbOnline = src.legacy.chargerUsbOnline; - dst.legacy.chargerWirelessOnline = src.legacy.chargerWirelessOnline; - dst.legacy.maxChargingCurrent = src.legacy.maxChargingCurrent; - dst.legacy.maxChargingVoltage = src.legacy.maxChargingVoltage; - dst.legacy.batteryStatus = src.legacy.batteryStatus; - dst.legacy.batteryHealth = src.legacy.batteryHealth; - dst.legacy.batteryPresent = src.legacy.batteryPresent; - dst.legacy.batteryLevel = src.legacy.batteryLevel; - dst.legacy.batteryVoltage = src.legacy.batteryVoltage; - dst.legacy.batteryTemperature = src.legacy.batteryTemperature; - dst.legacy.batteryCurrent = src.legacy.batteryCurrent; - dst.legacy.batteryCycleCount = src.legacy.batteryCycleCount; - dst.legacy.batteryFullCharge = src.legacy.batteryFullCharge; - dst.legacy.batteryChargeCounter = src.legacy.batteryChargeCounter; - dst.legacy.batteryTechnology = src.legacy.batteryTechnology; - dst.batteryCurrentAverage = src.batteryCurrentAverage; - dst.batteryCapacity = src.batteryCapacity; - dst.energyCounter = src.energyCounter; - } - - // TODO(b/62229583): remove this function when BatteryProperties are completely replaced. - private static void copy(HealthInfo dst, BatteryProperties src) { - dst.legacy.chargerAcOnline = src.chargerAcOnline; - dst.legacy.chargerUsbOnline = src.chargerUsbOnline; - dst.legacy.chargerWirelessOnline = src.chargerWirelessOnline; - dst.legacy.maxChargingCurrent = src.maxChargingCurrent; - dst.legacy.maxChargingVoltage = src.maxChargingVoltage; - dst.legacy.batteryStatus = src.batteryStatus; - dst.legacy.batteryHealth = src.batteryHealth; - dst.legacy.batteryPresent = src.batteryPresent; - dst.legacy.batteryLevel = src.batteryLevel; - dst.legacy.batteryVoltage = src.batteryVoltage; - dst.legacy.batteryTemperature = src.batteryTemperature; - dst.legacy.batteryFullCharge = src.batteryFullCharge; - dst.legacy.batteryChargeCounter = src.batteryChargeCounter; - dst.legacy.batteryTechnology = src.batteryTechnology; - } - private void processValuesLocked(boolean force) { boolean logOutlier = false; long dischargeDuration = 0; - mBatteryLevelCritical = (mHealthInfo.legacy.batteryLevel <= mCriticalBatteryLevel); - if (mHealthInfo.legacy.chargerAcOnline) { + mBatteryLevelCritical = (mBatteryProps.batteryLevel <= mCriticalBatteryLevel); + if (mBatteryProps.chargerAcOnline) { mPlugType = BatteryManager.BATTERY_PLUGGED_AC; - } else if (mHealthInfo.legacy.chargerUsbOnline) { + } else if (mBatteryProps.chargerUsbOnline) { mPlugType = BatteryManager.BATTERY_PLUGGED_USB; - } else if (mHealthInfo.legacy.chargerWirelessOnline) { + } else if (mBatteryProps.chargerWirelessOnline) { mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS; } else { mPlugType = BATTERY_PLUGGED_NONE; @@ -401,17 +352,30 @@ public final class BatteryService extends SystemService { if (DEBUG) { Slog.d(TAG, "Processing new values: " - + "info=" + mHealthInfo + + "chargerAcOnline=" + mBatteryProps.chargerAcOnline + + ", chargerUsbOnline=" + mBatteryProps.chargerUsbOnline + + ", chargerWirelessOnline=" + mBatteryProps.chargerWirelessOnline + + ", maxChargingCurrent" + mBatteryProps.maxChargingCurrent + + ", maxChargingVoltage" + mBatteryProps.maxChargingVoltage + + ", batteryStatus=" + mBatteryProps.batteryStatus + + ", batteryHealth=" + mBatteryProps.batteryHealth + + ", batteryPresent=" + mBatteryProps.batteryPresent + + ", batteryLevel=" + mBatteryProps.batteryLevel + + ", batteryTechnology=" + mBatteryProps.batteryTechnology + + ", batteryVoltage=" + mBatteryProps.batteryVoltage + + ", batteryChargeCounter=" + mBatteryProps.batteryChargeCounter + + ", batteryFullCharge=" + mBatteryProps.batteryFullCharge + + ", batteryTemperature=" + mBatteryProps.batteryTemperature + ", mBatteryLevelCritical=" + mBatteryLevelCritical + ", mPlugType=" + mPlugType); } // Let the battery stats keep track of the current level. try { - mBatteryStats.setBatteryState(mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth, - mPlugType, mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryTemperature, - mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryChargeCounter, - mHealthInfo.legacy.batteryFullCharge); + mBatteryStats.setBatteryState(mBatteryProps.batteryStatus, mBatteryProps.batteryHealth, + mPlugType, mBatteryProps.batteryLevel, mBatteryProps.batteryTemperature, + mBatteryProps.batteryVoltage, mBatteryProps.batteryChargeCounter, + mBatteryProps.batteryFullCharge); } catch (RemoteException e) { // Should never happen. } @@ -419,16 +383,16 @@ public final class BatteryService extends SystemService { shutdownIfNoPowerLocked(); shutdownIfOverTempLocked(); - if (force || (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus || - mHealthInfo.legacy.batteryHealth != mLastBatteryHealth || - mHealthInfo.legacy.batteryPresent != mLastBatteryPresent || - mHealthInfo.legacy.batteryLevel != mLastBatteryLevel || + if (force || (mBatteryProps.batteryStatus != mLastBatteryStatus || + mBatteryProps.batteryHealth != mLastBatteryHealth || + mBatteryProps.batteryPresent != mLastBatteryPresent || + mBatteryProps.batteryLevel != mLastBatteryLevel || mPlugType != mLastPlugType || - mHealthInfo.legacy.batteryVoltage != mLastBatteryVoltage || - mHealthInfo.legacy.batteryTemperature != mLastBatteryTemperature || - mHealthInfo.legacy.maxChargingCurrent != mLastMaxChargingCurrent || - mHealthInfo.legacy.maxChargingVoltage != mLastMaxChargingVoltage || - mHealthInfo.legacy.batteryChargeCounter != mLastChargeCounter || + mBatteryProps.batteryVoltage != mLastBatteryVoltage || + mBatteryProps.batteryTemperature != mLastBatteryTemperature || + mBatteryProps.maxChargingCurrent != mLastMaxChargingCurrent || + mBatteryProps.maxChargingVoltage != mLastMaxChargingVoltage || + mBatteryProps.batteryChargeCounter != mLastChargeCounter || mInvalidCharger != mLastInvalidCharger)) { if (mPlugType != mLastPlugType) { @@ -437,33 +401,33 @@ public final class BatteryService extends SystemService { // There's no value in this data unless we've discharged at least once and the // battery level has changed; so don't log until it does. - if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.legacy.batteryLevel) { + if (mDischargeStartTime != 0 && mDischargeStartLevel != mBatteryProps.batteryLevel) { dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime; logOutlier = true; EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration, - mDischargeStartLevel, mHealthInfo.legacy.batteryLevel); + mDischargeStartLevel, mBatteryProps.batteryLevel); // make sure we see a discharge event before logging again mDischargeStartTime = 0; } } else if (mPlugType == BATTERY_PLUGGED_NONE) { // charging -> discharging or we just powered up mDischargeStartTime = SystemClock.elapsedRealtime(); - mDischargeStartLevel = mHealthInfo.legacy.batteryLevel; + mDischargeStartLevel = mBatteryProps.batteryLevel; } } - if (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus || - mHealthInfo.legacy.batteryHealth != mLastBatteryHealth || - mHealthInfo.legacy.batteryPresent != mLastBatteryPresent || + if (mBatteryProps.batteryStatus != mLastBatteryStatus || + mBatteryProps.batteryHealth != mLastBatteryHealth || + mBatteryProps.batteryPresent != mLastBatteryPresent || mPlugType != mLastPlugType) { EventLog.writeEvent(EventLogTags.BATTERY_STATUS, - mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth, mHealthInfo.legacy.batteryPresent ? 1 : 0, - mPlugType, mHealthInfo.legacy.batteryTechnology); + mBatteryProps.batteryStatus, mBatteryProps.batteryHealth, mBatteryProps.batteryPresent ? 1 : 0, + mPlugType, mBatteryProps.batteryTechnology); } - if (mHealthInfo.legacy.batteryLevel != mLastBatteryLevel) { + if (mBatteryProps.batteryLevel != mLastBatteryLevel) { // Don't do this just from voltage or temperature changes, that is // too noisy. EventLog.writeEvent(EventLogTags.BATTERY_LEVEL, - mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryTemperature); + mBatteryProps.batteryLevel, mBatteryProps.batteryVoltage, mBatteryProps.batteryTemperature); } if (mBatteryLevelCritical && !mLastBatteryLevelCritical && mPlugType == BATTERY_PLUGGED_NONE) { @@ -476,16 +440,16 @@ public final class BatteryService extends SystemService { if (!mBatteryLevelLow) { // Should we now switch in to low battery mode? if (mPlugType == BATTERY_PLUGGED_NONE - && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel) { + && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel) { mBatteryLevelLow = true; } } else { // Should we now switch out of low battery mode? if (mPlugType != BATTERY_PLUGGED_NONE) { mBatteryLevelLow = false; - } else if (mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel) { + } else if (mBatteryProps.batteryLevel >= mLowBatteryCloseWarningLevel) { mBatteryLevelLow = false; - } else if (force && mHealthInfo.legacy.batteryLevel >= mLowBatteryWarningLevel) { + } else if (force && mBatteryProps.batteryLevel >= mLowBatteryWarningLevel) { // If being forced, the previous state doesn't matter, we will just // absolutely check to see if we are now above the warning level. mBatteryLevelLow = false; @@ -532,7 +496,7 @@ public final class BatteryService extends SystemService { } }); } else if (mSentLowBatteryBroadcast && - mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel) { + mBatteryProps.batteryLevel >= mLowBatteryCloseWarningLevel) { mSentLowBatteryBroadcast = false; final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY); statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); @@ -558,16 +522,16 @@ public final class BatteryService extends SystemService { logOutlierLocked(dischargeDuration); } - mLastBatteryStatus = mHealthInfo.legacy.batteryStatus; - mLastBatteryHealth = mHealthInfo.legacy.batteryHealth; - mLastBatteryPresent = mHealthInfo.legacy.batteryPresent; - mLastBatteryLevel = mHealthInfo.legacy.batteryLevel; + mLastBatteryStatus = mBatteryProps.batteryStatus; + mLastBatteryHealth = mBatteryProps.batteryHealth; + mLastBatteryPresent = mBatteryProps.batteryPresent; + mLastBatteryLevel = mBatteryProps.batteryLevel; mLastPlugType = mPlugType; - mLastBatteryVoltage = mHealthInfo.legacy.batteryVoltage; - mLastBatteryTemperature = mHealthInfo.legacy.batteryTemperature; - mLastMaxChargingCurrent = mHealthInfo.legacy.maxChargingCurrent; - mLastMaxChargingVoltage = mHealthInfo.legacy.maxChargingVoltage; - mLastChargeCounter = mHealthInfo.legacy.batteryChargeCounter; + mLastBatteryVoltage = mBatteryProps.batteryVoltage; + mLastBatteryTemperature = mBatteryProps.batteryTemperature; + mLastMaxChargingCurrent = mBatteryProps.maxChargingCurrent; + mLastMaxChargingVoltage = mBatteryProps.maxChargingVoltage; + mLastChargeCounter = mBatteryProps.batteryChargeCounter; mLastBatteryLevelCritical = mBatteryLevelCritical; mLastInvalidCharger = mInvalidCharger; } @@ -579,26 +543,38 @@ public final class BatteryService extends SystemService { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_REPLACE_PENDING); - int icon = getIconLocked(mHealthInfo.legacy.batteryLevel); + int icon = getIconLocked(mBatteryProps.batteryLevel); intent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence); - intent.putExtra(BatteryManager.EXTRA_STATUS, mHealthInfo.legacy.batteryStatus); - intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.legacy.batteryHealth); - intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.legacy.batteryPresent); - intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.legacy.batteryLevel); + intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryProps.batteryStatus); + intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryProps.batteryHealth); + intent.putExtra(BatteryManager.EXTRA_PRESENT, mBatteryProps.batteryPresent); + intent.putExtra(BatteryManager.EXTRA_LEVEL, mBatteryProps.batteryLevel); intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE); intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon); intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType); - intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.legacy.batteryVoltage); - intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.legacy.batteryTemperature); - intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.legacy.batteryTechnology); + intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryProps.batteryVoltage); + intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryProps.batteryTemperature); + intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryProps.batteryTechnology); intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger); - intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent); - intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage); - intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter); + intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mBatteryProps.maxChargingCurrent); + intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mBatteryProps.maxChargingVoltage); + intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mBatteryProps.batteryChargeCounter); if (DEBUG) { - Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE - + ", info:" + mHealthInfo.toString()); + Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. level:" + mBatteryProps.batteryLevel + + ", scale:" + BATTERY_SCALE + ", status:" + mBatteryProps.batteryStatus + + ", health:" + mBatteryProps.batteryHealth + + ", present:" + mBatteryProps.batteryPresent + + ", voltage: " + mBatteryProps.batteryVoltage + + ", temperature: " + mBatteryProps.batteryTemperature + + ", technology: " + mBatteryProps.batteryTechnology + + ", AC powered:" + mBatteryProps.chargerAcOnline + + ", USB powered:" + mBatteryProps.chargerUsbOnline + + ", Wireless powered:" + mBatteryProps.chargerWirelessOnline + + ", icon:" + icon + ", invalid charger:" + mInvalidCharger + + ", maxChargingCurrent:" + mBatteryProps.maxChargingCurrent + + ", maxChargingVoltage:" + mBatteryProps.maxChargingVoltage + + ", chargeCounter:" + mBatteryProps.batteryChargeCounter); } mHandler.post(new Runnable() { @@ -659,14 +635,14 @@ public final class BatteryService extends SystemService { long durationThreshold = Long.parseLong(durationThresholdString); int dischargeThreshold = Integer.parseInt(dischargeThresholdString); if (duration <= durationThreshold && - mDischargeStartLevel - mHealthInfo.legacy.batteryLevel >= dischargeThreshold) { + mDischargeStartLevel - mBatteryProps.batteryLevel >= dischargeThreshold) { // If the discharge cycle is bad enough we want to know about it. logBatteryStatsLocked(); } if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold + " discharge threshold: " + dischargeThreshold); if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " + - (mDischargeStartLevel - mHealthInfo.legacy.batteryLevel)); + (mDischargeStartLevel - mBatteryProps.batteryLevel)); } catch (NumberFormatException e) { Slog.e(TAG, "Invalid DischargeThresholds GService string: " + durationThresholdString + " or " + dischargeThresholdString); @@ -675,14 +651,14 @@ public final class BatteryService extends SystemService { } private int getIconLocked(int level) { - if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) { + if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) { return com.android.internal.R.drawable.stat_sys_battery_charge; - } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) { + } else if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) { return com.android.internal.R.drawable.stat_sys_battery; - } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING - || mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) { + } else if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING + || mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) { if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY) - && mHealthInfo.legacy.batteryLevel >= 100) { + && mBatteryProps.batteryLevel >= 100) { return com.android.internal.R.drawable.stat_sys_battery_charge; } else { return com.android.internal.R.drawable.stat_sys_battery; @@ -744,11 +720,11 @@ public final class BatteryService extends SystemService { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.DEVICE_POWER, null); if (!mUpdatesStopped) { - copy(mLastHealthInfo, mHealthInfo); + mLastBatteryProps.set(mBatteryProps); } - mHealthInfo.legacy.chargerAcOnline = false; - mHealthInfo.legacy.chargerUsbOnline = false; - mHealthInfo.legacy.chargerWirelessOnline = false; + mBatteryProps.chargerAcOnline = false; + mBatteryProps.chargerUsbOnline = false; + mBatteryProps.chargerWirelessOnline = false; long ident = Binder.clearCallingIdentity(); try { mUpdatesStopped = true; @@ -775,30 +751,30 @@ public final class BatteryService extends SystemService { } try { if (!mUpdatesStopped) { - copy(mLastHealthInfo, mHealthInfo); + mLastBatteryProps.set(mBatteryProps); } boolean update = true; switch (key) { case "present": - mHealthInfo.legacy.batteryPresent = Integer.parseInt(value) != 0; + mBatteryProps.batteryPresent = Integer.parseInt(value) != 0; break; case "ac": - mHealthInfo.legacy.chargerAcOnline = Integer.parseInt(value) != 0; + mBatteryProps.chargerAcOnline = Integer.parseInt(value) != 0; break; case "usb": - mHealthInfo.legacy.chargerUsbOnline = Integer.parseInt(value) != 0; + mBatteryProps.chargerUsbOnline = Integer.parseInt(value) != 0; break; case "wireless": - mHealthInfo.legacy.chargerWirelessOnline = Integer.parseInt(value) != 0; + mBatteryProps.chargerWirelessOnline = Integer.parseInt(value) != 0; break; case "status": - mHealthInfo.legacy.batteryStatus = Integer.parseInt(value); + mBatteryProps.batteryStatus = Integer.parseInt(value); break; case "level": - mHealthInfo.legacy.batteryLevel = Integer.parseInt(value); + mBatteryProps.batteryLevel = Integer.parseInt(value); break; case "temp": - mHealthInfo.legacy.batteryTemperature = Integer.parseInt(value); + mBatteryProps.batteryTemperature = Integer.parseInt(value); break; case "invalid": mInvalidCharger = Integer.parseInt(value); @@ -830,7 +806,7 @@ public final class BatteryService extends SystemService { try { if (mUpdatesStopped) { mUpdatesStopped = false; - copy(mHealthInfo, mLastHealthInfo); + mBatteryProps.set(mLastBatteryProps); processValuesFromShellLocked(pw, opts); } } finally { @@ -857,20 +833,20 @@ public final class BatteryService extends SystemService { if (mUpdatesStopped) { pw.println(" (UPDATES STOPPED -- use 'reset' to restart)"); } - pw.println(" AC powered: " + mHealthInfo.legacy.chargerAcOnline); - pw.println(" USB powered: " + mHealthInfo.legacy.chargerUsbOnline); - pw.println(" Wireless powered: " + mHealthInfo.legacy.chargerWirelessOnline); - pw.println(" Max charging current: " + mHealthInfo.legacy.maxChargingCurrent); - pw.println(" Max charging voltage: " + mHealthInfo.legacy.maxChargingVoltage); - pw.println(" Charge counter: " + mHealthInfo.legacy.batteryChargeCounter); - pw.println(" status: " + mHealthInfo.legacy.batteryStatus); - pw.println(" health: " + mHealthInfo.legacy.batteryHealth); - pw.println(" present: " + mHealthInfo.legacy.batteryPresent); - pw.println(" level: " + mHealthInfo.legacy.batteryLevel); + pw.println(" AC powered: " + mBatteryProps.chargerAcOnline); + pw.println(" USB powered: " + mBatteryProps.chargerUsbOnline); + pw.println(" Wireless powered: " + mBatteryProps.chargerWirelessOnline); + pw.println(" Max charging current: " + mBatteryProps.maxChargingCurrent); + pw.println(" Max charging voltage: " + mBatteryProps.maxChargingVoltage); + pw.println(" Charge counter: " + mBatteryProps.batteryChargeCounter); + pw.println(" status: " + mBatteryProps.batteryStatus); + pw.println(" health: " + mBatteryProps.batteryHealth); + pw.println(" present: " + mBatteryProps.batteryPresent); + pw.println(" level: " + mBatteryProps.batteryLevel); pw.println(" scale: " + BATTERY_SCALE); - pw.println(" voltage: " + mHealthInfo.legacy.batteryVoltage); - pw.println(" temperature: " + mHealthInfo.legacy.batteryTemperature); - pw.println(" technology: " + mHealthInfo.legacy.batteryTechnology); + pw.println(" voltage: " + mBatteryProps.batteryVoltage); + pw.println(" temperature: " + mBatteryProps.batteryTemperature); + pw.println(" technology: " + mBatteryProps.batteryTechnology); } else { Shell shell = new Shell(); shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null)); @@ -884,25 +860,25 @@ public final class BatteryService extends SystemService { synchronized (mLock) { proto.write(BatteryServiceDumpProto.ARE_UPDATES_STOPPED, mUpdatesStopped); int batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_NONE; - if (mHealthInfo.legacy.chargerAcOnline) { + if (mBatteryProps.chargerAcOnline) { batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_AC; - } else if (mHealthInfo.legacy.chargerUsbOnline) { + } else if (mBatteryProps.chargerUsbOnline) { batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_USB; - } else if (mHealthInfo.legacy.chargerWirelessOnline) { + } else if (mBatteryProps.chargerWirelessOnline) { batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_WIRELESS; } proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue); - proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent); - proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage); - proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter); - proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.legacy.batteryStatus); - proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.legacy.batteryHealth); - proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.legacy.batteryPresent); - proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.legacy.batteryLevel); + proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mBatteryProps.maxChargingCurrent); + proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mBatteryProps.maxChargingVoltage); + proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mBatteryProps.batteryChargeCounter); + proto.write(BatteryServiceDumpProto.STATUS, mBatteryProps.batteryStatus); + proto.write(BatteryServiceDumpProto.HEALTH, mBatteryProps.batteryHealth); + proto.write(BatteryServiceDumpProto.IS_PRESENT, mBatteryProps.batteryPresent); + proto.write(BatteryServiceDumpProto.LEVEL, mBatteryProps.batteryLevel); proto.write(BatteryServiceDumpProto.SCALE, BATTERY_SCALE); - proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.legacy.batteryVoltage); - proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.legacy.batteryTemperature); - proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.legacy.batteryTechnology); + proto.write(BatteryServiceDumpProto.VOLTAGE, mBatteryProps.batteryVoltage); + proto.write(BatteryServiceDumpProto.TEMPERATURE, mBatteryProps.batteryTemperature); + proto.write(BatteryServiceDumpProto.TECHNOLOGY, mBatteryProps.batteryTechnology); } proto.flush(); } @@ -935,8 +911,8 @@ public final class BatteryService extends SystemService { * Synchronize on BatteryService. */ public void updateLightsLocked() { - final int level = mHealthInfo.legacy.batteryLevel; - final int status = mHealthInfo.legacy.batteryStatus; + final int level = mBatteryProps.batteryLevel; + final int status = mBatteryProps.batteryStatus; if (level < mLowBatteryWarningLevel) { if (status == BatteryManager.BATTERY_STATUS_CHARGING) { // Solid red when battery is charging @@ -1009,7 +985,7 @@ public final class BatteryService extends SystemService { @Override public int getBatteryLevel() { synchronized (mLock) { - return mHealthInfo.legacy.batteryLevel; + return mBatteryProps.batteryLevel; } } @@ -1027,121 +1003,4 @@ public final class BatteryService extends SystemService { } } } - - /** - * HealthServiceWrapper wraps the internal IHealth service and refreshes the service when - * necessary. - * - * On new registration of IHealth service, {@link #onRegistration onRegistration} is called and - * the internal service is refreshed. - * On death of an existing IHealth service, the internal service is NOT cleared to avoid - * race condition between death notification and new service notification. Hence, - * a caller must check for transaction errors when calling into the service. - * - * @hide Should only be used internally. - */ - @VisibleForTesting - static final class HealthServiceWrapper { - private static final String TAG = "HealthServiceWrapper"; - public static final String INSTANCE_HEALTHD = "backup"; - public static final String INSTANCE_VENDOR = "default"; - // All interesting instances, sorted by priority high -> low. - private static final List<String> sAllInstances = - Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD); - - private final IServiceNotification mNotification = new Notification(); - private Callback mCallback; - private IHealthSupplier mHealthSupplier; - - /** - * init should be called after constructor. For testing purposes, init is not called by - * constructor. - */ - HealthServiceWrapper() { - } - - /** - * Start monitoring registration of new IHealth services. Only instances that are in - * {@code sAllInstances} and in device / framework manifest are used. This function should - * only be called once. - * @throws RemoteException transaction error when talking to IServiceManager - * @throws NoSuchElementException if one of the following cases: - * - No service manager; - * - none of {@code sAllInstances} are in manifests (i.e. not - * available on this device), or none of these instances are available to current - * process. - * @throws NullPointerException when callback is null or supplier is null - */ - void init(Callback callback, - IServiceManagerSupplier managerSupplier, - IHealthSupplier healthSupplier) - throws RemoteException, NoSuchElementException, NullPointerException { - if (callback == null || managerSupplier == null || healthSupplier == null) - throw new NullPointerException(); - - mCallback = callback; - mHealthSupplier = healthSupplier; - - IServiceManager manager = managerSupplier.get(); - for (String name : sAllInstances) { - if (manager.getTransport(IHealth.kInterfaceName, name) == - IServiceManager.Transport.EMPTY) { - continue; - } - - manager.registerForNotifications(IHealth.kInterfaceName, name, mNotification); - Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + name); - return; - } - - throw new NoSuchElementException(String.format( - "No IHealth service instance among %s is available. Perhaps no permission?", - sAllInstances.toString())); - } - - interface Callback { - /** - * This function is invoked asynchronously when a new and related IServiceNotification - * is received. - * @param service the recently retrieved service from IServiceManager. - * Can be a dead service before service notification of a new service is delivered. - * Implementation must handle cases for {@link RemoteException}s when calling - * into service. - * @param instance instance name. - */ - void onRegistration(IHealth service, String instance); - } - - /** - * Supplier of services. - * Must not return null; throw {@link NoSuchElementException} if a service is not available. - */ - interface IServiceManagerSupplier { - IServiceManager get() throws NoSuchElementException, RemoteException; - } - /** - * Supplier of services. - * Must not return null; throw {@link NoSuchElementException} if a service is not available. - */ - interface IHealthSupplier { - IHealth get(String instanceName) throws NoSuchElementException, RemoteException; - } - - private class Notification extends IServiceNotification.Stub { - @Override - public final void onRegistration(String interfaceName, String instanceName, - boolean preexisting) { - if (!IHealth.kInterfaceName.equals(interfaceName)) return; - if (!sAllInstances.contains(instanceName)) return; - try { - IHealth service = mHealthSupplier.get(instanceName); - Slog.i(TAG, "health: new instance registered " + instanceName); - mCallback.onRegistration(service, instanceName); - } catch (NoSuchElementException | RemoteException ex) { - Slog.e(TAG, "health: Cannot get instance '" + instanceName + "': " + - ex.getMessage() + ". Perhaps no permission?"); - } - } - } - } } diff --git a/com/android/server/IntentResolver.java b/com/android/server/IntentResolver.java index 119c9df6..40499c96 100644 --- a/com/android/server/IntentResolver.java +++ b/com/android/server/IntentResolver.java @@ -38,8 +38,6 @@ import android.util.Printer; import android.content.Intent; import android.content.IntentFilter; -import android.util.proto.ProtoOutputStream; - import com.android.internal.util.FastPrintWriter; /** @@ -281,31 +279,6 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { return printedSomething; } - void writeProtoMap(ProtoOutputStream proto, long fieldId, ArrayMap<String, F[]> map) { - int N = map.size(); - for (int mapi = 0; mapi < N; mapi++) { - long token = proto.start(fieldId); - proto.write(IntentResolverProto.ArrayMapEntry.KEY, map.keyAt(mapi)); - for (F f : map.valueAt(mapi)) { - if (f != null) { - proto.write(IntentResolverProto.ArrayMapEntry.VALUES, f.toString()); - } - } - proto.end(token); - } - } - - public void writeToProto(ProtoOutputStream proto, long fieldId) { - long token = proto.start(fieldId); - writeProtoMap(proto, IntentResolverProto.FULL_MIME_TYPES, mTypeToFilter); - writeProtoMap(proto, IntentResolverProto.BASE_MIME_TYPES, mBaseTypeToFilter); - writeProtoMap(proto, IntentResolverProto.WILD_MIME_TYPES, mWildTypeToFilter); - writeProtoMap(proto, IntentResolverProto.SCHEMES, mSchemeToFilter); - writeProtoMap(proto, IntentResolverProto.NON_DATA_ACTIONS, mActionToFilter); - writeProtoMap(proto, IntentResolverProto.MIME_TYPED_ACTIONS, mTypedActionToFilter); - proto.end(token); - } - public boolean dump(PrintWriter out, String title, String prefix, String packageName, boolean printFilter, boolean collapseDuplicates) { String innerPrefix = prefix + " "; diff --git a/com/android/server/SystemServer.java b/com/android/server/SystemServer.java index 49dd5285..92cbd3d5 100644 --- a/com/android/server/SystemServer.java +++ b/com/android/server/SystemServer.java @@ -125,7 +125,6 @@ import java.util.Timer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; -import static android.os.IServiceManager.DUMP_PRIORITY_CRITICAL; import static android.view.Display.DEFAULT_DISPLAY; public final class SystemServer { @@ -825,8 +824,7 @@ public final class SystemServer { wm = WindowManagerService.main(context, inputManager, mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL, !mFirstBoot, mOnlyCore, new PhoneWindowManager()); - ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false, - DUMP_PRIORITY_CRITICAL); + ServiceManager.addService(Context.WINDOW_SERVICE, wm); ServiceManager.addService(Context.INPUT_SERVICE, inputManager); traceEnd(); @@ -1642,7 +1640,11 @@ public final class SystemServer { traceEnd(); traceBeginAndSlog("MakePackageManagerServiceReady"); - mPackageManagerService.systemReady(); + try { + mPackageManagerService.systemReady(); + } catch (Throwable e) { + reportWtf("making Package Manager Service ready", e); + } traceEnd(); traceBeginAndSlog("MakeDisplayManagerServiceReady"); diff --git a/com/android/server/VibratorService.java b/com/android/server/VibratorService.java index 8b79b9dd..046eb761 100644 --- a/com/android/server/VibratorService.java +++ b/com/android/server/VibratorService.java @@ -373,24 +373,12 @@ public class VibratorService extends IVibratorService.Stub if (mCurrentVibration.hasLongerTimeout(newOneShot.getTiming()) && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) { if (DEBUG) { - Slog.d(TAG, "Ignoring incoming vibration in favor of current vibration"); + Slog.e(TAG, "Ignoring incoming vibration in favor of current vibration"); } return; } } - // If the current vibration is repeating and the incoming one is non-repeating, then ignore - // the non-repeating vibration. This is so that we don't cancel vibrations that are meant - // to grab the attention of the user, like ringtones and alarms, in favor of one-shot - // vibrations that are likely quite short. - if (!isRepeatingVibration(effect) - && mCurrentVibration != null && isRepeatingVibration(mCurrentVibration.mEffect)) { - if (DEBUG) { - Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration"); - } - return; - } - Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg); // Only link against waveforms since they potentially don't have a finish if @@ -416,16 +404,6 @@ public class VibratorService extends IVibratorService.Stub } } - private static boolean isRepeatingVibration(VibrationEffect effect) { - if (effect instanceof VibrationEffect.Waveform) { - final VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect; - if (waveform.getRepeatIndex() >= 0) { - return true; - } - } - return false; - } - private void addToPreviousVibrationsLocked(Vibration vib) { if (mPreviousVibrations.size() > mPreviousVibrationsLimit) { mPreviousVibrations.removeFirst(); diff --git a/com/android/server/accessibility/AccessibilityInputFilter.java b/com/android/server/accessibility/AccessibilityInputFilter.java index f6fcaae4..c60647fa 100644 --- a/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/com/android/server/accessibility/AccessibilityInputFilter.java @@ -19,9 +19,6 @@ package com.android.server.accessibility; import android.content.Context; import android.os.Handler; import android.os.PowerManager; -import android.util.DebugUtils; -import android.util.ExceptionUtils; -import android.util.Log; import android.util.Pools.SimplePool; import android.util.Slog; import android.util.SparseBooleanArray; @@ -34,7 +31,6 @@ import android.view.MotionEvent; import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; -import com.android.internal.util.BitUtils; import com.android.server.LocalServices; /** @@ -192,7 +188,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } if (mEventHandler == null) { - if (DEBUG) Slog.d(TAG, "mEventHandler == null for event " + event); super.onInputEvent(event, policyFlags); return; } @@ -344,8 +339,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo MotionEvent transformedEvent = MotionEvent.obtain(event); mEventHandler.onMotionEvent(transformedEvent, event, policyFlags); transformedEvent.recycle(); - } else { - if (DEBUG) Slog.d(TAG, "mEventHandler == null for " + event); } } @@ -373,20 +366,11 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } @Override - public EventStreamTransformation getNext() { - return null; - } - - @Override public void clearEvents(int inputSource) { /* do nothing */ } void setUserAndEnabledFeatures(int userId, int enabledFeatures) { - if (DEBUG) { - Slog.i(TAG, "setUserAndEnabledFeatures(userId = " + userId + ", enabledFeatures = 0x" - + Integer.toHexString(enabledFeatures) + ")"); - } if (mEnabledFeatures == enabledFeatures && mUserId == userId) { return; } @@ -413,8 +397,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } private void enableFeatures() { - if (DEBUG) Slog.i(TAG, "enableFeatures()"); - resetStreamState(); if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) { @@ -461,7 +443,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ private void addFirstEventHandler(EventStreamTransformation handler) { if (mEventHandler != null) { - handler.setNext(mEventHandler); + handler.setNext(mEventHandler); } else { handler.setNext(this); } diff --git a/com/android/server/accessibility/AutoclickController.java b/com/android/server/accessibility/AutoclickController.java index f5b0eb1e..892e9da4 100644 --- a/com/android/server/accessibility/AutoclickController.java +++ b/com/android/server/accessibility/AutoclickController.java @@ -23,12 +23,15 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.SystemClock; +import android.os.UserHandle; import android.provider.Settings; +import android.util.Slog; import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; +import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; /** @@ -52,10 +55,11 @@ import android.view.accessibility.AccessibilityManager; * * Each instance is associated to a single user (and it does not handle user switch itself). */ -public class AutoclickController extends BaseEventStreamTransformation { +public class AutoclickController implements EventStreamTransformation { private static final String LOG_TAG = AutoclickController.class.getSimpleName(); + private EventStreamTransformation mNext; private final Context mContext; private final int mUserId; @@ -84,7 +88,9 @@ public class AutoclickController extends BaseEventStreamTransformation { mClickScheduler.cancel(); } - super.onMotionEvent(event, rawEvent, policyFlags); + if (mNext != null) { + mNext.onMotionEvent(event, rawEvent, policyFlags); + } } @Override @@ -97,7 +103,21 @@ public class AutoclickController extends BaseEventStreamTransformation { } } - super.onKeyEvent(event, policyFlags); + if (mNext != null) { + mNext.onKeyEvent(event, policyFlags); + } + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + if (mNext != null) { + mNext.onAccessibilityEvent(event); + } + } + + @Override + public void setNext(EventStreamTransformation next) { + mNext = next; } @Override @@ -106,7 +126,9 @@ public class AutoclickController extends BaseEventStreamTransformation { mClickScheduler.cancel(); } - super.clearEvents(inputSource); + if (mNext != null) { + mNext.clearEvents(inputSource); + } } @Override @@ -396,7 +418,7 @@ public class AutoclickController extends BaseEventStreamTransformation { * Creates and forwards click event sequence. */ private void sendClick() { - if (mLastMotionEvent == null || getNext() == null) { + if (mLastMotionEvent == null || mNext == null) { return; } @@ -426,10 +448,10 @@ public class AutoclickController extends BaseEventStreamTransformation { MotionEvent upEvent = MotionEvent.obtain(downEvent); upEvent.setAction(MotionEvent.ACTION_UP); - AutoclickController.super.onMotionEvent(downEvent, downEvent, mEventPolicyFlags); + mNext.onMotionEvent(downEvent, downEvent, mEventPolicyFlags); downEvent.recycle(); - AutoclickController.super.onMotionEvent(upEvent, upEvent, mEventPolicyFlags); + mNext.onMotionEvent(upEvent, upEvent, mEventPolicyFlags); upEvent.recycle(); } diff --git a/com/android/server/accessibility/BaseEventStreamTransformation.java b/com/android/server/accessibility/BaseEventStreamTransformation.java deleted file mode 100644 index ce54586c..00000000 --- a/com/android/server/accessibility/BaseEventStreamTransformation.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - ** Copyright 2017, 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.accessibility; - -abstract class BaseEventStreamTransformation implements EventStreamTransformation { - private EventStreamTransformation mNext; - - @Override - public void setNext(EventStreamTransformation next) { - mNext = next; - } - - @Override - public EventStreamTransformation getNext() { - return mNext; - } -}
\ No newline at end of file diff --git a/com/android/server/accessibility/EventStreamTransformation.java b/com/android/server/accessibility/EventStreamTransformation.java index 7982996e..fdc40984 100644 --- a/com/android/server/accessibility/EventStreamTransformation.java +++ b/com/android/server/accessibility/EventStreamTransformation.java @@ -65,12 +65,7 @@ interface EventStreamTransformation { * @param rawEvent The raw motion event. * @param policyFlags Policy flags for the event. */ - default void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - EventStreamTransformation next = getNext(); - if (next != null) { - next.onMotionEvent(event, rawEvent, policyFlags); - } - } + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags); /** * Receives a key event. @@ -78,24 +73,14 @@ interface EventStreamTransformation { * @param event The key event. * @param policyFlags Policy flags for the event. */ - default void onKeyEvent(KeyEvent event, int policyFlags) { - EventStreamTransformation next = getNext(); - if (next != null) { - next.onKeyEvent(event, policyFlags); - } - } + public void onKeyEvent(KeyEvent event, int policyFlags); /** * Receives an accessibility event. * * @param event The accessibility event. */ - default void onAccessibilityEvent(AccessibilityEvent event) { - EventStreamTransformation next = getNext(); - if (next != null) { - next.onAccessibilityEvent(event); - } - }; + public void onAccessibilityEvent(AccessibilityEvent event); /** * Sets the next transformation. @@ -105,26 +90,14 @@ interface EventStreamTransformation { public void setNext(EventStreamTransformation next); /** - * Gets the next transformation. - * - * @return The next transformation. - */ - public EventStreamTransformation getNext(); - - /** * Clears internal state associated with events from specific input source. * * @param inputSource The input source class for which transformation state should be cleared. */ - default void clearEvents(int inputSource) { - EventStreamTransformation next = getNext(); - if (next != null) { - next.clearEvents(inputSource); - } - } + public void clearEvents(int inputSource); /** * Destroys this transformation. */ - default void onDestroy() {} + public void onDestroy(); } diff --git a/com/android/server/accessibility/KeyboardInterceptor.java b/com/android/server/accessibility/KeyboardInterceptor.java index 77249452..f00a9540 100644 --- a/com/android/server/accessibility/KeyboardInterceptor.java +++ b/com/android/server/accessibility/KeyboardInterceptor.java @@ -22,12 +22,14 @@ import android.os.SystemClock; import android.util.Pools; import android.util.Slog; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.WindowManagerPolicy; +import android.view.accessibility.AccessibilityEvent; /** * Intercepts key events and forwards them to accessibility manager service. */ -public class KeyboardInterceptor extends BaseEventStreamTransformation implements Handler.Callback { +public class KeyboardInterceptor implements EventStreamTransformation, Handler.Callback { private static final int MESSAGE_PROCESS_QUEUED_EVENTS = 1; private static final String LOG_TAG = "KeyboardInterceptor"; @@ -35,6 +37,7 @@ public class KeyboardInterceptor extends BaseEventStreamTransformation implement private final WindowManagerPolicy mPolicy; private final Handler mHandler; + private EventStreamTransformation mNext; private KeyEventHolder mEventQueueStart; private KeyEventHolder mEventQueueEnd; @@ -62,6 +65,13 @@ public class KeyboardInterceptor extends BaseEventStreamTransformation implement } @Override + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mNext != null) { + mNext.onMotionEvent(event, rawEvent, policyFlags); + } + } + + @Override public void onKeyEvent(KeyEvent event, int policyFlags) { /* * Certain keys have system-level behavior that affects accessibility services. @@ -80,6 +90,29 @@ public class KeyboardInterceptor extends BaseEventStreamTransformation implement } @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + if (mNext != null) { + mNext.onAccessibilityEvent(event); + } + } + + @Override + public void setNext(EventStreamTransformation next) { + mNext = next; + } + + @Override + public void clearEvents(int inputSource) { + if (mNext != null) { + mNext.clearEvents(inputSource); + } + } + + @Override + public void onDestroy() { + } + + @Override public boolean handleMessage(Message msg) { if (msg.what != MESSAGE_PROCESS_QUEUED_EVENTS) { Slog.e(LOG_TAG, "Unexpected message type"); diff --git a/com/android/server/accessibility/MagnificationController.java b/com/android/server/accessibility/MagnificationController.java index a10b7a20..98b8e6b7 100644 --- a/com/android/server/accessibility/MagnificationController.java +++ b/com/android/server/accessibility/MagnificationController.java @@ -56,7 +56,6 @@ import java.util.Locale; * constraints. */ class MagnificationController implements Handler.Callback { - private static final boolean DEBUG = false; private static final String LOG_TAG = "MagnificationController"; public static final float MIN_SCALE = 1.0f; @@ -510,12 +509,6 @@ class MagnificationController implements Handler.Callback { private boolean setScaleAndCenterLocked(float scale, float centerX, float centerY, boolean animate, int id) { - if (DEBUG) { - Slog.i(LOG_TAG, - "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX - + ", centerY = " + centerY + ", animate = " + animate + ", id = " + id - + ")"); - } final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY); sendSpecToAnimation(mCurrentMagnificationSpec, animate); if (isMagnifying() && (id != INVALID_ID)) { @@ -542,9 +535,7 @@ class MagnificationController implements Handler.Callback { final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX; final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY; - if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) { - onMagnificationChangedLocked(); - } + updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY); if (id != INVALID_ID) { mIdOfLastServiceToMagnify = id; } @@ -642,11 +633,6 @@ class MagnificationController implements Handler.Callback { } private boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) { - if (DEBUG) { - Slog.i(LOG_TAG, - "updateCurrentSpecWithOffsetsLocked(nonNormOffsetX = " + nonNormOffsetX - + ", nonNormOffsetY = " + nonNormOffsetY + ")"); - } boolean changed = false; final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0); if (Float.compare(mCurrentMagnificationSpec.offsetX, offsetX) != 0) { @@ -764,9 +750,6 @@ class MagnificationController implements Handler.Callback { } private void sendSpecToAnimation(MagnificationSpec spec, boolean animate) { - if (DEBUG) { - Slog.i(LOG_TAG, "sendSpecToAnimation(spec = " + spec + ", animate = " + animate + ")"); - } if (Thread.currentThread().getId() == mMainThreadId) { mSpecAnimationBridge.updateSentSpecMainThread(spec, animate); } else { diff --git a/com/android/server/accessibility/MagnificationGestureHandler.java b/com/android/server/accessibility/MagnificationGestureHandler.java index 9b2b4eb7..d6452f87 100644 --- a/com/android/server/accessibility/MagnificationGestureHandler.java +++ b/com/android/server/accessibility/MagnificationGestureHandler.java @@ -42,12 +42,14 @@ import android.util.Slog; import android.util.TypedValue; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector.OnScaleGestureListener; import android.view.ViewConfiguration; +import android.view.accessibility.AccessibilityEvent; import com.android.internal.annotations.VisibleForTesting; @@ -100,23 +102,31 @@ import com.android.internal.annotations.VisibleForTesting; * 7. The magnification scale will be persisted in settings and in the cloud. */ @SuppressWarnings("WeakerAccess") -class MagnificationGestureHandler extends BaseEventStreamTransformation { - private static final String LOG_TAG = "MagnificationGestureHandler"; +class MagnificationGestureHandler implements EventStreamTransformation { + private static final String LOG_TAG = "MagnificationEventHandler"; private static final boolean DEBUG_ALL = false; private static final boolean DEBUG_STATE_TRANSITIONS = false || DEBUG_ALL; private static final boolean DEBUG_DETECTING = false || DEBUG_ALL; - private static final boolean DEBUG_PANNING_SCALING = false || DEBUG_ALL; + private static final boolean DEBUG_PANNING = false || DEBUG_ALL; + + /** @see #handleMotionEventStateDelegating */ + @VisibleForTesting static final int STATE_DELEGATING = 1; + /** @see DetectingStateHandler */ + @VisibleForTesting static final int STATE_DETECTING = 2; + /** @see ViewportDraggingStateHandler */ + @VisibleForTesting static final int STATE_VIEWPORT_DRAGGING = 3; + /** @see PanningScalingStateHandler */ + @VisibleForTesting static final int STATE_PANNING_SCALING = 4; private static final float MIN_SCALE = 2.0f; private static final float MAX_SCALE = 5.0f; @VisibleForTesting final MagnificationController mMagnificationController; - @VisibleForTesting final DelegatingState mDelegatingState; - @VisibleForTesting final DetectingState mDetectingState; - @VisibleForTesting final PanningScalingState mPanningScalingState; - @VisibleForTesting final ViewportDraggingState mViewportDraggingState; + @VisibleForTesting final DetectingStateHandler mDetectingStateHandler; + @VisibleForTesting final PanningScalingStateHandler mPanningScalingStateHandler; + @VisibleForTesting final ViewportDraggingStateHandler mViewportDraggingStateHandler; private final ScreenStateReceiver mScreenStateReceiver; @@ -128,12 +138,21 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { final boolean mDetectTripleTap; /** - * Whether {@link DetectingState#mShortcutTriggered shortcut} is enabled + * Whether {@link #mShortcutTriggered shortcut} is enabled */ final boolean mDetectShortcutTrigger; - @VisibleForTesting State mCurrentState; - @VisibleForTesting State mPreviousState; + EventStreamTransformation mNext; + + @VisibleForTesting int mCurrentState; + @VisibleForTesting int mPreviousState; + + @VisibleForTesting boolean mShortcutTriggered; + + /** + * Time of last {@link MotionEvent#ACTION_DOWN} while in {@link #STATE_DELEGATING} + */ + long mDelegatingStateDownTime; private PointerCoords[] mTempPointerCoords; private PointerProperties[] mTempPointerProperties; @@ -155,10 +174,10 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { boolean detectShortcutTrigger) { mMagnificationController = magnificationController; - mDelegatingState = new DelegatingState(); - mDetectingState = new DetectingState(context); - mViewportDraggingState = new ViewportDraggingState(); - mPanningScalingState = new PanningScalingState(context); + mDetectingStateHandler = new DetectingStateHandler(context); + mViewportDraggingStateHandler = new ViewportDraggingStateHandler(); + mPanningScalingStateHandler = + new PanningScalingStateHandler(context); mDetectTripleTap = detectTripleTap; mDetectShortcutTrigger = detectShortcutTrigger; @@ -170,29 +189,62 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { mScreenStateReceiver = null; } - transitionTo(mDetectingState); + transitionTo(STATE_DETECTING); } @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (DEBUG_ALL) Slog.i(LOG_TAG, "onMotionEvent(" + event + ")"); - if ((!mDetectTripleTap && !mDetectShortcutTrigger) || !event.isFromSource(SOURCE_TOUCHSCREEN)) { dispatchTransformedEvent(event, rawEvent, policyFlags); return; } + // Local copy to avoid dispatching the same event to more than one state handler + // in case mPanningScalingStateHandler changes mCurrentState + int currentState = mCurrentState; + mPanningScalingStateHandler.onMotionEvent(event, rawEvent, policyFlags); + switch (currentState) { + case STATE_DELEGATING: { + handleMotionEventStateDelegating(event, rawEvent, policyFlags); + } + break; + case STATE_DETECTING: { + mDetectingStateHandler.onMotionEvent(event, rawEvent, policyFlags); + } + break; + case STATE_VIEWPORT_DRAGGING: { + mViewportDraggingStateHandler.onMotionEvent(event, rawEvent, policyFlags); + } + break; + case STATE_PANNING_SCALING: { + // mPanningScalingStateHandler handles events only + // if this is the current state since it uses ScaleGestureDetector + // and a GestureDetector which need well formed event stream. + } + break; + default: { + throw new IllegalStateException("Unknown state: " + currentState); + } + } + } - handleEventWith(mCurrentState, event, rawEvent, policyFlags); + @Override + public void onKeyEvent(KeyEvent event, int policyFlags) { + if (mNext != null) { + mNext.onKeyEvent(event, policyFlags); + } } - private void handleEventWith(State stateHandler, - MotionEvent event, MotionEvent rawEvent, int policyFlags) { - // To keep InputEventConsistencyVerifiers within GestureDetectors happy - mPanningScalingState.mScrollGestureDetector.onTouchEvent(event); - mPanningScalingState.mScaleGestureDetector.onTouchEvent(event); + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + if (mNext != null) { + mNext.onAccessibilityEvent(event); + } + } - stateHandler.onMotionEvent(event, rawEvent, policyFlags); + @Override + public void setNext(EventStreamTransformation next) { + mNext = next; } @Override @@ -201,16 +253,13 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { clearAndTransitionToStateDetecting(); } - super.clearEvents(inputSource); + if (mNext != null) { + mNext.clearEvents(inputSource); + } } @Override public void onDestroy() { - if (DEBUG_STATE_TRANSITIONS) { - Slog.i(LOG_TAG, "onDestroy(); delayed = " - + MotionEventInfo.toString(mDetectingState.mDelayedEventQueue)); - } - if (mScreenStateReceiver != null) { mScreenStateReceiver.unregister(); } @@ -223,21 +272,59 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { if (wasMagnifying) { clearAndTransitionToStateDetecting(); } else { - mDetectingState.toggleShortcutTriggered(); + toggleShortcutTriggered(); } } } + private void toggleShortcutTriggered() { + setShortcutTriggered(!mShortcutTriggered); + } + + private void setShortcutTriggered(boolean state) { + if (mShortcutTriggered == state) { + return; + } + + mShortcutTriggered = state; + mMagnificationController.setForceShowMagnifiableBounds(state); + } + void clearAndTransitionToStateDetecting() { - mCurrentState = mDelegatingState; - mDetectingState.clear(); - mViewportDraggingState.clear(); - mPanningScalingState.clear(); + setShortcutTriggered(false); + mCurrentState = STATE_DETECTING; + mDetectingStateHandler.clear(); + mViewportDraggingStateHandler.clear(); + mPanningScalingStateHandler.clear(); + } + + private void handleMotionEventStateDelegating(MotionEvent event, + MotionEvent rawEvent, int policyFlags) { + if (event.getActionMasked() == ACTION_UP) { + transitionTo(STATE_DETECTING); + } + delegateEvent(event, rawEvent, policyFlags); + } + + void delegateEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mDelegatingStateDownTime = event.getDownTime(); + } + if (mNext != null) { + // We cache some events to see if the user wants to trigger magnification. + // If no magnification is triggered we inject these events with adjusted + // time and down time to prevent subsequent transformations being confused + // by stale events. After the cached events, which always have a down, are + // injected we need to also update the down time of all subsequent non cached + // events. All delegated events cached and non-cached are delivered here. + event.setDownTime(mDelegatingStateDownTime); + dispatchTransformedEvent(event, rawEvent, policyFlags); + } } private void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (DEBUG_ALL) Slog.i(LOG_TAG, "dispatchTransformedEvent(event = " + event + ")"); + if (mNext == null) return; // Nowhere to dispatch to // If the touchscreen event is within the magnified portion of the screen we have // to change its location to be where the user thinks he is poking the @@ -264,7 +351,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, event.getSource(), event.getFlags()); } - super.onMotionEvent(event, rawEvent, policyFlags); + mNext.onMotionEvent(event, rawEvent, policyFlags); } private PointerCoords[] getTempPointerCoordsWithMinSize(int size) { @@ -299,10 +386,9 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { return mTempPointerProperties; } - private void transitionTo(State state) { + private void transitionTo(int state) { if (DEBUG_STATE_TRANSITIONS) { - Slog.i(LOG_TAG, - (State.nameOf(mCurrentState) + " -> " + State.nameOf(state) + Slog.i(LOG_TAG, (stateToString(mCurrentState) + " -> " + stateToString(state) + " at " + asList(copyOfRange(new RuntimeException().getStackTrace(), 1, 5))) .replace(getClass().getName(), "")); } @@ -310,40 +396,40 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { mCurrentState = state; } - interface State { - void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags); + private static String stateToString(int state) { + switch (state) { + case STATE_DELEGATING: return "STATE_DELEGATING"; + case STATE_DETECTING: return "STATE_DETECTING"; + case STATE_VIEWPORT_DRAGGING: return "STATE_VIEWPORT_DRAGGING"; + case STATE_PANNING_SCALING: return "STATE_PANNING_SCALING"; + case 0: return "0"; + default: throw new IllegalArgumentException("Unknown state: " + state); + } + } - default void clear() {} + private interface MotionEventHandler { - default String name() { - return getClass().getSimpleName(); - } + void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags); - static String nameOf(@Nullable State s) { - return s != null ? s.name() : "null"; - } + void clear(); } /** * This class determines if the user is performing a scale or pan gesture. * - * Unlike when {@link ViewportDraggingState dragging the viewport}, in panning mode the viewport - * moves in the same direction as the fingers, and allows to easily and precisely scale the - * magnification level. - * This makes it the preferred mode for one-off adjustments, due to its precision and ease of - * triggering. + * @see #STATE_PANNING_SCALING */ - final class PanningScalingState extends SimpleOnGestureListener - implements OnScaleGestureListener, State { + final class PanningScalingStateHandler extends SimpleOnGestureListener + implements OnScaleGestureListener, MotionEventHandler { private final ScaleGestureDetector mScaleGestureDetector; - private final GestureDetector mScrollGestureDetector; + private final GestureDetector mGestureDetector; final float mScalingThreshold; float mInitialScaleFactor = -1; boolean mScaling; - public PanningScalingState(Context context) { + public PanningScalingStateHandler(Context context) { final TypedValue scaleValue = new TypedValue(); context.getResources().getValue( com.android.internal.R.dimen.config_screen_magnification_scaling_threshold, @@ -351,27 +437,35 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { mScalingThreshold = scaleValue.getFloat(); mScaleGestureDetector = new ScaleGestureDetector(context, this); mScaleGestureDetector.setQuickScaleEnabled(false); - mScrollGestureDetector = new GestureDetector(context, this); + mGestureDetector = new GestureDetector(context, this); } @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - int action = event.getActionMasked(); + // Dispatches #onScaleBegin, #onScale, #onScaleEnd + mScaleGestureDetector.onTouchEvent(event); + // Dispatches #onScroll + mGestureDetector.onTouchEvent(event); + if (mCurrentState != STATE_PANNING_SCALING) { + return; + } + + int action = event.getActionMasked(); if (action == ACTION_POINTER_UP && event.getPointerCount() == 2 // includes the pointer currently being released - && mPreviousState == mViewportDraggingState) { + && mPreviousState == STATE_VIEWPORT_DRAGGING) { - persistScaleAndTransitionTo(mViewportDraggingState); + persistScaleAndTransitionTo(STATE_VIEWPORT_DRAGGING); } else if (action == ACTION_UP) { - persistScaleAndTransitionTo(mDetectingState); + persistScaleAndTransitionTo(STATE_DETECTING); } } - public void persistScaleAndTransitionTo(State state) { + public void persistScaleAndTransitionTo(int state) { mMagnificationController.persistScale(); clear(); transitionTo(state); @@ -380,16 +474,16 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { @Override public boolean onScroll(MotionEvent first, MotionEvent second, float distanceX, float distanceY) { - if (mCurrentState != mPanningScalingState) { + if (mCurrentState != STATE_PANNING_SCALING) { return true; } - if (DEBUG_PANNING_SCALING) { + if (DEBUG_PANNING) { Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX + " scrollY: " + distanceY); } mMagnificationController.offsetMagnifiedRegion(distanceX, distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); - return /* event consumed: */ true; + return true; } @Override @@ -400,8 +494,12 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { return false; } final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor; - mScaling = abs(deltaScale) > mScalingThreshold; - return mScaling; + if (abs(deltaScale) > mScalingThreshold) { + mScaling = true; + return true; + } else { + return false; + } } final float initialScale = mMagnificationController.getScale(); @@ -425,15 +523,14 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { final float pivotX = detector.getFocusX(); final float pivotY = detector.getFocusY(); - if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x"); mMagnificationController.setScale(scale, pivotX, pivotY, false, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); - return /* handled: */ true; + return true; } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { - return /* continue recognizing: */ (mCurrentState == mPanningScalingState); + return (mCurrentState == STATE_PANNING_SCALING); } @Override @@ -449,7 +546,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { @Override public String toString() { - return "PanningScalingState{" + + return "MagnifiedContentInteractionStateHandler{" + "mInitialScaleFactor=" + mInitialScaleFactor + ", mScaling=" + mScaling + '}'; @@ -461,11 +558,9 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { * determined that the user is performing a single-finger drag of the * magnification viewport. * - * Unlike when {@link PanningScalingState panning}, the viewport moves in the opposite direction - * of the finger, and any part of the screen is reachable without lifting the finger. - * This makes it the preferable mode for tasks like reading text spanning full screen width. + * @see #STATE_VIEWPORT_DRAGGING */ - final class ViewportDraggingState implements State { + final class ViewportDraggingStateHandler implements MotionEventHandler { /** Whether to disable zoom after dragging ends */ boolean mZoomedInBeforeDrag; @@ -477,7 +572,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { switch (action) { case ACTION_POINTER_DOWN: { clear(); - transitionTo(mPanningScalingState); + transitionTo(STATE_PANNING_SCALING); } break; case ACTION_MOVE: { @@ -499,7 +594,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { case ACTION_UP: { if (!mZoomedInBeforeDrag) zoomOff(); clear(); - transitionTo(mDetectingState); + transitionTo(STATE_DETECTING); } break; @@ -518,51 +613,25 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { @Override public String toString() { - return "ViewportDraggingState{" + + return "ViewportDraggingStateHandler{" + "mZoomedInBeforeDrag=" + mZoomedInBeforeDrag + ", mLastMoveOutsideMagnifiedRegion=" + mLastMoveOutsideMagnifiedRegion + '}'; } } - final class DelegatingState implements State { - /** - * Time of last {@link MotionEvent#ACTION_DOWN} while in {@link DelegatingState} - */ - public long mLastDelegatedDownEventTime; - - @Override - public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (event.getActionMasked() == ACTION_UP) { - transitionTo(mDetectingState); - } - - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mLastDelegatedDownEventTime = event.getDownTime(); - } - if (getNext() != null) { - // We cache some events to see if the user wants to trigger magnification. - // If no magnification is triggered we inject these events with adjusted - // time and down time to prevent subsequent transformations being confused - // by stale events. After the cached events, which always have a down, are - // injected we need to also update the down time of all subsequent non cached - // events. All delegated events cached and non-cached are delivered here. - event.setDownTime(mLastDelegatedDownEventTime); - dispatchTransformedEvent(event, rawEvent, policyFlags); - } - } - } - /** * This class handles motion events when the event dispatch has not yet * determined what the user is doing. It watches for various tap events. + * + * @see #STATE_DETECTING */ - final class DetectingState implements State, Handler.Callback { + final class DetectingStateHandler implements MotionEventHandler, Handler.Callback { private static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1; private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2; - final int mLongTapMinDelay; + final int mLongTapMinDelay = ViewConfiguration.getJumpTapTimeout(); final int mSwipeMinDistance; final int mMultiTapMaxDelay; final int mMultiTapMaxDistance; @@ -573,12 +642,9 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { private MotionEvent mLastUp; private MotionEvent mPreLastUp; - @VisibleForTesting boolean mShortcutTriggered; - Handler mHandler = new Handler(this); - public DetectingState(Context context) { - mLongTapMinDelay = ViewConfiguration.getLongPressTimeout(); + public DetectingStateHandler(Context context) { mMultiTapMaxDelay = ViewConfiguration.getDoubleTapTimeout() + context.getResources().getInteger( com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment); @@ -595,7 +661,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } break; case MESSAGE_TRANSITION_TO_DELEGATING_STATE: { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(/* andClear */ true); } break; default: { @@ -616,12 +682,12 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { if (!mMagnificationController.magnificationRegionContains( event.getX(), event.getY())) { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(/* andClear */ !mShortcutTriggered); } else if (isMultiTapTriggered(2 /* taps */)) { // 3tap and hold - afterLongTapTimeoutTransitionToDraggingState(event); + delayedTransitionToDraggingState(event); } else if (mDetectTripleTap // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay @@ -629,21 +695,21 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN) || mMagnificationController.isMagnifying()) { - afterMultiTapTimeoutTransitionToDelegatingState(); + delayedTransitionToDelegatingState(); } else { // Delegate pending events without delay - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(/* andClear */ true); } } break; case ACTION_POINTER_DOWN: { if (mMagnificationController.isMagnifying()) { - transitionTo(mPanningScalingState); + transitionTo(STATE_PANNING_SCALING); clear(); } else { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(/* andClear */ true); } } break; @@ -656,7 +722,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { && !isMultiTapTriggered(2 /* taps */)) { // Swipe detected - delegate skipping timeout - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(/* andClear */ true); } } break; @@ -667,7 +733,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { if (!mMagnificationController.magnificationRegionContains( event.getX(), event.getY())) { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(/* andClear */ !mShortcutTriggered); } else if (isMultiTapTriggered(3 /* taps */)) { @@ -676,11 +742,12 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } else if ( // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP isFingerDown() - //TODO long tap should never happen here - && ((timeBetween(mLastDown, mLastUp) >= mLongTapMinDelay) - || (distance(mLastDown, mLastUp) >= mSwipeMinDistance))) { + //TODO long tap should never happen here + && (timeBetween(mLastDown, /* mLastUp */ event) >= mLongTapMinDelay) + || distance(mLastDown, /* mLastUp */ event) + >= mSwipeMinDistance) { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(/* andClear */ true); } } @@ -728,15 +795,15 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { return MotionEventInfo.countOf(mDelayedEventQueue, ACTION_UP); } - /** -> {@link DelegatingState} */ - public void afterMultiTapTimeoutTransitionToDelegatingState() { + /** -> {@link #STATE_DELEGATING} */ + public void delayedTransitionToDelegatingState() { mHandler.sendEmptyMessageDelayed( MESSAGE_TRANSITION_TO_DELEGATING_STATE, mMultiTapMaxDelay); } - /** -> {@link ViewportDraggingState} */ - public void afterLongTapTimeoutTransitionToDraggingState(MotionEvent event) { + /** -> {@link #STATE_VIEWPORT_DRAGGING} */ + public void delayedTransitionToDraggingState(MotionEvent event) { mHandler.sendMessageDelayed( mHandler.obtainMessage(MESSAGE_ON_TRIPLE_TAP_AND_HOLD, event), ViewConfiguration.getLongPressTimeout()); @@ -779,7 +846,11 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { MotionEventInfo info = mDelayedEventQueue; mDelayedEventQueue = info.mNext; - handleEventWith(mDelegatingState, info.event, info.rawEvent, info.policyFlags); + // Because MagnifiedInteractionStateHandler requires well-formed event stream + mPanningScalingStateHandler.onMotionEvent( + info.event, info.rawEvent, info.policyFlags); + + delegateEvent(info.event, info.rawEvent, info.policyFlags); info.recycle(); } @@ -797,10 +868,10 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { mLastUp = null; } - void transitionToDelegatingStateAndClear() { - transitionTo(mDelegatingState); + void transitionToDelegatingState(boolean andClear) { + transitionTo(STATE_DELEGATING); sendDelayedMotionEvents(); - clear(); + if (andClear) clear(); } private void onTripleTap(MotionEvent up) { @@ -824,40 +895,24 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()"); clear(); - mViewportDraggingState.mZoomedInBeforeDrag = + mViewportDraggingStateHandler.mZoomedInBeforeDrag = mMagnificationController.isMagnifying(); zoomOn(down.getX(), down.getY()); - transitionTo(mViewportDraggingState); + transitionTo(STATE_VIEWPORT_DRAGGING); } @Override public String toString() { - return "DetectingState{" + + return "DetectingStateHandler{" + "tapCount()=" + tapCount() + - ", mShortcutTriggered=" + mShortcutTriggered + ", mDelayedEventQueue=" + MotionEventInfo.toString(mDelayedEventQueue) + '}'; } - - void toggleShortcutTriggered() { - setShortcutTriggered(!mShortcutTriggered); - } - - void setShortcutTriggered(boolean state) { - if (mShortcutTriggered == state) { - return; - } - - mShortcutTriggered = state; - mMagnificationController.setForceShowMagnifiableBounds(state); - } } private void zoomOn(float centerX, float centerY) { - if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOn(" + centerX + ", " + centerY + ")"); - final float scale = MathUtils.constrain( mMagnificationController.getPersistedScale(), MIN_SCALE, MAX_SCALE); @@ -868,8 +923,6 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } private void zoomOff() { - if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()"); - mMagnificationController.reset(/* animate */ true); } @@ -882,15 +935,16 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { @Override public String toString() { - return "MagnificationGesture{" + - "mDetectingState=" + mDetectingState + - ", mDelegatingState=" + mDelegatingState + - ", mMagnifiedInteractionState=" + mPanningScalingState + - ", mViewportDraggingState=" + mViewportDraggingState + + return "MagnificationGestureHandler{" + + "mDetectingStateHandler=" + mDetectingStateHandler + + ", mMagnifiedInteractionStateHandler=" + mPanningScalingStateHandler + + ", mViewportDraggingStateHandler=" + mViewportDraggingStateHandler + ", mDetectTripleTap=" + mDetectTripleTap + ", mDetectShortcutTrigger=" + mDetectShortcutTrigger + - ", mCurrentState=" + State.nameOf(mCurrentState) + - ", mPreviousState=" + State.nameOf(mPreviousState) + + ", mCurrentState=" + stateToString(mCurrentState) + + ", mPreviousState=" + stateToString(mPreviousState) + + ", mShortcutTriggered=" + mShortcutTriggered + + ", mDelegatingStateDownTime=" + mDelegatingStateDownTime + ", mMagnificationController=" + mMagnificationController + '}'; } @@ -997,7 +1051,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { @Override public void onReceive(Context context, Intent intent) { - mGestureHandler.mDetectingState.setShortcutTriggered(false); + mGestureHandler.setShortcutTriggered(false); } } } diff --git a/com/android/server/accessibility/MotionEventInjector.java b/com/android/server/accessibility/MotionEventInjector.java index b6b78129..48041adb 100644 --- a/com/android/server/accessibility/MotionEventInjector.java +++ b/com/android/server/accessibility/MotionEventInjector.java @@ -30,13 +30,13 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.InputDevice; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.WindowManagerPolicy; - +import android.view.accessibility.AccessibilityEvent; import com.android.internal.os.SomeArgs; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -45,7 +45,7 @@ import java.util.List; * <p> * All methods except {@code injectEvents} must be called only from the main thread. */ -public class MotionEventInjector extends BaseEventStreamTransformation implements Handler.Callback { +public class MotionEventInjector implements EventStreamTransformation, Handler.Callback { private static final String LOG_TAG = "MotionEventInjector"; private static final int MESSAGE_SEND_MOTION_EVENT = 1; private static final int MESSAGE_INJECT_EVENTS = 2; @@ -68,6 +68,7 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement private final Handler mHandler; private final SparseArray<Boolean> mOpenGesturesInProgress = new SparseArray<>(); + private EventStreamTransformation mNext; private IAccessibilityServiceClient mServiceInterfaceForCurrentGesture; private IntArray mSequencesInProgress = new IntArray(5); private boolean mIsDestroyed = false; @@ -116,6 +117,25 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement } @Override + public void onKeyEvent(KeyEvent event, int policyFlags) { + if (mNext != null) { + mNext.onKeyEvent(event, policyFlags); + } + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + if (mNext != null) { + mNext.onAccessibilityEvent(event); + } + } + + @Override + public void setNext(EventStreamTransformation next) { + mNext = next; + } + + @Override public void clearEvents(int inputSource) { /* * Reset state for motion events passing through so we won't send a cancel event for @@ -167,7 +187,7 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement return; } - if (getNext() == null) { + if (mNext == null) { notifyService(serviceInterface, sequence, false); return; } @@ -242,24 +262,17 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement int continuedPointerId = mStrokeIdToPointerId .get(touchPoint.mContinuedStrokeId, -1); if (continuedPointerId == -1) { - Slog.w(LOG_TAG, "Can't continue gesture due to unknown continued stroke id in " - + touchPoint); return false; } mStrokeIdToPointerId.put(touchPoint.mStrokeId, continuedPointerId); int lastPointIndex = findPointByStrokeId( mLastTouchPoints, mNumLastTouchPoints, touchPoint.mContinuedStrokeId); if (lastPointIndex < 0) { - Slog.w(LOG_TAG, "Can't continue gesture due continued gesture id of " - + touchPoint + " not matching any previous strokes in " - + Arrays.asList(mLastTouchPoints)); return false; } if (mLastTouchPoints[lastPointIndex].mIsEndOfPath || (mLastTouchPoints[lastPointIndex].mX != touchPoint.mX) || (mLastTouchPoints[lastPointIndex].mY != touchPoint.mY)) { - Slog.w(LOG_TAG, "Can't continue gesture due to points mismatch between " - + mLastTouchPoints[lastPointIndex] + " and " + touchPoint); return false; } // Update the last touch point to match the continuation, so the gestures will @@ -279,8 +292,8 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement private void sendMotionEventToNext(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (getNext() != null) { - super.onMotionEvent(event, rawEvent, policyFlags); + if (mNext != null) { + mNext.onMotionEvent(event, rawEvent, policyFlags); if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { mOpenGesturesInProgress.put(event.getSource(), true); } @@ -292,7 +305,7 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement } private void cancelAnyGestureInProgress(int source) { - if ((getNext() != null) && mOpenGesturesInProgress.get(source, false)) { + if ((mNext != null) && mOpenGesturesInProgress.get(source, false)) { long now = SystemClock.uptimeMillis(); MotionEvent cancelEvent = obtainMotionEvent(now, now, MotionEvent.ACTION_CANCEL, getLastTouchPoints(), 1); diff --git a/com/android/server/accessibility/TouchExplorer.java b/com/android/server/accessibility/TouchExplorer.java index a32686df..e380f2c6 100644 --- a/com/android/server/accessibility/TouchExplorer.java +++ b/com/android/server/accessibility/TouchExplorer.java @@ -55,8 +55,7 @@ import java.util.List; * * @hide */ -class TouchExplorer extends BaseEventStreamTransformation - implements AccessibilityGestureDetector.Listener { +class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDetector.Listener { private static final boolean DEBUG = false; @@ -132,6 +131,9 @@ class TouchExplorer extends BaseEventStreamTransformation // the two dragging pointers as opposed to use the location of the primary one. private final int mScaledMinPointerDistanceToUseMiddleLocation; + // The handler to which to delegate events. + private EventStreamTransformation mNext; + // Helper class to track received pointers. private final ReceivedPointerTracker mReceivedPointerTracker; @@ -196,7 +198,9 @@ class TouchExplorer extends BaseEventStreamTransformation if (inputSource == InputDevice.SOURCE_TOUCHSCREEN) { clear(); } - super.clearEvents(inputSource); + if (mNext != null) { + mNext.clearEvents(inputSource); + } } @Override @@ -254,9 +258,16 @@ class TouchExplorer extends BaseEventStreamTransformation } @Override + public void setNext(EventStreamTransformation next) { + mNext = next; + } + + @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { - super.onMotionEvent(event, rawEvent, policyFlags); + if (mNext != null) { + mNext.onMotionEvent(event, rawEvent, policyFlags); + } return; } @@ -300,6 +311,13 @@ class TouchExplorer extends BaseEventStreamTransformation } @Override + public void onKeyEvent(KeyEvent event, int policyFlags) { + if (mNext != null) { + mNext.onKeyEvent(event, policyFlags); + } + } + + @Override public void onAccessibilityEvent(AccessibilityEvent event) { final int eventType = event.getEventType(); @@ -335,7 +353,9 @@ class TouchExplorer extends BaseEventStreamTransformation mLastTouchedWindowId = event.getWindowId(); } break; } - super.onAccessibilityEvent(event); + if (mNext != null) { + mNext.onAccessibilityEvent(event); + } } @Override @@ -949,10 +969,12 @@ class TouchExplorer extends BaseEventStreamTransformation // Make sure that the user will see the event. policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; - // TODO: For now pass null for the raw event since the touch - // explorer is the last event transformation and it does - // not care about the raw event. - super.onMotionEvent(event, null, policyFlags); + if (mNext != null) { + // TODO: For now pass null for the raw event since the touch + // explorer is the last event transformation and it does + // not care about the raw event. + mNext.onMotionEvent(event, null, policyFlags); + } mInjectedPointerTracker.onMotionEvent(event); diff --git a/com/android/server/am/ActivityDisplay.java b/com/android/server/am/ActivityDisplay.java index 6ed05552..8bcbfbef 100644 --- a/com/android/server/am/ActivityDisplay.java +++ b/com/android/server/am/ActivityDisplay.java @@ -16,7 +16,9 @@ package com.android.server.am; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.ActivityManager.StackId.getStackIdForWindowingMode; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; @@ -46,14 +48,13 @@ import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.ConfigurationContainer; -import java.io.PrintWriter; import java.util.ArrayList; /** * Exactly one of these classes per Display in the system. Capable of holding zero or more * attached {@link ActivityStack}s. */ -class ActivityDisplay extends ConfigurationContainer<ActivityStack> { +class ActivityDisplay extends ConfigurationContainer { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_AM; private static final String TAG_STACK = TAG + POSTFIX_STACK; @@ -67,7 +68,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { /** All of the stacks on this display. Order matters, topmost stack is in front of all other * stacks, bottommost behind. Accessed directly by ActivityManager package classes */ - private final ArrayList<ActivityStack> mStacks = new ArrayList<>(); + final ArrayList<ActivityStack> mStacks = new ArrayList<>(); /** Array of all UIDs that are present on the display. */ private IntArray mDisplayAccessUIDs = new IntArray(); @@ -79,13 +80,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { private boolean mSleeping; - // Cached reference to some special stacks we tend to get a lot so we don't need to loop - // through the list to find them. - private ActivityStack mHomeStack = null; - private ActivityStack mRecentsStack = null; - private ActivityStack mPinnedStack = null; - private ActivityStack mSplitScreenPrimaryStack = null; - ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) { mSupervisor = supervisor; mDisplayId = displayId; @@ -104,7 +98,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } if (DEBUG_STACK) Slog.v(TAG_STACK, "addChild: attaching " + stack + " to displayId=" + mDisplayId + " position=" + position); - addStackReferenceIfNeeded(stack); positionChildAt(stack, position); mSupervisor.mService.updateSleepIfNeededLocked(); } @@ -113,7 +106,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { if (DEBUG_STACK) Slog.v(TAG_STACK, "removeChild: detaching " + stack + " from displayId=" + mDisplayId); mStacks.remove(stack); - removeStackReferenceIfNeeded(stack); mSupervisor.mService.updateSleepIfNeededLocked(); } @@ -158,16 +150,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { * @see ConfigurationContainer#isCompatible(int, int) */ <T extends ActivityStack> T getStack(int windowingMode, int activityType) { - if (activityType == ACTIVITY_TYPE_HOME) { - return (T) mHomeStack; - } else if (activityType == ACTIVITY_TYPE_RECENTS) { - return (T) mRecentsStack; - } - if (windowingMode == WINDOWING_MODE_PINNED) { - return (T) mPinnedStack; - } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - return (T) mSplitScreenPrimaryStack; - } for (int i = mStacks.size() - 1; i >= 0; --i) { final ActivityStack stack = mStacks.get(i); // TODO: Should undefined windowing and activity type be compatible with standard type? @@ -231,14 +213,10 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { if (windowingMode == WINDOWING_MODE_UNDEFINED) { // TODO: Should be okay to have stacks with with undefined windowing mode long term, but // have to set them to something for now due to logic that depending on them. - windowingMode = getWindowingMode(); // Put in current display's windowing mode - if (windowingMode == WINDOWING_MODE_UNDEFINED) { - // Else fullscreen for now... - windowingMode = WINDOWING_MODE_FULLSCREEN; - } + windowingMode = WINDOWING_MODE_FULLSCREEN; } - final boolean inSplitScreenMode = hasSplitScreenPrimaryStack(); + final boolean inSplitScreenMode = hasSplitScreenStack(); if (!inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) { // Switch to fullscreen windowing mode if we are not in split-screen mode and we are @@ -250,7 +228,24 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; } - final int stackId = mSupervisor.getNextStackId(); + int stackId = INVALID_STACK_ID; + if (mDisplayId == DEFAULT_DISPLAY && (activityType == ACTIVITY_TYPE_STANDARD + || activityType == ACTIVITY_TYPE_UNDEFINED)) { + // TODO: Will be removed once we are no longer using static stack ids. + stackId = getStackIdForWindowingMode(windowingMode); + if (stackId == INVALID_STACK_ID) { + // Whatever...put in fullscreen stack for now. + stackId = FULLSCREEN_WORKSPACE_STACK_ID; + } + final T stack = getStack(stackId); + if (stack != null) { + return stack; + } + } + + if (stackId == INVALID_STACK_ID) { + stackId = mSupervisor.getNextStackId(); + } final T stack = createStackUnchecked(windowingMode, activityType, stackId, onTop); @@ -296,7 +291,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { if (stack.getWindowingMode() != windowingMode) { continue; } - mSupervisor.removeStack(stack); + mSupervisor.removeStackLocked(stack.mStackId); } } } @@ -311,63 +306,12 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { for (int i = mStacks.size() - 1; i >= 0; --i) { final ActivityStack stack = mStacks.get(i); if (stack.getActivityType() == activityType) { - mSupervisor.removeStack(stack); + mSupervisor.removeStackLocked(stack.mStackId); } } } } - void onStackWindowingModeChanged(ActivityStack stack) { - removeStackReferenceIfNeeded(stack); - addStackReferenceIfNeeded(stack); - } - - private void addStackReferenceIfNeeded(ActivityStack stack) { - final int activityType = stack.getActivityType(); - final int windowingMode = stack.getWindowingMode(); - - if (activityType == ACTIVITY_TYPE_HOME) { - if (mHomeStack != null && mHomeStack != stack) { - throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack=" - + mHomeStack + " already exist on display=" + this + " stack=" + stack); - } - mHomeStack = stack; - } else if (activityType == ACTIVITY_TYPE_RECENTS) { - if (mRecentsStack != null && mRecentsStack != stack) { - throw new IllegalArgumentException("addStackReferenceIfNeeded: recents stack=" - + mRecentsStack + " already exist on display=" + this + " stack=" + stack); - } - mRecentsStack = stack; - } - if (windowingMode == WINDOWING_MODE_PINNED) { - if (mPinnedStack != null && mPinnedStack != stack) { - throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack=" - + mPinnedStack + " already exist on display=" + this - + " stack=" + stack); - } - mPinnedStack = stack; - } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - if (mSplitScreenPrimaryStack != null && mSplitScreenPrimaryStack != stack) { - throw new IllegalArgumentException("addStackReferenceIfNeeded:" - + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack - + " already exist on display=" + this + " stack=" + stack); - } - mSplitScreenPrimaryStack = stack; - } - } - - private void removeStackReferenceIfNeeded(ActivityStack stack) { - if (stack == mHomeStack) { - mHomeStack = null; - } else if (stack == mRecentsStack) { - mRecentsStack = null; - } else if (stack == mPinnedStack) { - mPinnedStack = null; - } else if (stack == mSplitScreenPrimaryStack) { - mSplitScreenPrimaryStack = null; - } - } - /** Returns the top visible stack activity type that isn't in the exclude windowing mode. */ int getTopVisibleStackActivityType(int excludeWindowingMode) { for (int i = mStacks.size() - 1; i >= 0; --i) { @@ -382,42 +326,20 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { return ACTIVITY_TYPE_UNDEFINED; } - /** - * Get the topmost stack on the display. It may be different from focused stack, because - * focus may be on another display. - */ - ActivityStack getTopStack() { - return mStacks.isEmpty() ? null : mStacks.get(mStacks.size() - 1); + ActivityStack getSplitScreenStack() { + return getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED); } - boolean isTopStack(ActivityStack stack) { - return stack == getTopStack(); - } - - int getIndexOf(ActivityStack stack) { - return mStacks.indexOf(stack); - } - - void onLockTaskPackagesUpdated() { - for (int i = mStacks.size() - 1; i >= 0; --i) { - mStacks.get(i).onLockTaskPackagesUpdated(); - } - } - - ActivityStack getSplitScreenPrimaryStack() { - return mSplitScreenPrimaryStack; - } - - boolean hasSplitScreenPrimaryStack() { - return mSplitScreenPrimaryStack != null; + boolean hasSplitScreenStack() { + return getSplitScreenStack() != null; } PinnedActivityStack getPinnedStack() { - return (PinnedActivityStack) mPinnedStack; + return getStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); } boolean hasPinnedStack() { - return mPinnedStack != null; + return getPinnedStack() != null; } @Override @@ -431,7 +353,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } @Override - protected ActivityStack getChildAt(int index) { + protected ConfigurationContainer getChildAt(int index) { return mStacks.get(index); } @@ -479,10 +401,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { mSleeping = asleep; } - public void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "displayId=" + mDisplayId + " mStacks=" + mStacks); - } - public void writeToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); super.writeToProto(proto, CONFIGURATION_CONTAINER); diff --git a/com/android/server/am/ActivityManagerDebugConfig.java b/com/android/server/am/ActivityManagerDebugConfig.java index ceb2ad62..3a9bf125 100644 --- a/com/android/server/am/ActivityManagerDebugConfig.java +++ b/com/android/server/am/ActivityManagerDebugConfig.java @@ -59,6 +59,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_FOCUS = false; static final boolean DEBUG_IDLE = DEBUG_ALL_ACTIVITIES || false; static final boolean DEBUG_IMMERSIVE = DEBUG_ALL || false; + static final boolean DEBUG_LOCKSCREEN = DEBUG_ALL || false; static final boolean DEBUG_LOCKTASK = DEBUG_ALL || false; static final boolean DEBUG_LRU = DEBUG_ALL || false; static final boolean DEBUG_MU = DEBUG_ALL || false; @@ -73,10 +74,10 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_PROVIDER = DEBUG_ALL || false; static final boolean DEBUG_PSS = DEBUG_ALL || false; static final boolean DEBUG_RECENTS = DEBUG_ALL || false; - static final boolean DEBUG_RECENTS_TRIM_TASKS = DEBUG_RECENTS || false; static final boolean DEBUG_RELEASE = DEBUG_ALL_ACTIVITIES || false; static final boolean DEBUG_RESULTS = DEBUG_ALL || false; static final boolean DEBUG_SAVED_STATE = DEBUG_ALL_ACTIVITIES || false; + static final boolean DEBUG_SCREENSHOTS = DEBUG_ALL_ACTIVITIES || false; static final boolean DEBUG_SERVICE = DEBUG_ALL || false; static final boolean DEBUG_FOREGROUND_SERVICE = DEBUG_ALL || false; static final boolean DEBUG_SERVICE_EXECUTING = DEBUG_ALL || false; @@ -84,6 +85,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_STATES = DEBUG_ALL_ACTIVITIES || false; static final boolean DEBUG_SWITCH = DEBUG_ALL || false; static final boolean DEBUG_TASKS = DEBUG_ALL || false; + static final boolean DEBUG_THUMBNAILS = DEBUG_ALL || false; static final boolean DEBUG_TRANSITION = DEBUG_ALL || false; static final boolean DEBUG_UID_OBSERVERS = DEBUG_ALL || false; static final boolean DEBUG_URI_PERMISSION = DEBUG_ALL || false; @@ -103,6 +105,7 @@ class ActivityManagerDebugConfig { static final String POSTFIX_FOCUS = (APPEND_CATEGORY_NAME) ? "_Focus" : ""; static final String POSTFIX_IDLE = (APPEND_CATEGORY_NAME) ? "_Idle" : ""; static final String POSTFIX_IMMERSIVE = (APPEND_CATEGORY_NAME) ? "_Immersive" : ""; + static final String POSTFIX_LOCKSCREEN = (APPEND_CATEGORY_NAME) ? "_LockScreen" : ""; static final String POSTFIX_LOCKTASK = (APPEND_CATEGORY_NAME) ? "_LockTask" : ""; static final String POSTFIX_LRU = (APPEND_CATEGORY_NAME) ? "_LRU" : ""; static final String POSTFIX_MU = "_MU"; @@ -119,6 +122,7 @@ class ActivityManagerDebugConfig { static final String POSTFIX_RELEASE = (APPEND_CATEGORY_NAME) ? "_Release" : ""; static final String POSTFIX_RESULTS = (APPEND_CATEGORY_NAME) ? "_Results" : ""; static final String POSTFIX_SAVED_STATE = (APPEND_CATEGORY_NAME) ? "_SavedState" : ""; + static final String POSTFIX_SCREENSHOTS = (APPEND_CATEGORY_NAME) ? "_Screenshots" : ""; static final String POSTFIX_SERVICE = (APPEND_CATEGORY_NAME) ? "_Service" : ""; static final String POSTFIX_SERVICE_EXECUTING = (APPEND_CATEGORY_NAME) ? "_ServiceExecuting" : ""; @@ -126,11 +130,13 @@ class ActivityManagerDebugConfig { static final String POSTFIX_STATES = (APPEND_CATEGORY_NAME) ? "_States" : ""; static final String POSTFIX_SWITCH = (APPEND_CATEGORY_NAME) ? "_Switch" : ""; static final String POSTFIX_TASKS = (APPEND_CATEGORY_NAME) ? "_Tasks" : ""; + static final String POSTFIX_THUMBNAILS = (APPEND_CATEGORY_NAME) ? "_Thumbnails" : ""; static final String POSTFIX_TRANSITION = (APPEND_CATEGORY_NAME) ? "_Transition" : ""; static final String POSTFIX_UID_OBSERVERS = (APPEND_CATEGORY_NAME) ? "_UidObservers" : ""; static final String POSTFIX_URI_PERMISSION = (APPEND_CATEGORY_NAME) ? "_UriPermission" : ""; static final String POSTFIX_USER_LEAVING = (APPEND_CATEGORY_NAME) ? "_UserLeaving" : ""; static final String POSTFIX_VISIBILITY = (APPEND_CATEGORY_NAME) ? "_Visibility" : ""; + static final String POSTFIX_VISIBLE_BEHIND = (APPEND_CATEGORY_NAME) ? "_VisibleBehind" : ""; } diff --git a/com/android/server/am/ActivityManagerService.java b/com/android/server/am/ActivityManagerService.java index f17c9ac3..e6fe6204 100644 --- a/com/android/server/am/ActivityManagerService.java +++ b/com/android/server/am/ActivityManagerService.java @@ -27,10 +27,18 @@ import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW; +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID; +import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static android.app.ActivityManager.StackId.getWindowingModeForStackId; +import static android.app.ActivityManager.StackId.isStaticStack; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS; @@ -49,9 +57,6 @@ import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground; import static android.os.Build.VERSION_CODES.N; -import static android.os.IServiceManager.DUMP_PRIORITY_CRITICAL; -import static android.os.IServiceManager.DUMP_PRIORITY_HIGH; -import static android.os.IServiceManager.DUMP_PRIORITY_NORMAL; import static android.os.Process.BLUETOOTH_UID; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.FIRST_ISOLATED_UID; @@ -132,6 +137,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH; @@ -176,6 +182,7 @@ import static com.android.server.am.TaskRecord.INVALID_TASK_ID; import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK; import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT; import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE; +import static com.android.server.am.proto.ActivityManagerServiceProto.ACTIVITIES; import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN; import static com.android.server.wm.AppTransition.TRANSIT_NONE; import static com.android.server.wm.AppTransition.TRANSIT_TASK_IN_PLACE; @@ -192,6 +199,7 @@ import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityManager.StackId; import android.app.ActivityManager.StackInfo; import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityManagerInternal; @@ -208,6 +216,7 @@ import android.app.ContentProviderHolder; import android.app.Dialog; import android.app.IActivityController; import android.app.IActivityManager; +import android.app.IAppTask; import android.app.IApplicationThread; import android.app.IInstrumentationWatcher; import android.app.INotificationManager; @@ -395,9 +404,6 @@ import com.android.server.SystemServiceManager; import com.android.server.ThreadPriorityBooster; import com.android.server.Watchdog; import com.android.server.am.ActivityStack.ActivityState; -import com.android.server.am.proto.ActivityManagerServiceProto; -import com.android.server.am.proto.BroadcastProto; -import com.android.server.am.proto.StickyBroadcastProto; import com.android.server.firewall.IntentFirewall; import com.android.server.job.JobSchedulerInternal; import com.android.server.pm.Installer; @@ -733,6 +739,9 @@ public class ActivityManagerService extends IActivityManager.Stub doDump(fd, pw, new String[] {"associations"}); } doDump(fd, pw, new String[] {"processes"}); + doDump(fd, pw, new String[] {"-v", "all"}); + doDump(fd, pw, new String[] {"service", "all"}); + doDump(fd, pw, new String[] {"provider", "all"}); } @Override @@ -744,8 +753,6 @@ public class ActivityManagerService extends IActivityManager.Stub public boolean canShowErrorDialogs() { return mShowDialogs && !mSleeping && !mShuttingDown && !mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY) - && !mUserController.hasUserRestriction(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, - mUserController.getCurrentUserId()) && !(UserManager.isDeviceInDemoMode(mContext) && mUserController.getCurrentUser().isDemo()); } @@ -1710,6 +1717,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final int PUSH_TEMP_WHITELIST_UI_MSG = 68; static final int SERVICE_FOREGROUND_CRASH_MSG = 69; static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70; + static final int TOP_APP_KILLED_BY_LMK_MSG = 73; static final int NOTIFY_VR_KEYGUARD_MSG = 74; static final int FIRST_ACTIVITY_STACK_MSG = 100; @@ -1730,6 +1738,9 @@ public class ActivityManagerService extends IActivityManager.Stub */ private boolean mUserIsMonkey; + /** Flag whether the device has a Recents UI */ + boolean mHasRecents; + /** The dimensions of the thumbnails in the Recents UI. */ int mThumbnailWidth; int mThumbnailHeight; @@ -1935,6 +1946,17 @@ public class ActivityManagerService extends IActivityManager.Stub dispatchProcessDied(pid, uid); break; } + case TOP_APP_KILLED_BY_LMK_MSG: { + final String appName = (String) msg.obj; + final AlertDialog d = new BaseErrorDialog(mUiContext); + d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); + d.setTitle(mUiContext.getText(R.string.top_app_killed_title)); + d.setMessage(mUiContext.getString(R.string.top_app_killed_message, appName)); + d.setButton(DialogInterface.BUTTON_POSITIVE, mUiContext.getText(R.string.close), + obtainMessage(DISMISS_DIALOG_UI_MSG, d)); + d.show(); + break; + } case DISPATCH_UIDS_CHANGED_UI_MSG: { dispatchUidsChanged(); } break; @@ -2089,8 +2111,7 @@ public class ActivityManagerService extends IActivityManager.Stub String text = mContext.getString(R.string.heavy_weight_notification, context.getApplicationInfo().loadLabel(context.getPackageManager())); Notification notification = - new Notification.Builder(context, - SystemNotificationChannels.HEAVY_WEIGHT_APP) + new Notification.Builder(context, SystemNotificationChannels.DEVELOPER) .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) .setWhen(0) .setOngoing(true) @@ -2124,7 +2145,7 @@ public class ActivityManagerService extends IActivityManager.Stub } try { inm.cancelNotificationWithTag("android", null, - SystemMessage.NOTE_HEAVY_WEIGHT_NOTIFICATION, msg.arg1); + SystemMessage.NOTE_HEAVY_WEIGHT_NOTIFICATION, msg.arg1); } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Error canceling notification for service", e); @@ -2482,16 +2503,13 @@ public class ActivityManagerService extends IActivityManager.Stub public void setSystemProcess() { try { - ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true, - DUMP_PRIORITY_CRITICAL | DUMP_PRIORITY_NORMAL); + ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true); ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats); - ServiceManager.addService("meminfo", new MemBinder(this), /* allowIsolated= */ false, - DUMP_PRIORITY_HIGH | DUMP_PRIORITY_NORMAL); + ServiceManager.addService("meminfo", new MemBinder(this)); ServiceManager.addService("gfxinfo", new GraphicsBinder(this)); ServiceManager.addService("dbinfo", new DbBinder(this)); if (MONITOR_CPU_USAGE) { - ServiceManager.addService("cpuinfo", new CpuBinder(this), - /* allowIsolated= */ false, DUMP_PRIORITY_CRITICAL); + ServiceManager.addService("cpuinfo", new CpuBinder(this)); } ServiceManager.addService("permission", new PermissionController(this)); ServiceManager.addService("processinfo", new ProcessInfoService(this)); @@ -2522,6 +2540,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { mWindowManager = wm; mStackSupervisor.setWindowManager(wm); + mActivityStarter.setWindowManager(wm); mLockTaskController.setWindowManager(wm); } } @@ -2763,9 +2782,8 @@ public class ActivityManagerService extends IActivityManager.Stub mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler); mTaskChangeNotificationController = new TaskChangeNotificationController(this, mStackSupervisor, mHandler); - mActivityStarter = new ActivityStarter(this); + mActivityStarter = new ActivityStarter(this, mStackSupervisor); mRecentTasks = new RecentTasks(this, mStackSupervisor); - mStackSupervisor.setRecentTasks(mRecentTasks); mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler); mProcessCpuThread = new Thread("CpuTracker") { @@ -3223,12 +3241,11 @@ public class ActivityManagerService extends IActivityManager.Stub // stack implementation changes in the future, keep in mind that the use of the fullscreen // stack is a means to move the activity to the main display and a moveActivityToDisplay() // option would be a better choice here. - if (r.requestedVrComponent != null && r.getDisplayId() != DEFAULT_DISPLAY) { + if (r.requestedVrComponent != null && r.getStackId() >= FIRST_DYNAMIC_STACK_ID) { Slog.i(TAG, "Moving " + r.shortComponentName + " from stack " + r.getStackId() + " to main stack for VR"); - final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getOrCreateStack( - WINDOWING_MODE_FULLSCREEN, r.getActivityType(), true /* toTop */); - moveTaskToStack(r.getTask().taskId, stack.mStackId, true /* toTop */); + setTaskWindowingMode(r.getTask().taskId, + WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, true /* toTop */); } mHandler.sendMessage( mHandler.obtainMessage(VR_MODE_CHANGE_MSG, 0, 0, r)); @@ -5078,12 +5095,11 @@ public class ActivityManagerService extends IActivityManager.Stub } synchronized(this) { - final ProcessRecord proc = mHeavyWeightProcess; - if (proc == null) { + if (mHeavyWeightProcess == null) { return; } - ArrayList<ActivityRecord> activities = new ArrayList<>(proc.activities); + ArrayList<ActivityRecord> activities = new ArrayList<>(mHeavyWeightProcess.activities); for (int i = 0; i < activities.size(); i++) { ActivityRecord r = activities.get(i); if (!r.finishing && r.isInStackLocked()) { @@ -5093,7 +5109,7 @@ public class ActivityManagerService extends IActivityManager.Stub } mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG, - proc.userId, 0)); + mHeavyWeightProcess.userId, 0)); mHeavyWeightProcess = null; } } @@ -5412,6 +5428,7 @@ public class ActivityManagerService extends IActivityManager.Stub boolean doLowMem = app.instr == null; boolean doOomAdj = doLowMem; if (!app.killedByAm) { + maybeNotifyTopAppKilled(app); Slog.i(TAG, "Process " + app.processName + " (pid " + pid + ") has died: " + ProcessList.makeOomAdjString(app.setAdj) + ProcessList.makeProcStateString(app.setProcState)); @@ -5445,6 +5462,23 @@ public class ActivityManagerService extends IActivityManager.Stub } } + /** Show system error dialog when a top app is killed by LMK */ + void maybeNotifyTopAppKilled(ProcessRecord app) { + if (!shouldNotifyTopAppKilled(app)) { + return; + } + + Message msg = mHandler.obtainMessage(TOP_APP_KILLED_BY_LMK_MSG); + msg.obj = mContext.getPackageManager().getApplicationLabel(app.info); + mUiHandler.sendMessage(msg); + } + + /** Only show notification when the top app is killed on low ram devices */ + private boolean shouldNotifyTopAppKilled(ProcessRecord app) { + return app.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP && + ActivityManager.isLowRamDeviceStatic(); + } + /** * If a stack trace dump file is configured, dump process stack traces. * @param clearTraces causes the dump file to be erased prior to the new @@ -5929,7 +5963,16 @@ public class ActivityManagerService extends IActivityManager.Stub if (appInfo != null) { forceStopPackageLocked(packageName, appInfo.uid, "clear data"); - mRecentTasks.removeTasksByPackageName(packageName, resolvedUserId); + // Remove all tasks match the cleared application package and user + for (int i = mRecentTasks.size() - 1; i >= 0; i--) { + final TaskRecord tr = mRecentTasks.get(i); + final String taskPackageName = + tr.getBaseIntent().getComponent().getPackageName(); + if (tr.userId != resolvedUserId) continue; + if (!taskPackageName.equals(packageName)) continue; + mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, + REMOVE_FROM_RECENTS); + } } } @@ -6510,7 +6553,7 @@ public class ActivityManagerService extends IActivityManager.Stub } // Clean-up disabled tasks - mRecentTasks.cleanupDisabledPackageTasksLocked(packageName, disabledClasses, userId); + cleanupDisabledPackageTasksLocked(packageName, disabledClasses, userId); // Clean-up disabled services. mServices.bringDownDisabledPackageServicesLocked( @@ -8041,8 +8084,8 @@ public class ActivityManagerService extends IActivityManager.Stub } private boolean isInPictureInPictureMode(ActivityRecord r) { - if (r == null || r.getStack() == null || !r.inPinnedWindowingMode() - || r.getStack().isInStackLocked(r) == null) { + if (r == null || r.getStack() == null || !r.getStack().isPinnedStack() || + r.getStack().isInStackLocked(r) == null) { return false; } @@ -8136,7 +8179,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Only update the saved args from the args that are set r.pictureInPictureArgs.copyOnlySet(params); - if (r.inPinnedWindowingMode()) { + if (r.getStack().getStackId() == PINNED_STACK_ID) { // If the activity is already in picture-in-picture, update the pinned stack now // if it is not already expanding to fullscreen. Otherwise, the arguments will // be used the next time the activity enters PiP @@ -9757,12 +9800,35 @@ public class ActivityManagerService extends IActivityManager.Stub public List<IBinder> getAppTasks(String callingPackage) { int callingUid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); - try { - synchronized(this) { - return mRecentTasks.getAppTasksList(callingUid, callingPackage); + + synchronized(this) { + ArrayList<IBinder> list = new ArrayList<IBinder>(); + try { + if (DEBUG_ALL) Slog.v(TAG, "getAppTasks"); + + final int N = mRecentTasks.size(); + for (int i = 0; i < N; i++) { + TaskRecord tr = mRecentTasks.get(i); + // Skip tasks that do not match the caller. We don't need to verify + // callingPackage, because we are also limiting to callingUid and know + // that will limit to the correct security sandbox. + if (tr.effectiveUid != callingUid) { + continue; + } + Intent intent = tr.getBaseIntent(); + if (intent == null || + !callingPackage.equals(intent.getComponent().getPackageName())) { + continue; + } + ActivityManager.RecentTaskInfo taskInfo = + createRecentTaskInfoFromTaskRecord(tr); + AppTaskImpl taskImpl = new AppTaskImpl(taskInfo.persistentId, callingUid); + list.add(taskImpl.asBinder()); + } + } finally { + Binder.restoreCallingIdentity(ident); } - } finally { - Binder.restoreCallingIdentity(ident); + return list; } } @@ -9785,6 +9851,58 @@ public class ActivityManagerService extends IActivityManager.Stub return list; } + /** + * Creates a new RecentTaskInfo from a TaskRecord. + */ + private ActivityManager.RecentTaskInfo createRecentTaskInfoFromTaskRecord(TaskRecord tr) { + // Update the task description to reflect any changes in the task stack + tr.updateTaskDescription(); + + // Compose the recent task info + ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); + rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId; + rti.persistentId = tr.taskId; + rti.baseIntent = new Intent(tr.getBaseIntent()); + rti.origActivity = tr.origActivity; + rti.realActivity = tr.realActivity; + rti.description = tr.lastDescription; + rti.stackId = tr.getStackId(); + rti.userId = tr.userId; + rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription); + rti.firstActiveTime = tr.firstActiveTime; + rti.lastActiveTime = tr.lastActiveTime; + rti.affiliatedTaskId = tr.mAffiliatedTaskId; + rti.affiliatedTaskColor = tr.mAffiliatedTaskColor; + rti.numActivities = 0; + if (tr.mBounds != null) { + rti.bounds = new Rect(tr.mBounds); + } + rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreenWindowingMode(); + rti.resizeMode = tr.mResizeMode; + rti.configuration.setTo(tr.getConfiguration()); + + ActivityRecord base = null; + ActivityRecord top = null; + ActivityRecord tmp; + + for (int i = tr.mActivities.size() - 1; i >= 0; --i) { + tmp = tr.mActivities.get(i); + if (tmp.finishing) { + continue; + } + base = tmp; + if (top == null || (top.state == ActivityState.INITIALIZING)) { + top = base; + } + rti.numActivities++; + } + + rti.baseActivity = (base != null) ? base.intent.getComponent() : null; + rti.topActivity = (top != null) ? top.intent.getComponent() : null; + + return rti; + } + private boolean isGetTasksAllowed(String caller, int callingPid, int callingUid) { boolean allowed = checkPermission(android.Manifest.permission.REAL_GET_TASKS, callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; @@ -9818,15 +9936,118 @@ public class ActivityManagerService extends IActivityManager.Stub final int callingUid = Binder.getCallingUid(); userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, false, ALLOW_FULL_ONLY, "getRecentTasks", null); - final boolean allowed = isGetTasksAllowed("getRecentTasks", Binder.getCallingPid(), - callingUid); - final boolean detailed = checkCallingPermission( - android.Manifest.permission.GET_DETAILED_TASKS) - == PackageManager.PERMISSION_GRANTED; + final boolean includeProfiles = (flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0; + final boolean withExcluded = (flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0; synchronized (this) { - return mRecentTasks.getRecentTasks(maxNum, flags, allowed, detailed, userId, + final boolean allowed = isGetTasksAllowed("getRecentTasks", Binder.getCallingPid(), callingUid); + final boolean detailed = checkCallingPermission( + android.Manifest.permission.GET_DETAILED_TASKS) + == PackageManager.PERMISSION_GRANTED; + + if (!isUserRunning(userId, ActivityManager.FLAG_AND_UNLOCKED)) { + Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents"); + return ParceledListSlice.emptyList(); + } + mRecentTasks.loadUserRecentsLocked(userId); + + final int recentsCount = mRecentTasks.size(); + ArrayList<ActivityManager.RecentTaskInfo> res = + new ArrayList<>(maxNum < recentsCount ? maxNum : recentsCount); + + final Set<Integer> includedUsers; + if (includeProfiles) { + includedUsers = mUserController.getProfileIds(userId); + } else { + includedUsers = new HashSet<>(); + } + includedUsers.add(Integer.valueOf(userId)); + + for (int i = 0; i < recentsCount && maxNum > 0; i++) { + TaskRecord tr = mRecentTasks.get(i); + // Only add calling user or related users recent tasks + if (!includedUsers.contains(Integer.valueOf(tr.userId))) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + tr); + continue; + } + + if (tr.realActivitySuspended) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, activity suspended: " + tr); + continue; + } + + // Return the entry if desired by the caller. We always return + // the first entry, because callers always expect this to be the + // foreground app. We may filter others if the caller has + // not supplied RECENT_WITH_EXCLUDED and there is some reason + // we should exclude the entry. + + if (i == 0 + || withExcluded + || (tr.intent == null) + || ((tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + == 0)) { + if (!allowed) { + // If the caller doesn't have the GET_TASKS permission, then only + // allow them to see a small subset of tasks -- their own and home. + if (!tr.isActivityTypeHome() && tr.effectiveUid != callingUid) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + tr); + continue; + } + } + final ActivityStack stack = tr.getStack(); + if ((flags & ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS) != 0) { + if (stack != null && stack.isHomeOrRecentsStack()) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "Skipping, home or recents stack task: " + tr); + continue; + } + } + if ((flags & ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK) != 0) { + if (stack != null && stack.isDockedStack() && stack.topTask() == tr) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "Skipping, top task in docked stack: " + tr); + continue; + } + } + if ((flags & ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS) != 0) { + if (stack != null && stack.isPinnedStack()) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "Skipping, pinned stack task: " + tr); + continue; + } + } + if (tr.autoRemoveRecents && tr.getTopActivity() == null) { + // Don't include auto remove tasks that are finished or finishing. + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "Skipping, auto-remove without activity: " + tr); + continue; + } + if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0 + && !tr.isAvailable) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "Skipping, unavail real act: " + tr); + continue; + } + + if (!tr.mUserSetupComplete) { + // Don't include task launched while user is not done setting-up. + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "Skipping, user setup not complete: " + tr); + continue; + } + + ActivityManager.RecentTaskInfo rti = createRecentTaskInfoFromTaskRecord(tr); + if (!detailed) { + rti.baseIntent.replaceExtras((Bundle)null); + } + + res.add(rti); + maxNum--; + } + } + return new ParceledListSlice<>(res); } } @@ -9898,10 +10119,23 @@ public class ActivityManagerService extends IActivityManager.Stub TaskRecord task = new TaskRecord(this, mStackSupervisor.getNextTaskIdForUserLocked(r.userId), ainfo, intent, description); - if (!mRecentTasks.addToBottom(task)) { + + int trimIdx = mRecentTasks.trimForTaskLocked(task, false); + if (trimIdx >= 0) { + // If this would have caused a trim, then we'll abort because that + // means it would be added at the end of the list but then just removed. return INVALID_TASK_ID; } - r.getStack().addTask(task, !ON_TOP, "addAppTask"); + + final int N = mRecentTasks.size(); + if (N >= (ActivityManager.getMaxRecentTasksStatic()-1)) { + final TaskRecord tr = mRecentTasks.remove(N - 1); + tr.removedFromRecents(); + } + + task.inRecents = true; + mRecentTasks.add(task); + r.getStack().addTask(task, false, "addAppTask"); // TODO: Send the thumbnail to WM to store it. @@ -10117,6 +10351,38 @@ public class ActivityManagerService extends IActivityManager.Stub mWindowManager.executeAppTransition(); } + private void removeTasksByPackageNameLocked(String packageName, int userId) { + // Remove all tasks with activities in the specified package from the list of recent tasks + for (int i = mRecentTasks.size() - 1; i >= 0; i--) { + TaskRecord tr = mRecentTasks.get(i); + if (tr.userId != userId) continue; + + ComponentName cn = tr.intent.getComponent(); + if (cn != null && cn.getPackageName().equals(packageName)) { + // If the package name matches, remove the task. + mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS); + } + } + } + + private void cleanupDisabledPackageTasksLocked(String packageName, Set<String> filterByClasses, + int userId) { + + for (int i = mRecentTasks.size() - 1; i >= 0; i--) { + TaskRecord tr = mRecentTasks.get(i); + if (userId != UserHandle.USER_ALL && tr.userId != userId) { + continue; + } + + ComponentName cn = tr.intent.getComponent(); + final boolean sameComponent = cn != null && cn.getPackageName().equals(packageName) + && (filterByClasses == null || filterByClasses.contains(cn.getClassName())); + if (sameComponent) { + mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS); + } + } + } + @Override public void removeStack(int stackId) { enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS, "removeStack()"); @@ -10124,14 +10390,11 @@ public class ActivityManagerService extends IActivityManager.Stub final long ident = Binder.clearCallingIdentity(); try { final ActivityStack stack = mStackSupervisor.getStack(stackId); - if (stack == null) { - return; - } - if (!stack.isActivityTypeStandardOrUndefined()) { + if (stack != null && !stack.isActivityTypeStandardOrUndefined()) { throw new IllegalArgumentException( "Removing non-standard stack is not allowed."); } - mStackSupervisor.removeStack(stack); + mStackSupervisor.removeStackLocked(stackId); } finally { Binder.restoreCallingIdentity(ident); } @@ -10346,7 +10609,7 @@ public class ActivityManagerService extends IActivityManager.Stub } final ActivityStack stack = r.getStack(); - if (stack == null || !stack.inFreeformWindowingMode()) { + if (stack == null || stack.mStackId != FREEFORM_WORKSPACE_STACK_ID) { throw new IllegalStateException( "exitFreeformMode: You can only go fullscreen from freeform."); } @@ -10414,20 +10677,27 @@ public class ActivityManagerService extends IActivityManager.Stub if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId + " to stackId=" + stackId + " toTop=" + toTop); + if (stackId == DOCKED_STACK_ID) { + mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, + null /* initialBounds */); + } - final ActivityStack stack = mStackSupervisor.getStack(stackId); + ActivityStack stack = mStackSupervisor.getStack(stackId); if (stack == null) { - throw new IllegalStateException( - "moveTaskToStack: No stack for stackId=" + stackId); + if (!isStaticStack(stackId)) { + throw new IllegalStateException( + "moveTaskToStack: No stack for stackId=" + stackId); + } + final ActivityDisplay display = task.getStack().getDisplay(); + final int windowingMode = + getWindowingModeForStackId(stackId, display.hasSplitScreenStack()); + stack = display.getOrCreateStack(windowingMode, + task.getStack().getActivityType(), toTop); } if (!stack.isActivityTypeStandardOrUndefined()) { throw new IllegalArgumentException("moveTaskToStack: Attempt to move task " + taskId + " to stack " + stackId); } - if (stack.inSplitScreenPrimaryWindowingMode()) { - mWindowManager.setDockedStackCreateState( - DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */); - } task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, "moveTaskToStack"); } finally { @@ -10497,9 +10767,9 @@ public class ActivityManagerService extends IActivityManager.Stub try { synchronized (this) { final ActivityStack stack = - mStackSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack(); + mStackSupervisor.getDefaultDisplay().getSplitScreenStack(); if (toTop) { - mStackSupervisor.resizeStackLocked(stack, null /* destBounds */, + mStackSupervisor.resizeStackLocked(stack.mStackId, null /* destBounds */, null /* tempTaskBounds */, null /* tempTaskInsetBounds */, true /* preserveWindows */, true /* allowResizeInDockedMode */, !DEFER_RESUME); @@ -10592,12 +10862,7 @@ public class ActivityManagerService extends IActivityManager.Stub stack.animateResizePinnedStack(null /* sourceHintBounds */, destBounds, animationDuration, false /* fromFullscreen */); } else { - final ActivityStack stack = mStackSupervisor.getStack(stackId); - if (stack == null) { - Slog.w(TAG, "resizeStack: stackId " + stackId + " not found."); - return; - } - mStackSupervisor.resizeStackLocked(stack, destBounds, null /* tempTaskBounds */, + mStackSupervisor.resizeStackLocked(stackId, destBounds, null /* tempTaskBounds */, null /* tempTaskInsetBounds */, preserveWindows, allowResizeInDockedMode, !DEFER_RESUME); } @@ -12673,10 +12938,6 @@ public class ActivityManagerService extends IActivityManager.Stub throw new IllegalArgumentException("Provided bugreport type is not correct, value: " + bugreportType); } - // Always log caller, even if it does not have permission to dump. - String type = extraOptions == null ? "bugreport" : extraOptions; - Slog.i(TAG, type + " requested by UID " + Binder.getCallingUid()); - enforceCallingPermission(android.Manifest.permission.DUMP, "requestBugReport"); if (extraOptions != null) { SystemProperties.set("dumpstate.options", extraOptions); @@ -13884,6 +14145,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Load resources only after the current configuration has been set. final Resources res = mContext.getResources(); + mHasRecents = res.getBoolean(com.android.internal.R.bool.config_hasRecents); mThumbnailWidth = res.getDimensionPixelSize( com.android.internal.R.dimen.thumbnail_width); mThumbnailHeight = res.getDimensionPixelSize( @@ -14842,31 +15104,10 @@ public class ActivityManagerService extends IActivityManager.Stub long origId = Binder.clearCallingIdentity(); if (useProto) { + //TODO: Options when dumping proto final ProtoOutputStream proto = new ProtoOutputStream(fd); - String cmd = opti < args.length ? args[opti] : ""; - opti++; - - if ("activities".equals(cmd) || "a".equals(cmd)) { - // output proto is ActivityStackSupervisorProto - synchronized (this) { - writeActivitiesToProtoLocked(proto); - } - } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) { - // output proto is BroadcastProto - synchronized (this) { - writeBroadcastsToProtoLocked(proto); - } - } else { - // default option, dump everything, output is ActivityManagerServiceProto - synchronized (this) { - long activityToken = proto.start(ActivityManagerServiceProto.ACTIVITIES); - writeActivitiesToProtoLocked(proto); - proto.end(activityToken); - - long broadcastToken = proto.start(ActivityManagerServiceProto.BROADCASTS); - writeBroadcastsToProtoLocked(proto); - proto.end(broadcastToken); - } + synchronized (this) { + writeActivitiesToProtoLocked(proto); } proto.flush(); Binder.restoreCallingIdentity(origId); @@ -14892,9 +15133,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } else if ("recents".equals(cmd) || "r".equals(cmd)) { synchronized (this) { - if (mRecentTasks != null) { - mRecentTasks.dump(pw, true /* dumpAll */, dumpPackage); - } + dumpRecentsLocked(fd, pw, args, opti, true, dumpPackage); } } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) { String[] newArgs; @@ -15115,9 +15354,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } - if (mRecentTasks != null) { - mRecentTasks.dump(pw, dumpAll, dumpPackage); - } + dumpRecentsLocked(fd, pw, args, opti, dumpAll, dumpPackage); pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); @@ -15187,9 +15424,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } - if (mRecentTasks != null) { - mRecentTasks.dump(pw, dumpAll, dumpPackage); - } + dumpRecentsLocked(fd, pw, args, opti, dumpAll, dumpPackage); pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); @@ -15223,8 +15458,7 @@ public class ActivityManagerService extends IActivityManager.Stub } private void writeActivitiesToProtoLocked(ProtoOutputStream proto) { - // The output proto of "activity --proto activities" is ActivityStackSupervisorProto - mStackSupervisor.writeToProto(proto); + mStackSupervisor.writeToProto(proto, ACTIVITIES); } private void dumpLastANRLocked(PrintWriter pw) { @@ -15276,6 +15510,42 @@ public class ActivityManagerService extends IActivityManager.Stub } } + void dumpRecentsLocked(FileDescriptor fd, PrintWriter pw, String[] args, + int opti, boolean dumpAll, String dumpPackage) { + pw.println("ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)"); + + boolean printedAnything = false; + + if (mRecentTasks != null && mRecentTasks.size() > 0) { + boolean printedHeader = false; + + final int N = mRecentTasks.size(); + for (int i=0; i<N; i++) { + TaskRecord tr = mRecentTasks.get(i); + if (dumpPackage != null) { + if (tr.realActivity == null || + !dumpPackage.equals(tr.realActivity.getPackageName())) { + continue; + } + } + if (!printedHeader) { + pw.println(" Recent tasks:"); + printedHeader = true; + printedAnything = true; + } + pw.print(" * Recent #"); pw.print(i); pw.print(": "); + pw.println(tr); + if (dumpAll) { + mRecentTasks.get(i).dump(pw, " "); + } + } + } + + if (!printedAnything) { + pw.println(" (nothing)"); + } + } + void dumpAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) { pw.println("ACTIVITY MANAGER ASSOCIATIONS (dumpsys activity associations)"); @@ -16102,40 +16372,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - void writeBroadcastsToProtoLocked(ProtoOutputStream proto) { - if (mRegisteredReceivers.size() > 0) { - Iterator it = mRegisteredReceivers.values().iterator(); - while (it.hasNext()) { - ReceiverList r = (ReceiverList)it.next(); - r.writeToProto(proto, BroadcastProto.RECEIVER_LIST); - } - } - mReceiverResolver.writeToProto(proto, BroadcastProto.RECEIVER_RESOLVER); - for (BroadcastQueue q : mBroadcastQueues) { - q.writeToProto(proto, BroadcastProto.BROADCAST_QUEUE); - } - for (int user=0; user<mStickyBroadcasts.size(); user++) { - long token = proto.start(BroadcastProto.STICKY_BROADCASTS); - proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user)); - for (Map.Entry<String, ArrayList<Intent>> ent - : mStickyBroadcasts.valueAt(user).entrySet()) { - long actionToken = proto.start(StickyBroadcastProto.ACTIONS); - proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey()); - for (Intent intent : ent.getValue()) { - intent.writeToProto(proto, StickyBroadcastProto.StickyAction.INTENTS, - false, true, true, false); - } - proto.end(actionToken); - } - proto.end(token); - } - - long handlerToken = proto.start(BroadcastProto.HANDLER); - proto.write(BroadcastProto.MainHandler.HANDLER, mHandler.toString()); - mHandler.getLooper().writeToProto(proto, BroadcastProto.MainHandler.LOOPER); - proto.end(handlerToken); - } - void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { boolean needSep = false; @@ -19124,7 +19360,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Remove all permissions granted from/to this package removeUriPermissionsForPackageLocked(ssp, userId, true); - mRecentTasks.removeTasksByPackageName(ssp, userId); + removeTasksByPackageNameLocked(ssp, userId); mServices.forceStopPackageLocked(ssp, userId); @@ -20461,10 +20697,9 @@ public class ActivityManagerService extends IActivityManager.Stub /** Helper method that requests bounds from WM and applies them to stack. */ private void resizeStackWithBoundsFromWindowManager(int stackId, boolean deferResume) { final Rect newStackBounds = new Rect(); - final ActivityStack stack = mStackSupervisor.getStack(stackId); - stack.getBoundsForNewConfiguration(newStackBounds); + mStackSupervisor.getStack(stackId).getBoundsForNewConfiguration(newStackBounds); mStackSupervisor.resizeStackLocked( - stack, !newStackBounds.isEmpty() ? newStackBounds : null /* bounds */, + stackId, !newStackBounds.isEmpty() ? newStackBounds : null /* bounds */, null /* tempTaskBounds */, null /* tempTaskInsetBounds */, false /* preserveWindows */, false /* allowResizeInDockedMode */, deferResume); } @@ -24132,6 +24367,125 @@ public class ActivityManagerService extends IActivityManager.Stub } /** + * An implementation of IAppTask, that allows an app to manage its own tasks via + * {@link android.app.ActivityManager.AppTask}. We keep track of the callingUid to ensure that + * only the process that calls getAppTasks() can call the AppTask methods. + */ + class AppTaskImpl extends IAppTask.Stub { + private int mTaskId; + private int mCallingUid; + + public AppTaskImpl(int taskId, int callingUid) { + mTaskId = taskId; + mCallingUid = callingUid; + } + + private void checkCaller() { + if (mCallingUid != Binder.getCallingUid()) { + throw new SecurityException("Caller " + mCallingUid + + " does not match caller of getAppTasks(): " + Binder.getCallingUid()); + } + } + + @Override + public void finishAndRemoveTask() { + checkCaller(); + + synchronized (ActivityManagerService.this) { + long origId = Binder.clearCallingIdentity(); + try { + // We remove the task from recents to preserve backwards + if (!mStackSupervisor.removeTaskByIdLocked(mTaskId, false, + REMOVE_FROM_RECENTS)) { + throw new IllegalArgumentException("Unable to find task ID " + mTaskId); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } + + @Override + public ActivityManager.RecentTaskInfo getTaskInfo() { + checkCaller(); + + synchronized (ActivityManagerService.this) { + long origId = Binder.clearCallingIdentity(); + try { + TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(mTaskId); + if (tr == null) { + throw new IllegalArgumentException("Unable to find task ID " + mTaskId); + } + return createRecentTaskInfoFromTaskRecord(tr); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } + + @Override + public void moveToFront() { + checkCaller(); + // Will bring task to front if it already has a root activity. + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (this) { + mStackSupervisor.startActivityFromRecentsInner(mTaskId, null); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + @Override + public int startActivity(IBinder whoThread, String callingPackage, + Intent intent, String resolvedType, Bundle bOptions) { + checkCaller(); + + int callingUser = UserHandle.getCallingUserId(); + TaskRecord tr; + IApplicationThread appThread; + synchronized (ActivityManagerService.this) { + tr = mStackSupervisor.anyTaskForIdLocked(mTaskId); + if (tr == null) { + throw new IllegalArgumentException("Unable to find task ID " + mTaskId); + } + appThread = IApplicationThread.Stub.asInterface(whoThread); + if (appThread == null) { + throw new IllegalArgumentException("Bad app thread " + appThread); + } + } + return mActivityStarter.startActivityMayWait(appThread, -1, callingPackage, intent, + resolvedType, null, null, null, null, 0, 0, null, null, + null, bOptions, false, callingUser, tr, "AppTaskImpl"); + } + + @Override + public void setExcludeFromRecents(boolean exclude) { + checkCaller(); + + synchronized (ActivityManagerService.this) { + long origId = Binder.clearCallingIdentity(); + try { + TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(mTaskId); + if (tr == null) { + throw new IllegalArgumentException("Unable to find task ID " + mTaskId); + } + Intent intent = tr.getBaseIntent(); + if (exclude) { + intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + } else { + intent.setFlags(intent.getFlags() + & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } + } + + /** * Kill processes for the user with id userId and that depend on the package named packageName */ @Override diff --git a/com/android/server/am/ActivityManagerShellCommand.java b/com/android/server/am/ActivityManagerShellCommand.java index f03d2d53..4c934232 100644 --- a/com/android/server/am/ActivityManagerShellCommand.java +++ b/com/android/server/am/ActivityManagerShellCommand.java @@ -73,8 +73,10 @@ import java.util.List; import static android.app.ActivityManager.RESIZE_MODE_SYSTEM; import static android.app.ActivityManager.RESIZE_MODE_USER; +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.INVALID_DISPLAY; @@ -84,6 +86,15 @@ final class ActivityManagerShellCommand extends ShellCommand { public static final String NO_CLASS_ERROR_CODE = "Error type 3"; private static final String SHELL_PACKAGE_NAME = "com.android.shell"; + // Is the object moving in a positive direction? + private static final boolean MOVING_FORWARD = true; + // Is the object moving in the horizontal plan? + private static final boolean MOVING_HORIZONTALLY = true; + // Is the object current point great then its target point? + private static final boolean GREATER_THAN_TARGET = true; + // Amount we reduce the stack size by when testing a task re-size. + private static final int STACK_BOUNDS_INSET = 10; + // IPC interface to activity manager -- don't need to do additional security checks. final IActivityManager mInterface; @@ -1933,6 +1944,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runStackInfo(pw); case "move-top-activity-to-pinned-stack": return runMoveTopActivityToPinnedStack(pw); + case "size-docked-stack-test": + return runStackSizeDockedStackTest(pw); case "remove": return runStackRemove(pw); default: @@ -2130,6 +2143,89 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + int runStackSizeDockedStackTest(PrintWriter pw) throws RemoteException { + final PrintWriter err = getErrPrintWriter(); + final int stepSize = Integer.parseInt(getNextArgRequired()); + final String side = getNextArgRequired(); + final String delayStr = getNextArg(); + final int delayMs = (delayStr != null) ? Integer.parseInt(delayStr) : 0; + + ActivityManager.StackInfo info = mInterface.getStackInfo( + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED); + if (info == null) { + err.println("Docked stack doesn't exist"); + return -1; + } + if (info.bounds == null) { + err.println("Docked stack doesn't have a bounds"); + return -1; + } + Rect bounds = info.bounds; + + final boolean horizontalGrowth = "l".equals(side) || "r".equals(side); + final int changeSize = (horizontalGrowth ? bounds.width() : bounds.height()) / 2; + int currentPoint; + switch (side) { + case "l": + currentPoint = bounds.left; + break; + case "r": + currentPoint = bounds.right; + break; + case "t": + currentPoint = bounds.top; + break; + case "b": + currentPoint = bounds.bottom; + break; + default: + err.println("Unknown growth side: " + side); + return -1; + } + + final int startPoint = currentPoint; + final int minPoint = currentPoint - changeSize; + final int maxPoint = currentPoint + changeSize; + + int maxChange; + pw.println("Shrinking docked stack side=" + side); + pw.flush(); + while (currentPoint > minPoint) { + maxChange = Math.min(stepSize, currentPoint - minPoint); + currentPoint -= maxChange; + setBoundsSide(bounds, side, currentPoint); + int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs); + if (res < 0) { + return res; + } + } + + pw.println("Growing docked stack side=" + side); + pw.flush(); + while (currentPoint < maxPoint) { + maxChange = Math.min(stepSize, maxPoint - currentPoint); + currentPoint += maxChange; + setBoundsSide(bounds, side, currentPoint); + int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs); + if (res < 0) { + return res; + } + } + + pw.println("Back to Original size side=" + side); + pw.flush(); + while (currentPoint > startPoint) { + maxChange = Math.min(stepSize, currentPoint - startPoint); + currentPoint -= maxChange; + setBoundsSide(bounds, side, currentPoint); + int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs); + if (res < 0) { + return res; + } + } + return 0; + } + void setBoundsSide(Rect bounds, String side, int value) { switch (side) { case "l": @@ -2591,6 +2687,10 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Change docked stack to <LEFT,TOP,RIGHT,BOTTOM>"); pw.println(" and supplying temporary different task bounds indicated by"); pw.println(" <TASK_LEFT,TOP,RIGHT,BOTTOM>"); + pw.println(" size-docked-stack-test: <STEP_SIZE> <l|t|r|b> [DELAY_MS]"); + pw.println(" Test command for sizing docked stack by"); + pw.println(" <STEP_SIZE> increments from the side <l>eft, <t>op, <r>ight, or <b>ottom"); + pw.println(" applying the optional [DELAY_MS] between each step."); pw.println(" move-top-activity-to-pinned-stack: <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>"); pw.println(" Moves the top activity from"); pw.println(" <STACK_ID> to the pinned stack using <LEFT,TOP,RIGHT,BOTTOM> for the"); diff --git a/com/android/server/am/ActivityMetricsLogger.java b/com/android/server/am/ActivityMetricsLogger.java index 93c0f772..fdcb8c69 100644 --- a/com/android/server/am/ActivityMetricsLogger.java +++ b/com/android/server/am/ActivityMetricsLogger.java @@ -5,7 +5,6 @@ import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; @@ -128,7 +127,7 @@ class ActivityMetricsLogger { case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: mWindowState = WINDOW_STATE_SIDE_BY_SIDE; break; - case WINDOWING_MODE_FREEFORM: + case WINDOW_STATE_FREEFORM: mWindowState = WINDOW_STATE_FREEFORM; break; default: diff --git a/com/android/server/am/ActivityRecord.java b/com/android/server/am/ActivityRecord.java index 2c72a4db..7b0b942a 100644 --- a/com/android/server/am/ActivityRecord.java +++ b/com/android/server/am/ActivityRecord.java @@ -17,7 +17,9 @@ package com.android.server.am; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.app.ActivityManager.TaskDescription.ATTR_TASKDESCRIPTION_PREFIX; import static android.app.ActivityOptions.ANIM_CLIP_REVEAL; import static android.app.ActivityOptions.ANIM_CUSTOM; @@ -58,10 +60,6 @@ import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP; -import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS; -import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; -import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED; -import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER; import static android.content.pm.ActivityInfo.PERSIST_ACROSS_REBOOTS; import static android.content.pm.ActivityInfo.PERSIST_ROOT_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; @@ -286,7 +284,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo int configChangeFlags; // which config values have changed private boolean keysPaused; // has key dispatching been paused for it? int launchMode; // the launch mode activity attribute. - int lockTaskLaunchMode; // the lockTaskMode manifest attribute, subject to override boolean visible; // does this activity's window need to be shown? boolean visibleIgnoringKeyguard; // is this activity visible, ignoring the fact that Keyguard // might hide this activity? @@ -423,13 +420,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo if (iconFilename != null || taskDescription.getLabel() != null || taskDescription.getPrimaryColor() != 0) { pw.print(prefix); pw.print("taskDescription:"); + pw.print(" iconFilename="); pw.print(taskDescription.getIconFilename()); pw.print(" label=\""); pw.print(taskDescription.getLabel()); pw.print("\""); - pw.print(" icon="); pw.print(taskDescription.getInMemoryIcon() != null - ? taskDescription.getInMemoryIcon().getByteCount() + " bytes" - : "null"); - pw.print(" iconResource="); pw.print(taskDescription.getIconResource()); - pw.print(" iconFilename="); pw.print(taskDescription.getIconFilename()); pw.print(" primaryColor="); pw.println(Integer.toHexString(taskDescription.getPrimaryColor())); pw.print(prefix + " backgroundColor="); @@ -439,6 +432,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo pw.print(prefix + " navigationBarColor="); pw.println(Integer.toHexString(taskDescription.getNavigationBarColor())); } + if (iconFilename == null && taskDescription.getIcon() != null) { + pw.print(prefix); pw.println("taskDescription contains Bitmap"); + } } if (results != null) { pw.print(prefix); pw.print("results="); pw.println(results); @@ -665,7 +661,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo return; } - final boolean inPictureInPictureMode = inPinnedWindowingMode() && targetStackBounds != null; + final boolean inPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID) && + (targetStackBounds != null); if (inPictureInPictureMode != mLastReportedPictureInPictureMode || forceUpdate) { // Picture-in-picture mode changes also trigger a multi-window mode change as well, so // update that here in order @@ -687,6 +684,10 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } } + boolean isFreeform() { + return task != null && task.getStackId() == FREEFORM_WORKSPACE_STACK_ID; + } + @Override protected int getChildCount() { // {@link ActivityRecord} is a leaf node and has no children. @@ -830,6 +831,23 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo hasBeenLaunched = false; mStackSupervisor = supervisor; + mRotationAnimationHint = aInfo.rotationAnimation; + + if (options != null) { + pendingOptions = options; + mLaunchTaskBehind = pendingOptions.getLaunchTaskBehind(); + + final int rotationAnimation = pendingOptions.getRotationAnimationHint(); + // Only override manifest supplied option if set. + if (rotationAnimation >= 0) { + mRotationAnimationHint = rotationAnimation; + } + PendingIntent usageReport = pendingOptions.getUsageTimeReport(); + if (usageReport != null) { + appTimeTracker = new AppTimeTracker(usageReport); + } + } + // This starts out true, since the initial state of an activity is that we have everything, // and we shouldn't never consider it lacking in state to be removed if it dies. haveState = true; @@ -896,32 +914,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo mShowWhenLocked = (aInfo.flags & FLAG_SHOW_WHEN_LOCKED) != 0; mTurnScreenOn = (aInfo.flags & FLAG_TURN_SCREEN_ON) != 0; - - mRotationAnimationHint = aInfo.rotationAnimation; - lockTaskLaunchMode = aInfo.lockTaskLaunchMode; - if (appInfo.isPrivilegedApp() && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS - || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) { - lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT; - } - - if (options != null) { - pendingOptions = options; - mLaunchTaskBehind = options.getLaunchTaskBehind(); - - final int rotationAnimation = pendingOptions.getRotationAnimationHint(); - // Only override manifest supplied option if set. - if (rotationAnimation >= 0) { - mRotationAnimationHint = rotationAnimation; - } - final PendingIntent usageReport = pendingOptions.getUsageTimeReport(); - if (usageReport != null) { - appTimeTracker = new AppTimeTracker(usageReport); - } - final boolean useLockTask = pendingOptions.getLockTaskMode(); - if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) { - lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED; - } - } } AppWindowContainerController getWindowContainerController() { @@ -956,7 +948,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo // update the initial multi-window modes so that the callbacks are scheduled correctly when // the user leaves that mode. mLastReportedMultiWindowMode = !task.mFullscreen; - mLastReportedPictureInPictureMode = inPinnedWindowingMode(); + mLastReportedPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID); } void removeWindowContainer() { @@ -1559,7 +1551,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo // On devices that support leanback only (Android TV), Recents activity can only be // visible if the home stack is the focused stack or we are in split-screen mode. final ActivityDisplay display = getDisplay(); - boolean hasSplitScreenStack = display != null && display.hasSplitScreenPrimaryStack(); + boolean hasSplitScreenStack = display != null && display.hasSplitScreenStack(); isVisible = hasSplitScreenStack || mStackSupervisor.isFocusedStack(getStack()); } @@ -2747,8 +2739,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo void setShowWhenLocked(boolean showWhenLocked) { mShowWhenLocked = showWhenLocked; - mStackSupervisor.ensureActivitiesVisibleLocked(null, 0 /* configChanges */, - false /* preserveWindows */); } /** diff --git a/com/android/server/am/ActivityStack.java b/com/android/server/am/ActivityStack.java index f0811dda..1940ca2b 100644 --- a/com/android/server/am/ActivityStack.java +++ b/com/android/server/am/ActivityStack.java @@ -16,25 +16,27 @@ package com.android.server.am; +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.app.WindowConfiguration.activityTypeToString; -import static android.app.WindowConfiguration.windowingModeToString; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; -import static android.view.Display.INVALID_DISPLAY; +import static android.view.Display.INVALID_DISPLAY; import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM; import static com.android.server.am.ActivityDisplay.POSITION_TOP; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE; @@ -98,6 +100,7 @@ import static java.lang.Integer.MAX_VALUE; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityManager.StackId; import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.IActivityController; @@ -107,7 +110,6 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; -import android.graphics.Point; import android.graphics.Rect; import android.net.Uri; import android.os.Binder; @@ -190,6 +192,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // finished destroying itself. private static final int DESTROY_TIMEOUT = 10 * 1000; + // How long until we reset a task when the user returns to it. Currently + // disabled. + private static final long ACTIVITY_INACTIVE_RESET_TIME = 0; + // Set to false to disable the preview that is shown while a new activity // is being started. private static final boolean SHOW_APP_STARTING_PREVIEW = true; @@ -346,11 +352,12 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai private final SparseArray<Rect> mTmpBounds = new SparseArray<>(); private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>(); private final Rect mTmpRect2 = new Rect(); - private final Point mTmpSize = new Point(); /** Run all ActivityStacks through this */ protected final ActivityStackSupervisor mStackSupervisor; + private final LaunchingTaskPositioner mTaskPositioner; + private boolean mTopActivityOccludesKeyguard; private ActivityRecord mTopDismissingKeyguardActivity; @@ -453,6 +460,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mWindowManager = mService.mWindowManager; mStackId = stackId; mCurrentUser = mService.mUserController.getCurrentUserId(); + mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID + ? new LaunchingTaskPositioner() : null; mTmpRect2.setEmpty(); setWindowingMode(windowingMode); setActivityType(activityType); @@ -470,16 +479,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return mWindowContainerController; } - @Override - public void onConfigurationChanged(Configuration newParentConfig) { - final int prevWindowingMode = getWindowingMode(); - super.onConfigurationChanged(newParentConfig); - final ActivityDisplay display = getDisplay(); - if (display != null && prevWindowingMode != getWindowingMode()) { - display.onStackWindowingModeChanged(this); - } - } - /** Adds the stack to specified display and calls WindowManager to do the same. */ void reparent(ActivityDisplay activityDisplay, boolean onTop) { removeFromDisplay(); @@ -503,11 +502,14 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mDisplayId = activityDisplay.mDisplayId; mBounds = bounds != null ? new Rect(bounds) : null; mFullscreen = mBounds == null; - + if (mTaskPositioner != null) { + mTaskPositioner.setDisplay(activityDisplay.mDisplay); + mTaskPositioner.configure(mBounds); + } onParentChanged(); activityDisplay.addChild(this, onTop ? POSITION_TOP : POSITION_BOTTOM); - if (inSplitScreenPrimaryWindowingMode()) { + if (mStackId == DOCKED_STACK_ID) { // If we created a docked stack we want to resize it so it resizes all other stacks // in the system. mStackSupervisor.resizeDockedStackLocked( @@ -531,6 +533,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai display.removeChild(this); } mDisplayId = INVALID_DISPLAY; + if (mTaskPositioner != null) { + mTaskPositioner.reset(); + } } /** Removes the stack completely. Also calls WindowManager to do the same on its side. */ @@ -634,6 +639,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai void setBounds(Rect bounds) { mBounds = mFullscreen ? null : new Rect(bounds); + if (mTaskPositioner != null) { + mTaskPositioner.configure(bounds); + } } ActivityRecord topRunningActivityLocked() { @@ -810,6 +818,14 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return isActivityTypeHome() || isActivityTypeRecents(); } + final boolean isDockedStack() { + return mStackId == DOCKED_STACK_ID; + } + + final boolean isPinnedStack() { + return mStackId == PINNED_STACK_ID; + } + final boolean isOnHomeDisplay() { return mDisplayId == DEFAULT_DISPLAY; } @@ -1489,9 +1505,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai * needed. A stack is considered translucent if it don't contain a visible or * starting (about to be visible) activity that is fullscreen (opaque). * @param starting The currently starting activity or null if there is none. - * @param stackBehind The stack directly behind this one. + * @param stackBehindId The id of the stack directly behind this one. */ - private boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) { + private boolean isStackTranslucent(ActivityRecord starting, int stackBehindId) { for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); final ArrayList<ActivityRecord> activities = task.mActivities; @@ -1516,6 +1532,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return false; } + final ActivityStack stackBehind = mStackSupervisor.getStack(stackBehindId); final boolean stackBehindHomeOrRecent = stackBehind != null && stackBehind.isHomeOrRecentsStack(); if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack() @@ -1536,10 +1553,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai && !mForceHidden; } - boolean isTopStackOnDisplay() { - return getDisplay().isTopStack(this); - } - /** * Returns true if the stack should be visible. * @@ -1550,15 +1563,23 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return false; } - final ActivityDisplay display = getDisplay(); - if (isTopStackOnDisplay() || mStackSupervisor.isFocusedStack(this)) { + if (mStackSupervisor.isFrontStackOnDisplay(this) || mStackSupervisor.isFocusedStack(this)) { return true; } - final int stackIndex = display.getIndexOf(this); + final ActivityDisplay display = getDisplay(); + final ArrayList<ActivityStack> displayStacks = display.mStacks; + final int stackIndex = displayStacks.indexOf(this); + + if (stackIndex == displayStacks.size() - 1) { + Slog.wtf(TAG, + "Stack=" + this + " isn't front stack but is at the top of the stack list"); + return false; + } // Check position and visibility of this stack relative to the front stack on its display. - final ActivityStack topStack = getDisplay().getTopStack(); + final ActivityStack topStack = getTopStackOnDisplay(); + final int topStackId = topStack.mStackId; final int windowingMode = getWindowingMode(); final int activityType = getActivityType(); @@ -1566,7 +1587,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // If the assistant stack is focused and translucent, then the docked stack is always // visible if (topStack.isActivityTypeAssistant()) { - return topStack.isStackTranslucent(starting, this); + return topStack.isStackTranslucent(starting, DOCKED_STACK_ID); } return true; } @@ -1575,31 +1596,34 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // A case would be if recents stack exists but has no tasks and is below the docked stack // and home stack is below recents if (activityType == ACTIVITY_TYPE_HOME) { - final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack(); - int dockedStackIndex = display.getIndexOf(splitScreenStack); + final ActivityStack splitScreenStack = display.getStack( + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED); + int dockedStackIndex = displayStacks.indexOf(splitScreenStack); if (dockedStackIndex > stackIndex && stackIndex != dockedStackIndex - 1) { return false; } } // Find the first stack behind front stack that actually got something visible. - int stackBehindTopIndex = display.getIndexOf(topStack) - 1; + int stackBehindTopIndex = displayStacks.indexOf(topStack) - 1; while (stackBehindTopIndex >= 0 && - display.getChildAt(stackBehindTopIndex).topRunningActivityLocked() == null) { + displayStacks.get(stackBehindTopIndex).topRunningActivityLocked() == null) { stackBehindTopIndex--; } final ActivityStack stackBehindTop = (stackBehindTopIndex >= 0) - ? display.getChildAt(stackBehindTopIndex) : null; + ? displayStacks.get(stackBehindTopIndex) : null; + int stackBehindTopId = INVALID_STACK_ID; int stackBehindTopWindowingMode = WINDOWING_MODE_UNDEFINED; int stackBehindTopActivityType = ACTIVITY_TYPE_UNDEFINED; if (stackBehindTop != null) { + stackBehindTopId = stackBehindTop.mStackId; stackBehindTopWindowingMode = stackBehindTop.getWindowingMode(); stackBehindTopActivityType = stackBehindTop.getActivityType(); } final boolean alwaysOnTop = topStack.getWindowConfiguration().isAlwaysOnTop(); if (topStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || alwaysOnTop) { - if (this == stackBehindTop) { + if (stackIndex == stackBehindTopIndex) { // Stacks directly behind the docked or pinned stack are always visible. return true; } else if (alwaysOnTop && stackIndex == stackBehindTopIndex - 1) { @@ -1608,13 +1632,14 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { return true; } else if (stackBehindTopActivityType == ACTIVITY_TYPE_ASSISTANT) { - return stackBehindTop.isStackTranslucent(starting, this); + return displayStacks.get(stackBehindTopIndex).isStackTranslucent( + starting, mStackId); } } } if (topStack.isBackdropToTranslucentActivity() - && topStack.isStackTranslucent(starting, stackBehindTop)) { + && topStack.isStackTranslucent(starting, stackBehindTopId)) { // Stacks behind the fullscreen or assistant stack with a translucent activity are // always visible so they can act as a backdrop to the translucent activity. // For example, dialog activities @@ -1632,15 +1657,14 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } - if (isOnHomeDisplay()) { - // Visibility of any stack on default display should have been determined by the - // conditions above. + if (StackId.isStaticStack(mStackId) + || isHomeOrRecentsStack() || isActivityTypeAssistant()) { + // Visibility of any static stack should have been determined by the conditions above. return false; } - final int stackCount = display.getChildCount(); - for (int i = stackIndex + 1; i < stackCount; i++) { - final ActivityStack stack = display.getChildAt(i); + for (int i = stackIndex + 1; i < displayStacks.size(); i++) { + final ActivityStack stack = displayStacks.get(i); if (!stack.mFullscreen && !stack.hasFullscreenTask()) { continue; @@ -1651,7 +1675,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return false; } - if (!stack.isStackTranslucent(starting, null /* stackBehind */)) { + if (!stack.isStackTranslucent(starting, INVALID_STACK_ID)) { return false; } } @@ -1777,8 +1801,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai makeInvisible(r); } } - final int windowingMode = getWindowingMode(); - if (windowingMode == WINDOWING_MODE_FREEFORM) { + if (mStackId == FREEFORM_WORKSPACE_STACK_ID) { // The visibility of tasks and the activities they contain in freeform stack are // determined individually unlike other stacks where the visibility or fullscreen // status of an activity in a previous task affects other. @@ -1793,8 +1816,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // show activities in the next application stack behind them vs. another // task in the home stack like recents. behindFullscreenActivity = true; - } else if (windowingMode == WINDOWING_MODE_FULLSCREEN - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { + } else if (mStackId == FULLSCREEN_WORKSPACE_STACK_ID) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping after task=" + task + " returning to non-application type=" + task.getTaskToReturnTo()); // Once we reach a fullscreen stack task that has a running activity and should @@ -1830,32 +1852,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } /** - * Returns true if this stack should be resized to match the bounds specified by - * {@link ActivityOptions#setLaunchBounds} when launching an activity into the stack. - */ - boolean resizeStackWithLaunchBounds() { - return inPinnedWindowingMode(); - } - - /** - * Returns true if we try to maintain focus in the current stack when the top activity finishes. - */ - private boolean keepFocusInStackIfPossible() { - final int windowingMode = getWindowingMode(); - return windowingMode == WINDOWING_MODE_FREEFORM - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || windowingMode == WINDOWING_MODE_PINNED; - } - - /** - * Returns true if the top task in the task is allowed to return home when finished and - * there are other tasks in the stack. - */ - boolean allowTopTaskToReturnHome() { - return !inPinnedWindowingMode(); - } - - /** * @return the top most visible activity that wants to dismiss Keyguard */ ActivityRecord getTopDismissingKeyguardActivity() { @@ -1871,7 +1867,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai */ boolean checkKeyguardVisibility(ActivityRecord r, boolean shouldBeVisible, boolean isTop) { - final boolean isInPinnedStack = r.inPinnedWindowingMode(); + final boolean isInPinnedStack = r.getStack().getStackId() == PINNED_STACK_ID; final boolean keyguardShowing = mStackSupervisor.mKeyguardController.isKeyguardShowing( mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY); final boolean keyguardLocked = mStackSupervisor.mKeyguardController.isKeyguardLocked(); @@ -2169,7 +2165,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mResumedActivity = r; r.state = ActivityState.RESUMED; mService.setResumedActivityUncheckLocked(r, reason); - mStackSupervisor.mRecentTasks.add(r.getTask()); + mStackSupervisor.addRecentActivity(r); } private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) { @@ -2576,8 +2572,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai Slog.i(TAG, "Restarting because process died: " + next); if (!next.hasBeenLaunched) { next.hasBeenLaunched = true; - } else if (SHOW_APP_STARTING_PREVIEW && lastStack != null - && lastStack.isTopStackOnDisplay()) { + } else if (SHOW_APP_STARTING_PREVIEW && lastStack != null && + mStackSupervisor.isFrontStackOnDisplay(lastStack)) { next.showStartingWindow(null /* prev */, false /* newTask */, false /* taskSwitch */); } @@ -2621,7 +2617,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai private boolean resumeTopActivityInNextFocusableStack(ActivityRecord prev, ActivityOptions options, String reason) { - if (adjustFocusToNextFocusableStackLocked(reason)) { + if ((!mFullscreen || !isOnHomeDisplay()) && adjustFocusToNextFocusableStackLocked(reason)) { // Try to move focus to the next visible stack with a running activity if this // stack is not covering the entire screen or is on a secondary display (with no home // stack). @@ -2750,8 +2746,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // make underlying task focused when this one will be finished. int returnToType = isLastTaskOverHome ? task.getTaskToReturnTo() : ACTIVITY_TYPE_STANDARD; - if (fromHomeOrRecents && allowTopTaskToReturnHome()) { - returnToType = topTask == null ? ACTIVITY_TYPE_HOME : topTask.getActivityType(); + if (fromHomeOrRecents && StackId.allowTopTaskToReturnHome(mStackId)) { + returnToType = topTask == null + ? ACTIVITY_TYPE_HOME : topTask.getActivityType(); } task.setTaskToReturnTo(returnToType); } @@ -2904,7 +2901,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // Ensure the caller has requested not to trigger auto-enter PiP return false; } - if (pipCandidate == null || pipCandidate.inPinnedWindowingMode()) { + if (pipCandidate == null || pipCandidate.getStackId() == PINNED_STACK_ID) { // Ensure that we do not trigger entering PiP an activity on the pinned stack return false; } @@ -3189,8 +3186,15 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final ActivityRecord resetTaskIfNeededLocked(ActivityRecord taskTop, ActivityRecord newActivity) { - final boolean forceReset = + boolean forceReset = (newActivity.info.flags & ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0; + if (ACTIVITY_INACTIVE_RESET_TIME > 0 + && taskTop.getTask().getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { + if ((newActivity.info.flags & ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) { + forceReset = true; + } + } + final TaskRecord task = taskTop.getTask(); /** False until we evaluate the TaskRecord associated with taskTop. Switches to true @@ -3287,7 +3291,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final String myReason = reason + " adjustFocus"; if (next != r) { - if (next != null && keepFocusInStackIfPossible() && isFocusable()) { + if (next != null && StackId.keepFocusInStackIfPossible(mStackId) && isFocusable()) { // For freeform, docked, and pinned stacks we always keep the focus within the // stack as long as there is a running activity. return; @@ -3753,7 +3757,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (mode == FINISH_IMMEDIATELY || (prevState == ActivityState.PAUSED - && (mode == FINISH_AFTER_PAUSE || inPinnedWindowingMode())) + && (mode == FINISH_AFTER_PAUSE || mStackId == PINNED_STACK_ID)) || finishingActivityInNonFocusedStack || prevState == STOPPING || prevState == STOPPED @@ -4432,7 +4436,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai AppTimeTracker timeTracker, String reason) { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr); - final ActivityStack topStack = getDisplay().getTopStack(); + final ActivityStack topStack = getTopStackOnDisplay(); final ActivityRecord topActivity = topStack != null ? topStack.topActivity() : null; final int numTasks = mTaskHistory.size(); final int index = mTaskHistory.indexOf(tr); @@ -4460,9 +4464,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // Don't refocus if invisible to current user final ActivityRecord top = tr.getTopActivity(); if (top == null || !top.okToShowLocked()) { - if (top != null) { - mStackSupervisor.mRecentTasks.add(top.getTask()); - } + mStackSupervisor.addRecentActivity(top); ActivityOptions.abort(options); return; } @@ -4522,7 +4524,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // If we have a watcher, preflight the move before committing to it. First check // for *other* available tasks, but if none are available, then try again allowing the // current task to be selected. - if (isTopStackOnDisplay() && mService.mController != null) { + if (mStackSupervisor.isFrontStackOnDisplay(this) && mService.mController != null) { ActivityRecord next = topRunningActivityLocked(null, taskId); if (next == null) { next = topRunningActivityLocked(null, 0); @@ -4569,8 +4571,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController()); } - if (inPinnedWindowingMode()) { - mStackSupervisor.removeStack(this); + if (mStackId == PINNED_STACK_ID) { + mStackSupervisor.removeStackLocked(PINNED_STACK_ID); return true; } @@ -4602,6 +4604,15 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return true; } + /** + * Get the topmost stack on the current display. It may be different from focused stack, because + * focus may be on another display. + */ + private ActivityStack getTopStackOnDisplay() { + final ArrayList<ActivityStack> stacks = getDisplay().mStacks; + return stacks.isEmpty() ? null : stacks.get(stacks.size() - 1); + } + static void logStartActivity(int tag, ActivityRecord r, TaskRecord task) { final Uri data = r.intent.getData(); final String strData = data != null ? data.toSafeString() : null; @@ -4676,7 +4687,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai for (int i = mTaskHistory.size() - 1; i >= 0; i--) { final TaskRecord task = mTaskHistory.get(i); if (task.isResizeable()) { - if (inFreeformWindowingMode()) { + if (mStackId == FREEFORM_WORKSPACE_STACK_ID) { // For freeform stack we don't adjust the size of the tasks to match that // of the stack, but we do try to make sure the tasks are still contained // with the bounds of the stack. @@ -4878,7 +4889,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (focusedStack && topTask) { // Give the latest time to ensure foreground task can be sorted // at the first, because lastActiveTime of creating task is 0. - ci.lastActiveTime = SystemClock.elapsedRealtime(); + ci.lastActiveTime = System.currentTimeMillis(); topTask = false; } @@ -5058,7 +5069,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (task.autoRemoveFromRecents() || isVoiceSession) { // Task creator asked to remove this when done, or this task was a voice // interaction, so it should not remain on the recent tasks list. - mStackSupervisor.mRecentTasks.remove(task); + mStackSupervisor.removeTaskFromRecents(task); } task.removeWindowContainer(); @@ -5086,7 +5097,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai task.setStack(null); // Notify if a task from the pinned stack is being removed (or moved depending on the mode) - if (inPinnedWindowingMode()) { + if (mStackId == PINNED_STACK_ID) { mService.mTaskChangeNotificationController.notifyActivityUnpinned(); } } @@ -5109,12 +5120,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } boolean layoutTaskInStack(TaskRecord task, ActivityInfo.WindowLayout windowLayout) { - if (!task.inFreeformWindowingMode()) { + if (mTaskPositioner == null) { return false; } - mStackSupervisor.getLaunchingTaskPositioner() - .updateDefaultBounds(task, mTaskHistory, windowLayout); - + mTaskPositioner.updateDefaultBounds(task, mTaskHistory, windowLayout); return true; } @@ -5239,12 +5248,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai @Override public String toString() { return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this)) - + " stackId=" + mStackId + " type=" + activityTypeToString(getActivityType()) - + " mode=" + windowingModeToString(getWindowingMode()) + ", " - + mTaskHistory.size() + " tasks}"; + + " stackId=" + mStackId + ", " + mTaskHistory.size() + " tasks}"; } - void onLockTaskPackagesUpdated() { + void onLockTaskPackagesUpdatedLocked() { for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { mTaskHistory.get(taskNdx).setLockTaskAuth(); } diff --git a/com/android/server/am/ActivityStackSupervisor.java b/com/android/server/am/ActivityStackSupervisor.java index 5c91e3cc..da2827a6 100644 --- a/com/android/server/am/ActivityStackSupervisor.java +++ b/com/android/server/am/ActivityStackSupervisor.java @@ -21,7 +21,11 @@ import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.Manifest.permission.START_ANY_ACTIVITY; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.ActivityManager.START_TASK_TO_FRONT; +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; +import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID; +import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; @@ -31,13 +35,10 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.app.WindowConfiguration.activityTypeToString; -import static android.app.WindowConfiguration.windowingModeToString; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.PowerManager.PARTIAL_WAKE_LOCK; @@ -46,7 +47,6 @@ import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.Display.TYPE_VIRTUAL; - import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS; @@ -86,13 +86,12 @@ import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV; import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT; import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE; import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT; -import static com.android.server.am.proto.ActivityStackSupervisorProto.CONFIGURATION_CONTAINER; import static com.android.server.am.proto.ActivityStackSupervisorProto.DISPLAYS; import static com.android.server.am.proto.ActivityStackSupervisorProto.FOCUSED_STACK_ID; import static com.android.server.am.proto.ActivityStackSupervisorProto.KEYGUARD_CONTROLLER; import static com.android.server.am.proto.ActivityStackSupervisorProto.RESUMED_ACTIVITY; +import static com.android.server.am.proto.ActivityStackSupervisorProto.CONFIGURATION_CONTAINER; import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS; - import static java.lang.Integer.MAX_VALUE; import android.Manifest; @@ -103,6 +102,7 @@ import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityManager.StackId; import android.app.ActivityManager.StackInfo; import android.app.ActivityManagerInternal.SleepToken; import android.app.ActivityOptions; @@ -176,8 +176,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; -public class ActivityStackSupervisor extends ConfigurationContainer implements DisplayListener, - RecentTasks.Callbacks { +public class ActivityStackSupervisor extends ConfigurationContainer implements DisplayListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM; private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; private static final String TAG_IDLE = TAG + POSTFIX_IDLE; @@ -287,7 +286,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ActivityManagerService mService; - RecentTasks mRecentTasks; + private RecentTasks mRecentTasks; final ActivityStackSupervisorHandler mHandler; @@ -295,10 +294,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D WindowManagerService mWindowManager; DisplayManager mDisplayManager; - LaunchingTaskPositioner mTaskPositioner = new LaunchingTaskPositioner(); - /** Counter for next free stack ID to use for dynamic activity stacks. */ - private int mNextFreeStackId = 0; + private int mNextFreeStackId = FIRST_DYNAMIC_STACK_ID; /** * Maps the task identifier that activities are currently being started in to the userId of the @@ -579,7 +576,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void setRecentTasks(RecentTasks recentTasks) { mRecentTasks = recentTasks; - mRecentTasks.registerCallback(this); } /** @@ -631,6 +627,15 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return stack != null && stack == mFocusedStack; } + /** The top most stack on its display. */ + boolean isFrontStackOnDisplay(ActivityStack stack) { + return isFrontOfStackList(stack, stack.getDisplay().mStacks); + } + + private boolean isFrontOfStackList(ActivityStack stack, List<ActivityStack> stackList) { + return stack == stackList.get((stackList.size() - 1)); + } + /** NOTE: Should only be called from {@link ActivityStack#moveToFront} */ void setFocusStackUnchecked(String reason, ActivityStack focusCandidate) { if (!focusCandidate.isFocusable()) { @@ -726,9 +731,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + ActivityStack stack = stacks.get(stackNdx); final TaskRecord task = stack.taskForIdLocked(id); if (task != null) { return task; @@ -744,7 +749,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // Otherwise, check the recent tasks and return if we find it there and we are not restoring // the task from recents if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Looking for task id=" + id + " in recents"); - final TaskRecord task = mRecentTasks.getTask(id); + final TaskRecord task = mRecentTasks.taskForIdLocked(id); if (task == null) { if (DEBUG_RECENTS) { @@ -771,10 +776,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ActivityRecord isInAnyStackLocked(IBinder token) { int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); - final ActivityRecord r = stack.isInStackLocked(token); + ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityRecord r = stacks.get(stackNdx).isInStackLocked(token); if (r != null) { return r; } @@ -812,21 +816,18 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void lockAllProfileTasks(@UserIdInt int userId) { mWindowManager.deferSurfaceLayout(); try { - for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); - final List<TaskRecord> tasks = stack.getAllTasks(); - for (int taskNdx = tasks.size() - 1; taskNdx >= 0; taskNdx--) { - final TaskRecord task = tasks.get(taskNdx); - - // Check the task for a top activity belonging to userId, or returning a - // result to an activity belonging to userId. Example case: a document - // picker for personal files, opened by a work app, should still get locked. - if (taskTopActivityIsUser(task, userId)) { - mService.mTaskChangeNotificationController.notifyTaskProfileLocked( - task.taskId, userId); - } + final List<ActivityStack> stacks = getStacks(); + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; stackNdx--) { + final List<TaskRecord> tasks = stacks.get(stackNdx).getAllTasks(); + for (int taskNdx = tasks.size() - 1; taskNdx >= 0; taskNdx--) { + final TaskRecord task = tasks.get(taskNdx); + + // Check the task for a top activity belonging to userId, or returning a result + // to an activity belonging to userId. Example case: a document picker for + // personal files, opened by a work app, should still get locked. + if (taskTopActivityIsUser(task, userId)) { + mService.mTaskChangeNotificationController.notifyTaskProfileLocked( + task.taskId, userId); } } } @@ -857,7 +858,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // [u*MAX_TASK_IDS_PER_USER, (u+1)*MAX_TASK_IDS_PER_USER-1], so if MAX_TASK_IDS_PER_USER // was 10, user 0 could only have taskIds 0 to 9, user 1: 10 to 19, user 2: 20 to 29, so on. int candidateTaskId = nextTaskIdForUser(currentTaskId, userId); - while (mRecentTasks.containsTaskId(candidateTaskId, userId) + while (mRecentTasks.taskIdTakenForUserLocked(candidateTaskId, userId) || anyTaskForIdLocked( candidateTaskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) != null) { candidateTaskId = nextTaskIdForUser(candidateTaskId, userId); @@ -892,9 +893,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final String processName = app.processName; boolean didSomething = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); if (!isFocusedStack(stack)) { continue; } @@ -927,9 +928,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean allResumedActivitiesIdle() { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); if (!isFocusedStack(stack) || stack.numActivities() == 0) { continue; } @@ -948,9 +949,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean allResumedActivitiesComplete() { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); if (isFocusedStack(stack)) { final ActivityRecord r = stack.mResumedActivity; if (r != null && r.state != RESUMED) { @@ -967,12 +968,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return true; } - private boolean allResumedActivitiesVisible() { + boolean allResumedActivitiesVisible() { boolean foundResumed = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); final ActivityRecord r = stack.mResumedActivity; if (r != null) { if (!r.nowVisible || mActivitiesWaitingForVisibleActivity.contains(r)) { @@ -996,9 +997,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) { boolean someActivityPaused = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); if (!isFocusedStack(stack) && stack.mResumedActivity != null) { if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack + " mResumedActivity=" + stack.mResumedActivity); @@ -1013,9 +1014,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean allPausedActivitiesComplete() { boolean pausing = true; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); final ActivityRecord r = stack.mPausingActivity; if (r != null && r.state != PAUSED && r.state != STOPPED && r.state != STOPPING) { if (DEBUG_STATES) { @@ -1033,10 +1034,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void cancelInitializingActivities() { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); - stack.cancelInitializingActivities(); + ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + stacks.get(stackNdx).cancelInitializingActivities(); } } } @@ -1134,10 +1134,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) { final int displayId = mTmpOrderedDisplayIds.get(i); - final ActivityDisplay display = mActivityDisplays.get(displayId); - for (int j = display.getChildCount() - 1; j >= 0; --j) { - final ActivityStack stack = display.getChildAt(j); - if (stack != focusedStack && stack.isTopStackOnDisplay() && stack.isFocusable()) { + final List<ActivityStack> stacks = mActivityDisplays.get(displayId).mStacks; + if (stacks == null) { + continue; + } + for (int j = stacks.size() - 1; j >= 0; --j) { + final ActivityStack stack = stacks.get(j); + if (stack != focusedStack && isFrontStackOnDisplay(stack) && stack.isFocusable()) { r = stack.topRunningActivityLocked(); if (r != null) { return r; @@ -1153,9 +1156,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ArrayList<ArrayList<RunningTaskInfo>> runningTaskLists = new ArrayList<>(); final int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<>(); runningTaskLists.add(stackTaskList); stack.getTasksLocked(stackTaskList, callingUid, allowed); @@ -1604,16 +1607,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D Slog.w(TAG, msg); throw new SecurityException(msg); } - // Check if someone tries to launch an unwhitelisted activity into LockTask mode. - final boolean lockTaskMode = options.getLockTaskMode(); - if (lockTaskMode && !mService.mLockTaskController.isPackageWhitelisted( - UserHandle.getUserId(callingUid), aInfo.packageName)) { - final String msg = "Permission Denial: starting " + intent.toString() - + " from " + callerApp + " (pid=" + callingPid - + ", uid=" + callingUid + ") with lockTaskMode=true"; - Slog.w(TAG, msg); - throw new SecurityException(msg); - } } return true; @@ -1935,10 +1928,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean handleAppDiedLocked(ProcessRecord app) { boolean hasVisibleActivities = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); - hasVisibleActivities |= stack.handleAppDiedLocked(app); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + hasVisibleActivities |= stacks.get(stackNdx).handleAppDiedLocked(app); } } return hasVisibleActivities; @@ -1946,10 +1938,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void closeSystemDialogsLocked() { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); - stack.closeSystemDialogsLocked(); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + stacks.get(stackNdx).closeSystemDialogsLocked(); } } } @@ -1975,9 +1966,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean doit, boolean evenPersistent, int userId) { boolean didSomething = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); if (stack.finishDisabledPackageActivitiesLocked( packageName, filterByClasses, doit, evenPersistent, userId)) { didSomething = true; @@ -1997,9 +1988,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // hosted by the process that is actually still the foreground. ProcessRecord fgApp = null; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); if (isFocusedStack(stack)) { if (stack.mResumedActivity != null) { fgApp = stack.mResumedActivity.app; @@ -2049,10 +2040,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void updateActivityApplicationInfoLocked(ApplicationInfo aInfo) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); - stack.updateActivityApplicationInfoLocked(aInfo); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + stacks.get(stackNdx).updateActivityApplicationInfoLocked(aInfo); } } } @@ -2061,10 +2051,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D TaskRecord finishedTask = null; ActivityStack focusedStack = getFocusedStack(); for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - final int numStacks = display.getChildCount(); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + final int numStacks = stacks.size(); for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ActivityStack stack = stacks.get(stackNdx); TaskRecord t = stack.finishTopRunningActivityLocked(app, reason); if (stack == focusedStack || finishedTask == null) { finishedTask = t; @@ -2076,10 +2066,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void finishVoiceTask(IVoiceInteractionSession session) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - final int numStacks = display.getChildCount(); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + final int numStacks = stacks.size(); for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ActivityStack stack = stacks.get(stackNdx); stack.finishVoiceTask(session); } } @@ -2115,8 +2105,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // moveTaskToStackUncheckedLocked() should already placed the task on top, // still need moveTaskToFrontLocked() below for any transition settings. } - if (stack.resizeStackWithLaunchBounds()) { - resizeStackLocked(stack, bounds, null /* tempTaskBounds */, + if (StackId.resizeStackWithLaunchBounds(stack.mStackId)) { + resizeStackLocked(stack.mStackId, bounds, null /* tempTaskBounds */, null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS, true /* allowResizeInDockedMode */, !DEFER_RESUME); } else { @@ -2135,7 +2125,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D "findTaskToMoveToFront: moved to front of stack=" + currentStack); handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, - currentStack, forceNonResizeable); + currentStack.mStackId, forceNonResizeable); } boolean canUseActivityOptionsLaunchBounds(ActivityOptions options) { @@ -2149,10 +2139,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D || mService.mSupportsFreeformWindowManagement; } - LaunchingTaskPositioner getLaunchingTaskPositioner() { - return mTaskPositioner; - } - protected <T extends ActivityStack> T getStack(int stackId) { for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { final T stack = mActivityDisplays.valueAt(i).getStack(stackId); @@ -2330,8 +2316,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId); if (display != null) { - for (int i = display.getChildCount() - 1; i >= 0; --i) { - stack = (T) display.getChildAt(i); + for (int i = display.mStacks.size() - 1; i >= 0; --i) { + stack = (T) display.mStacks.get(i); if (stack.isCompatible(windowingMode, activityType)) { return stack; } @@ -2399,8 +2385,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } // Return the topmost valid stack on the display. - for (int i = activityDisplay.getChildCount() - 1; i >= 0; --i) { - final ActivityStack stack = activityDisplay.getChildAt(i); + for (int i = activityDisplay.mStacks.size() - 1; i >= 0; --i) { + final ActivityStack stack = activityDisplay.mStacks.get(i); if (isValidLaunchStack(stack, displayId, r)) { return stack; } @@ -2431,13 +2417,25 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return r.supportsSplitScreenWindowingMode(); } - if (!stack.isOnHomeDisplay()) { + if (StackId.isDynamicStack(stack.mStackId)) { return r.canBeLaunchedOnDisplay(displayId); } Slog.e(TAG, "isValidLaunchStack: Unexpected stack=" + stack); return false; } + ArrayList<ActivityStack> getStacks() { + ArrayList<ActivityStack> allStacks = new ArrayList<>(); + for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { + allStacks.addAll(mActivityDisplays.valueAt(displayNdx).mStacks); + } + return allStacks; + } + + ArrayList<ActivityStack> getStacksOnDefaultDisplay() { + return mActivityDisplays.valueAt(DEFAULT_DISPLAY).mStacks; + } + /** * Get next focusable stack in the system. This will search across displays and stacks * in last-focused order for a focusable and visible stack, different from the target stack. @@ -2452,9 +2450,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) { final int displayId = mTmpOrderedDisplayIds.get(i); // If a display is registered in WM, it must also be available in AM. - final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId); - for (int j = display.getChildCount() - 1; j >= 0; --j) { - final ActivityStack stack = display.getChildAt(j); + @SuppressWarnings("ConstantConditions") + final List<ActivityStack> stacks = getActivityDisplayOrCreateLocked(displayId).mStacks; + for (int j = stacks.size() - 1; j >= 0; --j) { + final ActivityStack stack = stacks.get(j); if (stack != currentFocus && stack.isFocusable() && stack.shouldBeVisible(null)) { return stack; @@ -2512,17 +2511,20 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return null; } - void resizeStackLocked(ActivityStack stack, Rect bounds, Rect tempTaskBounds, - Rect tempTaskInsetBounds, boolean preserveWindows, boolean allowResizeInDockedMode, - boolean deferResume) { - - if (stack.inSplitScreenPrimaryWindowingMode()) { + void resizeStackLocked(int stackId, Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds, + boolean preserveWindows, boolean allowResizeInDockedMode, boolean deferResume) { + if (stackId == DOCKED_STACK_ID) { resizeDockedStackLocked(bounds, tempTaskBounds, tempTaskInsetBounds, null, null, preserveWindows, deferResume); return; } + final ActivityStack stack = getStack(stackId); + if (stack == null) { + Slog.w(TAG, "resizeStack: stackId " + stackId + " not found."); + return; + } - final boolean splitScreenActive = getDefaultDisplay().hasSplitScreenPrimaryStack(); + final boolean splitScreenActive = getDefaultDisplay().hasSplitScreenStack(); if (!allowResizeInDockedMode && !stack.getWindowConfiguration().tasksAreFloating() && splitScreenActive) { // If the docked stack exists, don't resize non-floating stacks independently of the @@ -2530,7 +2532,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return; } - Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stack.mStackId); + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId); mWindowManager.deferSurfaceLayout(); try { if (stack.supportsSplitScreenWindowingMode()) { @@ -2582,7 +2584,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D /** * TODO: This should just change the windowing mode and resize vs. actually moving task around. - * Can do that once we are no longer using static stack ids. + * Can do that once we are no longer using static stack ids. Specially when + * {@link ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} is removed. */ private void moveTasksToFullscreenStackInSurfaceTransaction(ActivityStack fromStack, int toDisplayId, boolean onTop) { @@ -2597,12 +2600,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // We are moving all tasks from the docked stack to the fullscreen stack, // which is dismissing the docked stack, so resize all other stacks to // fullscreen here already so we don't end up with resize trashing. - for (int i = toDisplay.getChildCount() - 1; i >= 0; --i) { - final ActivityStack otherStack = toDisplay.getChildAt(i); + final ArrayList<ActivityStack> displayStacks = toDisplay.mStacks; + for (int i = displayStacks.size() - 1; i >= 0; --i) { + final ActivityStack otherStack = displayStacks.get(i); if (!otherStack.inSplitScreenSecondaryWindowingMode()) { continue; } - resizeStackLocked(otherStack, null, null, null, PRESERVE_WINDOWS, + resizeStackLocked(otherStack.mStackId, null, null, null, PRESERVE_WINDOWS, true /* allowResizeInDockedMode */, DEFER_RESUME); } @@ -2693,7 +2697,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return; } - final ActivityStack stack = getDefaultDisplay().getSplitScreenPrimaryStack(); + final ActivityStack stack = getDefaultDisplay().getStack( + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED); if (stack == null) { Slog.w(TAG, "resizeDockedStackLocked: docked stack not found"); return; @@ -2722,10 +2727,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // static stacks need to be adjusted so they don't overlap with the docked stack. // We get the bounds to use from window manager which has been adjusted for any // screen controls and is also the same for all stacks. - final ActivityDisplay display = getDefaultDisplay(); + final ArrayList<ActivityStack> stacks = getStacksOnDefaultDisplay(); final Rect otherTaskRect = new Rect(); - for (int i = display.getChildCount() - 1; i >= 0; --i) { - final ActivityStack current = display.getChildAt(i); + for (int i = stacks.size() - 1; i >= 0; --i) { + final ActivityStack current = stacks.get(i); if (current.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { continue; } @@ -2739,7 +2744,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D tempRect /* outStackBounds */, otherTaskRect /* outTempTaskBounds */, true /* ignoreVisibility */); - resizeStackLocked(current, !tempRect.isEmpty() ? tempRect : null, + resizeStackLocked(current.mStackId, !tempRect.isEmpty() ? tempRect : null, !otherTaskRect.isEmpty() ? otherTaskRect : tempOtherTaskBounds, tempOtherTaskInsetBounds, preserveWindows, true /* allowResizeInDockedMode */, deferResume); @@ -2757,7 +2762,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void resizePinnedStackLocked(Rect pinnedBounds, Rect tempPinnedTaskBounds) { // TODO(multi-display): Pinned stack display should be passed in. - final PinnedActivityStack stack = getDefaultDisplay().getPinnedStack(); + final PinnedActivityStack stack = getDefaultDisplay().getStack( + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); if (stack == null) { Slog.w(TAG, "resizePinnedStackLocked: pinned stack not found"); return; @@ -2795,7 +2801,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - private void removeStackInSurfaceTransaction(ActivityStack stack) { + private void removeStackInSurfaceTransaction(int stackId) { + final ActivityStack stack = getStack(stackId); + if (stack == null) { + return; + } + final ArrayList<TaskRecord> tasks = stack.getAllTasks(); if (stack.getWindowingMode() == WINDOWING_MODE_PINNED) { /** @@ -2825,12 +2836,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } /** - * Removes the stack associated with the given {@param stack}. If the {@param stack} is the + * Removes the stack associated with the given {@param stackId}. If the {@param stackId} is the * pinned stack, then its tasks are not explicitly removed when the stack is destroyed, but * instead moved back onto the fullscreen stack. */ - void removeStack(ActivityStack stack) { - mWindowManager.inSurfaceTransaction(() -> removeStackInSurfaceTransaction(stack)); + void removeStackLocked(int stackId) { + mWindowManager.inSurfaceTransaction(() -> removeStackInSurfaceTransaction(stackId)); } /** @@ -2881,9 +2892,23 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return false; } + void addRecentActivity(ActivityRecord r) { + if (r == null) { + return; + } + final TaskRecord task = r.getTask(); + mRecentTasks.addLocked(task); + task.touchActiveTime(); + } + + void removeTaskFromRecents(TaskRecord task) { + mRecentTasks.remove(task); + task.removedFromRecents(); + } + void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess, boolean removeFromRecents) { if (removeFromRecents) { - mRecentTasks.remove(tr); + removeTaskFromRecents(tr); } ComponentName component = tr.getBaseIntent().getComponent(); if (component == null) { @@ -2954,7 +2979,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D int getNextStackId() { while (true) { - if (getStack(mNextFreeStackId) == null) { + if (mNextFreeStackId >= FIRST_DYNAMIC_STACK_ID + && getStack(mNextFreeStackId) == null) { break; } mNextFreeStackId++; @@ -2963,8 +2989,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } /** - * Called to restore the state of the task into the stack that it's supposed to go into. - * + * Restores a recent task to a stack * @param task The recent task to be restored. * @param aOptions The activity options to use for restoration. * @return true if the task has been restored successfully. @@ -2995,22 +3020,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return true; } - @Override - public void onRecentTaskAdded(TaskRecord task) { - task.touchActiveTime(); - } - - @Override - public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) { - if (wasTrimmed) { - // Task was trimmed from the recent tasks list -- remove the active task record as well - // since the user won't really be able to go back to it - removeTaskByIdLocked(task.taskId, false /* killProcess */, - false /* removeFromRecents */); - } - task.removedFromRecents(); - } - /** * Move stack with all its existing content to specified display. * @param stackId Id of stack to move. @@ -3151,7 +3160,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // Resize the pinned stack to match the current size of the task the activity we are // going to be moving is currently contained in. We do this to have the right starting // animation bounds for the pinned stack to the desired bounds the caller wants. - resizeStackLocked(stack, task.mBounds, null /* tempTaskBounds */, + resizeStackLocked(PINNED_STACK_ID, task.mBounds, null /* tempTaskBounds */, null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS, true /* allowResizeInDockedMode */, !DEFER_RESUME); @@ -3248,9 +3257,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ActivityRecord affinityMatch = null; if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r); for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); if (!r.hasCompatibleActivityType(stack)) { if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) " + stack); @@ -3283,13 +3292,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } ActivityRecord findActivityLocked(Intent intent, ActivityInfo info, - boolean compareIntentFilters) { + boolean compareIntentFilters) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); - final ActivityRecord ar = stack.findActivityLocked( - intent, info, compareIntentFilters); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityRecord ar = stacks.get(stackNdx) + .findActivityLocked(intent, info, compareIntentFilters); if (ar != null) { return ar; } @@ -3383,8 +3391,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } // Set the sleeping state of the stacks on the display. - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ArrayList<ActivityStack> stacks = display.mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); if (displayShouldSleep) { stack.goToSleepIfPossible(false /* shuttingDown */); } else { @@ -3446,13 +3455,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private boolean putStacksToSleepLocked(boolean allowDelay, boolean shuttingDown) { boolean allSleep = true; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { if (allowDelay) { - allSleep &= stack.goToSleepIfPossible(shuttingDown); + allSleep &= stacks.get(stackNdx).goToSleepIfPossible(shuttingDown); } else { - stack.goToSleep(); + stacks.get(stackNdx).goToSleep(); } } } @@ -3477,10 +3485,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void handleAppCrashLocked(ProcessRecord app) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); - stack.handleAppCrashLocked(app); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + int stackNdx = stacks.size() - 1; + while (stackNdx >= 0) { + stacks.get(stackNdx).handleAppCrashLocked(app); + stackNdx--; } } } @@ -3491,7 +3500,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ActivityStack stack = task.getStack(); r.mLaunchTaskBehind = false; - mRecentTasks.add(task); + mRecentTasks.addLocked(task); mService.mTaskChangeNotificationController.notifyTaskStackChanged(); r.setVisibility(false); @@ -3513,9 +3522,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D try { // First the front stacks. In case any are not fullscreen and are in front of home. for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + final int topStackNdx = stacks.size() - 1; + for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); stack.ensureActivitiesVisibleLocked(starting, configChanges, preserveWindows); } } @@ -3526,9 +3536,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void addStartingWindowsForVisibleActivities(boolean taskSwitch) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + final int topStackNdx = stacks.size() - 1; + for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); stack.addStartingWindowsForVisibleActivities(taskSwitch); } } @@ -3544,20 +3555,20 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } mTaskLayersChanged = false; for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); displayNdx++) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; int baseLayer = 0; - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); - baseLayer += stack.rankTaskLayers(baseLayer); + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + baseLayer += stacks.get(stackNdx).rankTaskLayers(baseLayer); } } } void clearOtherAppTimeTrackers(AppTimeTracker except) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + final int topStackNdx = stacks.size() - 1; + for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); stack.clearOtherAppTimeTrackers(except); } } @@ -3565,9 +3576,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void scheduleDestroyAllActivities(ProcessRecord app, String reason) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + final int numStacks = stacks.size(); + for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); stack.scheduleDestroyActivities(app, reason); } } @@ -3619,11 +3631,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // let's iterate through the tasks and release the oldest one. final int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - final int stackCount = display.getChildCount(); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; // Step through all stacks starting from behind, to hit the oldest things first. - for (int stackNdx = 0; stackNdx < stackCount; stackNdx++) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = 0; stackNdx < stacks.size(); stackNdx++) { + final ActivityStack stack = stacks.get(stackNdx); // Try to release activities in this stack; if we manage to, we are done. if (stack.releaseSomeActivitiesLocked(app, tasks, reason) > 0) { return; @@ -3635,14 +3646,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean switchUserLocked(int userId, UserState uss) { final int focusStackId = mFocusedStack.getStackId(); // We dismiss the docked stack whenever we switch users. - final ActivityStack dockedStack = getDefaultDisplay().getSplitScreenPrimaryStack(); + final ActivityStack dockedStack = getDefaultDisplay().getSplitScreenStack(); if (dockedStack != null) { moveTasksToFullscreenStackLocked(dockedStack, mFocusedStack == dockedStack); } // Also dismiss the pinned stack whenever we switch users. Removing the pinned stack will // also cause all tasks to be moved to the fullscreen stack at a position that is // appropriate. - removeStacksInWindowingModes(WINDOWING_MODE_PINNED); + removeStackLocked(PINNED_STACK_ID); mUserStackInFront.put(mCurrentUser, focusStackId); final int restoreStackId = mUserStackInFront.get(userId, mHomeStack.mStackId); @@ -3650,9 +3661,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mStartingUsers.add(uss); for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); stack.switchUserLocked(userId); TaskRecord task = stack.topTask(); if (task != null) { @@ -3750,9 +3761,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void validateTopActivitiesLocked() { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); final ActivityRecord r = stack.topRunningActivityLocked(); final ActivityState state = r == null ? DESTROYED : r.state; if (isFocusedStack(stack)) { @@ -3787,7 +3798,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront); for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { final ActivityDisplay display = mActivityDisplays.valueAt(i); - display.dump(pw, prefix); + pw.println(prefix + "displayId=" + display.mDisplayId + " mStacks=" + display.mStacks); } if (!mWaitingForActivityVisible.isEmpty()) { pw.print(prefix); pw.println("mWaitingForActivityVisible="); @@ -3800,7 +3811,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mService.mLockTaskController.dump(pw, prefix); } - public void writeToProto(ProtoOutputStream proto) { + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); super.writeToProto(proto, CONFIGURATION_CONTAINER); for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) { ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx); @@ -3816,6 +3828,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } else { proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID); } + proto.end(token); } /** @@ -3844,9 +3857,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ArrayList<ActivityRecord> activities = new ArrayList<>(); int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + ActivityStack stack = stacks.get(stackNdx); if (!dumpVisibleStacksOnly || stack.shouldBeVisible(null)) { activities.addAll(stack.getDumpActivitiesLocked(name)); } @@ -3879,13 +3892,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx); pw.print("Display #"); pw.print(activityDisplay.mDisplayId); pw.println(" (activities from top to bottom):"); - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + ArrayList<ActivityStack> stacks = activityDisplay.mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); pw.println(); - pw.println(" Stack #" + stack.mStackId - + ": type=" + activityTypeToString(stack.getActivityType()) - + " mode=" + windowingModeToString(stack.getWindowingMode())); + pw.println(" Stack #" + stack.mStackId + ":"); pw.println(" mFullscreen=" + stack.mFullscreen); pw.println(" isSleeping=" + stack.shouldSleepActivities()); pw.println(" mBounds=" + stack.mBounds); @@ -4129,29 +4140,30 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } synchronized (mService) { - final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId); - if (activityDisplay == null) { - return; - } - final boolean destroyContentOnRemoval - = activityDisplay.shouldDestroyContentOnRemove(); - while (activityDisplay.getChildCount() > 0) { - final ActivityStack stack = activityDisplay.getChildAt(0); - if (destroyContentOnRemoval) { - moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY, false /* onTop */); - stack.finishAllActivitiesLocked(true /* immediately */); - } else { - // Moving all tasks to fullscreen stack, because it's guaranteed to be - // a valid launch stack for all activities. This way the task history from - // external display will be preserved on primary after move. - moveTasksToFullscreenStackLocked(stack, true /* onTop */); + ActivityDisplay activityDisplay = mActivityDisplays.get(displayId); + if (activityDisplay != null) { + final boolean destroyContentOnRemoval + = activityDisplay.shouldDestroyContentOnRemove(); + final ArrayList<ActivityStack> stacks = activityDisplay.mStacks; + while (!stacks.isEmpty()) { + final ActivityStack stack = stacks.get(0); + if (destroyContentOnRemoval) { + moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY, + false /* onTop */); + stack.finishAllActivitiesLocked(true /* immediately */); + } else { + // Moving all tasks to fullscreen stack, because it's guaranteed to be + // a valid launch stack for all activities. This way the task history from + // external display will be preserved on primary after move. + moveTasksToFullscreenStackLocked(stack, true /* onTop */); + } } - } - releaseSleepTokens(activityDisplay); + releaseSleepTokens(activityDisplay); - mActivityDisplays.remove(displayId); - mWindowManager.onDisplayRemoved(displayId); + mActivityDisplays.remove(displayId); + mWindowManager.onDisplayRemoved(displayId); + } } } @@ -4223,7 +4235,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D info.userId = stack.mCurrentUser; info.visible = stack.shouldBeVisible(null); // A stack might be not attached to a display. - info.position = display != null ? display.getIndexOf(stack) : 0; + info.position = display != null ? display.mStacks.indexOf(stack) : 0; info.configuration.setTo(stack.getConfiguration()); ArrayList<TaskRecord> tasks = stack.getAllTasks(); @@ -4269,25 +4281,25 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ArrayList<StackInfo> getAllStackInfosLocked() { ArrayList<StackInfo> list = new ArrayList<>(); for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); - list.add(getStackInfo(stack)); + ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int ndx = stacks.size() - 1; ndx >= 0; --ndx) { + list.add(getStackInfo(stacks.get(ndx))); } } return list; } void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode, - int preferredDisplayId, ActivityStack actualStack) { + int preferredDisplayId, int actualStackId) { handleNonResizableTaskIfNeeded(task, preferredWindowingMode, preferredDisplayId, - actualStack, false /* forceNonResizable */); + actualStackId, false /* forceNonResizable */); } void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode, - int preferredDisplayId, ActivityStack actualStack, boolean forceNonResizable) { + int preferredDisplayId, int actualStackId, boolean forceNonResizable) { final boolean isSecondaryDisplayPreferred = (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY); + final ActivityStack actualStack = getStack(actualStackId); final boolean inSplitScreenMode = actualStack != null && actualStack.inSplitScreenWindowingMode(); if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) @@ -4303,8 +4315,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // The task landed on an inappropriate display somehow, move it to the default // display. // TODO(multi-display): Find proper stack for the task on the default display. - mService.setTaskWindowingMode(task.taskId, - WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, true /* toTop */); + mService.moveTaskToStack(task.taskId, FULLSCREEN_WORKSPACE_STACK_ID, + true /* toTop */); launchOnSecondaryDisplayFailed = true; } else { // The task might have landed on a display different from requested. @@ -4334,9 +4346,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // Dismiss docked stack. If task appeared to be in docked stack but is not resizable - // we need to move it to top of fullscreen stack, otherwise it will be covered. - final ActivityStack dockedStack = task.getStack().getDisplay().getSplitScreenPrimaryStack(); + final ActivityStack dockedStack = task.getStack().getDisplay().getSplitScreenStack(); if (dockedStack != null) { - moveTasksToFullscreenStackLocked(dockedStack, actualStack == dockedStack); + moveTasksToFullscreenStackLocked(dockedStack, + actualStackId == dockedStack.getStackId()); } } else if (topActivity != null && topActivity.isNonResizableOrForcedResizable() && !topActivity.noDisplay) { @@ -4389,7 +4402,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, ActivityStack prevStack) { final ActivityStack stack = task.getStack(); if (prevStack == null || prevStack == stack - || (!prevStack.inPinnedWindowingMode() && !stack.inPinnedWindowingMode())) { + || (prevStack.mStackId != PINNED_STACK_ID && stack.mStackId != PINNED_STACK_ID)) { return; } @@ -4548,13 +4561,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (display == null) { return null; } - for (int i = display.getChildCount() - 1; i >= 0; i--) { - if (display.getChildAt(i) == stack && i > 0) { - return display.getChildAt(i - 1); + final ArrayList<ActivityStack> stacks = display.mStacks; + for (int i = stacks.size() - 1; i >= 0; i--) { + if (stacks.get(i) == stack && i > 0) { + return stacks.get(i - 1); } } throw new IllegalStateException("Failed to find a stack behind stack=" + stack - + " in=" + display); + + " in=" + stacks); } /** @@ -4667,8 +4681,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D for (int i = mActivityDisplays.size() - 1; i >= 0; i--) { final ActivityDisplay display = mActivityDisplays.valueAt(i); // Traverse all stacks on a display. - for (int j = display.getChildCount() - 1; j >= 0; --j) { - final ActivityStack stack = display.getChildAt(j); + for (int j = display.mStacks.size() - 1; j >= 0; j--) { + final ActivityStack stack = display.mStacks.get(j); // Get top activity from a visible stack and add it to the list. if (stack.shouldBeVisible(null /* starting */)) { final ActivityRecord top = stack.topActivity(); diff --git a/com/android/server/am/ActivityStarter.java b/com/android/server/am/ActivityStarter.java index 6f74d851..d444c663 100644 --- a/com/android/server/am/ActivityStarter.java +++ b/com/android/server/am/ActivityStarter.java @@ -26,9 +26,15 @@ import static android.app.ActivityManager.START_RETURN_INTENT_TO_CALLER; import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; +import static android.app.ActivityManager.StackId; +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static android.app.ActivityManager.StackId.isDynamicStack; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; @@ -115,6 +121,7 @@ import com.android.internal.app.HeavyWeightSwitcherActivity; import com.android.internal.app.IVoiceInteractor; import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch; import com.android.server.pm.InstantAppResolver; +import com.android.server.wm.WindowManagerService; import java.io.PrintWriter; import java.text.DateFormat; @@ -133,11 +140,11 @@ class ActivityStarter { private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING; - private static final int INVALID_LAUNCH_MODE = -1; private final ActivityManagerService mService; private final ActivityStackSupervisor mSupervisor; private final ActivityStartInterceptor mInterceptor; + private WindowManagerService mWindowManager; final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>(); @@ -147,7 +154,9 @@ class ActivityStarter { private int mCallingUid; private ActivityOptions mOptions; - private int mLaunchMode; + private boolean mLaunchSingleTop; + private boolean mLaunchSingleInstance; + private boolean mLaunchSingleTask; private boolean mLaunchTaskBehind; private int mLaunchFlags; @@ -157,7 +166,6 @@ class ActivityStarter { private boolean mDoResume; private int mStartFlags; private ActivityRecord mSourceRecord; - // The display to launch the activity onto, barring any strong reason to do otherwise. private int mPreferredDisplayId; @@ -187,6 +195,8 @@ class ActivityStarter { private IVoiceInteractionSession mVoiceSession; private IVoiceInteractor mVoiceInteractor; + private boolean mUsingVr2dDisplay; + // Last home activity record we attempted to start private final ActivityRecord[] mLastHomeActivityStartRecord = new ActivityRecord[1]; // The result of the last home activity we attempted to start. @@ -206,9 +216,11 @@ class ActivityStarter { mCallingUid = -1; mOptions = null; + mLaunchSingleTop = false; + mLaunchSingleInstance = false; + mLaunchSingleTask = false; mLaunchTaskBehind = false; mLaunchFlags = 0; - mLaunchMode = INVALID_LAUNCH_MODE; mLaunchBounds = null; @@ -236,13 +248,16 @@ class ActivityStarter { mVoiceSession = null; mVoiceInteractor = null; + mUsingVr2dDisplay = false; + mIntentDelivered = false; } - ActivityStarter(ActivityManagerService service) { + ActivityStarter(ActivityManagerService service, ActivityStackSupervisor supervisor) { mService = service; - mSupervisor = mService.mStackSupervisor; + mSupervisor = supervisor; mInterceptor = new ActivityStartInterceptor(mService, mSupervisor); + mUsingVr2dDisplay = false; } int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent, @@ -595,41 +610,38 @@ class ActivityStarter { mSupervisor.reportTaskToFrontNoLaunch(mStartActivity); } - ActivityStack startedActivityStack = null; + int startedActivityStackId = INVALID_STACK_ID; final ActivityStack currentStack = r.getStack(); if (currentStack != null) { - startedActivityStack = currentStack; + startedActivityStackId = currentStack.mStackId; } else if (mTargetStack != null) { - startedActivityStack = targetStack; + startedActivityStackId = targetStack.mStackId; } - if (startedActivityStack == null) { - return; - } - - if (startedActivityStack.inSplitScreenPrimaryWindowingMode()) { - final ActivityStack homeStack = mSupervisor.mHomeStack; + if (startedActivityStackId == DOCKED_STACK_ID) { + final ActivityStack homeStack = mSupervisor.getDefaultDisplay().getStack( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME); final boolean homeStackVisible = homeStack != null && homeStack.isVisible(); if (homeStackVisible) { // We launch an activity while being in home stack, which means either launcher or // recents into docked stack. We don't want the launched activity to be alone in a // docked stack, so we want to immediately launch recents too. if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch."); - mService.mWindowManager.showRecentApps(true /* fromHome */); + mWindowManager.showRecentApps(true /* fromHome */); } return; } boolean clearedTask = (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK) && (mReuseTask != null); - if (startedActivityStack.inPinnedWindowingMode() - && (result == START_TASK_TO_FRONT || result == START_DELIVERED_TO_TOP - || clearedTask)) { + if (startedActivityStackId == PINNED_STACK_ID && (result == START_TASK_TO_FRONT + || result == START_DELIVERED_TO_TOP || clearedTask)) { // The activity was already running in the pinned stack so it wasn't started, but either // brought to the front or the new intent was delivered to it since it was already in // front. Notify anyone interested in this piece of information. mService.mTaskChangeNotificationController.notifyPinnedActivityRestartAttempt( clearedTask); + return; } } @@ -1050,7 +1062,7 @@ class ActivityStarter { // operations. if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0 || isDocumentLaunchesIntoExisting(mLaunchFlags) - || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) { + || mLaunchSingleInstance || mLaunchSingleTask) { final TaskRecord task = reusedActivity.getTask(); // In this situation we want to remove all activities from the task up to the one @@ -1133,7 +1145,7 @@ class ActivityStarter { && top.userId == mStartActivity.userId && top.app != null && top.app.thread != null && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 - || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK)); + || mLaunchSingleTop || mLaunchSingleTask); if (dontStart) { // For paranoia, make sure we have correctly resumed the top activity. topStack.mLastPausedActivity = null; @@ -1152,7 +1164,7 @@ class ActivityStarter { // Don't use mStartActivity.task to show the toast. We're not starting a new activity // but reusing 'top'. Fields in mStartActivity may not be fully initialized. mSupervisor.handleNonResizableTaskIfNeeded(top.getTask(), preferredWindowingMode, - preferredLaunchDisplayId, topStack); + preferredLaunchDisplayId, topStack.mStackId); return START_DELIVERED_TO_TOP; } @@ -1215,7 +1227,7 @@ class ActivityStarter { mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); // Go ahead and tell window manager to execute app transition for this activity // since the app transition will not be triggered through the resume channel. - mService.mWindowManager.executeAppTransition(); + mWindowManager.executeAppTransition(); } else { // If the target stack was not previously focusable (previous top running activity // on that stack was not visible) then any prior calls to move the stack to the @@ -1228,13 +1240,13 @@ class ActivityStarter { mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity, mOptions); } - } else if (mStartActivity != null) { - mSupervisor.mRecentTasks.add(mStartActivity.getTask()); + } else { + mSupervisor.addRecentActivity(mStartActivity); } mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack); mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredWindowingMode, - preferredLaunchDisplayId, mTargetStack); + preferredLaunchDisplayId, mTargetStack.mStackId); return START_SUCCESS; } @@ -1256,13 +1268,13 @@ class ActivityStarter { mLaunchBounds = getOverrideBounds(r, options, inTask); - mLaunchMode = r.launchMode; - + mLaunchSingleTop = r.launchMode == LAUNCH_SINGLE_TOP; + mLaunchSingleInstance = r.launchMode == LAUNCH_SINGLE_INSTANCE; + mLaunchSingleTask = r.launchMode == LAUNCH_SINGLE_TASK; mLaunchFlags = adjustLaunchFlagsToDocumentMode( - r, LAUNCH_SINGLE_INSTANCE == mLaunchMode, - LAUNCH_SINGLE_TASK == mLaunchMode, mIntent.getFlags()); + r, mLaunchSingleInstance, mLaunchSingleTask, mIntent.getFlags()); mLaunchTaskBehind = r.mLaunchTaskBehind - && !isLaunchModeOneOf(LAUNCH_SINGLE_TASK, LAUNCH_SINGLE_INSTANCE) + && !mLaunchSingleTask && !mLaunchSingleInstance && (mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0; sendNewTaskResultRequestIfNeeded(); @@ -1372,7 +1384,7 @@ class ActivityStarter { // If this task is empty, then we are adding the first activity -- it // determines the root, and must be launching as a NEW_TASK. - if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) { + if (mLaunchSingleInstance || mLaunchSingleTask) { if (!baseIntent.getComponent().equals(mStartActivity.intent.getComponent())) { ActivityOptions.abort(mOptions); throw new IllegalArgumentException("Trying to launch singleInstance/Task " @@ -1415,7 +1427,7 @@ class ActivityStarter { // in any task/stack, however it could launch other activities like ResolverActivity, // and we want those to stay in the original task. if ((mStartActivity.isResolverActivity() || mStartActivity.noDisplay) && mSourceRecord != null - && mSourceRecord.inFreeformWindowingMode()) { + && mSourceRecord.isFreeform()) { mAddingToTask = true; } } @@ -1434,7 +1446,7 @@ class ActivityStarter { // instance... this new activity it is starting must go on its // own task. mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK; - } else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) { + } else if (mLaunchSingleInstance || mLaunchSingleTask) { // The activity being started is a single instance... it always // gets launched into its own task. mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK; @@ -1485,7 +1497,7 @@ class ActivityStarter { // launch this as a new task behind the current one. boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 && (mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0) - || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK); + || mLaunchSingleInstance || mLaunchSingleTask; // If bring to front is requested, and no result is requested and we have not been given // an explicit task to launch in to, and we can find a task that was started with this // same component, then instead of launching bring that one to the front. @@ -1495,7 +1507,7 @@ class ActivityStarter { final TaskRecord task = mSupervisor.anyTaskForIdLocked(mOptions.getLaunchTaskId()); intentActivity = task != null ? task.getTopActivity() : null; } else if (putIntoExistingTask) { - if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) { + if (mLaunchSingleInstance) { // There can be one and only one instance of single instance activity in the // history, and it is always in its own unique task, so we do a special search. intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info, @@ -1504,7 +1516,7 @@ class ActivityStarter { // For the launch adjacent case we only want to put the activity in an existing // task if the activity already exists in the history. intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info, - !(LAUNCH_SINGLE_TASK == mLaunchMode)); + !mLaunchSingleTask); } else { // Otherwise find the best task to put the activity in. intentActivity = mSupervisor.findTaskLocked(mStartActivity, mPreferredDisplayId); @@ -1533,6 +1545,7 @@ class ActivityStarter { if (DEBUG_STACK) { Slog.d(TAG, "getSourceDisplayId :" + displayId); } + mUsingVr2dDisplay = true; return displayId; } @@ -1654,7 +1667,7 @@ class ActivityStarter { } mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTask(), - WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack); + WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack.mStackId); // If the caller has requested that the target task be reset, then do so. if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { @@ -1706,7 +1719,7 @@ class ActivityStarter { // mTaskToReturnTo values and we don't want to overwrite them accidentally. mMovedOtherTask = true; } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0 - || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) { + || mLaunchSingleInstance || mLaunchSingleTask) { ActivityRecord top = intentActivity.getTask().performClearTaskLocked(mStartActivity, mLaunchFlags); if (top == null) { @@ -1735,8 +1748,7 @@ class ActivityStarter { // so we take that as a request to bring the task to the foreground. If the top // activity in the task is the root activity, deliver this new intent to it if it // desires. - if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 - || LAUNCH_SINGLE_TOP == mLaunchMode) + if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop) && intentActivity.realActivity.equals(mStartActivity.realActivity)) { if (intentActivity.frontOfTask) { intentActivity.getTask().setIntent(mStartActivity); @@ -1940,7 +1952,7 @@ class ActivityStarter { if (top != null && top.realActivity.equals(mStartActivity.realActivity) && top.userId == mStartActivity.userId) { if ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 - || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK)) { + || mLaunchSingleTop || mLaunchSingleTask) { mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, "inTaskToFront"); if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) { @@ -1989,9 +2001,9 @@ class ActivityStarter { return; } - final ActivityStack stack = task.getStack(); - if (stack != null && stack.resizeStackWithLaunchBounds()) { - mService.resizeStack(stack.mStackId, bounds, true, !PRESERVE_WINDOWS, ANIMATE, -1); + final int stackId = task.getStackId(); + if (StackId.resizeStackWithLaunchBounds(stackId)) { + mService.resizeStack(stackId, bounds, true, !PRESERVE_WINDOWS, ANIMATE, -1); } else { task.updateOverrideConfiguration(bounds); } @@ -2103,10 +2115,11 @@ class ActivityStarter { } if (stack == null) { // We first try to put the task in the first dynamic stack on home display. - final ActivityDisplay display = mSupervisor.getDefaultDisplay(); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - stack = display.getChildAt(stackNdx); - if (!stack.isOnHomeDisplay()) { + final ArrayList<ActivityStack> homeDisplayStacks = + mSupervisor.getStacksOnDefaultDisplay(); + for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) { + stack = homeDisplayStacks.get(stackNdx); + if (isDynamicStack(stack.mStackId)) { if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: Setting focused stack=" + stack); return stack; @@ -2125,6 +2138,7 @@ class ActivityStarter { private boolean canLaunchIntoFocusedStack(ActivityRecord r, boolean newTask) { final ActivityStack focusedStack = mSupervisor.mFocusedStack; final boolean canUseFocusedStack; + final int focusedStackId = mSupervisor.mFocusedStack.mStackId; if (focusedStack.isActivityTypeAssistant()) { canUseFocusedStack = r.isActivityTypeAssistant(); } else { @@ -2147,7 +2161,7 @@ class ActivityStarter { default: // Dynamic stacks behave similarly to the fullscreen stack and can contain any // resizeable task. - canUseFocusedStack = !focusedStack.isOnHomeDisplay() + canUseFocusedStack = isDynamicStack(focusedStackId) && r.canBeLaunchedOnDisplay(focusedStack.mDisplayId); } } @@ -2163,8 +2177,7 @@ class ActivityStarter { return mReuseTask.getStack(); } - final int vrDisplayId = mPreferredDisplayId == mService.mVr2dDisplayId - ? mPreferredDisplayId : INVALID_DISPLAY; + final int vrDisplayId = mUsingVr2dDisplay ? mPreferredDisplayId : INVALID_DISPLAY; final ActivityStack launchStack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP, vrDisplayId); @@ -2192,7 +2205,7 @@ class ActivityStarter { return mSupervisor.mFocusedStack; } - if (parentStack != null && parentStack.inSplitScreenPrimaryWindowingMode()) { + if (parentStack != null && parentStack.isDockedStack()) { // If parent was in docked stack, the natural place to launch another activity // will be fullscreen, so it can appear alongside the docked window. final int activityType = mSupervisor.resolveActivityType(r, mOptions, task); @@ -2202,8 +2215,8 @@ class ActivityStarter { // If the parent is not in the docked stack, we check if there is docked window // and if yes, we will launch into that stack. If not, we just put the new // activity into parent's stack, because we can't find a better place. - final ActivityStack dockedStack = - mSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack(); + final ActivityStack dockedStack = mSupervisor.getDefaultDisplay().getStack( + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED); if (dockedStack != null && !dockedStack.shouldBeVisible(r)) { // There is a docked stack, but it isn't visible, so we can't launch into that. return null; @@ -2223,8 +2236,8 @@ class ActivityStarter { return newBounds; } - private boolean isLaunchModeOneOf(int mode1, int mode2) { - return mode1 == mLaunchMode || mode2 == mLaunchMode; + void setWindowManager(WindowManagerService wm) { + mWindowManager = wm; } static boolean isDocumentLaunchesIntoExisting(int flags) { @@ -2305,11 +2318,11 @@ class ActivityStarter { } pw.print(prefix); pw.print("mLaunchSingleTop="); - pw.print(LAUNCH_SINGLE_TOP == mLaunchMode); + pw.print(mLaunchSingleTop); pw.print(" mLaunchSingleInstance="); - pw.print(LAUNCH_SINGLE_INSTANCE == mLaunchMode); + pw.print(mLaunchSingleInstance); pw.print(" mLaunchSingleTask="); - pw.println(LAUNCH_SINGLE_TASK == mLaunchMode); + pw.println(mLaunchSingleTask); pw.print(prefix); pw.print("mLaunchFlags=0x"); pw.print(Integer.toHexString(mLaunchFlags)); diff --git a/com/android/server/am/AppTaskImpl.java b/com/android/server/am/AppTaskImpl.java deleted file mode 100644 index a4e2e70e..00000000 --- a/com/android/server/am/AppTaskImpl.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2017 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.am; - -import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS; - -import android.app.ActivityManager; -import android.app.IAppTask; -import android.app.IApplicationThread; -import android.content.Intent; -import android.os.Binder; -import android.os.Bundle; -import android.os.IBinder; -import android.os.UserHandle; - -/** - * An implementation of IAppTask, that allows an app to manage its own tasks via - * {@link android.app.ActivityManager.AppTask}. We keep track of the callingUid to ensure that - * only the process that calls getAppTasks() can call the AppTask methods. - */ -class AppTaskImpl extends IAppTask.Stub { - private ActivityManagerService mService; - - private int mTaskId; - private int mCallingUid; - - public AppTaskImpl(ActivityManagerService service, int taskId, int callingUid) { - mService = service; - mTaskId = taskId; - mCallingUid = callingUid; - } - - private void checkCaller() { - if (mCallingUid != Binder.getCallingUid()) { - throw new SecurityException("Caller " + mCallingUid - + " does not match caller of getAppTasks(): " + Binder.getCallingUid()); - } - } - - @Override - public void finishAndRemoveTask() { - checkCaller(); - - synchronized (mService) { - long origId = Binder.clearCallingIdentity(); - try { - // We remove the task from recents to preserve backwards - if (!mService.mStackSupervisor.removeTaskByIdLocked(mTaskId, false, - REMOVE_FROM_RECENTS)) { - throw new IllegalArgumentException("Unable to find task ID " + mTaskId); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - } - - @Override - public ActivityManager.RecentTaskInfo getTaskInfo() { - checkCaller(); - - synchronized (mService) { - long origId = Binder.clearCallingIdentity(); - try { - TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId); - if (tr == null) { - throw new IllegalArgumentException("Unable to find task ID " + mTaskId); - } - return RecentTasks.createRecentTaskInfo(tr); - } finally { - Binder.restoreCallingIdentity(origId); - } - } - } - - @Override - public void moveToFront() { - checkCaller(); - // Will bring task to front if it already has a root activity. - final long origId = Binder.clearCallingIdentity(); - try { - synchronized (this) { - mService.mStackSupervisor.startActivityFromRecentsInner(mTaskId, null); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - - @Override - public int startActivity(IBinder whoThread, String callingPackage, - Intent intent, String resolvedType, Bundle bOptions) { - checkCaller(); - - int callingUser = UserHandle.getCallingUserId(); - TaskRecord tr; - IApplicationThread appThread; - synchronized (mService) { - tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId); - if (tr == null) { - throw new IllegalArgumentException("Unable to find task ID " + mTaskId); - } - appThread = IApplicationThread.Stub.asInterface(whoThread); - if (appThread == null) { - throw new IllegalArgumentException("Bad app thread " + appThread); - } - } - return mService.mActivityStarter.startActivityMayWait(appThread, -1, callingPackage, - intent, resolvedType, null, null, null, null, 0, 0, null, null, - null, bOptions, false, callingUser, tr, "AppTaskImpl"); - } - - @Override - public void setExcludeFromRecents(boolean exclude) { - checkCaller(); - - synchronized (mService) { - long origId = Binder.clearCallingIdentity(); - try { - TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId); - if (tr == null) { - throw new IllegalArgumentException("Unable to find task ID " + mTaskId); - } - Intent intent = tr.getBaseIntent(); - if (exclude) { - intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - } else { - intent.setFlags(intent.getFlags() - & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - } -}
\ No newline at end of file diff --git a/com/android/server/am/BatteryStatsService.java b/com/android/server/am/BatteryStatsService.java index 68ed9aec..7c9cd00e 100644 --- a/com/android/server/am/BatteryStatsService.java +++ b/com/android/server/am/BatteryStatsService.java @@ -297,12 +297,26 @@ public final class BatteryStatsService extends IBatteryStats.Stub void noteProcessStart(String name, int uid) { synchronized (mStats) { mStats.noteProcessStartLocked(name, uid); + + // TODO: remove this once we figure out properly where and how + // PROCESS_EVENT = 1112 + // KEY_STATE = 1 + // KEY_PACKAGE_NAME: 1002 + // KEY_UID: 2 + StatsLog.writeArray(1112, 1, 1, 1002, name, 2, uid); } } void noteProcessCrash(String name, int uid) { synchronized (mStats) { mStats.noteProcessCrashLocked(name, uid); + + // TODO: remove this once we figure out properly where and how + // PROCESS_EVENT = 1112 + // KEY_STATE = 1 + // KEY_PACKAGE_NAME: 1002 + // KEY_UID: 2 + StatsLog.writeArray(1112, 1, 2, 1002, name, 2, uid); } } @@ -320,9 +334,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub void noteUidProcessState(int uid, int state) { synchronized (mStats) { - // TODO: remove this once we figure out properly where and how - StatsLog.write(StatsLog.PROCESS_STATE_CHANGED, uid, state); - mStats.noteUidProcessStateLocked(uid, state); } } @@ -537,10 +548,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub enforceCallingPermission(); if (DBG) Slog.d(TAG, "begin noteScreenState"); synchronized (mStats) { - // TODO: remove this once we figure out properly where and how - StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, state); - mStats.noteScreenStateLocked(state); + // TODO: remove this once we figure out properly where and how + // SCREEN_EVENT = 2 + // KEY_STATE: 1 + // State value: state. We can change this to our own def later. + StatsLog.writeArray(2, 1, state); } if (DBG) Slog.d(TAG, "end noteScreenState"); } diff --git a/com/android/server/am/BroadcastFilter.java b/com/android/server/am/BroadcastFilter.java index 7ff227f5..f96b06fa 100644 --- a/com/android/server/am/BroadcastFilter.java +++ b/com/android/server/am/BroadcastFilter.java @@ -19,9 +19,6 @@ package com.android.server.am; import android.content.IntentFilter; import android.util.PrintWriterPrinter; import android.util.Printer; -import android.util.proto.ProtoOutputStream; - -import com.android.server.am.proto.BroadcastFilterProto; import java.io.PrintWriter; @@ -47,38 +44,27 @@ final class BroadcastFilter extends IntentFilter { instantApp = _instantApp; visibleToInstantApp = _visibleToInstantApp; } - - public void writeToProto(ProtoOutputStream proto, long fieldId) { - long token = proto.start(fieldId); - super.writeToProto(proto, BroadcastFilterProto.INTENT_FILTER); - if (requiredPermission != null) { - proto.write(BroadcastFilterProto.REQUIRED_PERMISSION, requiredPermission); - } - proto.write(BroadcastFilterProto.HEX_HASH, Integer.toHexString(System.identityHashCode(this))); - proto.write(BroadcastFilterProto.OWNING_USER_ID, owningUserId); - proto.end(token); - } - + public void dump(PrintWriter pw, String prefix) { dumpInReceiverList(pw, new PrintWriterPrinter(pw), prefix); receiverList.dumpLocal(pw, prefix); } - + public void dumpBrief(PrintWriter pw, String prefix) { dumpBroadcastFilterState(pw, prefix); } - + public void dumpInReceiverList(PrintWriter pw, Printer pr, String prefix) { super.dump(pr, prefix); dumpBroadcastFilterState(pw, prefix); } - + void dumpBroadcastFilterState(PrintWriter pw, String prefix) { if (requiredPermission != null) { pw.print(prefix); pw.print("requiredPermission="); pw.println(requiredPermission); } } - + public String toString() { StringBuilder sb = new StringBuilder(); sb.append("BroadcastFilter{"); diff --git a/com/android/server/am/BroadcastQueue.java b/com/android/server/am/BroadcastQueue.java index c62cc38b..d8354549 100644 --- a/com/android/server/am/BroadcastQueue.java +++ b/com/android/server/am/BroadcastQueue.java @@ -51,12 +51,9 @@ import android.os.UserHandle; import android.util.EventLog; import android.util.Slog; import android.util.TimeUtils; -import android.util.proto.ProtoOutputStream; import static com.android.server.am.ActivityManagerDebugConfig.*; -import com.android.server.am.proto.BroadcastQueueProto; - /** * BROADCASTS * @@ -1588,55 +1585,6 @@ public final class BroadcastQueue { && (mPendingBroadcast == null); } - void writeToProto(ProtoOutputStream proto, long fieldId) { - long token = proto.start(fieldId); - proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName); - int N; - N = mParallelBroadcasts.size(); - for (int i = N - 1; i >= 0; i--) { - mParallelBroadcasts.get(i).writeToProto(proto, BroadcastQueueProto.PARALLEL_BROADCASTS); - } - N = mOrderedBroadcasts.size(); - for (int i = N - 1; i >= 0; i--) { - mOrderedBroadcasts.get(i).writeToProto(proto, BroadcastQueueProto.ORDERED_BROADCASTS); - } - if (mPendingBroadcast != null) { - mPendingBroadcast.writeToProto(proto, BroadcastQueueProto.PENDING_BROADCAST); - } - - int lastIndex = mHistoryNext; - int ringIndex = lastIndex; - do { - // increasing index = more recent entry, and we want to print the most - // recent first and work backwards, so we roll through the ring backwards. - ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY); - BroadcastRecord r = mBroadcastHistory[ringIndex]; - if (r != null) { - r.writeToProto(proto, BroadcastQueueProto.HISTORICAL_BROADCASTS); - } - } while (ringIndex != lastIndex); - - lastIndex = ringIndex = mSummaryHistoryNext; - do { - ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY); - Intent intent = mBroadcastSummaryHistory[ringIndex]; - if (intent == null) { - continue; - } - long summaryToken = proto.start(BroadcastQueueProto.HISTORICAL_BROADCASTS_SUMMARY); - intent.writeToProto(proto, BroadcastQueueProto.BroadcastSummary.INTENT, - false, true, true, false); - proto.write(BroadcastQueueProto.BroadcastSummary.ENQUEUE_CLOCK_TIME_MS, - mSummaryHistoryEnqueueTime[ringIndex]); - proto.write(BroadcastQueueProto.BroadcastSummary.DISPATCH_CLOCK_TIME_MS, - mSummaryHistoryDispatchTime[ringIndex]); - proto.write(BroadcastQueueProto.BroadcastSummary.FINISH_CLOCK_TIME_MS, - mSummaryHistoryFinishTime[ringIndex]); - proto.end(summaryToken); - } while (ringIndex != lastIndex); - proto.end(token); - } - final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage, boolean needSep) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); diff --git a/com/android/server/am/BroadcastRecord.java b/com/android/server/am/BroadcastRecord.java index 5b3b2a8e..6bc0744f 100644 --- a/com/android/server/am/BroadcastRecord.java +++ b/com/android/server/am/BroadcastRecord.java @@ -30,9 +30,6 @@ import android.os.SystemClock; import android.os.UserHandle; import android.util.PrintWriterPrinter; import android.util.TimeUtils; -import android.util.proto.ProtoOutputStream; - -import com.android.server.am.proto.BroadcastRecordProto; import java.io.PrintWriter; import java.text.SimpleDateFormat; @@ -334,17 +331,9 @@ final class BroadcastRecord extends Binder { return didSomething; } - @Override public String toString() { return "BroadcastRecord{" + Integer.toHexString(System.identityHashCode(this)) + " u" + userId + " " + intent.getAction() + "}"; } - - public void writeToProto(ProtoOutputStream proto, long fieldId) { - long token = proto.start(fieldId); - proto.write(BroadcastRecordProto.USER_ID, userId); - proto.write(BroadcastRecordProto.INTENT_ACTION, intent.getAction()); - proto.end(token); - } } diff --git a/com/android/server/am/KeyguardController.java b/com/android/server/am/KeyguardController.java index c3fed171..5c48f90d 100644 --- a/com/android/server/am/KeyguardController.java +++ b/com/android/server/am/KeyguardController.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; @@ -46,6 +47,7 @@ import com.android.internal.policy.IKeyguardDismissCallback; import com.android.server.wm.WindowManagerService; import java.io.PrintWriter; +import java.util.ArrayList; /** * Controls Keyguard occluding, dismissing and transitions depending on what kind of activities are @@ -236,9 +238,9 @@ class KeyguardController { final ActivityRecord lastDismissingKeyguardActivity = mDismissingKeyguardActivity; mOccluded = false; mDismissingKeyguardActivity = null; - final ActivityDisplay display = mStackSupervisor.getDefaultDisplay(); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ArrayList<ActivityStack> stacks = mStackSupervisor.getStacksOnDefaultDisplay(); + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); // Only the focused stack top activity may control occluded state if (mStackSupervisor.isFocusedStack(stack)) { @@ -340,7 +342,7 @@ class KeyguardController { // show on top of the lock screen. In this can we want to dismiss the docked // stack since it will be complicated/risky to try to put the activity on top // of the lock screen in the right fullscreen configuration. - final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack(); + final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getSplitScreenStack(); if (stack == null) { return; } diff --git a/com/android/server/am/LaunchingTaskPositioner.java b/com/android/server/am/LaunchingTaskPositioner.java index 0dc73e98..d6523418 100644 --- a/com/android/server/am/LaunchingTaskPositioner.java +++ b/com/android/server/am/LaunchingTaskPositioner.java @@ -24,8 +24,8 @@ import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; import android.util.Slog; +import android.view.Display; import android.view.Gravity; -import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; @@ -64,11 +64,43 @@ class LaunchingTaskPositioner { private static final int SHIFT_POLICY_HORIZONTAL_RIGHT = 2; private static final int SHIFT_POLICY_HORIZONTAL_LEFT = 3; + private boolean mDefaultStartBoundsConfigurationSet = false; private final Rect mAvailableRect = new Rect(); private final Rect mTmpProposal = new Rect(); private final Rect mTmpOriginal = new Rect(); - private final Point mDisplaySize = new Point(); + private int mDefaultFreeformStartX; + private int mDefaultFreeformStartY; + private int mDefaultFreeformWidth; + private int mDefaultFreeformHeight; + private int mDefaultFreeformStepHorizontal; + private int mDefaultFreeformStepVertical; + private int mDisplayWidth; + private int mDisplayHeight; + + void setDisplay(Display display) { + Point size = new Point(); + display.getSize(size); + mDisplayWidth = size.x; + mDisplayHeight = size.y; + } + + void configure(Rect stackBounds) { + if (stackBounds == null) { + mAvailableRect.set(0, 0, mDisplayWidth, mDisplayHeight); + } else { + mAvailableRect.set(stackBounds); + } + int width = mAvailableRect.width(); + int height = mAvailableRect.height(); + mDefaultFreeformStartX = mAvailableRect.left + width / MARGIN_SIZE_DENOMINATOR; + mDefaultFreeformStartY = mAvailableRect.top + height / MARGIN_SIZE_DENOMINATOR; + mDefaultFreeformWidth = width / WINDOW_SIZE_DENOMINATOR; + mDefaultFreeformHeight = height / WINDOW_SIZE_DENOMINATOR; + mDefaultFreeformStepHorizontal = Math.max(width / STEP_DENOMINATOR, MINIMAL_STEP); + mDefaultFreeformStepVertical = Math.max(height / STEP_DENOMINATOR, MINIMAL_STEP); + mDefaultStartBoundsConfigurationSet = true; + } /** * Tries to set task's bound in a way that it won't collide with any other task. By colliding @@ -82,154 +114,104 @@ class LaunchingTaskPositioner { */ void updateDefaultBounds(TaskRecord task, ArrayList<TaskRecord> tasks, @Nullable ActivityInfo.WindowLayout windowLayout) { - updateAvailableRect(task, mAvailableRect); - + if (!mDefaultStartBoundsConfigurationSet) { + return; + } if (windowLayout == null) { - positionCenter(task, tasks, mAvailableRect, getFreeformWidth(mAvailableRect), - getFreeformHeight(mAvailableRect)); + positionCenter(task, tasks, mDefaultFreeformWidth, mDefaultFreeformHeight); return; } - int width = getFinalWidth(windowLayout, mAvailableRect); - int height = getFinalHeight(windowLayout, mAvailableRect); + int width = getFinalWidth(windowLayout); + int height = getFinalHeight(windowLayout); int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK; int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; if (verticalGravity == Gravity.TOP) { if (horizontalGravity == Gravity.RIGHT) { - positionTopRight(task, tasks, mAvailableRect, width, height); + positionTopRight(task, tasks, width, height); } else { - positionTopLeft(task, tasks, mAvailableRect, width, height); + positionTopLeft(task, tasks, width, height); } } else if (verticalGravity == Gravity.BOTTOM) { if (horizontalGravity == Gravity.RIGHT) { - positionBottomRight(task, tasks, mAvailableRect, width, height); + positionBottomRight(task, tasks, width, height); } else { - positionBottomLeft(task, tasks, mAvailableRect, width, height); + positionBottomLeft(task, tasks, width, height); } } else { // Some fancy gravity setting that we don't support yet. We just put the activity in the // center. Slog.w(TAG, "Received unsupported gravity: " + windowLayout.gravity + ", positioning in the center instead."); - positionCenter(task, tasks, mAvailableRect, width, height); + positionCenter(task, tasks, width, height); } } - private void updateAvailableRect(TaskRecord task, Rect availableRect) { - final Rect stackBounds = task.getStack().mBounds; - - if (stackBounds != null) { - availableRect.set(stackBounds); - } else { - task.getStack().getDisplay().mDisplay.getSize(mDisplaySize); - availableRect.set(0, 0, mDisplaySize.x, mDisplaySize.y); - } - } - - @VisibleForTesting - static int getFreeformStartLeft(Rect bounds) { - return bounds.left + bounds.width() / MARGIN_SIZE_DENOMINATOR; - } - - @VisibleForTesting - static int getFreeformStartTop(Rect bounds) { - return bounds.top + bounds.height() / MARGIN_SIZE_DENOMINATOR; - } - - @VisibleForTesting - static int getFreeformWidth(Rect bounds) { - return bounds.width() / WINDOW_SIZE_DENOMINATOR; - } - - @VisibleForTesting - static int getFreeformHeight(Rect bounds) { - return bounds.height() / WINDOW_SIZE_DENOMINATOR; - } - - @VisibleForTesting - static int getHorizontalStep(Rect bounds) { - return Math.max(bounds.width() / STEP_DENOMINATOR, MINIMAL_STEP); - } - - @VisibleForTesting - static int getVerticalStep(Rect bounds) { - return Math.max(bounds.height() / STEP_DENOMINATOR, MINIMAL_STEP); - } - - - - private int getFinalWidth(ActivityInfo.WindowLayout windowLayout, Rect availableRect) { - int width = getFreeformWidth(availableRect); + private int getFinalWidth(ActivityInfo.WindowLayout windowLayout) { + int width = mDefaultFreeformWidth; if (windowLayout.width > 0) { width = windowLayout.width; } if (windowLayout.widthFraction > 0) { - width = (int) (availableRect.width() * windowLayout.widthFraction); + width = (int) (mAvailableRect.width() * windowLayout.widthFraction); } return width; } - private int getFinalHeight(ActivityInfo.WindowLayout windowLayout, Rect availableRect) { - int height = getFreeformHeight(availableRect); + private int getFinalHeight(ActivityInfo.WindowLayout windowLayout) { + int height = mDefaultFreeformHeight; if (windowLayout.height > 0) { height = windowLayout.height; } if (windowLayout.heightFraction > 0) { - height = (int) (availableRect.height() * windowLayout.heightFraction); + height = (int) (mAvailableRect.height() * windowLayout.heightFraction); } return height; } - private void positionBottomLeft(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { - mTmpProposal.set(availableRect.left, availableRect.bottom - height, - availableRect.left + width, availableRect.bottom); - position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_RIGHT); + private void positionBottomLeft(TaskRecord task, ArrayList<TaskRecord> tasks, int width, + int height) { + mTmpProposal.set(mAvailableRect.left, mAvailableRect.bottom - height, + mAvailableRect.left + width, mAvailableRect.bottom); + position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT); } - private void positionBottomRight(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { - mTmpProposal.set(availableRect.right - width, availableRect.bottom - height, - availableRect.right, availableRect.bottom); - position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_LEFT); + private void positionBottomRight(TaskRecord task, ArrayList<TaskRecord> tasks, int width, + int height) { + mTmpProposal.set(mAvailableRect.right - width, mAvailableRect.bottom - height, + mAvailableRect.right, mAvailableRect.bottom); + position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT); } - private void positionTopLeft(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { - mTmpProposal.set(availableRect.left, availableRect.top, - availableRect.left + width, availableRect.top + height); - position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_RIGHT); + private void positionTopLeft(TaskRecord task, ArrayList<TaskRecord> tasks, int width, + int height) { + mTmpProposal.set(mAvailableRect.left, mAvailableRect.top, + mAvailableRect.left + width, mAvailableRect.top + height); + position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT); } - private void positionTopRight(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { - mTmpProposal.set(availableRect.right - width, availableRect.top, - availableRect.right, availableRect.top + height); - position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_LEFT); + private void positionTopRight(TaskRecord task, ArrayList<TaskRecord> tasks, int width, + int height) { + mTmpProposal.set(mAvailableRect.right - width, mAvailableRect.top, + mAvailableRect.right, mAvailableRect.top + height); + position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT); } - private void positionCenter(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { - final int defaultFreeformLeft = getFreeformStartLeft(availableRect); - final int defaultFreeformTop = getFreeformStartTop(availableRect); - mTmpProposal.set(defaultFreeformLeft, defaultFreeformTop, - defaultFreeformLeft + width, defaultFreeformTop + height); - position(task, tasks, availableRect, mTmpProposal, ALLOW_RESTART, - SHIFT_POLICY_DIAGONAL_DOWN); + private void positionCenter(TaskRecord task, ArrayList<TaskRecord> tasks, int width, + int height) { + mTmpProposal.set(mDefaultFreeformStartX, mDefaultFreeformStartY, + mDefaultFreeformStartX + width, mDefaultFreeformStartY + height); + position(task, tasks, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN); } - private void position(TaskRecord task, ArrayList<TaskRecord> tasks, Rect availableRect, - Rect proposal, boolean allowRestart, int shiftPolicy) { + private void position(TaskRecord task, ArrayList<TaskRecord> tasks, Rect proposal, + boolean allowRestart, int shiftPolicy) { mTmpOriginal.set(proposal); boolean restarted = false; while (boundsConflict(proposal, tasks)) { // Unfortunately there is already a task at that spot, so we need to look for some // other place. - shiftStartingPoint(proposal, availableRect, shiftPolicy); - if (shiftedTooFar(proposal, availableRect, shiftPolicy)) { + shiftStartingPoint(proposal, shiftPolicy); + if (shiftedToFar(proposal, shiftPolicy)) { // We don't want the task to go outside of the stack, because it won't look // nice. Depending on the starting point we either restart, or immediately give up. if (!allowRestart) { @@ -238,13 +220,13 @@ class LaunchingTaskPositioner { } // We must have started not from the top. Let's restart from there because there // might be some space there. - proposal.set(availableRect.left, availableRect.top, - availableRect.left + proposal.width(), - availableRect.top + proposal.height()); + proposal.set(mAvailableRect.left, mAvailableRect.top, + mAvailableRect.left + proposal.width(), + mAvailableRect.top + proposal.height()); restarted = true; } - if (restarted && (proposal.left > getFreeformStartLeft(availableRect) - || proposal.top > getFreeformStartTop(availableRect))) { + if (restarted && (proposal.left > mDefaultFreeformStartX + || proposal.top > mDefaultFreeformStartY)) { // If we restarted and crossed the initial position, let's not struggle anymore. // The user already must have ton of tasks visible, we can just smack the new // one in the center. @@ -255,30 +237,27 @@ class LaunchingTaskPositioner { task.updateOverrideConfiguration(proposal); } - private boolean shiftedTooFar(Rect start, Rect availableRect, int shiftPolicy) { + private boolean shiftedToFar(Rect start, int shiftPolicy) { switch (shiftPolicy) { case SHIFT_POLICY_HORIZONTAL_LEFT: - return start.left < availableRect.left; + return start.left < mAvailableRect.left; case SHIFT_POLICY_HORIZONTAL_RIGHT: - return start.right > availableRect.right; + return start.right > mAvailableRect.right; default: // SHIFT_POLICY_DIAGONAL_DOWN - return start.right > availableRect.right || start.bottom > availableRect.bottom; + return start.right > mAvailableRect.right || start.bottom > mAvailableRect.bottom; } } - private void shiftStartingPoint(Rect posposal, Rect availableRect, int shiftPolicy) { - final int defaultFreeformStepHorizontal = getHorizontalStep(availableRect); - final int defaultFreeformStepVertical = getVerticalStep(availableRect); - + private void shiftStartingPoint(Rect posposal, int shiftPolicy) { switch (shiftPolicy) { case SHIFT_POLICY_HORIZONTAL_LEFT: - posposal.offset(-defaultFreeformStepHorizontal, 0); + posposal.offset(-mDefaultFreeformStepHorizontal, 0); break; case SHIFT_POLICY_HORIZONTAL_RIGHT: - posposal.offset(defaultFreeformStepHorizontal, 0); + posposal.offset(mDefaultFreeformStepHorizontal, 0); break; default: // SHIFT_POLICY_DIAGONAL_DOWN: - posposal.offset(defaultFreeformStepHorizontal, defaultFreeformStepVertical); + posposal.offset(mDefaultFreeformStepHorizontal, mDefaultFreeformStepVertical); break; } } @@ -317,4 +296,8 @@ class LaunchingTaskPositioner { return Math.abs(first.right - second.right) < BOUNDS_CONFLICT_MIN_DISTANCE && Math.abs(first.bottom - second.bottom) < BOUNDS_CONFLICT_MIN_DISTANCE; } + + void reset() { + mDefaultStartBoundsConfigurationSet = false; + } } diff --git a/com/android/server/am/LockTaskController.java b/com/android/server/am/LockTaskController.java index 940f9051..72b5de88 100644 --- a/com/android/server/am/LockTaskController.java +++ b/com/android/server/am/LockTaskController.java @@ -19,6 +19,7 @@ package com.android.server.am; import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.StatusBarManager.DISABLE_BACK; import static android.app.StatusBarManager.DISABLE_HOME; import static android.app.StatusBarManager.DISABLE_MASK; @@ -58,6 +59,7 @@ import android.provider.Settings; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.widget.LockPatternUtils; @@ -431,7 +433,7 @@ public class LockTaskController { mWindowManager.executeAppTransition(); } else if (lockTaskModeState != LOCK_TASK_MODE_NONE) { mSupervisor.handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED, - DEFAULT_DISPLAY, task.getStack(), true /* forceNonResizable */); + DEFAULT_DISPLAY, task.getStackId(), true /* forceNonResizable */); } } @@ -492,7 +494,11 @@ public class LockTaskController { } for (int displayNdx = mSupervisor.getChildCount() - 1; displayNdx >= 0; --displayNdx) { - mSupervisor.getChildAt(displayNdx).onLockTaskPackagesUpdated(); + ArrayList<ActivityStack> stacks = mSupervisor.getChildAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + stack.onLockTaskPackagesUpdatedLocked(); + } } final ActivityRecord r = mSupervisor.topRunningActivityLocked(); diff --git a/com/android/server/am/ProcessRecord.java b/com/android/server/am/ProcessRecord.java index e8477231..0e318d9c 100644 --- a/com/android/server/am/ProcessRecord.java +++ b/com/android/server/am/ProcessRecord.java @@ -27,7 +27,6 @@ import android.util.Slog; import com.android.internal.app.procstats.ProcessStats; import com.android.internal.app.procstats.ProcessState; import com.android.internal.os.BatteryStatsImpl; -import com.android.server.am.proto.ProcessRecordProto; import android.app.ActivityManager; import android.app.Dialog; @@ -45,7 +44,6 @@ import android.os.Trace; import android.os.UserHandle; import android.util.ArrayMap; import android.util.TimeUtils; -import android.util.proto.ProtoOutputStream; import java.io.PrintWriter; import java.util.ArrayList; @@ -623,22 +621,6 @@ final class ProcessRecord { } } - public void writeToProto(ProtoOutputStream proto, long fieldId) { - long token = proto.start(fieldId); - proto.write(ProcessRecordProto.PID, pid); - proto.write(ProcessRecordProto.PROCESS_NAME, processName); - if (info.uid < Process.FIRST_APPLICATION_UID) { - proto.write(ProcessRecordProto.UID, uid); - } else { - proto.write(ProcessRecordProto.USER_ID, userId); - proto.write(ProcessRecordProto.APP_ID, UserHandle.getAppId(info.uid)); - if (uid != info.uid) { - proto.write(ProcessRecordProto.ISOLATED_APP_ID, UserHandle.getAppId(uid)); - } - } - proto.end(token); - } - public String toShortString() { if (shortStringName != null) { return shortStringName; diff --git a/com/android/server/am/ReceiverList.java b/com/android/server/am/ReceiverList.java index a9890631..6ade7361 100644 --- a/com/android/server/am/ReceiverList.java +++ b/com/android/server/am/ReceiverList.java @@ -21,8 +21,6 @@ import android.os.Binder; import android.os.IBinder; import android.util.PrintWriterPrinter; import android.util.Printer; -import android.util.proto.ProtoOutputStream; -import com.android.server.am.proto.ReceiverListProto; import java.io.PrintWriter; import java.util.ArrayList; @@ -43,7 +41,7 @@ final class ReceiverList extends ArrayList<BroadcastFilter> boolean linkedToDeath = false; String stringName; - + ReceiverList(ActivityManagerService _owner, ProcessRecord _app, int _pid, int _uid, int _userId, IIntentReceiver _receiver) { owner = _owner; @@ -61,31 +59,12 @@ final class ReceiverList extends ArrayList<BroadcastFilter> public int hashCode() { return System.identityHashCode(this); } - + public void binderDied() { linkedToDeath = false; owner.unregisterReceiver(receiver); } - - void writeToProto(ProtoOutputStream proto, long fieldId) { - long token = proto.start(fieldId); - app.writeToProto(proto, ReceiverListProto.APP); - proto.write(ReceiverListProto.PID, pid); - proto.write(ReceiverListProto.UID, uid); - proto.write(ReceiverListProto.USER, userId); - if (curBroadcast != null) { - curBroadcast.writeToProto(proto, ReceiverListProto.CURRENT); - } - proto.write(ReceiverListProto.LINKED_TO_DEATH, linkedToDeath); - final int N = size(); - for (int i=0; i<N; i++) { - BroadcastFilter bf = get(i); - bf.writeToProto(proto, ReceiverListProto.FILTERS); - } - proto.write(ReceiverListProto.HEX_HASH, Integer.toHexString(System.identityHashCode(this))); - proto.end(token); - } - + void dumpLocal(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("app="); pw.print(app != null ? app.toShortString() : null); pw.print(" pid="); pw.print(pid); pw.print(" uid="); pw.print(uid); @@ -95,7 +74,7 @@ final class ReceiverList extends ArrayList<BroadcastFilter> pw.print(" linkedToDeath="); pw.println(linkedToDeath); } } - + void dump(PrintWriter pw, String prefix) { Printer pr = new PrintWriterPrinter(pw); dumpLocal(pw, prefix); @@ -110,7 +89,7 @@ final class ReceiverList extends ArrayList<BroadcastFilter> bf.dumpInReceiverList(pw, pr, p2); } } - + public String toString() { if (stringName != null) { return stringName; diff --git a/com/android/server/am/RecentTasks.java b/com/android/server/am/RecentTasks.java index 78274bdc..365c5b1d 100644 --- a/com/android/server/am/RecentTasks.java +++ b/com/android/server/am/RecentTasks.java @@ -16,25 +16,15 @@ package com.android.server.am; -import static android.app.ActivityManager.FLAG_AND_UNLOCKED; -import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; -import static android.app.ActivityManager.RECENT_WITH_EXCLUDED; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; - import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS_TRIM_TASKS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS; import static com.android.server.am.TaskRecord.INVALID_TASK_ID; import com.google.android.collect.Sets; @@ -47,87 +37,43 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; -import android.content.pm.ParceledListSlice; -import android.content.pm.UserInfo; -import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Rect; -import android.os.Bundle; import android.os.Environment; -import android.os.IBinder; import android.os.RemoteException; -import android.os.SystemProperties; import android.os.UserHandle; import android.util.ArraySet; -import android.util.MutableBoolean; -import android.util.MutableInt; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.am.ActivityStack.ActivityState; - import java.io.File; -import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.Set; -import java.util.concurrent.TimeUnit; /** - * Class for managing the recent tasks list. The list is ordered by most recent (index 0) to the - * least recent. + * Class for managing the recent tasks list. */ -class RecentTasks { +class RecentTasks extends ArrayList<TaskRecord> { private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM; private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; private static final String TAG_TASKS = TAG + POSTFIX_TASKS; - private static final boolean TRIMMED = true; + // Maximum number recent bitmaps to keep in memory. + private static final int MAX_RECENT_BITMAPS = 3; private static final int DEFAULT_INITIAL_CAPACITY = 5; // Whether or not to move all affiliated tasks to the front when one of the tasks is launched private static final boolean MOVE_AFFILIATED_TASKS_TO_FRONT = false; - // Comparator to sort by taskId - private static final Comparator<TaskRecord> TASK_ID_COMPARATOR = - (lhs, rhs) -> rhs.taskId - lhs.taskId; - - // Placeholder variables to keep track of activities/apps that are no longer avialble while - // iterating through the recents list - private static final ActivityInfo NO_ACTIVITY_INFO_TOKEN = new ActivityInfo(); - private static final ApplicationInfo NO_APPLICATION_INFO_TOKEN = new ApplicationInfo(); - - /** - * Callbacks made when manipulating the list. - */ - interface Callbacks { - /** - * Called when a task is added to the recent tasks list. - */ - void onRecentTaskAdded(TaskRecord task); - - /** - * Called when a task is removed from the recent tasks list. - */ - void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed); - } - /** * Save recent tasks information across reboots. */ private final TaskPersister mTaskPersister; private final ActivityManagerService mService; - private final UserController mUserController; - - /** - * Mapping of user id -> whether recent tasks have been loaded for that user. - */ private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray( DEFAULT_INITIAL_CAPACITY); @@ -135,106 +81,21 @@ class RecentTasks { * Stores for each user task ids that are taken by tasks residing in persistent storage. These * tasks may or may not currently be in memory. */ - private final SparseArray<SparseBooleanArray> mPersistedTaskIds = new SparseArray<>( + final SparseArray<SparseBooleanArray> mPersistedTaskIds = new SparseArray<>( DEFAULT_INITIAL_CAPACITY); - // List of all active recent tasks - private final ArrayList<TaskRecord> mTasks = new ArrayList<>(); - private final ArrayList<Callbacks> mCallbacks = new ArrayList<>(); - - // These values are generally loaded from resources, but can be set dynamically in the tests - private boolean mHasVisibleRecentTasks; - private int mGlobalMaxNumTasks; - private int mMinNumVisibleTasks; - private int mMaxNumVisibleTasks; - private long mActiveTasksSessionDurationMs; - // Mainly to avoid object recreation on multiple calls. - private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<>(); + private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>(); private final HashMap<ComponentName, ActivityInfo> mTmpAvailActCache = new HashMap<>(); private final HashMap<String, ApplicationInfo> mTmpAvailAppCache = new HashMap<>(); - private final SparseBooleanArray mTmpQuietProfileUserIds = new SparseBooleanArray(); + private final ActivityInfo mTmpActivityInfo = new ActivityInfo(); + private final ApplicationInfo mTmpAppInfo = new ApplicationInfo(); - @VisibleForTesting - RecentTasks(ActivityManagerService service, TaskPersister taskPersister, - UserController userController) { + RecentTasks(ActivityManagerService service, ActivityStackSupervisor mStackSupervisor) { + File systemDir = Environment.getDataSystemDirectory(); mService = service; - mUserController = userController; - mTaskPersister = taskPersister; - mGlobalMaxNumTasks = ActivityManager.getMaxRecentTasksStatic(); - mHasVisibleRecentTasks = true; - } - - RecentTasks(ActivityManagerService service, ActivityStackSupervisor stackSupervisor) { - final File systemDir = Environment.getDataSystemDirectory(); - final Resources res = service.mContext.getResources(); - mService = service; - mUserController = service.mUserController; - mTaskPersister = new TaskPersister(systemDir, stackSupervisor, service, this); - mGlobalMaxNumTasks = ActivityManager.getMaxRecentTasksStatic(); - mHasVisibleRecentTasks = res.getBoolean(com.android.internal.R.bool.config_hasRecents); - loadParametersFromResources(service.mContext.getResources()); - } - - @VisibleForTesting - void setParameters(int minNumVisibleTasks, int maxNumVisibleTasks, - long activeSessionDurationMs) { - mMinNumVisibleTasks = minNumVisibleTasks; - mMaxNumVisibleTasks = maxNumVisibleTasks; - mActiveTasksSessionDurationMs = activeSessionDurationMs; - } - - @VisibleForTesting - void setGlobalMaxNumTasks(int globalMaxNumTasks) { - mGlobalMaxNumTasks = globalMaxNumTasks; - } - - /** - * Loads the parameters from the system resources. - */ - @VisibleForTesting - void loadParametersFromResources(Resources res) { - if (ActivityManager.isLowRamDeviceStatic()) { - mMinNumVisibleTasks = res.getInteger( - com.android.internal.R.integer.config_minNumVisibleRecentTasks_lowRam); - mMaxNumVisibleTasks = res.getInteger( - com.android.internal.R.integer.config_maxNumVisibleRecentTasks_lowRam); - } else if (SystemProperties.getBoolean("ro.recents.grid", false)) { - mMinNumVisibleTasks = res.getInteger( - com.android.internal.R.integer.config_minNumVisibleRecentTasks_grid); - mMaxNumVisibleTasks = res.getInteger( - com.android.internal.R.integer.config_maxNumVisibleRecentTasks_grid); - } else { - mMinNumVisibleTasks = res.getInteger( - com.android.internal.R.integer.config_minNumVisibleRecentTasks); - mMaxNumVisibleTasks = res.getInteger( - com.android.internal.R.integer.config_maxNumVisibleRecentTasks); - } - final int sessionDurationHrs = res.getInteger( - com.android.internal.R.integer.config_activeTaskDurationHours); - mActiveTasksSessionDurationMs = (sessionDurationHrs > 0) - ? TimeUnit.HOURS.toMillis(sessionDurationHrs) - : -1; - } - - void registerCallback(Callbacks callback) { - mCallbacks.add(callback); - } - - void unregisterCallback(Callbacks callback) { - mCallbacks.remove(callback); - } - - private void notifyTaskAdded(TaskRecord task) { - for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onRecentTaskAdded(task); - } - } - - private void notifyTaskRemoved(TaskRecord task, boolean wasTrimmed) { - for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onRecentTaskRemoved(task, wasTrimmed); - } + mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, service, this); + mStackSupervisor.setRecentTasks(this); } /** @@ -245,7 +106,6 @@ class RecentTasks { */ void loadUserRecentsLocked(int userId) { if (mUsersWithRecentsLoaded.get(userId)) { - // User already loaded, return early return; } @@ -254,14 +114,14 @@ class RecentTasks { // Check if any tasks are added before recents is loaded final SparseBooleanArray preaddedTasks = new SparseBooleanArray(); - for (final TaskRecord task : mTasks) { + for (final TaskRecord task : this) { if (task.userId == userId && shouldPersistTaskLocked(task)) { preaddedTasks.put(task.taskId, true); } } Slog.i(TAG, "Loading recents for user " + userId + " into memory."); - mTasks.addAll(mTaskPersister.restoreTasksForUserLocked(userId, preaddedTasks)); + addAll(mTaskPersister.restoreTasksForUserLocked(userId, preaddedTasks)); cleanupLocked(userId); mUsersWithRecentsLoaded.put(userId, true); @@ -280,25 +140,11 @@ class RecentTasks { } } - /** - * @return whether the {@param taskId} is currently in use for the given user. - */ - boolean containsTaskId(int taskId, int userId) { + boolean taskIdTakenForUserLocked(int taskId, int userId) { loadPersistedTaskIdsForUserLocked(userId); return mPersistedTaskIds.get(userId).get(taskId); } - /** - * @return all the task ids for the user with the given {@param userId}. - */ - SparseBooleanArray getTaskIdsForUser(int userId) { - loadPersistedTaskIdsForUserLocked(userId); - return mPersistedTaskIds.get(userId); - } - - /** - * Kicks off the task persister to write any pending tasks to disk. - */ void notifyTaskPersisterLocked(TaskRecord task, boolean flush) { final ActivityStack stack = task != null ? task.getStack() : null; if (stack != null && stack.isHomeOrRecentsStack()) { @@ -318,8 +164,8 @@ class RecentTasks { mPersistedTaskIds.valueAt(i).clear(); } } - for (int i = mTasks.size() - 1; i >= 0; i--) { - final TaskRecord task = mTasks.get(i); + for (int i = size() - 1; i >= 0; i--) { + final TaskRecord task = get(i); if (shouldPersistTaskLocked(task)) { // Set of persisted taskIds for task.userId should not be null here // TODO Investigate why it can happen. For now initialize with an empty set @@ -334,12 +180,12 @@ class RecentTasks { } private static boolean shouldPersistTaskLocked(TaskRecord task) { - final ActivityStack stack = task.getStack(); + final ActivityStack<?> stack = task.getStack(); return task.isPersistable && (stack == null || !stack.isHomeOrRecentsStack()); } void onSystemReadyLocked() { - mTasks.clear(); + clear(); mTaskPersister.startPersisting(); } @@ -379,6 +225,14 @@ class RecentTasks { return usersWithRecentsLoaded; } + private void unloadUserRecentsLocked(int userId) { + if (mUsersWithRecentsLoaded.get(userId)) { + Slog.i(TAG, "Unloading recents for user " + userId + " from memory."); + mUsersWithRecentsLoaded.delete(userId); + removeTasksForUserLocked(userId); + } + } + /** * Removes recent tasks and any other state kept in memory for the passed in user. Does not * touch the information present on persistent storage. @@ -386,36 +240,44 @@ class RecentTasks { * @param userId the id of the user */ void unloadUserDataFromMemoryLocked(int userId) { - if (mUsersWithRecentsLoaded.get(userId)) { - Slog.i(TAG, "Unloading recents for user " + userId + " from memory."); - mUsersWithRecentsLoaded.delete(userId); - removeTasksForUserLocked(userId); - } + unloadUserRecentsLocked(userId); mPersistedTaskIds.delete(userId); mTaskPersister.unloadUserDataFromMemory(userId); } + TaskRecord taskForIdLocked(int id) { + final int recentsCount = size(); + for (int i = 0; i < recentsCount; i++) { + TaskRecord tr = get(i); + if (tr.taskId == id) { + return tr; + } + } + return null; + } + /** Remove recent tasks for a user. */ - private void removeTasksForUserLocked(int userId) { + void removeTasksForUserLocked(int userId) { if(userId <= 0) { Slog.i(TAG, "Can't remove recent task on user " + userId); return; } - for (int i = mTasks.size() - 1; i >= 0; --i) { - TaskRecord tr = mTasks.get(i); + for (int i = size() - 1; i >= 0; --i) { + TaskRecord tr = get(i); if (tr.userId == userId) { if(DEBUG_TASKS) Slog.i(TAG_TASKS, "remove RecentTask " + tr + " when finishing user" + userId); - remove(mTasks.get(i)); + remove(i); + tr.removedFromRecents(); } } } void onPackagesSuspendedChanged(String[] packages, boolean suspended, int userId) { final Set<String> packageNames = Sets.newHashSet(packages); - for (int i = mTasks.size() - 1; i >= 0; --i) { - final TaskRecord tr = mTasks.get(i); + for (int i = size() - 1; i >= 0; --i) { + final TaskRecord tr = get(i); if (tr.realActivity != null && packageNames.contains(tr.realActivity.getPackageName()) && tr.userId == userId @@ -424,36 +286,7 @@ class RecentTasks { notifyTaskPersisterLocked(tr, false); } } - } - - void removeTasksByPackageName(String packageName, int userId) { - for (int i = mTasks.size() - 1; i >= 0; --i) { - final TaskRecord tr = mTasks.get(i); - final String taskPackageName = - tr.getBaseIntent().getComponent().getPackageName(); - if (tr.userId != userId) return; - if (!taskPackageName.equals(packageName)) return; - - mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS); - } - } - - void cleanupDisabledPackageTasksLocked(String packageName, Set<String> filterByClasses, - int userId) { - for (int i = mTasks.size() - 1; i >= 0; --i) { - final TaskRecord tr = mTasks.get(i); - if (userId != UserHandle.USER_ALL && tr.userId != userId) { - continue; - } - ComponentName cn = tr.intent.getComponent(); - final boolean sameComponent = cn != null && cn.getPackageName().equals(packageName) - && (filterByClasses == null || filterByClasses.contains(cn.getClassName())); - if (sameComponent) { - mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, - REMOVE_FROM_RECENTS); - } - } } /** @@ -462,28 +295,24 @@ class RecentTasks { * of affiliations. */ void cleanupLocked(int userId) { - int recentsCount = mTasks.size(); + int recentsCount = size(); if (recentsCount == 0) { // Happens when called from the packagemanager broadcast before boot, // or just any empty list. return; } - // Clear the temp lists - mTmpAvailActCache.clear(); - mTmpAvailAppCache.clear(); - final IPackageManager pm = AppGlobals.getPackageManager(); for (int i = recentsCount - 1; i >= 0; i--) { - final TaskRecord task = mTasks.get(i); + final TaskRecord task = get(i); if (userId != UserHandle.USER_ALL && task.userId != userId) { // Only look at tasks for the user ID of interest. continue; } if (task.autoRemoveRecents && task.getTopActivity() == null) { // This situation is broken, and we should just get rid of it now. - mTasks.remove(i); - notifyTaskRemoved(task, !TRIMMED); + remove(i); + task.removedFromRecents(); Slog.w(TAG, "Removing auto-remove without activity: " + task); continue; } @@ -502,11 +331,11 @@ class RecentTasks { continue; } if (ai == null) { - ai = NO_ACTIVITY_INFO_TOKEN; + ai = mTmpActivityInfo; } mTmpAvailActCache.put(task.realActivity, ai); } - if (ai == NO_ACTIVITY_INFO_TOKEN) { + if (ai == mTmpActivityInfo) { // This could be either because the activity no longer exists, or the // app is temporarily gone. For the former we want to remove the recents // entry; for the latter we want to mark it as unavailable. @@ -521,15 +350,15 @@ class RecentTasks { continue; } if (app == null) { - app = NO_APPLICATION_INFO_TOKEN; + app = mTmpAppInfo; } mTmpAvailAppCache.put(task.realActivity.getPackageName(), app); } - if (app == NO_APPLICATION_INFO_TOKEN + if (app == mTmpAppInfo || (app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { // Doesn't exist any more! Good-bye. - mTasks.remove(i); - notifyTaskRemoved(task, !TRIMMED); + remove(i); + task.removedFromRecents(); Slog.w(TAG, "Removing no longer valid recent: " + task); continue; } else { @@ -561,197 +390,121 @@ class RecentTasks { // Verify the affiliate chain for each task. int i = 0; - recentsCount = mTasks.size(); + recentsCount = size(); while (i < recentsCount) { i = processNextAffiliateChainLocked(i); } // recent tasks are now in sorted, affiliated order. } - /** - * @return whether the given {@param task} can be added to the list without causing another - * task to be trimmed as a result of that add. - */ - private boolean canAddTaskWithoutTrim(TaskRecord task) { - return findTrimIndexForAddTask(task) == -1; - } - - /** - * Returns the list of {@link ActivityManager.AppTask}s. - */ - ArrayList<IBinder> getAppTasksList(int callingUid, String callingPackage) { - final ArrayList<IBinder> list = new ArrayList<>(); - final int size = mTasks.size(); - for (int i = 0; i < size; i++) { - final TaskRecord tr = mTasks.get(i); - // Skip tasks that do not match the caller. We don't need to verify - // callingPackage, because we are also limiting to callingUid and know - // that will limit to the correct security sandbox. - if (tr.effectiveUid != callingUid) { - continue; - } - Intent intent = tr.getBaseIntent(); - if (intent == null || !callingPackage.equals(intent.getComponent().getPackageName())) { - continue; - } - ActivityManager.RecentTaskInfo taskInfo = createRecentTaskInfo(tr); - AppTaskImpl taskImpl = new AppTaskImpl(mService, taskInfo.persistentId, callingUid); - list.add(taskImpl.asBinder()); - } - return list; - } - - /** - * @return the list of recent tasks for presentation. - */ - ParceledListSlice<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags, - boolean getTasksAllowed, boolean getDetailedTasks, int userId, int callingUid) { - final boolean withExcluded = (flags & RECENT_WITH_EXCLUDED) != 0; - - if (!mService.isUserRunning(userId, FLAG_AND_UNLOCKED)) { - Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents"); - return ParceledListSlice.emptyList(); + private final boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) { + int recentsCount = size(); + TaskRecord top = task; + int topIndex = taskIndex; + while (top.mNextAffiliate != null && topIndex > 0) { + top = top.mNextAffiliate; + topIndex--; } - loadUserRecentsLocked(userId); - - final Set<Integer> includedUsers = mUserController.getProfileIds(userId); - includedUsers.add(Integer.valueOf(userId)); - - final ArrayList<ActivityManager.RecentTaskInfo> res = new ArrayList<>(); - final int size = mTasks.size(); - int numVisibleTasks = 0; - for (int i = 0; i < size; i++) { - final TaskRecord tr = mTasks.get(i); - - if (isVisibleRecentTask(tr)) { - numVisibleTasks++; - if (isInVisibleRange(tr, numVisibleTasks)) { - // Fall through - } else { - // Not in visible range - continue; + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding affilliates starting at " + + topIndex + " from intial " + taskIndex); + // Find the end of the chain, doing a sanity check along the way. + boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId; + int endIndex = topIndex; + TaskRecord prev = top; + while (endIndex < recentsCount) { + TaskRecord cur = get(endIndex); + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: looking at next chain @" + + endIndex + " " + cur); + if (cur == top) { + // Verify start of the chain. + if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": first task has next affiliate: " + prev); + sane = false; + break; } } else { - // Not visible - continue; - } - - // Skip remaining tasks once we reach the requested size - if (res.size() >= maxNum) { - continue; - } - - // Only add calling user or related users recent tasks - if (!includedUsers.contains(Integer.valueOf(tr.userId))) { - if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + tr); - continue; - } - - if (tr.realActivitySuspended) { - if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, activity suspended: " + tr); - continue; - } - - // Return the entry if desired by the caller. We always return - // the first entry, because callers always expect this to be the - // foreground app. We may filter others if the caller has - // not supplied RECENT_WITH_EXCLUDED and there is some reason - // we should exclude the entry. - - if (i == 0 - || withExcluded - || (tr.intent == null) - || ((tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - == 0)) { - if (!getTasksAllowed) { - // If the caller doesn't have the GET_TASKS permission, then only - // allow them to see a small subset of tasks -- their own and home. - if (!tr.isActivityTypeHome() && tr.effectiveUid != callingUid) { - if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + tr); - continue; - } - } - if (tr.autoRemoveRecents && tr.getTopActivity() == null) { - // Don't include auto remove tasks that are finished or finishing. - if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, - "Skipping, auto-remove without activity: " + tr); - continue; - } - if ((flags & RECENT_IGNORE_UNAVAILABLE) != 0 && !tr.isAvailable) { - if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, - "Skipping, unavail real act: " + tr); - continue; + // Verify middle of the chain's next points back to the one before. + if (cur.mNextAffiliate != prev + || cur.mNextAffiliateTaskId != prev.taskId) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": middle task " + cur + " @" + endIndex + + " has bad next affiliate " + + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId + + ", expected " + prev); + sane = false; + break; } - - if (!tr.mUserSetupComplete) { - // Don't include task launched while user is not done setting-up. - if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, - "Skipping, user setup not complete: " + tr); - continue; + } + if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) { + // Chain ends here. + if (cur.mPrevAffiliate != null) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": last task " + cur + " has previous affiliate " + + cur.mPrevAffiliate); + sane = false; } - - ActivityManager.RecentTaskInfo rti = RecentTasks.createRecentTaskInfo(tr); - if (!getDetailedTasks) { - rti.baseIntent.replaceExtras((Bundle)null); + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: end of chain @" + endIndex); + break; + } else { + // Verify middle of the chain's prev points to a valid item. + if (cur.mPrevAffiliate == null) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": task " + cur + " has previous affiliate " + + cur.mPrevAffiliate + " but should be id " + + cur.mPrevAffiliate); + sane = false; + break; } - - res.add(rti); + } + if (cur.mAffiliatedTaskId != task.mAffiliatedTaskId) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": task " + cur + " has affiliated id " + + cur.mAffiliatedTaskId + " but should be " + + task.mAffiliatedTaskId); + sane = false; + break; + } + prev = cur; + endIndex++; + if (endIndex >= recentsCount) { + Slog.wtf(TAG, "Bad chain ran off index " + endIndex + + ": last task " + prev); + sane = false; + break; } } - return new ParceledListSlice<>(res); - } - - /** - * @return the list of persistable task ids. - */ - void getPersistableTaskIds(ArraySet<Integer> persistentTaskIds) { - final int size = mTasks.size(); - for (int i = 0; i < size; i++) { - final TaskRecord task = mTasks.get(i); - if (TaskPersister.DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task - + " persistable=" + task.isPersistable); - final ActivityStack stack = task.getStack(); - if ((task.isPersistable || task.inRecents) - && (stack == null || !stack.isHomeOrRecentsStack())) { - if (TaskPersister.DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task); - persistentTaskIds.add(task.taskId); - } else { - if (TaskPersister.DEBUG) Slog.d(TAG, "omitting from persistentTaskIds task=" - + task); + if (sane) { + if (endIndex < taskIndex) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": did not extend to task " + task + " @" + taskIndex); + sane = false; } } - } - - @VisibleForTesting - ArrayList<TaskRecord> getRawTasks() { - return mTasks; - } - - /** - * @return the task in the task list with the given {@param id} if one exists. - */ - TaskRecord getTask(int id) { - final int recentsCount = mTasks.size(); - for (int i = 0; i < recentsCount; i++) { - TaskRecord tr = mTasks.get(i); - if (tr.taskId == id) { - return tr; + if (sane) { + // All looks good, we can just move all of the affiliated tasks + // to the top. + for (int i=topIndex; i<=endIndex; i++) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving affiliated " + task + + " from " + i + " to " + (i-topIndex)); + TaskRecord cur = remove(i); + add(i - topIndex, cur); } + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: done moving tasks " + topIndex + + " to " + endIndex); + return true; } - return null; - } - /** - * Add a new task to the recent tasks list. - */ - void add(TaskRecord task) { - if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "add: task=" + task); + // Whoops, couldn't do it. + return false; + } + final void addLocked(TaskRecord task) { final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId || task.mNextAffiliateTaskId != INVALID_TASK_ID || task.mPrevAffiliateTaskId != INVALID_TASK_ID; - int recentsCount = mTasks.size(); + int recentsCount = size(); // Quick case: never add voice sessions. // TODO: VI what about if it's just an activity? // Probably nothing to do here @@ -761,15 +514,15 @@ class RecentTasks { return; } // Another quick case: check if the top-most recent task is the same. - if (!isAffiliated && recentsCount > 0 && mTasks.get(0) == task) { + if (!isAffiliated && recentsCount > 0 && get(0) == task) { if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: already at top: " + task); return; } // Another quick case: check if this is part of a set of affiliated // tasks that are at the top. if (isAffiliated && recentsCount > 0 && task.inRecents - && task.mAffiliatedTaskId == mTasks.get(0).mAffiliatedTaskId) { - if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: affiliated " + mTasks.get(0) + && task.mAffiliatedTaskId == get(0).mAffiliatedTaskId) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: affiliated " + get(0) + " at top when adding " + task); return; } @@ -779,12 +532,12 @@ class RecentTasks { // Slightly less quick case: the task is already in recents, so all we need // to do is move it. if (task.inRecents) { - int taskIndex = mTasks.indexOf(task); + int taskIndex = indexOf(task); if (taskIndex >= 0) { - if (!isAffiliated || !MOVE_AFFILIATED_TASKS_TO_FRONT) { + if (!isAffiliated || MOVE_AFFILIATED_TASKS_TO_FRONT) { // Simple case: this is not an affiliated task, so we just move it to the front. - mTasks.remove(taskIndex); - mTasks.add(0, task); + remove(taskIndex); + add(0, task); notifyTaskPersisterLocked(task, false); if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving to top " + task + " from " + taskIndex); @@ -807,14 +560,20 @@ class RecentTasks { } if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: trimming tasks for " + task); - trimForAddTask(task); + trimForTaskLocked(task, true); + recentsCount = size(); + final int maxRecents = ActivityManager.getMaxRecentTasksStatic(); + while (recentsCount >= maxRecents) { + final TaskRecord tr = remove(recentsCount - 1); + tr.removedFromRecents(); + recentsCount--; + } task.inRecents = true; if (!isAffiliated || needAffiliationFix) { // If this is a simple non-affiliated task, or we had some failure trying to // handle it as part of an affilated task, then just place it at the top. - mTasks.add(0, task); - notifyTaskAdded(task); + add(0, task); if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding " + task); } else if (isAffiliated) { // If this is a new affiliated task, then move all of the affiliated tasks @@ -824,7 +583,7 @@ class RecentTasks { other = task.mPrevAffiliate; } if (other != null) { - int otherIndex = mTasks.indexOf(other); + int otherIndex = indexOf(other); if (otherIndex >= 0) { // Insert new task at appropriate location. int taskIndex; @@ -839,8 +598,7 @@ class RecentTasks { } if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: new affiliated task added at " + taskIndex + ": " + task); - mTasks.add(taskIndex, task); - notifyTaskAdded(task); + add(taskIndex, task); // Now move everything to the front. if (moveAffiliatedTasksToFront(task, taskIndex)) { @@ -867,235 +625,21 @@ class RecentTasks { if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: regrouping affiliations"); cleanupLocked(task.userId); } - - // Trim the set of tasks to the active set - trimInactiveRecentTasks(); - } - - /** - * Add the task to the bottom if possible. - */ - boolean addToBottom(TaskRecord task) { - if (!canAddTaskWithoutTrim(task)) { - // Adding this task would cause the task to be removed (since it's appended at - // the bottom and would be trimmed) so just return now - return false; - } - - add(task); - return true; - } - - /** - * Remove a task from the recent tasks list. - */ - void remove(TaskRecord task) { - mTasks.remove(task); - notifyTaskRemoved(task, !TRIMMED); - } - - /** - * Trims the recents task list to the global max number of recents. - */ - private void trimInactiveRecentTasks() { - int recentsCount = mTasks.size(); - - // Remove from the end of the list until we reach the max number of recents - while (recentsCount > mGlobalMaxNumTasks) { - final TaskRecord tr = mTasks.remove(recentsCount - 1); - notifyTaskRemoved(tr, TRIMMED); - recentsCount--; - if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming over max-recents task=" + tr - + " max=" + mGlobalMaxNumTasks); - } - - // Remove any tasks that belong to currently quiet profiles - final int[] profileUserIds = mUserController.getCurrentProfileIds(); - mTmpQuietProfileUserIds.clear(); - for (int userId : profileUserIds) { - final UserInfo userInfo = mUserController.getUserInfo(userId); - if (userInfo.isManagedProfile() && userInfo.isQuietModeEnabled()) { - mTmpQuietProfileUserIds.put(userId, true); - } - if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "User: " + userInfo - + " quiet=" + mTmpQuietProfileUserIds.get(userId)); - } - - // Remove any inactive tasks, calculate the latest set of visible tasks - int numVisibleTasks = 0; - for (int i = 0; i < mTasks.size();) { - final TaskRecord task = mTasks.get(i); - - if (isActiveRecentTask(task, mTmpQuietProfileUserIds)) { - if (!mHasVisibleRecentTasks) { - // Keep all active tasks if visible recent tasks is not supported - i++; - continue; - } - - if (!isVisibleRecentTask(task)) { - // Keep all active-but-invisible tasks - i++; - continue; - } else { - numVisibleTasks++; - if (isInVisibleRange(task, numVisibleTasks)) { - // Keep visible tasks in range - i++; - continue; - } else { - // Fall through to trim visible tasks that are no longer in range - if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, - "Trimming out-of-range visible task=" + task); - } - } - } else { - // Fall through to trim inactive tasks - if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming inactive task=" + task); - } - - // Task is no longer active, trim it from the list - mTasks.remove(task); - notifyTaskRemoved(task, TRIMMED); - notifyTaskPersisterLocked(task, false /* flush */); - } - } - - /** - * @return whether the given task should be considered active. - */ - private boolean isActiveRecentTask(TaskRecord task, SparseBooleanArray quietProfileUserIds) { - if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "isActiveRecentTask: task=" + task - + " globalMax=" + mGlobalMaxNumTasks); - - if (quietProfileUserIds.get(task.userId)) { - // Quiet profile user's tasks are never active - if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\tisQuietProfileTask=true"); - return false; - } - - if (task.mAffiliatedTaskId != INVALID_TASK_ID && task.mAffiliatedTaskId != task.taskId) { - // Keep the task active if its affiliated task is also active - final TaskRecord affiliatedTask = getTask(task.mAffiliatedTaskId); - if (affiliatedTask != null) { - if (!isActiveRecentTask(affiliatedTask, quietProfileUserIds)) { - if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, - "\taffiliatedWithTask=" + affiliatedTask + " is not active"); - return false; - } - } - } - - // All other tasks are considered active - return true; - } - - /** - * @return whether the given active task should be presented to the user through SystemUI. - */ - private boolean isVisibleRecentTask(TaskRecord task) { - if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "isVisibleRecentTask: task=" + task - + " minVis=" + mMinNumVisibleTasks + " maxVis=" + mMaxNumVisibleTasks - + " sessionDuration=" + mActiveTasksSessionDurationMs - + " inactiveDuration=" + task.getInactiveDuration() - + " activityType=" + task.getActivityType() - + " windowingMode=" + task.getWindowingMode()); - - // Ignore certain activity types completely - switch (task.getActivityType()) { - case ACTIVITY_TYPE_HOME: - case ACTIVITY_TYPE_RECENTS: - return false; - } - - // Ignore certain windowing modes - switch (task.getWindowingMode()) { - case WINDOWING_MODE_PINNED: - return false; - case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: - if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\ttop=" + task.getStack().topTask()); - final ActivityStack stack = task.getStack(); - if (stack != null && stack.topTask() == task) { - // Only the non-top task of the primary split screen mode is visible - return false; - } - } - - return true; - } - - /** - * @return whether the given visible task is within the policy range. - */ - private boolean isInVisibleRange(TaskRecord task, int numVisibleTasks) { - // Keep the last most task even if it is excluded from recents - final boolean isExcludeFromRecents = - (task.getBaseIntent().getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; - if (isExcludeFromRecents) { - if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\texcludeFromRecents=true"); - return numVisibleTasks == 1; - } - - if (mMinNumVisibleTasks >= 0 && numVisibleTasks <= mMinNumVisibleTasks) { - // Always keep up to the min number of recent tasks, after that fall through to the - // checks below - return true; - } - - if (mMaxNumVisibleTasks >= 0) { - // Always keep up to the max number of recent tasks, but return false afterwards - return numVisibleTasks <= mMaxNumVisibleTasks; - } - - if (mActiveTasksSessionDurationMs > 0) { - // Keep the task if the inactive time is within the session window, this check must come - // after the checks for the min/max visible task range - if (task.getInactiveDuration() <= mActiveTasksSessionDurationMs) { - return true; - } - } - - return false; } /** * If needed, remove oldest existing entries in recents that are for the same kind * of task as the given one. */ - private void trimForAddTask(TaskRecord task) { - final int removeIndex = findTrimIndexForAddTask(task); - if (removeIndex == -1) { - // Nothing to trim - return; - } - - // There is a similar task that will be removed for the addition of {@param task}, but it - // can be the same task, and if so, the task will be re-added in add(), so skip the - // callbacks here. - final TaskRecord removedTask = mTasks.remove(removeIndex); - if (removedTask != task) { - notifyTaskRemoved(removedTask, TRIMMED); - if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming task=" + removedTask - + " for addition of task=" + task); - } - notifyTaskPersisterLocked(removedTask, false /* flush */); - } - - /** - * Find the task that would be removed if the given {@param task} is added to the recent tasks - * list (if any). - */ - private int findTrimIndexForAddTask(TaskRecord task) { - int recentsCount = mTasks.size(); + int trimForTaskLocked(TaskRecord task, boolean doTrim) { + int recentsCount = size(); final Intent intent = task.intent; final boolean document = intent != null && intent.isDocument(); int maxRecents = task.maxRecents - 1; final ActivityStack stack = task.getStack(); for (int i = 0; i < recentsCount; i++) { - final TaskRecord tr = mTasks.get(i); + final TaskRecord tr = get(i); final ActivityStack trStack = tr.getStack(); - if (task != tr) { if (stack != null && trStack != null && stack != trStack) { continue; @@ -1106,7 +650,7 @@ class RecentTasks { final Intent trIntent = tr.intent; final boolean sameAffinity = task.affinity != null && task.affinity.equals(tr.affinity); - final boolean sameIntent = intent != null && intent.filterEquals(trIntent); + final boolean sameIntentFilter = intent != null && intent.filterEquals(trIntent); boolean multiTasksAllowed = false; final int flags = intent.getFlags(); if ((flags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NEW_DOCUMENT)) != 0 @@ -1115,7 +659,7 @@ class RecentTasks { } final boolean trIsDocument = trIntent != null && trIntent.isDocument(); final boolean bothDocuments = document && trIsDocument; - if (!sameAffinity && !sameIntent && !bothDocuments) { + if (!sameAffinity && !sameIntentFilter && !bothDocuments) { continue; } @@ -1124,17 +668,17 @@ class RecentTasks { final boolean sameActivity = task.realActivity != null && tr.realActivity != null && task.realActivity.equals(tr.realActivity); + // If the document is open in another app or is not the same + // document, we don't need to trim it. if (!sameActivity) { - // If the document is open in another app or is not the same document, we - // don't need to trim it. continue; + // Otherwise only trim if we are over our max recents for this task } else if (maxRecents > 0) { - // Otherwise only trim if we are over our max recents for this task --maxRecents; - if (!sameIntent || multiTasksAllowed) { + if (!doTrim || !sameIntentFilter || multiTasksAllowed) { // We don't want to trim if we are not over the max allowed entries and - // the tasks are not of the same intent filter, or multiple entries for - // the task is allowed. + // the caller doesn't want us to trim, the tasks are not of the same + // intent filter, or multiple entries fot the task is allowed. continue; } } @@ -1145,14 +689,44 @@ class RecentTasks { continue; } } - return i; + + if (!doTrim) { + // If the caller is not actually asking for a trim, just tell them we reached + // a point where the trim would happen. + return i; + } + + // Either task and tr are the same or, their affinities match or their intents match + // and neither of them is a document, or they are documents using the same activity + // and their maxRecents has been reached. + remove(i); + if (task != tr) { + tr.removedFromRecents(); + } + i--; + recentsCount--; + if (task.intent == null) { + // If the new recent task we are adding is not fully + // specified, then replace it with the existing recent task. + task = tr; + } + notifyTaskPersisterLocked(tr, false); } + return -1; } + // Sort by taskId + private static Comparator<TaskRecord> sTaskRecordComparator = new Comparator<TaskRecord>() { + @Override + public int compare(TaskRecord lhs, TaskRecord rhs) { + return rhs.taskId - lhs.taskId; + } + }; + // Extract the affiliates of the chain containing recent at index start. private int processNextAffiliateChainLocked(int start) { - final TaskRecord startTask = mTasks.get(start); + final TaskRecord startTask = get(start); final int affiliateId = startTask.mAffiliatedTaskId; // Quick identification of isolated tasks. I.e. those not launched behind. @@ -1167,17 +741,17 @@ class RecentTasks { // Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents. mTmpRecents.clear(); - for (int i = mTasks.size() - 1; i >= start; --i) { - final TaskRecord task = mTasks.get(i); + for (int i = size() - 1; i >= start; --i) { + final TaskRecord task = get(i); if (task.mAffiliatedTaskId == affiliateId) { - mTasks.remove(i); + remove(i); mTmpRecents.add(task); } } // Sort them all by taskId. That is the order they were create in and that order will // always be correct. - Collections.sort(mTmpRecents, TASK_ID_COMPARATOR); + Collections.sort(mTmpRecents, sTaskRecordComparator); // Go through and fix up the linked list. // The first one is the end of the chain and has no next. @@ -1215,197 +789,11 @@ class RecentTasks { notifyTaskPersisterLocked(last, false); } - // Insert the group back into mTmpTasks at start. - mTasks.addAll(start, mTmpRecents); + // Insert the group back into mRecentTasks at start. + addAll(start, mTmpRecents); mTmpRecents.clear(); // Let the caller know where we left off. return start + tmpSize; } - - private boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) { - int recentsCount = mTasks.size(); - TaskRecord top = task; - int topIndex = taskIndex; - while (top.mNextAffiliate != null && topIndex > 0) { - top = top.mNextAffiliate; - topIndex--; - } - if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding affilliates starting at " - + topIndex + " from intial " + taskIndex); - // Find the end of the chain, doing a sanity check along the way. - boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId; - int endIndex = topIndex; - TaskRecord prev = top; - while (endIndex < recentsCount) { - TaskRecord cur = mTasks.get(endIndex); - if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: looking at next chain @" - + endIndex + " " + cur); - if (cur == top) { - // Verify start of the chain. - if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": first task has next affiliate: " + prev); - sane = false; - break; - } - } else { - // Verify middle of the chain's next points back to the one before. - if (cur.mNextAffiliate != prev - || cur.mNextAffiliateTaskId != prev.taskId) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": middle task " + cur + " @" + endIndex - + " has bad next affiliate " - + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId - + ", expected " + prev); - sane = false; - break; - } - } - if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) { - // Chain ends here. - if (cur.mPrevAffiliate != null) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": last task " + cur + " has previous affiliate " - + cur.mPrevAffiliate); - sane = false; - } - if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: end of chain @" + endIndex); - break; - } else { - // Verify middle of the chain's prev points to a valid item. - if (cur.mPrevAffiliate == null) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": task " + cur + " has previous affiliate " - + cur.mPrevAffiliate + " but should be id " - + cur.mPrevAffiliate); - sane = false; - break; - } - } - if (cur.mAffiliatedTaskId != task.mAffiliatedTaskId) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": task " + cur + " has affiliated id " - + cur.mAffiliatedTaskId + " but should be " - + task.mAffiliatedTaskId); - sane = false; - break; - } - prev = cur; - endIndex++; - if (endIndex >= recentsCount) { - Slog.wtf(TAG, "Bad chain ran off index " + endIndex - + ": last task " + prev); - sane = false; - break; - } - } - if (sane) { - if (endIndex < taskIndex) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": did not extend to task " + task + " @" + taskIndex); - sane = false; - } - } - if (sane) { - // All looks good, we can just move all of the affiliated tasks - // to the top. - for (int i=topIndex; i<=endIndex; i++) { - if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving affiliated " + task - + " from " + i + " to " + (i-topIndex)); - TaskRecord cur = mTasks.remove(i); - mTasks.add(i - topIndex, cur); - } - if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: done moving tasks " + topIndex - + " to " + endIndex); - return true; - } - - // Whoops, couldn't do it. - return false; - } - - void dump(PrintWriter pw, boolean dumpAll, String dumpPackage) { - pw.println("ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)"); - if (mTasks.isEmpty()) { - return; - } - - final MutableBoolean printedAnything = new MutableBoolean(false); - final MutableBoolean printedHeader = new MutableBoolean(false); - final int size = mTasks.size(); - for (int i = 0; i < size; i++) { - final TaskRecord tr = mTasks.get(i); - if (dumpPackage != null && (tr.realActivity == null || - !dumpPackage.equals(tr.realActivity.getPackageName()))) { - continue; - } - - if (!printedHeader.value) { - pw.println(" Recent tasks:"); - printedHeader.value = true; - printedAnything.value = true; - } - pw.print(" * Recent #"); pw.print(i); pw.print(": "); - pw.println(tr); - if (dumpAll) { - tr.dump(pw, " "); - } - } - - if (!printedAnything.value) { - pw.println(" (nothing)"); - } - } - - /** - * Creates a new RecentTaskInfo from a TaskRecord. - */ - static ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr) { - // Update the task description to reflect any changes in the task stack - tr.updateTaskDescription(); - - // Compose the recent task info - ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); - rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId; - rti.persistentId = tr.taskId; - rti.baseIntent = new Intent(tr.getBaseIntent()); - rti.origActivity = tr.origActivity; - rti.realActivity = tr.realActivity; - rti.description = tr.lastDescription; - rti.stackId = tr.getStackId(); - rti.userId = tr.userId; - rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription); - rti.lastActiveTime = tr.lastActiveTime; - rti.affiliatedTaskId = tr.mAffiliatedTaskId; - rti.affiliatedTaskColor = tr.mAffiliatedTaskColor; - rti.numActivities = 0; - if (tr.mBounds != null) { - rti.bounds = new Rect(tr.mBounds); - } - rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreenWindowingMode(); - rti.resizeMode = tr.mResizeMode; - rti.configuration.setTo(tr.getConfiguration()); - - ActivityRecord base = null; - ActivityRecord top = null; - ActivityRecord tmp; - - for (int i = tr.mActivities.size() - 1; i >= 0; --i) { - tmp = tr.mActivities.get(i); - if (tmp.finishing) { - continue; - } - base = tmp; - if (top == null || (top.state == ActivityState.INITIALIZING)) { - top = base; - } - rti.numActivities++; - } - - rti.baseActivity = (base != null) ? base.intent.getComponent() : null; - rti.topActivity = (top != null) ? top.intent.getComponent() : null; - - return rti; - } } diff --git a/com/android/server/am/ServiceRecord.java b/com/android/server/am/ServiceRecord.java index 16995e50..ac85e6b1 100644 --- a/com/android/server/am/ServiceRecord.java +++ b/com/android/server/am/ServiceRecord.java @@ -33,7 +33,6 @@ import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.Binder; -import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; @@ -518,27 +517,14 @@ final class ServiceRecord extends Binder { } catch (PackageManager.NameNotFoundException e) { } } - if (nm.getNotificationChannel(localPackageName, appUid, + if (localForegroundNoti.getSmallIcon() == null + || nm.getNotificationChannel(localPackageName, appUid, localForegroundNoti.getChannelId()) == null) { - int targetSdkVersion = Build.VERSION_CODES.O_MR1; - try { - final ApplicationInfo applicationInfo = - ams.mContext.getPackageManager().getApplicationInfoAsUser( - appInfo.packageName, 0, userId); - targetSdkVersion = applicationInfo.targetSdkVersion; - } catch (PackageManager.NameNotFoundException e) { - } - if (targetSdkVersion >= Build.VERSION_CODES.O_MR1) { - throw new RuntimeException( - "invalid channel for service notification: " - + foregroundNoti); - } - } - if (localForegroundNoti.getSmallIcon() == null) { // Notifications whose icon is 0 are defined to not show // a notification, silently ignoring it. We don't want to // just ignore it, we want to prevent the service from // being foreground. + // Also every notification needs a channel. throw new RuntimeException("invalid service notification: " + foregroundNoti); } diff --git a/com/android/server/am/TaskPersister.java b/com/android/server/am/TaskPersister.java index 2689d6a4..61994b55 100644 --- a/com/android/server/am/TaskPersister.java +++ b/com/android/server/am/TaskPersister.java @@ -567,7 +567,7 @@ public class TaskPersister { SparseArray<SparseBooleanArray> changedTaskIdsPerUser = new SparseArray<>(); synchronized (mService) { for (int userId : mRecentTasks.usersWithRecentsLoadedLocked()) { - SparseBooleanArray taskIdsToSave = mRecentTasks.getTaskIdsForUser(userId); + SparseBooleanArray taskIdsToSave = mRecentTasks.mPersistedTaskIds.get(userId); SparseBooleanArray persistedIdsInFile = mTaskIdsInFile.get(userId); if (persistedIdsInFile != null && persistedIdsInFile.equals(taskIdsToSave)) { continue; @@ -640,7 +640,7 @@ public class TaskPersister { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - ArraySet<Integer> persistentTaskIds = new ArraySet<>(); + ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>(); while (true) { // We can't lock mService while holding TaskPersister.this, but we don't want to // call removeObsoleteFiles every time through the loop, only the last time before @@ -654,7 +654,20 @@ public class TaskPersister { persistentTaskIds.clear(); synchronized (mService) { if (DEBUG) Slog.d(TAG, "mRecents=" + mRecentTasks); - mRecentTasks.getPersistableTaskIds(persistentTaskIds); + for (int taskNdx = mRecentTasks.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord task = mRecentTasks.get(taskNdx); + if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + + " persistable=" + task.isPersistable); + final ActivityStack stack = task.getStack(); + if ((task.isPersistable || task.inRecents) + && (stack == null || !stack.isHomeOrRecentsStack())) { + if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task); + persistentTaskIds.add(task.taskId); + } else { + if (DEBUG) Slog.d(TAG, + "omitting from persistentTaskIds task=" + task); + } + } mService.mWindowManager.removeObsoleteTaskFiles(persistentTaskIds, mRecentTasks.usersWithRecentsLoadedLocked()); } diff --git a/com/android/server/am/TaskRecord.java b/com/android/server/am/TaskRecord.java index a1b45a1e..0d8df796 100644 --- a/com/android/server/am/TaskRecord.java +++ b/com/android/server/am/TaskRecord.java @@ -18,7 +18,10 @@ package com.android.server.am; import static android.app.ActivityManager.RESIZE_MODE_FORCED; import static android.app.ActivityManager.RESIZE_MODE_SYSTEM; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; @@ -28,7 +31,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; @@ -43,6 +45,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; +import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.view.Display.DEFAULT_DISPLAY; @@ -83,6 +86,7 @@ import android.annotation.IntDef; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; +import android.app.ActivityManager.StackId; import android.app.ActivityManager.TaskDescription; import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityOptions; @@ -98,7 +102,6 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.Debug; import android.os.RemoteException; -import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; @@ -152,6 +155,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi private static final String ATTR_EFFECTIVE_UID = "effective_uid"; @Deprecated private static final String ATTR_TASKTYPE = "task_type"; + private static final String ATTR_FIRSTACTIVETIME = "first_active_time"; + private static final String ATTR_LASTACTIVETIME = "last_active_time"; private static final String ATTR_LASTDESCRIPTION = "last_description"; private static final String ATTR_LASTTIMEMOVED = "last_time_moved"; private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity"; @@ -163,6 +168,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi private static final String ATTR_CALLING_PACKAGE = "calling_package"; private static final String ATTR_SUPPORTS_PICTURE_IN_PICTURE = "supports_picture_in_picture"; private static final String ATTR_RESIZE_MODE = "resize_mode"; + private static final String ATTR_PRIVILEGED = "privileged"; private static final String ATTR_NON_FULLSCREEN_BOUNDS = "non_fullscreen_bounds"; private static final String ATTR_MIN_WIDTH = "min_width"; private static final String ATTR_MIN_HEIGHT = "min_height"; @@ -206,10 +212,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi ComponentName realActivity; // The actual activity component that started the task. boolean realActivitySuspended; // True if the actual activity component that started the // task is suspended. + long firstActiveTime; // First time this task was active. + long lastActiveTime; // Last time this task was active, including sleep. boolean inRecents; // Actually in the recents list? - long lastActiveTime; // Last time this task was active in the current device session, - // including sleep. This time is initialized to the elapsed time when - // restored from disk. boolean isAvailable; // Is the activity available to be launched? boolean rootWasReset; // True if the intent at the root of the task had // the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag. @@ -232,6 +237,10 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi // of the root activity. boolean mTemporarilyUnresizable; // Separate flag from mResizeMode used to suppress resize // changes on a temporary basis. + private int mLockTaskMode; // Which tasklock mode to launch this task in. One of + // ActivityManager.LOCK_TASK_LAUNCH_MODE_* + private boolean mPrivileged; // The root activity application of this task holds + // privileged permissions. /** Can't be put in lockTask mode. */ final static int LOCK_TASK_AUTH_DONT_LOCK = 0; @@ -330,7 +339,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi TaskPersister.IMAGE_EXTENSION; userId = UserHandle.getUserId(info.applicationInfo.uid); taskId = _taskId; - lastActiveTime = SystemClock.elapsedRealtime(); mAffiliatedTaskId = _taskId; voiceSession = _voiceSession; voiceInteractor = _voiceInteractor; @@ -351,7 +359,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi TaskPersister.IMAGE_EXTENSION; userId = UserHandle.getUserId(info.applicationInfo.uid); taskId = _taskId; - lastActiveTime = SystemClock.elapsedRealtime(); mAffiliatedTaskId = _taskId; voiceSession = null; voiceInteractor = null; @@ -378,11 +385,12 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset, boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId, int _effectiveUid, String _lastDescription, ArrayList<ActivityRecord> activities, - long lastTimeMoved, boolean neverRelinquishIdentity, - TaskDescription _lastTaskDescription, int taskAffiliation, int prevTaskId, - int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage, - int resizeMode, boolean supportsPictureInPicture, boolean _realActivitySuspended, - boolean userSetupComplete, int minWidth, int minHeight) { + long _firstActiveTime, long _lastActiveTime, long lastTimeMoved, + boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription, + int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor, + int callingUid, String callingPackage, int resizeMode, boolean supportsPictureInPicture, + boolean privileged, boolean _realActivitySuspended, boolean userSetupComplete, + int minWidth, int minHeight) { mService = service; mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION; @@ -404,7 +412,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi userId = _userId; mUserSetupComplete = userSetupComplete; effectiveUid = _effectiveUid; - lastActiveTime = SystemClock.elapsedRealtime(); + firstActiveTime = _firstActiveTime; + lastActiveTime = _lastActiveTime; lastDescription = _lastDescription; mActivities = activities; mLastTimeMoved = lastTimeMoved; @@ -418,6 +427,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi mCallingPackage = callingPackage; mResizeMode = resizeMode; mSupportsPictureInPicture = supportsPictureInPicture; + mPrivileged = privileged; mMinWidth = minWidth; mMinHeight = minHeight; mService.mTaskChangeNotificationController.notifyTaskCreated(_taskId, realActivity); @@ -510,7 +520,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi // All we can do for now is update the bounds so it can be used when the task is // added to window manager. updateOverrideConfiguration(bounds); - if (!inFreeformWindowingMode()) { + if (getStackId() != FREEFORM_WORKSPACE_STACK_ID) { // re-restore the task so it can have the proper stack association. mService.mStackSupervisor.restoreRecentTaskLocked(this, null); } @@ -606,7 +616,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi * @return whether the task was reparented */ // TODO: Inspect all call sites and change to just changing windowing mode of the stack vs. - // re-parenting the task. Can only be done when we are no longer using static stack Ids. + // re-parenting the task. Can only be done when we are no longer using static stack Ids like + /** {@link ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} */ boolean reparent(ActivityStack preferredStack, int position, @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume, boolean schedulePictureInPictureModeChange, String reason) { @@ -619,12 +630,12 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi return false; } - final int toStackWindowingMode = toStack.getWindowingMode(); + final int sourceStackId = getStackId(); + final int stackId = toStack.getStackId(); final ActivityRecord topActivity = getTopActivity(); - final boolean mightReplaceWindow = - replaceWindowsOnTaskMove(getWindowingMode(), toStackWindowingMode) - && topActivity != null; + final boolean mightReplaceWindow = StackId.replaceWindowsOnTaskMove(sourceStackId, stackId) + && topActivity != null; if (mightReplaceWindow) { // We are about to relaunch the activity because its configuration changed due to // being maximized, i.e. size change. The activity will first remove the old window @@ -649,7 +660,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi // In some cases the focused stack isn't the front stack. E.g. pinned stack. // Whenever we are moving the top activity from the front stack we want to make sure to // move the stack to the front. - final boolean wasFront = r != null && sourceStack.isTopStackOnDisplay() + final boolean wasFront = r != null && supervisor.isFrontStackOnDisplay(sourceStack) && (sourceStack.topRunningActivityLocked() == r); // Adjust the position for the new parent stack as needed. @@ -696,10 +707,10 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi toStack.prepareFreezingTaskBounds(); // Make sure the task has the appropriate bounds/size for the stack it is in. + final int toStackWindowingMode = toStack.getWindowingMode(); final boolean toStackSplitScreenPrimary = toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; - if ((toStackWindowingMode == WINDOWING_MODE_FULLSCREEN - || toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) + if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && !Objects.equals(mBounds, toStack.mBounds)) { kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow, deferResume); @@ -738,9 +749,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } // TODO: Handle incorrect request to move before the actual move, not after. - final boolean inSplitScreenMode = supervisor.getDefaultDisplay().hasSplitScreenPrimaryStack(); + final boolean inSplitScreenMode = supervisor.getDefaultDisplay().hasSplitScreenStack(); supervisor.handleNonResizableTaskIfNeeded(this, preferredStack.getWindowingMode(), - DEFAULT_DISPLAY, toStack); + DEFAULT_DISPLAY, stackId); boolean successful = (preferredStack == toStack); if (successful && toStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { @@ -750,18 +761,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi return successful; } - /** - * Returns true if the windows of tasks being moved to the target stack from the source - * stack should be replaced, meaning that window manager will keep the old window around - * until the new is ready. - * @hide - */ - private static boolean replaceWindowsOnTaskMove( - int sourceWindowingMode, int targetWindowingMode) { - return sourceWindowingMode == WINDOWING_MODE_FREEFORM - || targetWindowingMode == WINDOWING_MODE_FREEFORM; - } - void cancelWindowTransition() { mWindowContainerController.cancelWindowTransition(); } @@ -781,11 +780,14 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } void touchActiveTime() { - lastActiveTime = SystemClock.elapsedRealtime(); + lastActiveTime = System.currentTimeMillis(); + if (firstActiveTime == 0) { + firstActiveTime = lastActiveTime; + } } long getInactiveDuration() { - return SystemClock.elapsedRealtime() - lastActiveTime; + return System.currentTimeMillis() - lastActiveTime; } /** Sets the original intent, and the calling uid and package. */ @@ -793,7 +795,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi mCallingUid = r.launchedFromUid; mCallingPackage = r.launchedFromPackage; setIntent(r.intent, r.info); - setLockTaskAuth(r); } /** Sets the original intent, _without_ updating the calling uid or package. */ @@ -877,6 +878,14 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } mResizeMode = info.resizeMode; mSupportsPictureInPicture = info.supportsPictureInPicture(); + mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0; + mLockTaskMode = info.lockTaskLaunchMode; + if (!mPrivileged && (mLockTaskMode == LOCK_TASK_LAUNCH_MODE_ALWAYS + || mLockTaskMode == LOCK_TASK_LAUNCH_MODE_NEVER)) { + // Non-priv apps are not allowed to use always or never, fall back to default + mLockTaskMode = LOCK_TASK_LAUNCH_MODE_DEFAULT; + } + setLockTaskAuth(); } /** Sets the original minimal width and height. */ @@ -1254,7 +1263,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi mService.notifyTaskPersisterLocked(this, false); } - if (inPinnedWindowingMode()) { + if (getStackId() == PINNED_STACK_ID) { // We normally notify listeners of task stack changes on pause, however pinned stack // activities are normally in the paused state so no notification will be sent there // before the activity is removed. We send it here so instead. @@ -1413,17 +1422,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } void setLockTaskAuth() { - setLockTaskAuth(getRootActivity()); - } - - private void setLockTaskAuth(@Nullable ActivityRecord r) { - if (r == null) { - mLockTaskAuth = LOCK_TASK_AUTH_PINNABLE; - return; - } - final String pkg = (realActivity != null) ? realActivity.getPackageName() : null; - switch (r.lockTaskLaunchMode) { + switch (mLockTaskMode) { case LOCK_TASK_LAUNCH_MODE_DEFAULT: mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg) ? LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE; @@ -1493,7 +1493,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi * @return True if the requested bounds are okay for a resizing request. */ private boolean canResizeToBounds(Rect bounds) { - if (bounds == null || !inFreeformWindowingMode()) { + if (bounds == null || getStackId() != FREEFORM_WORKSPACE_STACK_ID) { // Note: If not on the freeform workspace, we ignore the bounds. return true; } @@ -1559,7 +1559,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi // values in the TaskRecord. String label = null; String iconFilename = null; - int iconResource = -1; int colorPrimary = 0; int colorBackground = 0; int statusBarColor = 0; @@ -1571,9 +1570,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi if (label == null) { label = r.taskDescription.getLabel(); } - if (iconResource == -1) { - iconResource = r.taskDescription.getIconResource(); - } if (iconFilename == null) { iconFilename = r.taskDescription.getIconFilename(); } @@ -1588,8 +1584,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } topActivity = false; } - lastTaskDescription = new TaskDescription(label, null, iconResource, iconFilename, - colorPrimary, colorBackground, statusBarColor, navigationBarColor); + lastTaskDescription = new TaskDescription(label, null, iconFilename, colorPrimary, + colorBackground, statusBarColor, navigationBarColor); if (mWindowContainerController != null) { mWindowContainerController.setTaskDescription(lastTaskDescription); } @@ -1651,6 +1647,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi out.attribute(null, ATTR_USERID, String.valueOf(userId)); out.attribute(null, ATTR_USER_SETUP_COMPLETE, String.valueOf(mUserSetupComplete)); out.attribute(null, ATTR_EFFECTIVE_UID, String.valueOf(effectiveUid)); + out.attribute(null, ATTR_FIRSTACTIVETIME, String.valueOf(firstActiveTime)); + out.attribute(null, ATTR_LASTACTIVETIME, String.valueOf(lastActiveTime)); out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved)); out.attribute(null, ATTR_NEVERRELINQUISH, String.valueOf(mNeverRelinquishIdentity)); if (lastDescription != null) { @@ -1668,6 +1666,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi out.attribute(null, ATTR_RESIZE_MODE, String.valueOf(mResizeMode)); out.attribute(null, ATTR_SUPPORTS_PICTURE_IN_PICTURE, String.valueOf(mSupportsPictureInPicture)); + out.attribute(null, ATTR_PRIVILEGED, String.valueOf(mPrivileged)); if (mLastNonFullscreenBounds != null) { out.attribute( null, ATTR_NON_FULLSCREEN_BOUNDS, mLastNonFullscreenBounds.flattenToString()); @@ -1722,6 +1721,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi boolean userSetupComplete = true; int effectiveUid = -1; String lastDescription = null; + long firstActiveTime = -1; + long lastActiveTime = -1; long lastTimeOnTop = 0; boolean neverRelinquishIdentity = true; int taskId = INVALID_TASK_ID; @@ -1735,6 +1736,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi String callingPackage = ""; int resizeMode = RESIZE_MODE_FORCE_RESIZEABLE; boolean supportsPictureInPicture = false; + boolean privileged = false; Rect bounds = null; int minWidth = INVALID_MIN_SIZE; int minHeight = INVALID_MIN_SIZE; @@ -1772,6 +1774,10 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi effectiveUid = Integer.parseInt(attrValue); } else if (ATTR_TASKTYPE.equals(attrName)) { taskType = Integer.parseInt(attrValue); + } else if (ATTR_FIRSTACTIVETIME.equals(attrName)) { + firstActiveTime = Long.parseLong(attrValue); + } else if (ATTR_LASTACTIVETIME.equals(attrName)) { + lastActiveTime = Long.parseLong(attrValue); } else if (ATTR_LASTDESCRIPTION.equals(attrName)) { lastDescription = attrValue; } else if (ATTR_LASTTIMEMOVED.equals(attrName)) { @@ -1796,6 +1802,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi resizeMode = Integer.parseInt(attrValue); } else if (ATTR_SUPPORTS_PICTURE_IN_PICTURE.equals(attrName)) { supportsPictureInPicture = Boolean.parseBoolean(attrValue); + } else if (ATTR_PRIVILEGED.equals(attrName)) { + privileged = Boolean.parseBoolean(attrValue); } else if (ATTR_NON_FULLSCREEN_BOUNDS.equals(attrName)) { bounds = Rect.unflattenFromString(attrValue); } else if (ATTR_MIN_WIDTH.equals(attrName)) { @@ -1880,10 +1888,10 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent, affinityIntent, affinity, rootAffinity, realActivity, origActivity, rootHasReset, autoRemoveRecents, askedCompatMode, userId, effectiveUid, lastDescription, - activities, lastTimeOnTop, neverRelinquishIdentity, taskDescription, - taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor, callingUid, - callingPackage, resizeMode, supportsPictureInPicture, realActivitySuspended, - userSetupComplete, minWidth, minHeight); + activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity, + taskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor, + callingUid, callingPackage, resizeMode, supportsPictureInPicture, privileged, + realActivitySuspended, userSetupComplete, minWidth, minHeight); task.updateOverrideConfiguration(bounds); for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) { @@ -1903,7 +1911,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi // If the task has no requested minimal size, we'd like to enforce a minimal size // so that the user can not render the task too small to manipulate. We don't need // to do this for the pinned stack as the bounds are controlled by the system. - if (!inPinnedWindowingMode()) { + if (getStackId() != PINNED_STACK_ID) { if (minWidth == INVALID_MIN_SIZE) { minWidth = mService.mStackSupervisor.mDefaultMinSizeOfResizeableTask; } @@ -2077,7 +2085,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi return; } - if (inStack.inFreeformWindowingMode()) { + if (inStack.mStackId == FREEFORM_WORKSPACE_STACK_ID) { if (!isResizeable()) { throw new IllegalArgumentException("Can not position non-resizeable task=" + this + " in stack=" + inStack); @@ -2212,6 +2220,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode)); pw.print(" mSupportsPictureInPicture=" + mSupportsPictureInPicture); pw.print(" isResizeable=" + isResizeable()); + pw.print(" firstActiveTime=" + firstActiveTime); pw.print(" lastActiveTime=" + lastActiveTime); pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)"); } diff --git a/com/android/server/appwidget/AppWidgetServiceImpl.java b/com/android/server/appwidget/AppWidgetServiceImpl.java index 51afada2..a6aaaa67 100644 --- a/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -71,6 +71,7 @@ import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; import android.service.appwidget.AppWidgetServiceDumpProto; import android.service.appwidget.WidgetProto; import android.text.TextUtils; @@ -158,9 +159,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // Bump if the stored widgets need to be upgraded. private static final int CURRENT_VERSION = 1; - // Every widget update request is associated which an increasing sequence number. This is - // used to verify which request has successfully been received by the host. - private static final AtomicLong UPDATE_COUNTER = new AtomicLong(); + private static final AtomicLong REQUEST_COUNTER = new AtomicLong(); private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -815,9 +814,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku Host host = lookupOrAddHostLocked(id); host.callbacks = callbacks; - long updateSequenceNo = UPDATE_COUNTER.incrementAndGet(); int N = appWidgetIds.length; ArrayList<PendingHostUpdate> outUpdates = new ArrayList<>(N); + LongSparseArray<PendingHostUpdate> updatesMap = new LongSparseArray<>(); for (int i = 0; i < N; i++) { if (host.getPendingUpdatesForId(appWidgetIds[i], updatesMap)) { @@ -829,8 +828,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } } - // Reset the update counter once all the updates have been calculated - host.lastWidgetUpdateSequenceNo = updateSequenceNo; return new ParceledListSlice<>(outUpdates); } } @@ -1917,9 +1914,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // method with a wrong id. In that case, ignore the call. return; } - long requestId = UPDATE_COUNTER.incrementAndGet(); + long requestId = REQUEST_COUNTER.incrementAndGet(); if (widget != null) { - widget.updateSequenceNos.put(viewId, requestId); + widget.updateRequestIds.put(viewId, requestId); } if (widget == null || widget.host == null || widget.host.zombie || widget.host.callbacks == null || widget.provider == null @@ -1944,7 +1941,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku int appWidgetId, int viewId, long requestId) { try { callbacks.viewDataChanged(appWidgetId, viewId); - host.lastWidgetUpdateSequenceNo = requestId; + host.lastWidgetUpdateRequestId = requestId; } catch (RemoteException re) { // It failed; remove the callback. No need to prune because // we know that this host is still referenced by this instance. @@ -1991,9 +1988,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) { - long requestId = UPDATE_COUNTER.incrementAndGet(); + long requestId = REQUEST_COUNTER.incrementAndGet(); if (widget != null) { - widget.updateSequenceNos.put(ID_VIEWS_UPDATE, requestId); + widget.updateRequestIds.put(ID_VIEWS_UPDATE, requestId); } if (widget == null || widget.provider == null || widget.provider.zombie || widget.host.callbacks == null || widget.host.zombie) { @@ -2016,7 +2013,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku int appWidgetId, RemoteViews views, long requestId) { try { callbacks.updateAppWidget(appWidgetId, views); - host.lastWidgetUpdateSequenceNo = requestId; + host.lastWidgetUpdateRequestId = requestId; } catch (RemoteException re) { synchronized (mLock) { Slog.e(TAG, "Widget host dead: " + host.id, re); @@ -2026,11 +2023,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void scheduleNotifyProviderChangedLocked(Widget widget) { - long requestId = UPDATE_COUNTER.incrementAndGet(); + long requestId = REQUEST_COUNTER.incrementAndGet(); if (widget != null) { // When the provider changes, reset everything else. - widget.updateSequenceNos.clear(); - widget.updateSequenceNos.append(ID_PROVIDER_CHANGED, requestId); + widget.updateRequestIds.clear(); + widget.updateRequestIds.append(ID_PROVIDER_CHANGED, requestId); } if (widget == null || widget.provider == null || widget.provider.zombie || widget.host.callbacks == null || widget.host.zombie) { @@ -2053,7 +2050,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku int appWidgetId, AppWidgetProviderInfo info, long requestId) { try { callbacks.providerChanged(appWidgetId, info); - host.lastWidgetUpdateSequenceNo = requestId; + host.lastWidgetUpdateRequestId = requestId; } catch (RemoteException re) { synchronized (mLock){ Slog.e(TAG, "Widget host dead: " + host.id, re); @@ -3890,11 +3887,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku boolean zombie; // if we're in safe mode, don't prune this just because nobody references it int tag = TAG_UNDEFINED; // for use while saving state (the index) - // Sequence no for the last update successfully sent. This is updated whenever a - // widget update is successfully sent to the host callbacks. As all new/undelivered updates - // will have sequenceNo greater than this, all those updates will be sent when the host - // callbacks are attached again. - long lastWidgetUpdateSequenceNo; + long lastWidgetUpdateRequestId; // request id for the last update successfully sent public int getUserId() { return UserHandle.getUserId(id.uid); @@ -3921,18 +3914,18 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku */ public boolean getPendingUpdatesForId(int appWidgetId, LongSparseArray<PendingHostUpdate> outUpdates) { - long updateSequenceNo = lastWidgetUpdateSequenceNo; + long updateRequestId = lastWidgetUpdateRequestId; int N = widgets.size(); for (int i = 0; i < N; i++) { Widget widget = widgets.get(i); if (widget.appWidgetId == appWidgetId) { outUpdates.clear(); - for (int j = widget.updateSequenceNos.size() - 1; j >= 0; j--) { - long requestId = widget.updateSequenceNos.valueAt(j); - if (requestId <= updateSequenceNo) { + for (int j = widget.updateRequestIds.size() - 1; j >= 0; j--) { + long requestId = widget.updateRequestIds.valueAt(j); + if (requestId <= updateRequestId) { continue; } - int id = widget.updateSequenceNos.keyAt(j); + int id = widget.updateRequestIds.keyAt(j); final PendingHostUpdate update; switch (id) { case ID_PROVIDER_CHANGED: @@ -4028,8 +4021,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku RemoteViews maskedViews; Bundle options; Host host; - // Map of request type to updateSequenceNo. - SparseLongArray updateSequenceNos = new SparseLongArray(2); + // Request ids for various operations + SparseLongArray updateRequestIds = new SparseLongArray(2); @Override public String toString() { diff --git a/com/android/server/audio/PlaybackActivityMonitor.java b/com/android/server/audio/PlaybackActivityMonitor.java index 49431733..6506cf7f 100644 --- a/com/android/server/audio/PlaybackActivityMonitor.java +++ b/com/android/server/audio/PlaybackActivityMonitor.java @@ -184,15 +184,11 @@ public final class PlaybackActivityMonitor } } - private static final int FLAGS_FOR_SILENCE_OVERRIDE = - AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY | - AudioAttributes.FLAG_BYPASS_MUTE; - private void checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event) { if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED || apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) { - if ((apc.getAudioAttributes().getAllFlags() & FLAGS_FOR_SILENCE_OVERRIDE) - == FLAGS_FOR_SILENCE_OVERRIDE && + if ((apc.getAudioAttributes().getAllFlags() & + AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0 && apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_ALARM && mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, apc.getClientPid(), apc.getClientUid()) == diff --git a/com/android/server/autofill/AutofillManagerServiceImpl.java b/com/android/server/autofill/AutofillManagerServiceImpl.java index 880f236c..862070ad 100644 --- a/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -216,12 +216,9 @@ final class AutofillManagerServiceImpl { serviceComponent = ComponentName.unflattenFromString(componentName); serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 0, mUserId); - if (serviceInfo == null) { - Slog.e(TAG, "Bad AutofillService name: " + componentName); - } } catch (RuntimeException | RemoteException e) { - Slog.e(TAG, "Error getting service info for '" + componentName + "': " + e); - serviceInfo = null; + Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e); + return; } } try { @@ -231,24 +228,21 @@ final class AutofillManagerServiceImpl { if (sDebug) Slog.d(TAG, "Set component for user " + mUserId + " as " + mInfo); } else { mInfo = null; - if (sDebug) { - Slog.d(TAG, "Reset component for user " + mUserId + " (" + componentName + ")"); - } + if (sDebug) Slog.d(TAG, "Reset component for user " + mUserId); } - } catch (Exception e) { - Slog.e(TAG, "Bad AutofillServiceInfo for '" + componentName + "': " + e); - mInfo = null; - } - final boolean isEnabled = isEnabled(); - if (wasEnabled != isEnabled) { - if (!isEnabled) { - final int sessionCount = mSessions.size(); - for (int i = sessionCount - 1; i >= 0; i--) { - final Session session = mSessions.valueAt(i); - session.removeSelfLocked(); + final boolean isEnabled = isEnabled(); + if (wasEnabled != isEnabled) { + if (!isEnabled) { + final int sessionCount = mSessions.size(); + for (int i = sessionCount - 1; i >= 0; i--) { + final Session session = mSessions.valueAt(i); + session.removeSelfLocked(); + } } + sendStateToClients(false); } - sendStateToClients(false); + } catch (Exception e) { + Slog.e(TAG, "Bad AutofillService '" + componentName + "': " + e); } } diff --git a/com/android/server/autofill/Session.java b/com/android/server/autofill/Session.java index 3c12d670..ed00ffed 100644 --- a/com/android/server/autofill/Session.java +++ b/com/android/server/autofill/Session.java @@ -495,7 +495,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState notifyUnavailableToClient(false); } synchronized (mLock) { - processResponseLocked(response, null, requestFlags); + processResponseLocked(response, requestFlags); } final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST, servicePackageName) @@ -762,21 +762,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT); - final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE); - if (sDebug) { - Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result - + ", clientState=" + newClientState); - } + if (sDebug) Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result); if (result instanceof FillResponse) { writeLog(MetricsEvent.AUTOFILL_AUTHENTICATED); - replaceResponseLocked(authenticatedResponse, (FillResponse) result, newClientState); + replaceResponseLocked(authenticatedResponse, (FillResponse) result); } else if (result instanceof Dataset) { if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) { writeLog(MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED); - if (newClientState != null) { - if (sDebug) Slog.d(TAG, "Updating client state from auth dataset"); - mClientState = newClientState; - } final Dataset dataset = (Dataset) result; authenticatedResponse.getDatasets().set(datasetIdx, dataset); autoFill(requestId, datasetIdx, dataset, false); @@ -1499,14 +1491,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState ArraySet<AutofillId> trackedViews = null; boolean saveOnAllViewsInvisible = false; - boolean saveOnFinish = true; final SaveInfo saveInfo = response.getSaveInfo(); - final AutofillId saveTriggerId; if (saveInfo != null) { - saveTriggerId = saveInfo.getTriggerId(); - if (saveTriggerId != null) { - writeLog(MetricsEvent.AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION); - } saveOnAllViewsInvisible = (saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0; @@ -1523,12 +1509,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Collections.addAll(trackedViews, saveInfo.getOptionalIds()); } } - if ((saveInfo.getFlags() & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) { - saveOnFinish = false; - } - - } else { - saveTriggerId = null; } // Must also track that are part of datasets, otherwise the FillUI won't be hidden when @@ -1553,18 +1533,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState try { if (sVerbose) { - Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds - + " (triggering on " + saveTriggerId + ")"); + Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds); } mClient.setTrackedViews(id, toArray(trackedViews), saveOnAllViewsInvisible, - saveOnFinish, toArray(fillableIds), saveTriggerId); + toArray(fillableIds)); } catch (RemoteException e) { Slog.w(TAG, "Cannot set tracked ids", e); } } private void replaceResponseLocked(@NonNull FillResponse oldResponse, - @NonNull FillResponse newResponse, @Nullable Bundle newClientState) { + @NonNull FillResponse newResponse) { // Disassociate view states with the old response setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, true); // Move over the id @@ -1572,7 +1551,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Replace the old response mResponses.put(newResponse.getRequestId(), newResponse); // Now process the new response - processResponseLocked(newResponse, newClientState, 0); + processResponseLocked(newResponse, 0); } private void processNullResponseLocked(int flags) { @@ -1586,8 +1565,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState removeSelf(); } - private void processResponseLocked(@NonNull FillResponse newResponse, - @Nullable Bundle newClientState, int flags) { + private void processResponseLocked(@NonNull FillResponse newResponse, int flags) { // Make sure we are hiding the UI which will be shown // only if handling the current response requires it. mUi.hideAll(this); @@ -1595,15 +1573,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final int requestId = newResponse.getRequestId(); if (sVerbose) { Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId - + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse - + ",newClientState=" + newClientState); + + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse); } if (mResponses == null) { mResponses = new SparseArray<>(4); } mResponses.put(requestId, newResponse); - mClientState = newClientState != null ? newClientState : newResponse.getClientState(); + mClientState = newResponse.getClientState(); setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false); updateTrackedIdsLocked(); diff --git a/com/android/server/backup/BackupManagerService.java b/com/android/server/backup/BackupManagerService.java index f9213aab..eabe21fe 100644 --- a/com/android/server/backup/BackupManagerService.java +++ b/com/android/server/backup/BackupManagerService.java @@ -319,6 +319,7 @@ public class BackupManagerService implements BackupManagerServiceInterface { boolean mProvisioned; boolean mAutoRestore; PowerManager.WakeLock mWakelock; + HandlerThread mHandlerThread; BackupHandler mBackupHandler; PendingIntent mRunBackupIntent, mRunInitIntent; BroadcastReceiver mRunBackupReceiver, mRunInitReceiver; @@ -408,37 +409,43 @@ public class BackupManagerService implements BackupManagerServiceInterface { // Called through the trampoline from onUnlockUser(), then we buck the work // off to the background thread to keep the unlock time down. public void unlockSystemUser() { - // Migrate legacy setting - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate"); - if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) { - if (DEBUG) { - Slog.i(TAG, "Backup enable apparently not migrated"); - } - final ContentResolver r = sInstance.mContext.getContentResolver(); - final int enableState = Settings.Secure.getIntForUser(r, - Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM); - if (enableState >= 0) { + mBackupHandler.post(() -> { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init"); + sInstance.initialize(UserHandle.USER_SYSTEM); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + // Migrate legacy setting + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate"); + if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) { if (DEBUG) { - Slog.i(TAG, "Migrating enable state " + (enableState != 0)); + Slog.i(TAG, "Backup enable apparently not migrated"); } - writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM); - Settings.Secure.putStringForUser(r, - Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM); - } else { - if (DEBUG) { - Slog.i(TAG, "Backup not yet configured; retaining null enable state"); + final ContentResolver r = sInstance.mContext.getContentResolver(); + final int enableState = Settings.Secure.getIntForUser(r, + Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM); + if (enableState >= 0) { + if (DEBUG) { + Slog.i(TAG, "Migrating enable state " + (enableState != 0)); + } + writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM); + Settings.Secure.putStringForUser(r, + Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM); + } else { + if (DEBUG) { + Slog.i(TAG, "Backup not yet configured; retaining null enable state"); + } } } - } - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable"); - try { - sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM)); - } catch (RemoteException e) { - // can't happen; it's a local object - } - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable"); + try { + sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM)); + } catch (RemoteException e) { + // can't happen; it's a local object + } + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + }); } class ProvisionedObserver extends ContentObserver { @@ -1213,7 +1220,7 @@ public class BackupManagerService implements BackupManagerServiceInterface { // ----- Main service implementation ----- - public BackupManagerService(Context context, Trampoline parent, HandlerThread backupThread) { + public BackupManagerService(Context context, Trampoline parent) { mContext = context; mPackageManager = context.getPackageManager(); mPackageManagerBinder = AppGlobals.getPackageManager(); @@ -1226,7 +1233,9 @@ public class BackupManagerService implements BackupManagerServiceInterface { mBackupManagerBinder = Trampoline.asInterface(parent.asBinder()); // spin up the backup/restore handler thread - mBackupHandler = new BackupHandler(backupThread.getLooper()); + mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND); + mHandlerThread.start(); + mBackupHandler = new BackupHandler(mHandlerThread.getLooper()); // Set up our bookkeeping final ContentResolver resolver = context.getContentResolver(); @@ -1351,7 +1360,7 @@ public class BackupManagerService implements BackupManagerServiceInterface { if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport); mTransportManager = new TransportManager(context, transportWhitelist, currentTransport, - mTransportBoundListener, backupThread.getLooper()); + mTransportBoundListener, mHandlerThread.getLooper()); mTransportManager.registerAllTransports(); // Now that we know about valid backup participants, parse any diff --git a/com/android/server/backup/RefactoredBackupManagerService.java b/com/android/server/backup/RefactoredBackupManagerService.java index 20f23690..f2980659 100644 --- a/com/android/server/backup/RefactoredBackupManagerService.java +++ b/com/android/server/backup/RefactoredBackupManagerService.java @@ -237,6 +237,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter private boolean mProvisioned; private boolean mAutoRestore; private PowerManager.WakeLock mWakelock; + private HandlerThread mHandlerThread; private BackupHandler mBackupHandler; private PendingIntent mRunBackupIntent; private PendingIntent mRunInitIntent; @@ -555,37 +556,43 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter // Called through the trampoline from onUnlockUser(), then we buck the work // off to the background thread to keep the unlock time down. public void unlockSystemUser() { - // Migrate legacy setting - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate"); - if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) { - if (DEBUG) { - Slog.i(TAG, "Backup enable apparently not migrated"); - } - final ContentResolver r = sInstance.mContext.getContentResolver(); - final int enableState = Settings.Secure.getIntForUser(r, - Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM); - if (enableState >= 0) { + mBackupHandler.post(() -> { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init"); + sInstance.initialize(UserHandle.USER_SYSTEM); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + // Migrate legacy setting + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate"); + if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) { if (DEBUG) { - Slog.i(TAG, "Migrating enable state " + (enableState != 0)); + Slog.i(TAG, "Backup enable apparently not migrated"); } - writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM); - Settings.Secure.putStringForUser(r, - Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM); - } else { - if (DEBUG) { - Slog.i(TAG, "Backup not yet configured; retaining null enable state"); + final ContentResolver r = sInstance.mContext.getContentResolver(); + final int enableState = Settings.Secure.getIntForUser(r, + Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM); + if (enableState >= 0) { + if (DEBUG) { + Slog.i(TAG, "Migrating enable state " + (enableState != 0)); + } + writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM); + Settings.Secure.putStringForUser(r, + Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM); + } else { + if (DEBUG) { + Slog.i(TAG, "Backup not yet configured; retaining null enable state"); + } } } - } - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable"); - try { - sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM)); - } catch (RemoteException e) { - // can't happen; it's a local object - } - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable"); + try { + sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM)); + } catch (RemoteException e) { + // can't happen; it's a local object + } + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + }); } // Bookkeeping of in-flight operations for timeout etc. purposes. The operation @@ -722,8 +729,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter // ----- Main service implementation ----- - public RefactoredBackupManagerService(Context context, Trampoline parent, - HandlerThread backupThread) { + public RefactoredBackupManagerService(Context context, Trampoline parent) { mContext = context; mPackageManager = context.getPackageManager(); mPackageManagerBinder = AppGlobals.getPackageManager(); @@ -736,7 +742,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter mBackupManagerBinder = Trampoline.asInterface(parent.asBinder()); // spin up the backup/restore handler thread - mBackupHandler = new BackupHandler(this, backupThread.getLooper()); + mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND); + mHandlerThread.start(); + mBackupHandler = new BackupHandler(this, mHandlerThread.getLooper()); // Set up our bookkeeping final ContentResolver resolver = context.getContentResolver(); @@ -816,7 +824,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport); mTransportManager = new TransportManager(context, transportWhitelist, currentTransport, - mTransportBoundListener, backupThread.getLooper()); + mTransportBoundListener, mHandlerThread.getLooper()); mTransportManager.registerAllTransports(); // Now that we know about valid backup participants, parse any diff --git a/com/android/server/backup/Trampoline.java b/com/android/server/backup/Trampoline.java index 9847edf8..9739e380 100644 --- a/com/android/server/backup/Trampoline.java +++ b/com/android/server/backup/Trampoline.java @@ -28,15 +28,11 @@ import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Environment; -import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; -import android.os.Looper; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; -import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; @@ -79,8 +75,6 @@ public class Trampoline extends IBackupManager.Stub { final boolean mGlobalDisable; volatile BackupManagerServiceInterface mService; - private HandlerThread mHandlerThread; - public Trampoline(Context context) { mContext = context; mGlobalDisable = isBackupDisabled(); @@ -117,11 +111,11 @@ public class Trampoline extends IBackupManager.Stub { } protected BackupManagerServiceInterface createRefactoredBackupManagerService() { - return new RefactoredBackupManagerService(mContext, this, mHandlerThread); + return new RefactoredBackupManagerService(mContext, this); } protected BackupManagerServiceInterface createBackupManagerService() { - return new BackupManagerService(mContext, this, mHandlerThread); + return new BackupManagerService(mContext, this); } // internal control API @@ -146,21 +140,10 @@ public class Trampoline extends IBackupManager.Stub { } void unlockSystemUser() { - mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND); - mHandlerThread.start(); - - Handler h = new Handler(mHandlerThread.getLooper()); - h.post(() -> { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init"); - initialize(UserHandle.USER_SYSTEM); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - - BackupManagerServiceInterface svc = mService; - Slog.i(TAG, "Unlocking system user; mService=" + mService); - if (svc != null) { - svc.unlockSystemUser(); - } - }); + BackupManagerServiceInterface svc = mService; + if (svc != null) { + svc.unlockSystemUser(); + } } public void setBackupServiceActive(final int userHandle, boolean makeActive) { diff --git a/com/android/server/clipboard/ClipboardService.java b/com/android/server/clipboard/ClipboardService.java index 0c9d70a9..e6228d46 100644 --- a/com/android/server/clipboard/ClipboardService.java +++ b/com/android/server/clipboard/ClipboardService.java @@ -435,12 +435,11 @@ public class ClipboardService extends SystemService { } private boolean isDeviceLocked() { - int callingUserId = UserHandle.getCallingUserId(); final long token = Binder.clearCallingIdentity(); try { final KeyguardManager keyguardManager = getContext().getSystemService( KeyguardManager.class); - return keyguardManager != null && keyguardManager.isDeviceLocked(callingUserId); + return keyguardManager != null && keyguardManager.isDeviceLocked(); } finally { Binder.restoreCallingIdentity(token); } diff --git a/com/android/server/connectivity/Tethering.java b/com/android/server/connectivity/Tethering.java index d7cd81ff..5583e86c 100644 --- a/com/android/server/connectivity/Tethering.java +++ b/com/android/server/connectivity/Tethering.java @@ -1371,7 +1371,6 @@ public class Tethering extends BaseNetworkObserver { sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS); } } - mUpstreamNetworkMonitor.setCurrentUpstream((ns != null) ? ns.network : null); setUpstreamNetwork(ns); } diff --git a/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index b35ed751..c5f75280 100644 --- a/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -95,10 +95,7 @@ public class UpstreamNetworkMonitor { private NetworkCallback mDefaultNetworkCallback; private NetworkCallback mMobileNetworkCallback; private boolean mDunRequired; - // The current system default network (not really used yet). - private Network mDefaultInternetNetwork; - // The current upstream network used for tethering. - private Network mTetheringUpstreamNetwork; + private Network mCurrentDefault; public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) { mContext = ctx; @@ -133,12 +130,10 @@ public class UpstreamNetworkMonitor { releaseCallback(mDefaultNetworkCallback); mDefaultNetworkCallback = null; - mDefaultInternetNetwork = null; releaseCallback(mListenAllCallback); mListenAllCallback = null; - mTetheringUpstreamNetwork = null; mNetworkMap.clear(); } @@ -212,7 +207,7 @@ public class UpstreamNetworkMonitor { break; default: /* If we've found an active upstream connection that's not DUN/HIPRI - * we should stop any outstanding DUN/HIPRI requests. + * we should stop any outstanding DUN/HIPRI start requests. * * If we found NONE we don't want to do this as we want any previous * requests to keep trying to bring up something we can use. @@ -224,10 +219,6 @@ public class UpstreamNetworkMonitor { return typeStatePair.ns; } - public void setCurrentUpstream(Network upstream) { - mTetheringUpstreamNetwork = upstream; - } - public Set<IpPrefix> getLocalPrefixes() { return (Set<IpPrefix>) mLocalPrefixes.clone(); } @@ -259,7 +250,7 @@ public class UpstreamNetworkMonitor { // These request*() calls can be deleted post oag/339444. return; } - mDefaultInternetNetwork = network; + mCurrentDefault = network; break; case CALLBACK_MOBILE_REQUEST: @@ -311,13 +302,6 @@ public class UpstreamNetworkMonitor { network, newNc)); } - // Log changes in upstream network signal strength, if available. - if (network.equals(mTetheringUpstreamNetwork) && newNc.hasSignalStrength()) { - final int newSignal = newNc.getSignalStrength(); - final String prevSignal = getSignalStrength(prev.networkCapabilities); - mLog.logf("upstream network signal strength: %s -> %s", prevSignal, newSignal); - } - mNetworkMap.put(network, new NetworkState( null, prev.linkProperties, newNc, network, null, null)); // TODO: If sufficient information is available to select a more @@ -346,21 +330,9 @@ public class UpstreamNetworkMonitor { notifyTarget(EVENT_ON_LINKPROPERTIES, network); } - private void handleSuspended(int callbackType, Network network) { - if (callbackType != CALLBACK_LISTEN_ALL) return; - if (!network.equals(mTetheringUpstreamNetwork)) return; - mLog.log("SUSPENDED current upstream: " + network); - } - - private void handleResumed(int callbackType, Network network) { - if (callbackType != CALLBACK_LISTEN_ALL) return; - if (!network.equals(mTetheringUpstreamNetwork)) return; - mLog.log("RESUMED current upstream: " + network); - } - private void handleLost(int callbackType, Network network) { if (callbackType == CALLBACK_TRACK_DEFAULT) { - mDefaultInternetNetwork = null; + mCurrentDefault = null; // Receiving onLost() for a default network does not necessarily // mean the network is gone. We wait for a separate notification // on either the LISTEN_ALL or MOBILE_REQUEST callbacks before @@ -429,15 +401,8 @@ public class UpstreamNetworkMonitor { recomputeLocalPrefixes(); } - @Override - public void onNetworkSuspended(Network network) { - handleSuspended(mCallbackType, network); - } - - @Override - public void onNetworkResumed(Network network) { - handleResumed(mCallbackType, network); - } + // TODO: Handle onNetworkSuspended(); + // TODO: Handle onNetworkResumed(); @Override public void onLost(Network network) { @@ -502,9 +467,4 @@ public class UpstreamNetworkMonitor { return prefixSet; } - - private static String getSignalStrength(NetworkCapabilities nc) { - if (nc == null || !nc.hasSignalStrength()) return "unknown"; - return Integer.toString(nc.getSignalStrength()); - } } diff --git a/com/android/server/display/AutomaticBrightnessController.java b/com/android/server/display/AutomaticBrightnessController.java index 9a6e6094..9aabdab7 100644 --- a/com/android/server/display/AutomaticBrightnessController.java +++ b/com/android/server/display/AutomaticBrightnessController.java @@ -29,7 +29,6 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; -import android.os.Trace; import android.text.format.DateUtils; import android.util.EventLog; import android.util.MathUtils; @@ -305,7 +304,6 @@ class AutomaticBrightnessController { } private void handleLightSensorEvent(long time, float lux) { - Trace.traceCounter(Trace.TRACE_TAG_POWER, "ALS", (int) lux); mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); if (mAmbientLightRingBuffer.size() == 0) { diff --git a/com/android/server/display/DisplayPowerController.java b/com/android/server/display/DisplayPowerController.java index f930b523..f8e58362 100644 --- a/com/android/server/display/DisplayPowerController.java +++ b/com/android/server/display/DisplayPowerController.java @@ -683,8 +683,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Configure auto-brightness. boolean autoBrightnessEnabled = false; if (mAutomaticBrightnessController != null) { - final boolean autoBrightnessEnabledInDoze = - mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state); + final boolean autoBrightnessEnabledInDoze = mAllowAutoBrightnessWhileDozingConfig + && (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND); autoBrightnessEnabled = mPowerRequest.useAutoBrightness && (state == Display.STATE_ON || autoBrightnessEnabledInDoze) && brightness < 0; @@ -726,7 +726,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } // Use default brightness when dozing unless overridden. - if (brightness < 0 && Display.isDozeState(state)) { + if (brightness < 0 && (state == Display.STATE_DOZE + || state == Display.STATE_DOZE_SUSPEND)) { brightness = mScreenBrightnessDozeConfig; } @@ -776,6 +777,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Skip the animation when the screen is off or suspended or transition to/from VR. if (!mPendingScreenOff) { if (mSkipScreenOnBrightnessRamp) { + if (state == Display.STATE_ON) { if (mSkipRampState == RAMP_STATE_SKIP_NONE && mDozing) { mInitialAutoBrightness = brightness; @@ -792,25 +794,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } - final boolean wasOrWillBeInVr = - (state == Display.STATE_VR || oldState == Display.STATE_VR); - final boolean initialRampSkip = - state == Display.STATE_ON && mSkipRampState != RAMP_STATE_SKIP_NONE; - // While dozing, sometimes the brightness is split into buckets. Rather than animating - // through the buckets, which is unlikely to be smooth in the first place, just jump - // right to the suggested brightness. - final boolean hasBrightnessBuckets = - Display.isDozeState(state) && mBrightnessBucketsInDozeConfig; - // If the color fade is totally covering the screen then we can change the backlight - // level without it being a noticeable jump since any actual content isn't yet visible. - final boolean isDisplayContentVisible = - mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f; - if (initialRampSkip || hasBrightnessBuckets - || wasOrWillBeInVr || !isDisplayContentVisible) { - animateScreenBrightness(brightness, 0); - } else { + boolean wasOrWillBeInVr = (state == Display.STATE_VR || oldState == Display.STATE_VR); + if ((state == Display.STATE_ON + && mSkipRampState == RAMP_STATE_SKIP_NONE + || state == Display.STATE_DOZE && !mBrightnessBucketsInDozeConfig) + && !wasOrWillBeInVr) { animateScreenBrightness(brightness, slowChange ? mBrightnessRampRateSlow : mBrightnessRampRateFast); + } else { + animateScreenBrightness(brightness, 0); } } @@ -933,7 +925,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } if (!reportOnly) { - Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state); mPowerState.setScreenState(state); // Tell battery stats about the transition. try { diff --git a/com/android/server/job/controllers/TimeController.java b/com/android/server/job/controllers/TimeController.java index ee4c606f..d90699a6 100644 --- a/com/android/server/job/controllers/TimeController.java +++ b/com/android/server/job/controllers/TimeController.java @@ -110,7 +110,7 @@ public final class TimeController extends StateController { maybeUpdateAlarmsLocked( job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE, job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE, - new WorkSource(job.getSourceUid(), job.getSourcePackageName())); + job.getSourceUid()); } } @@ -156,7 +156,6 @@ public final class TimeController extends StateController { synchronized (mLock) { long nextExpiryTime = Long.MAX_VALUE; int nextExpiryUid = 0; - String nextExpiryPackageName = null; final long nowElapsedMillis = SystemClock.elapsedRealtime(); Iterator<JobStatus> it = mTrackedJobs.iterator(); @@ -172,13 +171,10 @@ public final class TimeController extends StateController { } else { // Sorted by expiry time, so take the next one and stop. nextExpiryTime = job.getLatestRunTimeElapsed(); nextExpiryUid = job.getSourceUid(); - nextExpiryPackageName = job.getSourcePackageName(); break; } } - setDeadlineExpiredAlarmLocked(nextExpiryTime, nextExpiryPackageName != null - ? new WorkSource(nextExpiryUid, nextExpiryPackageName) - : new WorkSource(nextExpiryUid)); + setDeadlineExpiredAlarmLocked(nextExpiryTime, nextExpiryUid); } } @@ -204,7 +200,6 @@ public final class TimeController extends StateController { final long nowElapsedMillis = SystemClock.elapsedRealtime(); long nextDelayTime = Long.MAX_VALUE; int nextDelayUid = 0; - String nextDelayPackageName = null; boolean ready = false; Iterator<JobStatus> it = mTrackedJobs.iterator(); while (it.hasNext()) { @@ -226,16 +221,13 @@ public final class TimeController extends StateController { if (nextDelayTime > jobDelayTime) { nextDelayTime = jobDelayTime; nextDelayUid = job.getSourceUid(); - nextDelayPackageName = job.getSourcePackageName(); } } } if (ready) { mStateChangedListener.onControllerStateChanged(); } - setDelayExpiredAlarmLocked(nextDelayTime, nextDelayPackageName != null - ? new WorkSource(nextDelayUid, nextDelayPackageName) - : new WorkSource(nextDelayUid)); + setDelayExpiredAlarmLocked(nextDelayTime, nextDelayUid); } } @@ -249,12 +241,12 @@ public final class TimeController extends StateController { } private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed, - WorkSource ws) { + int uid) { if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) { - setDelayExpiredAlarmLocked(delayExpiredElapsed, ws); + setDelayExpiredAlarmLocked(delayExpiredElapsed, uid); } if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) { - setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, ws); + setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, uid); } } @@ -263,11 +255,11 @@ public final class TimeController extends StateController { * delay will expire. * This alarm <b>will</b> wake up the phone. */ - private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) { + private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) { alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis); mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis; updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener, - mNextDelayExpiredElapsedMillis, ws); + mNextDelayExpiredElapsedMillis, uid); } /** @@ -275,11 +267,11 @@ public final class TimeController extends StateController { * deadline will expire. * This alarm <b>will</b> wake up the phone. */ - private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) { + private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) { alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis); mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis; updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener, - mNextJobExpiredElapsedMillis, ws); + mNextJobExpiredElapsedMillis, uid); } private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) { @@ -291,7 +283,7 @@ public final class TimeController extends StateController { } private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener, - long alarmTimeElapsed, WorkSource ws) { + long alarmTimeElapsed, int uid) { ensureAlarmServiceLocked(); if (alarmTimeElapsed == Long.MAX_VALUE) { mAlarmService.cancel(listener); @@ -300,7 +292,7 @@ public final class TimeController extends StateController { Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed); } mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsed, - AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, ws); + AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, new WorkSource(uid)); } } diff --git a/com/android/server/location/GnssLocationProvider.java b/com/android/server/location/GnssLocationProvider.java index e41c17df..0aa6a90e 100644 --- a/com/android/server/location/GnssLocationProvider.java +++ b/com/android/server/location/GnssLocationProvider.java @@ -417,11 +417,7 @@ public class GnssLocationProvider implements LocationProviderInterface { // stops output right at 600m/s, depriving this of the information of a device that reaches // greater than 600m/s, and higher than the speed of sound to avoid impacting most use cases. private static final float ITAR_SPEED_LIMIT_METERS_PER_SECOND = 400.0F; - - // TODO: improve comment - // Volatile to ensure that potentially near-concurrent outputs from HAL - // react to this value change promptly - private volatile boolean mItarSpeedLimitExceeded = false; + private boolean mItarSpeedLimitExceeded = false; // GNSS Metrics private GnssMetrics mGnssMetrics; diff --git a/com/android/server/media/MediaSessionRecord.java b/com/android/server/media/MediaSessionRecord.java index 664d2f97..0b11479a 100644 --- a/com/android/server/media/MediaSessionRecord.java +++ b/com/android/server/media/MediaSessionRecord.java @@ -784,14 +784,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { mService.enforcePhoneStatePermission(pid, uid); } mFlags = flags; - if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) { - final long token = Binder.clearCallingIdentity(); - try { - mService.setGlobalPrioritySession(MediaSessionRecord.this); - } finally { - Binder.restoreCallingIdentity(token); - } - } mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); } diff --git a/com/android/server/media/MediaSessionService.java b/com/android/server/media/MediaSessionService.java index aa652445..b9a2d184 100644 --- a/com/android/server/media/MediaSessionService.java +++ b/com/android/server/media/MediaSessionService.java @@ -178,6 +178,17 @@ public class MediaSessionService extends SystemService implements Monitor { return; } if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) { + if (mGlobalPrioritySession != record) { + Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession + + " to " + record); + mGlobalPrioritySession = record; + if (user != null && user.mPriorityStack.contains(record)) { + // Handle the global priority session separately. + // Otherwise, it will be the media button session even after it becomes + // inactive because it has been the lastly played media app. + user.mPriorityStack.removeSession(record); + } + } if (DEBUG_KEY_EVENT) { Log.d(TAG, "Global priority session is updated, active=" + record.isActive()); } @@ -193,27 +204,10 @@ public class MediaSessionService extends SystemService implements Monitor { } } - public void setGlobalPrioritySession(MediaSessionRecord record) { - synchronized (mLock) { - FullUserRecord user = getFullUserRecordLocked(record.getUserId()); - if (mGlobalPrioritySession != record) { - Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession - + " to " + record); - mGlobalPrioritySession = record; - if (user != null && user.mPriorityStack.contains(record)) { - // Handle the global priority session separately. - // Otherwise, it can be the media button session regardless of the active state - // because it or other system components might have been the lastly played media - // app. - user.mPriorityStack.removeSession(record); - } - } - } - } - private List<MediaSessionRecord> getActiveSessionsLocked(int userId) { - List<MediaSessionRecord> records = new ArrayList<>(); + List<MediaSessionRecord> records; if (userId == UserHandle.USER_ALL) { + records = new ArrayList<>(); int size = mUserRecords.size(); for (int i = 0; i < size; i++) { records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId)); @@ -222,9 +216,9 @@ public class MediaSessionService extends SystemService implements Monitor { FullUserRecord user = getFullUserRecordLocked(userId); if (user == null) { Log.w(TAG, "getSessions failed. Unknown user " + userId); - return records; + return new ArrayList<>(); } - records.addAll(user.mPriorityStack.getActiveSessions(userId)); + records = user.mPriorityStack.getActiveSessions(userId); } // Return global priority session at the first whenever it's asked. diff --git a/com/android/server/notification/NotificationManagerService.java b/com/android/server/notification/NotificationManagerService.java index 238d87b7..14cd0555 100644 --- a/com/android/server/notification/NotificationManagerService.java +++ b/com/android/server/notification/NotificationManagerService.java @@ -133,6 +133,7 @@ import android.service.notification.NotificationListenerService; import android.service.notification.NotificationRankingUpdate; import android.service.notification.NotificationRecordProto; import android.service.notification.NotificationServiceDumpProto; +import android.service.notification.NotificationServiceProto; import android.service.notification.NotificationStats; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; @@ -281,7 +282,6 @@ public class NotificationManagerService extends SystemService { private WindowManagerInternal mWindowManagerInternal; private AlarmManager mAlarmManager; private ICompanionDeviceManager mCompanionManager; - private AccessibilityManager mAccessibilityManager; final IBinder mForegroundToken = new Binder(); private WorkerHandler mHandler; @@ -1221,12 +1221,6 @@ public class NotificationManagerService extends SystemService { mUsageStats = us; } - @VisibleForTesting - void setAccessibilityManager(AccessibilityManager am) { - mAccessibilityManager = am; - } - - // TODO: All tests should use this init instead of the one-off setters above. @VisibleForTesting void init(Looper looper, IPackageManager packageManager, @@ -1241,8 +1235,6 @@ public class NotificationManagerService extends SystemService { Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE, DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE); - mAccessibilityManager = - (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); mAm = ActivityManager.getService(); mPackageManager = packageManager; mPackageManagerClient = packageManagerClient; @@ -3262,7 +3254,7 @@ public class NotificationManagerService extends SystemService { final NotificationRecord nr = mNotificationList.get(i); if (filter.filtered && !filter.matches(nr.sbn)) continue; nr.dump(proto, filter.redact); - proto.write(NotificationRecordProto.STATE, NotificationRecordProto.POSTED); + proto.write(NotificationRecordProto.STATE, NotificationServiceProto.POSTED); } } N = mEnqueuedNotifications.size(); @@ -3271,7 +3263,7 @@ public class NotificationManagerService extends SystemService { final NotificationRecord nr = mEnqueuedNotifications.get(i); if (filter.filtered && !filter.matches(nr.sbn)) continue; nr.dump(proto, filter.redact); - proto.write(NotificationRecordProto.STATE, NotificationRecordProto.ENQUEUED); + proto.write(NotificationRecordProto.STATE, NotificationServiceProto.ENQUEUED); } } List<NotificationRecord> snoozed = mSnoozeHelper.getSnoozed(); @@ -3281,7 +3273,7 @@ public class NotificationManagerService extends SystemService { final NotificationRecord nr = snoozed.get(i); if (filter.filtered && !filter.matches(nr.sbn)) continue; nr.dump(proto, filter.redact); - proto.write(NotificationRecordProto.STATE, NotificationRecordProto.SNOOZED); + proto.write(NotificationRecordProto.STATE, NotificationServiceProto.SNOOZED); } } proto.end(records); @@ -4125,16 +4117,13 @@ public class NotificationManagerService extends SystemService { // These are set inside the conditional if the notification is allowed to make noise. boolean hasValidVibrate = false; boolean hasValidSound = false; - boolean sentAccessibilityEvent = false; - // If the notification will appear in the status bar, it should send an accessibility - // event - if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) { - sendAccessibilityEvent(notification, record.sbn.getPackageName()); - sentAccessibilityEvent = true; - } if (aboveThreshold && isNotificationForCurrentUser(record)) { - + // If the notification will appear in the status bar, it should send an accessibility + // event + if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) { + sendAccessibilityEvent(notification, record.sbn.getPackageName()); + } if (mSystemReady && mAudioManager != null) { Uri soundUri = record.getSound(); hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri); @@ -4152,10 +4141,6 @@ public class NotificationManagerService extends SystemService { boolean hasAudibleAlert = hasValidSound || hasValidVibrate; if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) { - if (!sentAccessibilityEvent) { - sendAccessibilityEvent(notification, record.sbn.getPackageName()); - sentAccessibilityEvent = true; - } if (DBG) Slog.v(TAG, "Interrupting!"); if (hasValidSound) { mSoundNotificationKey = key; @@ -4676,7 +4661,8 @@ public class NotificationManagerService extends SystemService { } void sendAccessibilityEvent(Notification notification, CharSequence packageName) { - if (!mAccessibilityManager.isEnabled()) { + AccessibilityManager manager = AccessibilityManager.getInstance(getContext()); + if (!manager.isEnabled()) { return; } @@ -4690,7 +4676,7 @@ public class NotificationManagerService extends SystemService { event.getText().add(tickerText); } - mAccessibilityManager.sendAccessibilityEvent(event); + manager.sendAccessibilityEvent(event); } /** diff --git a/com/android/server/notification/ZenModeFiltering.java b/com/android/server/notification/ZenModeFiltering.java index abf29006..a7a2743d 100644 --- a/com/android/server/notification/ZenModeFiltering.java +++ b/com/android/server/notification/ZenModeFiltering.java @@ -117,19 +117,15 @@ public class ZenModeFiltering { ZenLog.traceIntercepted(record, "alarmsOnly"); return true; case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: + if (isAlarm(record)) { + // Alarms are always priority + return false; + } // allow user-prioritized packages through in priority mode if (record.getPackagePriority() == Notification.PRIORITY_MAX) { ZenLog.traceNotIntercepted(record, "priorityApp"); return false; } - - if (isAlarm(record)) { - if (!config.allowAlarms) { - ZenLog.traceIntercepted(record, "!allowAlarms"); - return true; - } - return false; - } if (isCall(record)) { if (config.allowRepeatCallers && REPEAT_CALLERS.isRepeat(mContext, extras(record))) { @@ -163,15 +159,6 @@ public class ZenModeFiltering { } return false; } - AudioAttributes aa = record.getAudioAttributes(); - if (aa != null && AudioAttributes.SUPPRESSIBLE_USAGES.get(aa.getUsage()) == - AudioAttributes.SUPPRESSIBLE_MEDIA_SYSTEM_OTHER) { - if (!config.allowMediaSystemOther) { - ZenLog.traceIntercepted(record, "!allowMediaSystemOther"); - return true; - } - return false; - } ZenLog.traceIntercepted(record, "!priority"); return true; default: diff --git a/com/android/server/notification/ZenModeHelper.java b/com/android/server/notification/ZenModeHelper.java index 710684f4..9fcc67df 100644 --- a/com/android/server/notification/ZenModeHelper.java +++ b/com/android/server/notification/ZenModeHelper.java @@ -59,7 +59,6 @@ import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.server.LocalServices; @@ -88,7 +87,7 @@ public class ZenModeHelper { private final Context mContext; private final H mHandler; private final SettingsObserver mSettingsObserver; - @VisibleForTesting protected final AppOpsManager mAppOps; + private final AppOpsManager mAppOps; protected ZenModeConfig mDefaultConfig; private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); private final ZenModeFiltering mFiltering; @@ -103,9 +102,9 @@ public class ZenModeHelper { private final String SCHEDULED_DEFAULT_RULE_1 = "SCHEDULED_DEFAULT_RULE_1"; private final String SCHEDULED_DEFAULT_RULE_2 = "SCHEDULED_DEFAULT_RULE_2"; - @VisibleForTesting protected int mZenMode; + private int mZenMode; private int mUser = UserHandle.USER_SYSTEM; - @VisibleForTesting protected ZenModeConfig mConfig; + protected ZenModeConfig mConfig; private AudioManagerInternal mAudioManager; protected PackageManager mPm; private long mSuppressedEffects; @@ -596,9 +595,8 @@ public class ZenModeHelper { pw.println(config); return; } - pw.printf("allow(alarms=%b,media=%bcalls=%b,callsFrom=%s,repeatCallers=%b,messages=%b,messagesFrom=%s," + pw.printf("allow(calls=%b,callsFrom=%s,repeatCallers=%b,messages=%b,messagesFrom=%s," + "events=%b,reminders=%b,whenScreenOff=%b,whenScreenOn=%b)\n", - config.allowAlarms, config.allowMediaSystemOther, config.allowCalls, ZenModeConfig.sourceToString(config.allowCallsFrom), config.allowRepeatCallers, config.allowMessages, ZenModeConfig.sourceToString(config.allowMessagesFrom), @@ -815,8 +813,7 @@ public class ZenModeHelper { } } - @VisibleForTesting - protected void applyRestrictions() { + private void applyRestrictions() { final boolean zen = mZenMode != Global.ZEN_MODE_OFF; // notification restrictions @@ -825,10 +822,6 @@ public class ZenModeHelper { // call restrictions final boolean muteCalls = zen && !mConfig.allowCalls && !mConfig.allowRepeatCallers || (mSuppressedEffects & SUPPRESSED_EFFECT_CALLS) != 0; - // alarm restrictions - final boolean muteAlarms = zen && !mConfig.allowAlarms; - // alarm restrictions - final boolean muteMediaAndSystemSounds = zen && !mConfig.allowMediaSystemOther; // total silence restrictions final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; @@ -840,18 +833,13 @@ public class ZenModeHelper { applyRestrictions(muteNotifications || muteEverything, usage); } else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_CALL) { applyRestrictions(muteCalls || muteEverything, usage); - } else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_ALARM) { - applyRestrictions(muteAlarms || muteEverything, usage); - } else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_MEDIA_SYSTEM_OTHER) { - applyRestrictions(muteMediaAndSystemSounds || muteEverything, usage); } else { applyRestrictions(muteEverything, usage); } } } - @VisibleForTesting - protected void applyRestrictions(boolean mute, int usage) { + private void applyRestrictions(boolean mute, int usage) { final String[] exceptionPackages = null; // none (for now) mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, usage, mute ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, diff --git a/com/android/server/pm/LauncherAppsService.java b/com/android/server/pm/LauncherAppsService.java index b06b5838..4a5ce120 100644 --- a/com/android/server/pm/LauncherAppsService.java +++ b/com/android/server/pm/LauncherAppsService.java @@ -30,10 +30,12 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ILauncherApps; import android.content.pm.IOnAppsChangedListener; +import android.content.pm.IPackageManager; import android.content.pm.LauncherApps.ShortcutQuery; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; @@ -62,6 +64,7 @@ import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -86,14 +89,10 @@ public class LauncherAppsService extends SystemService { static class BroadcastCookie { public final UserHandle user; public final String packageName; - public final int callingUid; - public final int callingPid; - BroadcastCookie(UserHandle userHandle, String packageName, int callingPid, int callingUid) { + BroadcastCookie(UserHandle userHandle, String packageName) { this.user = userHandle; this.packageName = packageName; - this.callingUid = callingUid; - this.callingPid = callingPid; } } @@ -128,11 +127,6 @@ public class LauncherAppsService extends SystemService { return getCallingUid(); } - @VisibleForTesting - int injectBinderCallingPid() { - return getCallingPid(); - } - final int injectCallingUserId() { return UserHandle.getUserId(injectBinderCallingUid()); } @@ -172,7 +166,7 @@ public class LauncherAppsService extends SystemService { } mListeners.unregister(listener); mListeners.register(listener, new BroadcastCookie(UserHandle.of(getCallingUserId()), - callingPackage, injectBinderCallingPid(), injectBinderCallingUid())); + callingPackage)); } } @@ -444,7 +438,7 @@ public class LauncherAppsService extends SystemService { private void ensureShortcutPermission(@NonNull String callingPackage) { verifyCallingPackage(callingPackage); if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(), - callingPackage, injectBinderCallingPid(), injectBinderCallingUid())) { + callingPackage)) { throw new SecurityException("Caller can't access shortcut information"); } } @@ -467,8 +461,7 @@ public class LauncherAppsService extends SystemService { return new ParceledListSlice<>((List<ShortcutInfo>) mShortcutServiceInternal.getShortcuts(getCallingUserId(), callingPackage, changedSince, packageName, shortcutIds, - componentName, flags, targetUser.getIdentifier(), - injectBinderCallingPid(), injectBinderCallingUid())); + componentName, flags, targetUser.getIdentifier())); } @Override @@ -521,7 +514,7 @@ public class LauncherAppsService extends SystemService { public boolean hasShortcutHostPermission(String callingPackage) { verifyCallingPackage(callingPackage); return mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(), - callingPackage, injectBinderCallingPid(), injectBinderCallingUid()); + callingPackage); } @Override @@ -543,8 +536,7 @@ public class LauncherAppsService extends SystemService { } final Intent[] intents = mShortcutServiceInternal.createShortcutIntents( - getCallingUserId(), callingPackage, packageName, shortcutId, targetUserId, - injectBinderCallingPid(), injectBinderCallingUid()); + getCallingUserId(), callingPackage, packageName, shortcutId, targetUserId); if (intents == null || intents.length == 0) { return false; } @@ -909,8 +901,7 @@ public class LauncherAppsService extends SystemService { // Make sure the caller has the permission. if (!mShortcutServiceInternal.hasShortcutHostPermission( - launcherUserId, cookie.packageName, - cookie.callingPid, cookie.callingUid)) { + launcherUserId, cookie.packageName)) { continue; } // Each launcher has a different set of pinned shortcuts, so we need to do a @@ -923,8 +914,8 @@ public class LauncherAppsService extends SystemService { /* changedSince= */ 0, packageName, /* shortcutIds=*/ null, /* component= */ null, ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY - | ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED - , userId, cookie.callingPid, cookie.callingUid); + | ShortcutQuery.FLAG_GET_ALL_KINDS + , userId); try { listener.onShortcutChanged(user, packageName, new ParceledListSlice<>(list)); diff --git a/com/android/server/pm/PackageDexOptimizer.java b/com/android/server/pm/PackageDexOptimizer.java index cf0ffbb1..8ebeeae2 100644 --- a/com/android/server/pm/PackageDexOptimizer.java +++ b/com/android/server/pm/PackageDexOptimizer.java @@ -236,10 +236,9 @@ public class PackageDexOptimizer { */ @GuardedBy("mInstallLock") private int dexOptPath(PackageParser.Package pkg, String path, String isa, - String compilerFilter, boolean profileUpdated, String classLoaderContext, + String compilerFilter, boolean profileUpdated, String sharedLibrariesPath, int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade) { - int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext, - profileUpdated, downgrade); + int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, profileUpdated, downgrade); if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) { return DEX_OPT_SKIPPED; } @@ -252,8 +251,8 @@ public class PackageDexOptimizer { Log.i(TAG, "Running dexopt (dexoptNeeded=" + dexoptNeeded + ") on: " + path + " pkg=" + pkg.applicationInfo.packageName + " isa=" + isa + " dexoptFlags=" + printDexoptFlags(dexoptFlags) - + " targetFilter=" + compilerFilter + " oatDir=" + oatDir - + " classLoaderContext=" + classLoaderContext); + + " target-filter=" + compilerFilter + " oatDir=" + oatDir + + " sharedLibraries=" + sharedLibrariesPath); try { long startTime = System.currentTimeMillis(); @@ -262,7 +261,7 @@ public class PackageDexOptimizer { // installd only uses downgrade flag for secondary dex files and ignores it for // primary dex files. mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags, - compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo, + compilerFilter, pkg.volumeUuid, sharedLibrariesPath, pkg.applicationInfo.seInfo, false /* downgrade*/); if (packageStats != null) { @@ -509,11 +508,11 @@ public class PackageDexOptimizer { * configuration (isa, compiler filter, profile). */ private int getDexoptNeeded(String path, String isa, String compilerFilter, - String classLoaderContext, boolean newProfile, boolean downgrade) { + boolean newProfile, boolean downgrade) { int dexoptNeeded; try { - dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, classLoaderContext, - newProfile, downgrade); + dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, newProfile, + downgrade); } catch (IOException ioe) { Slog.w(TAG, "IOException reading apk: " + path, ioe); return DEX_OPT_FAILED; diff --git a/com/android/server/pm/PackageInstallerSession.java b/com/android/server/pm/PackageInstallerSession.java index 496ebc23..d62f0934 100644 --- a/com/android/server/pm/PackageInstallerSession.java +++ b/com/android/server/pm/PackageInstallerSession.java @@ -1127,8 +1127,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mResolvedInstructionSets.add(archSubDir.getName()); List<File> oatFiles = Arrays.asList(archSubDir.listFiles()); - if (!oatFiles.isEmpty()) { - mResolvedInheritedFiles.addAll(oatFiles); + + // Only add compiled files associated with the base. + // Once b/62269291 is resolved, we can add all compiled files again. + for (File oatFile : oatFiles) { + if (oatFile.getName().equals("base.art") + || oatFile.getName().equals("base.odex") + || oatFile.getName().equals("base.vdex")) { + mResolvedInheritedFiles.add(oatFile); + } } } } diff --git a/com/android/server/pm/PackageManagerService.java b/com/android/server/pm/PackageManagerService.java index 275db1fa..7d1a6470 100644 --- a/com/android/server/pm/PackageManagerService.java +++ b/com/android/server/pm/PackageManagerService.java @@ -398,7 +398,7 @@ public class PackageManagerService extends IPackageManager.Stub static final boolean DEBUG_DOMAIN_VERIFICATION = false; private static final boolean DEBUG_BACKUP = false; private static final boolean DEBUG_INSTALL = false; - public static final boolean DEBUG_REMOVE = false; + private static final boolean DEBUG_REMOVE = false; private static final boolean DEBUG_BROADCASTS = false; private static final boolean DEBUG_SHOW_INFO = false; private static final boolean DEBUG_PACKAGE_INFO = false; @@ -406,7 +406,7 @@ public class PackageManagerService extends IPackageManager.Stub public static final boolean DEBUG_PACKAGE_SCANNING = false; private static final boolean DEBUG_VERIFY = false; private static final boolean DEBUG_FILTERS = false; - public static final boolean DEBUG_PERMISSIONS = false; + private static final boolean DEBUG_PERMISSIONS = false; private static final boolean DEBUG_SHARED_LIBRARIES = false; private static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE; @@ -954,6 +954,9 @@ public class PackageManagerService extends IPackageManager.Stub final SparseArray<PackageVerificationState> mPendingVerification = new SparseArray<PackageVerificationState>(); + /** Set of packages associated with each app op permission. */ + final ArrayMap<String, ArraySet<String>> mAppOpPermissionPackages = new ArrayMap<>(); + final PackageInstallerService mInstallerService; private final PackageDexOptimizer mPackageDexOptimizer; @@ -2878,7 +2881,7 @@ public class PackageManagerService extends IPackageManager.Stub + mSdkVersion + "; regranting permissions for internal storage"); updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL; } - updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags); + updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags); ver.sdkVersion = mSdkVersion; // If this is the first boot or an update from pre-M, and it is a normal @@ -3261,6 +3264,23 @@ public class PackageManagerService extends IPackageManager.Stub return null; } + // If we have a profile for a compressed APK, copy it to the reference location. + // Since the package is the stub one, remove the stub suffix to get the normal package and + // APK name. + File profileFile = new File(getPrebuildProfilePath(pkg).replace(STUB_SUFFIX, "")); + if (profileFile.exists()) { + try { + // We could also do this lazily before calling dexopt in + // PackageDexOptimizer to prevent this happening on first boot. The issue + // is that we don't have a good way to say "do this only once". + if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(), + pkg.applicationInfo.uid, pkg.packageName)) { + Log.e(TAG, "decompressPackage failed to copy system profile!"); + } + } catch (Exception e) { + Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ", e); + } + } return dstCodePath; } @@ -3348,9 +3368,7 @@ public class PackageManagerService extends IPackageManager.Stub @Override public boolean isUpgrade() { // allow instant applications - // The system property allows testing ota flow when upgraded to the same image. - return mIsUpgrade || SystemProperties.getBoolean( - "persist.pm.mock-upgrade", false /* default */); + return mIsUpgrade; } private @Nullable String getRequiredButNotReallyRequiredVerifierLPr() { @@ -5164,7 +5182,7 @@ public class PackageManagerService extends IPackageManager.Stub final PermissionsState permissionsState = settingBase.getPermissionsState(); if (permissionsState.hasPermission(permName, userId)) { if (isUidInstantApp) { - if (mSettings.mPermissions.isPermissionInstant(permName)) { + if (mPermissionManager.isPermissionInstant(permName)) { return PackageManager.PERMISSION_GRANTED; } } else { @@ -5233,8 +5251,8 @@ public class PackageManagerService extends IPackageManager.Stub } } - private boolean addDynamicPermission(PermissionInfo info, final boolean async) { - return mPermissionManager.addDynamicPermission( + boolean addPermission(PermissionInfo info, final boolean async) { + return mPermissionManager.addPermission( info, async, getCallingUid(), new PermissionCallback() { @Override public void onPermissionChanged() { @@ -5250,20 +5268,20 @@ public class PackageManagerService extends IPackageManager.Stub @Override public boolean addPermission(PermissionInfo info) { synchronized (mPackages) { - return addDynamicPermission(info, false); + return addPermission(info, false); } } @Override public boolean addPermissionAsync(PermissionInfo info) { synchronized (mPackages) { - return addDynamicPermission(info, true); + return addPermission(info, true); } } @Override public void removePermission(String permName) { - mPermissionManager.removeDynamicPermission(permName, getCallingUid(), mPermissionCallback); + mPermissionManager.removePermission(permName, getCallingUid(), mPermissionCallback); } @Override @@ -5924,8 +5942,17 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public String[] getAppOpPermissionPackages(String permName) { - return mPermissionManager.getAppOpPermissionPackages(permName); + public String[] getAppOpPermissionPackages(String permissionName) { + if (getInstantAppPackageName(Binder.getCallingUid()) != null) { + return null; + } + synchronized (mPackages) { + ArraySet<String> pkgs = mAppOpPermissionPackages.get(permissionName); + if (pkgs == null) { + return null; + } + return pkgs.toArray(new String[pkgs.size()]); + } } @Override @@ -9097,30 +9124,10 @@ public class PackageManagerService extends IPackageManager.Stub // package and APK names. String systemProfilePath = getPrebuildProfilePath(disabledPs.pkg).replace(STUB_SUFFIX, ""); - profileFile = new File(systemProfilePath); - // If we have a profile for a compressed APK, copy it to the reference - // location. - // Note that copying the profile here will cause it to override the - // reference profile every OTA even though the existing reference profile - // may have more data. We can't copy during decompression since the - // directories are not set up at that point. - if (profileFile.exists()) { - try { - // We could also do this lazily before calling dexopt in - // PackageDexOptimizer to prevent this happening on first boot. The - // issue is that we don't have a good way to say "do this only - // once". - if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(), - pkg.applicationInfo.uid, pkg.packageName)) { - Log.e(TAG, "Failed to copy system profile for stub package!"); - } else { - useProfileForDexopt = true; - } - } catch (Exception e) { - Log.e(TAG, "Failed to copy profile " + - profileFile.getAbsolutePath() + " ", e); - } - } + File systemProfile = new File(systemProfilePath); + // Use the profile for compilation if there exists one for the same package + // in the system partition. + useProfileForDexopt = systemProfile.exists(); } } } @@ -10339,7 +10346,7 @@ public class PackageManagerService extends IPackageManager.Stub Slog.i(TAG, "Adopting permissions from " + origName + " to " + pkg.packageName); // SIDE EFFECTS; updates permissions system state; move elsewhere - mSettings.mPermissions.transferPermissions(origName, pkg.packageName); + mSettings.transferPermissionsLPw(origName, pkg.packageName); } } } @@ -11187,13 +11194,54 @@ public class PackageManagerService extends IPackageManager.Stub if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Permission Groups: " + r); } + N = pkg.permissions.size(); + r = null; + for (i=0; i<N; i++) { + PackageParser.Permission p = pkg.permissions.get(i); - // Dont allow ephemeral apps to define new permissions. - if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) { - Slog.w(TAG, "Permissions from package " + pkg.packageName - + " ignored: instant apps cannot define new permissions."); - } else { - mPermissionManager.addAllPermissions(pkg, chatty); + // Dont allow ephemeral apps to define new permissions. + if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) { + Slog.w(TAG, "Permission " + p.info.name + " from package " + + p.info.packageName + + " ignored: instant apps cannot define new permissions."); + continue; + } + + // Assume by default that we did not install this permission into the system. + p.info.flags &= ~PermissionInfo.FLAG_INSTALLED; + + // Now that permission groups have a special meaning, we ignore permission + // groups for legacy apps to prevent unexpected behavior. In particular, + // permissions for one app being granted to someone just because they happen + // to be in a group defined by another app (before this had no implications). + if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { + p.group = mPermissionGroups.get(p.info.group); + // Warn for a permission in an unknown group. + if (DEBUG_PERMISSIONS && p.info.group != null && p.group == null) { + Slog.i(TAG, "Permission " + p.info.name + " from package " + + p.info.packageName + " in an unknown group " + p.info.group); + } + } + + // TODO Move to PermissionManager once mPermissionTrees moves there. +// p.tree ? mSettings.mPermissionTrees +// : mSettings.mPermissions; +// final BasePermission bp = BasePermission.createOrUpdate( +// permissionMap.get(p.info.name), p, pkg, mSettings.mPermissionTrees, chatty); +// permissionMap.put(p.info.name, bp); + if (p.tree) { + final ArrayMap<String, BasePermission> permissionMap = + mSettings.mPermissionTrees; + final BasePermission bp = BasePermission.createOrUpdate( + permissionMap.get(p.info.name), p, pkg, mSettings.mPermissionTrees, + chatty); + permissionMap.put(p.info.name, bp); + } else { + final BasePermission bp = BasePermission.createOrUpdate( + (BasePermission) mPermissionManager.getPermissionTEMP(p.info.name), + p, pkg, mSettings.mPermissionTrees, chatty); + mPermissionManager.putPermissionTEMP(p.info.name, bp); + } } N = pkg.instrumentation.size(); @@ -11919,7 +11967,53 @@ public class PackageManagerService extends IPackageManager.Stub if (DEBUG_REMOVE) Log.d(TAG, " Activities: " + r); } - mPermissionManager.removeAllPermissions(pkg, chatty); + N = pkg.permissions.size(); + r = null; + for (i=0; i<N; i++) { + PackageParser.Permission p = pkg.permissions.get(i); + BasePermission bp = (BasePermission) mPermissionManager.getPermissionTEMP(p.info.name); + if (bp == null) { + bp = mSettings.mPermissionTrees.get(p.info.name); + } + if (bp != null && bp.isPermission(p)) { + bp.setPermission(null); + if (DEBUG_REMOVE && chatty) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); + } + r.append(p.info.name); + } + } + if ((p.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) != 0) { + ArraySet<String> appOpPkgs = mAppOpPermissionPackages.get(p.info.name); + if (appOpPkgs != null) { + appOpPkgs.remove(pkg.packageName); + } + } + } + if (r != null) { + if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); + } + + N = pkg.requestedPermissions.size(); + r = null; + for (i=0; i<N; i++) { + String perm = pkg.requestedPermissions.get(i); + if (mPermissionManager.isPermissionAppOp(perm)) { + ArraySet<String> appOpPkgs = mAppOpPermissionPackages.get(perm); + if (appOpPkgs != null) { + appOpPkgs.remove(pkg.packageName); + if (appOpPkgs.isEmpty()) { + mAppOpPermissionPackages.remove(perm); + } + } + } + } + if (r != null) { + if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); + } N = pkg.instrumentation.size(); r = null; @@ -11980,9 +12074,18 @@ public class PackageManagerService extends IPackageManager.Stub } } - public static final int UPDATE_PERMISSIONS_ALL = 1<<0; - public static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1; - public static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2; + private static boolean hasPermission(PackageParser.Package pkgInfo, String perm) { + for (int i=pkgInfo.permissions.size()-1; i>=0; i--) { + if (pkgInfo.permissions.get(i).info.name.equals(perm)) { + return true; + } + } + return false; + } + + static final int UPDATE_PERMISSIONS_ALL = 1<<0; + static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1; + static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2; private void updatePermissionsLPw(PackageParser.Package pkg, int flags) { // Update the parent permissions @@ -11998,10 +12101,10 @@ public class PackageManagerService extends IPackageManager.Stub private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo, int flags) { final String volumeUuid = (pkgInfo != null) ? getVolumeUuidForPackage(pkgInfo) : null; - updatePermissionsLocked(changingPkg, pkgInfo, volumeUuid, flags); + updatePermissionsLPw(changingPkg, pkgInfo, volumeUuid, flags); } - private void updatePermissionsLocked(String changingPkg, + private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) { // TODO: Most of the methods exposing BasePermission internals [source package name, // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't @@ -12013,11 +12116,55 @@ public class PackageManagerService extends IPackageManager.Stub // normal permissions. Today, we need two separate loops because these BasePermission // objects are stored separately. // Make sure there are no dangling permission trees. - flags = mPermissionManager.updatePermissionTrees(changingPkg, pkgInfo, flags); + Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator(); + while (it.hasNext()) { + final BasePermission bp = it.next(); + if (bp.getSourcePackageSetting() == null) { + // We may not yet have parsed the package, so just see if + // we still know about its settings. + bp.setSourcePackageSetting(mSettings.mPackages.get(bp.getSourcePackageName())); + } + if (bp.getSourcePackageSetting() == null) { + Slog.w(TAG, "Removing dangling permission tree: " + bp.getName() + + " from package " + bp.getSourcePackageName()); + it.remove(); + } else if (changingPkg != null && changingPkg.equals(bp.getSourcePackageName())) { + if (pkgInfo == null || !hasPermission(pkgInfo, bp.getName())) { + Slog.i(TAG, "Removing old permission tree: " + bp.getName() + + " from package " + bp.getSourcePackageName()); + flags |= UPDATE_PERMISSIONS_ALL; + it.remove(); + } + } + } // Make sure all dynamic permissions have been assigned to a package, // and make sure there are no dangling permissions. - flags = mPermissionManager.updatePermissions(changingPkg, pkgInfo, flags); + final Iterator<BasePermission> permissionIter = + mPermissionManager.getPermissionIteratorTEMP(); + while (permissionIter.hasNext()) { + final BasePermission bp = permissionIter.next(); + if (bp.isDynamic()) { + bp.updateDynamicPermission(mSettings.mPermissionTrees); + } + if (bp.getSourcePackageSetting() == null) { + // We may not yet have parsed the package, so just see if + // we still know about its settings. + bp.setSourcePackageSetting(mSettings.mPackages.get(bp.getSourcePackageName())); + } + if (bp.getSourcePackageSetting() == null) { + Slog.w(TAG, "Removing dangling permission: " + bp.getName() + + " from package " + bp.getSourcePackageName()); + permissionIter.remove(); + } else if (changingPkg != null && changingPkg.equals(bp.getSourcePackageName())) { + if (pkgInfo == null || !hasPermission(pkgInfo, bp.getName())) { + Slog.i(TAG, "Removing old permission: " + bp.getName() + + " from package " + bp.getSourcePackageName()); + flags |= UPDATE_PERMISSIONS_ALL; + permissionIter.remove(); + } + } + } Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions"); // Now update the permissions for all packages, in particular @@ -12139,7 +12286,12 @@ public class PackageManagerService extends IPackageManager.Stub // Keep track of app op permissions. if (bp.isAppOp()) { - mSettings.addAppOpPackage(perm, pkg.packageName); + ArraySet<String> pkgs = mAppOpPermissionPackages.get(perm); + if (pkgs == null) { + pkgs = new ArraySet<>(); + mAppOpPermissionPackages.put(perm, pkgs); + } + pkgs.add(pkg.packageName); } if (bp.isNormal()) { @@ -21086,7 +21238,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); // permissions, ensure permissions are updated. Beware of dragons if you // try optimizing this. synchronized (mPackages) { - updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL, + updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL, UPDATE_PERMISSIONS_ALL); } @@ -21137,8 +21289,9 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL); if (mPrivappPermissionsViolations != null) { - throw new IllegalStateException("Signature|privileged permissions not in " + Slog.wtf(TAG,"Signature|privileged permissions not in " + "privapp-permissions whitelist: " + mPrivappPermissionsViolations); + mPrivappPermissionsViolations = null; } } @@ -21631,6 +21784,22 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) { mSettings.dumpPermissionsLPr(pw, packageName, permissionNames, dumpState); + if (packageName == null && permissionNames == null) { + for (int iperm=0; iperm<mAppOpPermissionPackages.size(); iperm++) { + if (iperm == 0) { + if (dumpState.onTitlePrinted()) + pw.println(); + pw.println("AppOp Permissions:"); + } + pw.print(" AppOp Permission "); + pw.print(mAppOpPermissionPackages.keyAt(iperm)); + pw.println(":"); + ArraySet<String> pkgs = mAppOpPermissionPackages.valueAt(iperm); + for (int ipkg=0; ipkg<pkgs.size(); ipkg++) { + pw.print(" "); pw.println(pkgs.valueAt(ipkg)); + } + } + } } if (!checkin && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) { @@ -21860,7 +22029,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); synchronized (mAvailableFeatures) { final int count = mAvailableFeatures.size(); for (int i = 0; i < count; i++) { - mAvailableFeatures.valueAt(i).writeToProto(proto, PackageServiceDumpProto.FEATURES); + final FeatureInfo feat = mAvailableFeatures.valueAt(i); + final long featureToken = proto.start(PackageServiceDumpProto.FEATURES); + proto.write(PackageServiceDumpProto.FeatureProto.NAME, feat.name); + proto.write(PackageServiceDumpProto.FeatureProto.VERSION, feat.version); + proto.end(featureToken); } } } @@ -21892,7 +22065,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } private void dumpDexoptStateLPr(PrintWriter pw, String packageName) { - final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); ipw.println(); ipw.println("Dexopt state:"); ipw.increaseIndent(); @@ -21919,7 +22092,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } private void dumpCompilerStatsLPr(PrintWriter pw, String packageName) { - final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); ipw.println(); ipw.println("Compiler stats:"); ipw.increaseIndent(); @@ -22130,7 +22303,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); + mSdkVersion + "; regranting permissions for " + volumeUuid); updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL; } - updatePermissionsLocked(null, null, volumeUuid, updateFlags); + updatePermissionsLPw(null, null, volumeUuid, updateFlags); // Yay, everything is now upgraded ver.forceCurrent(); @@ -23138,15 +23311,15 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); void onNewUserCreated(final int userId) { synchronized(mPackages) { mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId); - // If permission review for legacy apps is required, we represent - // dagerous permissions for such apps as always granted runtime - // permissions to keep per user flag state whether review is needed. - // Hence, if a new user is added we have to propagate dangerous - // permission grants for these legacy apps. - if (mPermissionReviewRequired) { - updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL - | UPDATE_PERMISSIONS_REPLACE_ALL); - } + } + // If permission review for legacy apps is required, we represent + // dagerous permissions for such apps as always granted runtime + // permissions to keep per user flag state whether review is needed. + // Hence, if a new user is added we have to propagate dangerous + // permission grants for these legacy apps. + if (mPermissionReviewRequired) { + updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL + | UPDATE_PERMISSIONS_REPLACE_ALL); } } @@ -23590,12 +23763,12 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } @Override - public PackageParser.PermissionGroup getPermissionGroupTEMP(String groupName) { + public Object enforcePermissionTreeTEMP(String permName, int callingUid) { synchronized (mPackages) { - return mPermissionGroups.get(groupName); + return BasePermission.enforcePermissionTreeLP( + mSettings.mPermissionTrees, permName, callingUid); } } - @Override public boolean isInstantApp(String packageName, int userId) { return PackageManagerService.this.isInstantApp(packageName, userId); diff --git a/com/android/server/pm/Settings.java b/com/android/server/pm/Settings.java index 191b43a6..00844114 100644 --- a/com/android/server/pm/Settings.java +++ b/com/android/server/pm/Settings.java @@ -378,6 +378,10 @@ public final class Settings { private final ArrayMap<Long, Integer> mKeySetRefs = new ArrayMap<Long, Integer>(); + // Mapping from permission tree names to info about them. + final ArrayMap<String, BasePermission> mPermissionTrees = + new ArrayMap<String, BasePermission>(); + // Packages that have been uninstalled and still need their external // storage data deleted. final ArrayList<PackageCleanItem> mPackagesToBeCleaned = new ArrayList<PackageCleanItem>(); @@ -412,7 +416,7 @@ public final class Settings { public final KeySetManagerService mKeySetManagerService = new KeySetManagerService(mPackages); /** Settings and other information about permissions */ - final PermissionSettings mPermissions; + private final PermissionSettings mPermissions; Settings(PermissionSettings permissions, Object lock) { this(Environment.getDataDirectory(), permissions, lock); @@ -618,10 +622,6 @@ public final class Settings { return null; } - void addAppOpPackage(String permName, String packageName) { - mPermissions.addAppOpPackage(permName, packageName); - } - SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) { SharedUserSetting s = mSharedUsers.get(name); if (s != null) { @@ -666,6 +666,13 @@ public final class Settings { } /** + * Transfers ownership of permissions from one package to another. + */ + void transferPermissionsLPw(String origPackageName, String newPackageName) { + mPermissions.transferPermissions(origPackageName, newPackageName, mPermissionTrees); + } + + /** * Creates a new {@code PackageSetting} object. * Use this method instead of the constructor to ensure a settings object is created * with the correct base. @@ -2489,7 +2496,9 @@ public final class Settings { } serializer.startTag(null, "permission-trees"); - mPermissions.writePermissionTrees(serializer); + for (BasePermission bp : mPermissionTrees.values()) { + writePermissionLPr(serializer, bp); + } serializer.endTag(null, "permission-trees"); serializer.startTag(null, "permissions"); @@ -3033,7 +3042,7 @@ public final class Settings { } else if (tagName.equals("permissions")) { mPermissions.readPermissions(parser); } else if (tagName.equals("permission-trees")) { - mPermissions.readPermissionTrees(parser); + PermissionSettings.readPermissions(mPermissionTrees, parser); } else if (tagName.equals("shared-user")) { readSharedUserLPw(parser); } else if (tagName.equals("preferred-packages")) { @@ -4929,7 +4938,11 @@ public final class Settings { void dumpSharedUsersProto(ProtoOutputStream proto) { final int count = mSharedUsers.size(); for (int i = 0; i < count; i++) { - mSharedUsers.valueAt(i).writeToProto(proto, PackageServiceDumpProto.SHARED_USERS); + final SharedUserSetting su = mSharedUsers.valueAt(i); + final long sharedUserToken = proto.start(PackageServiceDumpProto.SHARED_USERS); + proto.write(PackageServiceDumpProto.SharedUserProto.USER_ID, su.userId); + proto.write(PackageServiceDumpProto.SharedUserProto.NAME, su.name); + proto.end(sharedUserToken); } } diff --git a/com/android/server/pm/SharedUserSetting.java b/com/android/server/pm/SharedUserSetting.java index 877da144..a0dadae3 100644 --- a/com/android/server/pm/SharedUserSetting.java +++ b/com/android/server/pm/SharedUserSetting.java @@ -18,9 +18,7 @@ package com.android.server.pm; import android.annotation.Nullable; import android.content.pm.PackageParser; -import android.service.pm.PackageServiceDumpProto; import android.util.ArraySet; -import android.util.proto.ProtoOutputStream; import java.util.ArrayList; import java.util.Collection; @@ -55,13 +53,6 @@ public final class SharedUserSetting extends SettingBase { + name + "/" + userId + "}"; } - public void writeToProto(ProtoOutputStream proto, long fieldId) { - long token = proto.start(fieldId); - proto.write(PackageServiceDumpProto.SharedUserProto.USER_ID, userId); - proto.write(PackageServiceDumpProto.SharedUserProto.NAME, name); - proto.end(token); - } - void removePackage(PackageSetting packageSetting) { if (packages.remove(packageSetting)) { // recalculate the pkgFlags for this shared user if needed diff --git a/com/android/server/pm/ShortcutBitmapSaver.java b/com/android/server/pm/ShortcutBitmapSaver.java index 815f8851..4f5d1560 100644 --- a/com/android/server/pm/ShortcutBitmapSaver.java +++ b/com/android/server/pm/ShortcutBitmapSaver.java @@ -21,8 +21,6 @@ import android.content.pm.ShortcutInfo; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.drawable.Icon; -import android.os.StrictMode; -import android.os.StrictMode.ThreadPolicy; import android.os.SystemClock; import android.util.Log; import android.util.Slog; @@ -167,13 +165,7 @@ public class ShortcutBitmapSaver { // Compress it and enqueue to the requests. final byte[] bytes; - final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); try { - // compress() triggers a slow call, but in this case it's needed to save RAM and also - // the target bitmap is of an icon size, so let's just permit it. - StrictMode.setThreadPolicy(new ThreadPolicy.Builder(oldPolicy) - .permitCustomSlowCalls() - .build()); final Bitmap shrunk = mService.shrinkBitmap(original, maxDimension); try { try (final ByteArrayOutputStream out = new ByteArrayOutputStream(64 * 1024)) { @@ -192,8 +184,6 @@ public class ShortcutBitmapSaver { } catch (IOException | RuntimeException | OutOfMemoryError e) { Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e); return; - } finally { - StrictMode.setThreadPolicy(oldPolicy); } shortcut.addFlags( diff --git a/com/android/server/pm/ShortcutLauncher.java b/com/android/server/pm/ShortcutLauncher.java index cedf4763..f922ad19 100644 --- a/com/android/server/pm/ShortcutLauncher.java +++ b/com/android/server/pm/ShortcutLauncher.java @@ -83,16 +83,11 @@ class ShortcutLauncher extends ShortcutPackageItem { return mOwnerUserId; } - @Override - protected boolean canRestoreAnyVersion() { - // Launcher's pinned shortcuts can be restored to an older version. - return true; - } - /** * Called when the new package can't receive the backup, due to signature or version mismatch. */ - private void onRestoreBlocked() { + @Override + protected void onRestoreBlocked() { final ArrayList<PackageWithUser> pinnedPackages = new ArrayList<>(mPinnedShortcuts.keySet()); mPinnedShortcuts.clear(); @@ -106,21 +101,15 @@ class ShortcutLauncher extends ShortcutPackageItem { } @Override - protected void onRestored(int restoreBlockReason) { - // For launcher, possible reasons here are DISABLED_REASON_SIGNATURE_MISMATCH or - // DISABLED_REASON_BACKUP_NOT_SUPPORTED. - // DISABLED_REASON_VERSION_LOWER will NOT happen because we don't check version - // code for launchers. - if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) { - onRestoreBlocked(); - } + protected void onRestored() { + // Nothing to do. } /** * Pin the given shortcuts, replacing the current pinned ones. */ public void pinShortcuts(@UserIdInt int packageUserId, - @NonNull String packageName, @NonNull List<String> ids, boolean forPinRequest) { + @NonNull String packageName, @NonNull List<String> ids) { final ShortcutPackage packageShortcuts = mShortcutUser.getPackageShortcutsIfExists(packageName); if (packageShortcuts == null) { @@ -135,12 +124,8 @@ class ShortcutLauncher extends ShortcutPackageItem { } else { final ArraySet<String> prevSet = mPinnedShortcuts.get(pu); - // Actually pin shortcuts. - // This logic here is to make sure a launcher cannot pin a shortcut that is floating - // (i.e. not dynamic nor manifest but is pinned) and pinned by another launcher. - // In this case, technically the shortcut doesn't exist to this launcher, so it can't - // pin it. - // (Maybe unnecessarily strict...) + // Pin shortcuts. Make sure only pin the ones that were visible to the caller. + // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here. final ArraySet<String> newSet = new ArraySet<>(); @@ -150,10 +135,8 @@ class ShortcutLauncher extends ShortcutPackageItem { if (si == null) { continue; } - if (si.isDynamic() - || si.isManifestShortcut() - || (prevSet != null && prevSet.contains(id)) - || forPinRequest) { + if (si.isDynamic() || si.isManifestShortcut() + || (prevSet != null && prevSet.contains(id))) { newSet.add(id); } } @@ -172,7 +155,7 @@ class ShortcutLauncher extends ShortcutPackageItem { } /** - * Return true if the given shortcut is pinned by this launcher.<code></code> + * Return true if the given shortcut is pinned by this launcher. */ public boolean hasPinned(ShortcutInfo shortcut) { final ArraySet<String> pinned = @@ -181,10 +164,10 @@ class ShortcutLauncher extends ShortcutPackageItem { } /** - * Additionally pin a shortcut. c.f. {@link #pinShortcuts(int, String, List, boolean)} + * Additionally pin a shortcut. c.f. {@link #pinShortcuts(int, String, List)} */ public void addPinnedShortcut(@NonNull String packageName, @UserIdInt int packageUserId, - String id, boolean forPinRequest) { + String id) { final ArraySet<String> pinnedSet = getPinnedShortcutIds(packageName, packageUserId); final ArrayList<String> pinnedList; if (pinnedSet != null) { @@ -195,21 +178,21 @@ class ShortcutLauncher extends ShortcutPackageItem { } pinnedList.add(id); - pinShortcuts(packageUserId, packageName, pinnedList, forPinRequest); + pinShortcuts(packageUserId, packageName, pinnedList); } boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) { return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null; } - public void ensurePackageInfo() { + public void ensureVersionInfo() { final PackageInfo pi = mShortcutUser.mService.getPackageInfoWithSignatures( getPackageName(), getPackageUserId()); if (pi == null) { Slog.w(TAG, "Package not found: " + getPackageName()); return; } - getPackageInfo().updateFromPackageInfo(pi); + getPackageInfo().updateVersionInfo(pi); } /** @@ -218,10 +201,6 @@ class ShortcutLauncher extends ShortcutPackageItem { @Override public void saveToXml(XmlSerializer out, boolean forBackup) throws IOException { - if (forBackup && !getPackageInfo().isBackupAllowed()) { - // If an launcher app doesn't support backup&restore, then nothing to do. - return; - } final int size = mPinnedShortcuts.size(); if (size == 0) { return; // Nothing to write. @@ -230,7 +209,7 @@ class ShortcutLauncher extends ShortcutPackageItem { out.startTag(null, TAG_ROOT); ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, getPackageName()); ShortcutService.writeAttr(out, ATTR_LAUNCHER_USER_ID, getPackageUserId()); - getPackageInfo().saveToXml(out, forBackup); + getPackageInfo().saveToXml(out); for (int i = 0; i < size; i++) { final PackageWithUser pu = mPinnedShortcuts.keyAt(i); diff --git a/com/android/server/pm/ShortcutPackage.java b/com/android/server/pm/ShortcutPackage.java index 12f490fa..6fc1e738 100644 --- a/com/android/server/pm/ShortcutPackage.java +++ b/com/android/server/pm/ShortcutPackage.java @@ -84,7 +84,6 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String ATTR_DISABLED_MESSAGE = "dmessage"; private static final String ATTR_DISABLED_MESSAGE_RES_ID = "dmessageid"; private static final String ATTR_DISABLED_MESSAGE_RES_NAME = "dmessagename"; - private static final String ATTR_DISABLED_REASON = "disabled-reason"; private static final String ATTR_INTENT_LEGACY = "intent"; private static final String ATTR_INTENT_NO_EXTRA = "intent-base"; private static final String ATTR_RANK = "rank"; @@ -157,25 +156,13 @@ class ShortcutPackage extends ShortcutPackageItem { } @Override - protected boolean canRestoreAnyVersion() { - return false; + protected void onRestoreBlocked() { + // Can't restore due to version/signature mismatch. Remove all shortcuts. + mShortcuts.clear(); } @Override - protected void onRestored(int restoreBlockReason) { - // Shortcuts have been restored. - // - Unshadow all shortcuts. - // - Set disabled reason. - // - Disable if needed. - for (int i = mShortcuts.size() - 1; i >= 0; i--) { - ShortcutInfo si = mShortcuts.valueAt(i); - si.clearFlags(ShortcutInfo.FLAG_SHADOW); - - si.setDisabledReason(restoreBlockReason); - if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) { - si.addFlags(ShortcutInfo.FLAG_DISABLED); - } - } + protected void onRestored() { // Because some launchers may not have been restored (e.g. allowBackup=false), // we need to re-calculate the pinned shortcuts. refreshPinnedFlags(); @@ -189,47 +176,31 @@ class ShortcutPackage extends ShortcutPackageItem { return mShortcuts.get(id); } - public boolean isShortcutExistsAndInvisibleToPublisher(String id) { - ShortcutInfo si = findShortcutById(id); - return si != null && !si.isVisibleToPublisher(); - } - - public boolean isShortcutExistsAndVisibleToPublisher(String id) { - ShortcutInfo si = findShortcutById(id); - return si != null && si.isVisibleToPublisher(); - } - - private void ensureNotImmutable(@Nullable ShortcutInfo shortcut, boolean ignoreInvisible) { - if (shortcut != null && shortcut.isImmutable() - && (!ignoreInvisible || shortcut.isVisibleToPublisher())) { + private void ensureNotImmutable(@Nullable ShortcutInfo shortcut) { + if (shortcut != null && shortcut.isImmutable()) { throw new IllegalArgumentException( "Manifest shortcut ID=" + shortcut.getId() + " may not be manipulated via APIs"); } } - public void ensureNotImmutable(@NonNull String id, boolean ignoreInvisible) { - ensureNotImmutable(mShortcuts.get(id), ignoreInvisible); + public void ensureNotImmutable(@NonNull String id) { + ensureNotImmutable(mShortcuts.get(id)); } - public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds, - boolean ignoreInvisible) { + public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds) { for (int i = shortcutIds.size() - 1; i >= 0; i--) { - ensureNotImmutable(shortcutIds.get(i), ignoreInvisible); + ensureNotImmutable(shortcutIds.get(i)); } } - public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts, - boolean ignoreInvisible) { + public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts) { for (int i = shortcuts.size() - 1; i >= 0; i--) { - ensureNotImmutable(shortcuts.get(i).getId(), ignoreInvisible); + ensureNotImmutable(shortcuts.get(i).getId()); } } - /** - * Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible. - */ - private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) { + private ShortcutInfo deleteShortcutInner(@NonNull String id) { final ShortcutInfo shortcut = mShortcuts.remove(id); if (shortcut != null) { mShortcutUser.mService.removeIconLocked(shortcut); @@ -239,14 +210,10 @@ class ShortcutPackage extends ShortcutPackageItem { return shortcut; } - /** - * Force replace a shortcut. If there's already a shortcut with the same ID, it'll be removed, - * even if it's invisible. - */ - private void forceReplaceShortcutInner(@NonNull ShortcutInfo newShortcut) { + private void addShortcutInner(@NonNull ShortcutInfo newShortcut) { final ShortcutService s = mShortcutUser.mService; - forceDeleteShortcutInner(newShortcut.getId()); + deleteShortcutInner(newShortcut.getId()); // Extract Icon and update the icon res ID and the bitmap path. s.saveIconAndFixUpShortcutLocked(newShortcut); @@ -255,12 +222,11 @@ class ShortcutPackage extends ShortcutPackageItem { } /** - * Add a shortcut. If there's already a one with the same ID, it'll be removed, even if it's - * invisible. + * Add a shortcut, or update one with the same ID, with taking over existing flags. * * It checks the max number of dynamic shortcuts. */ - public void addOrReplaceDynamicShortcut(@NonNull ShortcutInfo newShortcut) { + public void addOrUpdateDynamicShortcut(@NonNull ShortcutInfo newShortcut) { Preconditions.checkArgument(newShortcut.isEnabled(), "add/setDynamicShortcuts() cannot publish disabled shortcuts"); @@ -276,7 +242,7 @@ class ShortcutPackage extends ShortcutPackageItem { } else { // It's an update case. // Make sure the target is updatable. (i.e. should be mutable.) - oldShortcut.ensureUpdatableWith(newShortcut, /*isUpdating=*/ false); + oldShortcut.ensureUpdatableWith(newShortcut); wasPinned = oldShortcut.isPinned(); } @@ -286,7 +252,7 @@ class ShortcutPackage extends ShortcutPackageItem { newShortcut.addFlags(ShortcutInfo.FLAG_PINNED); } - forceReplaceShortcutInner(newShortcut); + addShortcutInner(newShortcut); } /** @@ -307,7 +273,7 @@ class ShortcutPackage extends ShortcutPackageItem { } if (removeList != null) { for (int i = removeList.size() - 1; i >= 0; i--) { - forceDeleteShortcutInner(removeList.get(i)); + deleteShortcutInner(removeList.get(i)); } } } @@ -315,13 +281,13 @@ class ShortcutPackage extends ShortcutPackageItem { /** * Remove all dynamic shortcuts. */ - public void deleteAllDynamicShortcuts(boolean ignoreInvisible) { + public void deleteAllDynamicShortcuts() { final long now = mShortcutUser.mService.injectCurrentTimeMillis(); boolean changed = false; for (int i = mShortcuts.size() - 1; i >= 0; i--) { final ShortcutInfo si = mShortcuts.valueAt(i); - if (si.isDynamic() && (!ignoreInvisible || si.isVisibleToPublisher())) { + if (si.isDynamic()) { changed = true; si.setTimestamp(now); @@ -341,10 +307,9 @@ class ShortcutPackage extends ShortcutPackageItem { * @return true if it's actually removed because it wasn't pinned, or false if it's still * pinned. */ - public boolean deleteDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible) { + public boolean deleteDynamicWithId(@NonNull String shortcutId) { final ShortcutInfo removed = deleteOrDisableWithId( - shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false, ignoreInvisible, - ShortcutInfo.DISABLED_REASON_NOT_DISABLED); + shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false); return removed == null; } @@ -355,11 +320,9 @@ class ShortcutPackage extends ShortcutPackageItem { * @return true if it's actually removed because it wasn't pinned, or false if it's still * pinned. */ - private boolean disableDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible, - int disabledReason) { + private boolean disableDynamicWithId(@NonNull String shortcutId) { final ShortcutInfo disabled = deleteOrDisableWithId( - shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false, ignoreInvisible, - disabledReason); + shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false); return disabled == null; } @@ -368,10 +331,9 @@ class ShortcutPackage extends ShortcutPackageItem { * is pinned, it'll remain as a pinned shortcut but will be disabled. */ public void disableWithId(@NonNull String shortcutId, String disabledMessage, - int disabledMessageResId, boolean overrideImmutable, boolean ignoreInvisible, - int disabledReason) { + int disabledMessageResId, boolean overrideImmutable) { final ShortcutInfo disabled = deleteOrDisableWithId(shortcutId, /* disable =*/ true, - overrideImmutable, ignoreInvisible, disabledReason); + overrideImmutable); if (disabled != null) { if (disabledMessage != null) { @@ -386,18 +348,14 @@ class ShortcutPackage extends ShortcutPackageItem { @Nullable private ShortcutInfo deleteOrDisableWithId(@NonNull String shortcutId, boolean disable, - boolean overrideImmutable, boolean ignoreInvisible, int disabledReason) { - Preconditions.checkState( - (disable == (disabledReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED)), - "disable and disabledReason disagree: " + disable + " vs " + disabledReason); + boolean overrideImmutable) { final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId); - if (oldShortcut == null || !oldShortcut.isEnabled() - && (ignoreInvisible && !oldShortcut.isVisibleToPublisher())) { + if (oldShortcut == null || !oldShortcut.isEnabled()) { return null; // Doesn't exist or already disabled. } if (!overrideImmutable) { - ensureNotImmutable(oldShortcut, /*ignoreInvisible=*/ true); + ensureNotImmutable(oldShortcut); } if (oldShortcut.isPinned()) { @@ -405,10 +363,6 @@ class ShortcutPackage extends ShortcutPackageItem { oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST); if (disable) { oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED); - // Do not overwrite the disabled reason if one is alreay set. - if (oldShortcut.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) { - oldShortcut.setDisabledReason(disabledReason); - } } oldShortcut.setTimestamp(mShortcutUser.mService.injectCurrentTimeMillis()); @@ -419,7 +373,7 @@ class ShortcutPackage extends ShortcutPackageItem { return oldShortcut; } else { - forceDeleteShortcutInner(shortcutId); + deleteShortcutInner(shortcutId); return null; } } @@ -427,25 +381,11 @@ class ShortcutPackage extends ShortcutPackageItem { public void enableWithId(@NonNull String shortcutId) { final ShortcutInfo shortcut = mShortcuts.get(shortcutId); if (shortcut != null) { - ensureNotImmutable(shortcut, /*ignoreInvisible=*/ true); + ensureNotImmutable(shortcut); shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED); - shortcut.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED); } } - public void updateInvisibleShortcutForPinRequestWith(@NonNull ShortcutInfo shortcut) { - final ShortcutInfo source = mShortcuts.get(shortcut.getId()); - Preconditions.checkNotNull(source); - - mShortcutUser.mService.validateShortcutForPinRequest(shortcut); - - shortcut.addFlags(ShortcutInfo.FLAG_PINNED); - - forceReplaceShortcutInner(shortcut); - - adjustRanks(); - } - /** * Called after a launcher updates the pinned set. For each shortcut in this package, * set FLAG_PINNED if any launcher has pinned it. Otherwise, clear it. @@ -570,7 +510,7 @@ class ShortcutPackage extends ShortcutPackageItem { */ public void findAll(@NonNull List<ShortcutInfo> result, @Nullable Predicate<ShortcutInfo> query, int cloneFlag) { - findAll(result, query, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false); + findAll(result, query, cloneFlag, null, 0); } /** @@ -582,7 +522,7 @@ class ShortcutPackage extends ShortcutPackageItem { */ public void findAll(@NonNull List<ShortcutInfo> result, @Nullable Predicate<ShortcutInfo> query, int cloneFlag, - @Nullable String callingLauncher, int launcherUserId, boolean getPinnedByAnyLauncher) { + @Nullable String callingLauncher, int launcherUserId) { if (getPackageInfo().isShadow()) { // Restored and the app not installed yet, so don't return any. return; @@ -604,11 +544,9 @@ class ShortcutPackage extends ShortcutPackageItem { final boolean isPinnedByCaller = (callingLauncher == null) || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId())); - if (!getPinnedByAnyLauncher) { - if (si.isFloating()) { - if (!isPinnedByCaller) { - continue; - } + if (si.isFloating()) { + if (!isPinnedByCaller) { + continue; } } final ShortcutInfo clone = si.clone(cloneFlag); @@ -755,27 +693,7 @@ class ShortcutPackage extends ShortcutPackageItem { getPackageInfo().getVersionCode(), pi.versionCode)); } - getPackageInfo().updateFromPackageInfo(pi); - final int newVersionCode = getPackageInfo().getVersionCode(); - - // See if there are any shortcuts that were prevented restoring because the app was of a - // lower version, and re-enable them. - for (int i = mShortcuts.size() - 1; i >= 0; i--) { - final ShortcutInfo si = mShortcuts.valueAt(i); - if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) { - continue; - } - if (getPackageInfo().getBackupSourceVersionCode() > newVersionCode) { - if (ShortcutService.DEBUG) { - Slog.d(TAG, String.format("Shortcut %s require version %s, still not restored.", - si.getId(), getPackageInfo().getBackupSourceVersionCode())); - } - continue; - } - Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId())); - si.clearFlags(ShortcutInfo.FLAG_DISABLED); - si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED); - } + getPackageInfo().updateVersionInfo(pi); // For existing shortcuts, update timestamps if they have any resources. // Also check if shortcuts' activities are still main activities. Otherwise, disable them. @@ -795,8 +713,7 @@ class ShortcutPackage extends ShortcutPackageItem { Slog.w(TAG, String.format( "%s is no longer main activity. Disabling shorcut %s.", getPackageName(), si.getId())); - if (disableDynamicWithId(si.getId(), /*ignoreInvisible*/ false, - ShortcutInfo.DISABLED_REASON_APP_CHANGED)) { + if (disableDynamicWithId(si.getId())) { continue; // Actually removed. } // Still pinned, so fall-through and possibly update the resources. @@ -892,7 +809,7 @@ class ShortcutPackage extends ShortcutPackageItem { // Note even if enabled=false, we still need to update all fields, so do it // regardless. - forceReplaceShortcutInner(newShortcut); // This will clean up the old one too. + addShortcutInner(newShortcut); // This will clean up the old one too. if (!newDisabled && toDisableList != null) { // Still alive, don't remove. @@ -914,8 +831,7 @@ class ShortcutPackage extends ShortcutPackageItem { final String id = toDisableList.valueAt(i); disableWithId(id, /* disable message =*/ null, /* disable message resid */ 0, - /* overrideImmutable=*/ true, /*ignoreInvisible=*/ false, - ShortcutInfo.DISABLED_REASON_APP_CHANGED); + /* overrideImmutable=*/ true); } removeOrphans(); } @@ -953,7 +869,7 @@ class ShortcutPackage extends ShortcutPackageItem { service.wtf("Found manifest shortcuts in excess list."); continue; } - deleteDynamicWithId(shortcut.getId(), /*ignoreInvisible=*/ true); + deleteDynamicWithId(shortcut.getId()); } } @@ -1159,7 +1075,7 @@ class ShortcutPackage extends ShortcutPackageItem { if (ret != 0) { return ret; } - // If they're still tie, just sort by their IDs. + // If they're stil tie, just sort by their IDs. // This may happen with updateShortcuts() -- see // the testUpdateShortcuts_noManifestShortcuts() test. return a.getId().compareTo(b.getId()); @@ -1341,34 +1257,25 @@ class ShortcutPackage extends ShortcutPackageItem { ShortcutService.writeAttr(out, ATTR_NAME, getPackageName()); ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount); ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime); - getPackageInfo().saveToXml(out, forBackup); + getPackageInfo().saveToXml(out); for (int j = 0; j < size; j++) { - saveShortcut(out, mShortcuts.valueAt(j), forBackup, - getPackageInfo().isBackupAllowed()); + saveShortcut(out, mShortcuts.valueAt(j), forBackup); } out.endTag(null, TAG_ROOT); } - private void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup, - boolean appSupportsBackup) + private void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup) throws IOException, XmlPullParserException { final ShortcutService s = mShortcutUser.mService; if (forBackup) { if (!(si.isPinned() && si.isEnabled())) { - // We only backup pinned shortcuts that are enabled. - // Note, this means, shortcuts that are restored but are blocked restore, e.g. due - // to a lower version code, will not be ported to a new device. - return; + return; // We only backup pinned shortcuts that are enabled. } } - final boolean shouldBackupDetails = - !forBackup // It's not backup - || appSupportsBackup; // Or, it's a backup and app supports backup. - // Note: at this point no shortcuts should have bitmaps pending save, but if they do, // just remove the bitmap. if (si.isIconPendingSave()) { @@ -1385,31 +1292,20 @@ class ShortcutPackage extends ShortcutPackageItem { ShortcutService.writeAttr(out, ATTR_TEXT, si.getText()); ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId()); ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName()); - if (shouldBackupDetails) { - ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage()); - ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID, - si.getDisabledMessageResourceId()); - ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME, - si.getDisabledMessageResName()); - } - ShortcutService.writeAttr(out, ATTR_DISABLED_REASON, si.getDisabledReason()); + ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage()); + ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID, + si.getDisabledMessageResourceId()); + ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME, + si.getDisabledMessageResName()); ShortcutService.writeAttr(out, ATTR_TIMESTAMP, si.getLastChangedTimestamp()); if (forBackup) { // Don't write icon information. Also drop the dynamic flag. - - int flags = si.getFlags() & - ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES + ShortcutService.writeAttr(out, ATTR_FLAGS, + si.getFlags() & + ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES | ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE - | ShortcutInfo.FLAG_DYNAMIC); - ShortcutService.writeAttr(out, ATTR_FLAGS, flags); - - // Set the publisher version code at every backup. - final int packageVersionCode = getPackageInfo().getVersionCode(); - if (packageVersionCode == 0) { - s.wtf("Package version code should be available at this point."); - // However, 0 is a valid version code, so we just go ahead with it... - } + | ShortcutInfo.FLAG_DYNAMIC)); } else { // When writing for backup, ranks shouldn't be saved, since shortcuts won't be restored // as dynamic. @@ -1421,28 +1317,26 @@ class ShortcutPackage extends ShortcutPackageItem { ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath()); } - if (shouldBackupDetails) { - { - final Set<String> cat = si.getCategories(); - if (cat != null && cat.size() > 0) { - out.startTag(null, TAG_CATEGORIES); - XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]), - NAME_CATEGORIES, out); - out.endTag(null, TAG_CATEGORIES); - } - } - final Intent[] intentsNoExtras = si.getIntentsNoExtras(); - final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases(); - final int numIntents = intentsNoExtras.length; - for (int i = 0; i < numIntents; i++) { - out.startTag(null, TAG_INTENT); - ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]); - ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]); - out.endTag(null, TAG_INTENT); + { + final Set<String> cat = si.getCategories(); + if (cat != null && cat.size() > 0) { + out.startTag(null, TAG_CATEGORIES); + XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]), + NAME_CATEGORIES, out); + out.endTag(null, TAG_CATEGORIES); } - - ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras()); } + final Intent[] intentsNoExtras = si.getIntentsNoExtras(); + final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases(); + final int numIntents = intentsNoExtras.length; + for (int i = 0; i < numIntents; i++) { + out.startTag(null, TAG_INTENT); + ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]); + ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]); + out.endTag(null, TAG_INTENT); + } + + ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras()); out.endTag(null, TAG_SHORTCUT); } @@ -1462,7 +1356,6 @@ class ShortcutPackage extends ShortcutPackageItem { ret.mLastResetTime = ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET); - final int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -1476,11 +1369,10 @@ class ShortcutPackage extends ShortcutPackageItem { switch (tag) { case ShortcutPackageInfo.TAG_ROOT: ret.getPackageInfo().loadFromXml(parser, fromBackup); - continue; case TAG_SHORTCUT: final ShortcutInfo si = parseShortcut(parser, packageName, - shortcutUser.getUserId(), fromBackup); + shortcutUser.getUserId()); // Don't use addShortcut(), we don't need to save the icon. ret.mShortcuts.put(si.getId(), si); @@ -1493,8 +1385,7 @@ class ShortcutPackage extends ShortcutPackageItem { } private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName, - @UserIdInt int userId, boolean fromBackup) - throws IOException, XmlPullParserException { + @UserIdInt int userId) throws IOException, XmlPullParserException { String id; ComponentName activityComponent; // Icon icon; @@ -1507,7 +1398,6 @@ class ShortcutPackage extends ShortcutPackageItem { String disabledMessage; int disabledMessageResId; String disabledMessageResName; - int disabledReason; Intent intentLegacy; PersistableBundle intentPersistableExtrasLegacy = null; ArrayList<Intent> intents = new ArrayList<>(); @@ -1518,7 +1408,6 @@ class ShortcutPackage extends ShortcutPackageItem { int iconResId; String iconResName; String bitmapPath; - int backupVersionCode; ArraySet<String> categories = null; id = ShortcutService.parseStringAttribute(parser, ATTR_ID); @@ -1535,7 +1424,6 @@ class ShortcutPackage extends ShortcutPackageItem { ATTR_DISABLED_MESSAGE_RES_ID); disabledMessageResName = ShortcutService.parseStringAttribute(parser, ATTR_DISABLED_MESSAGE_RES_NAME); - disabledReason = ShortcutService.parseIntAttribute(parser, ATTR_DISABLED_REASON); intentLegacy = ShortcutService.parseIntentAttributeNoDefault(parser, ATTR_INTENT_LEGACY); rank = (int) ShortcutService.parseLongAttribute(parser, ATTR_RANK); lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP); @@ -1592,19 +1480,6 @@ class ShortcutPackage extends ShortcutPackageItem { intents.add(intentLegacy); } - - if ((disabledReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) - && ((flags & ShortcutInfo.FLAG_DISABLED) != 0)) { - // We didn't used to have the disabled reason, so if a shortcut is disabled - // and has no reason, we assume it was disabled by publisher. - disabledReason = ShortcutInfo.DISABLED_REASON_BY_APP; - } - - // All restored shortcuts are initially "shadow". - if (fromBackup) { - flags |= ShortcutInfo.FLAG_SHADOW; - } - return new ShortcutInfo( userId, id, packageName, activityComponent, /* icon =*/ null, title, titleResId, titleResName, text, textResId, textResName, @@ -1612,7 +1487,7 @@ class ShortcutPackage extends ShortcutPackageItem { categories, intents.toArray(new Intent[intents.size()]), rank, extras, lastChangedTimestamp, flags, - iconResId, iconResName, bitmapPath, disabledReason); + iconResId, iconResName, bitmapPath); } private static Intent parseIntent(XmlPullParser parser) @@ -1727,20 +1602,6 @@ class ShortcutPackage extends ShortcutPackageItem { Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + " has both resource and bitmap icons"); } - if (si.isEnabled() - != (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)) { - failed = true; - Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() - + " isEnabled() and getDisabledReason() disagree: " - + si.isEnabled() + " vs " + si.getDisabledReason()); - } - if ((si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER) - && (getPackageInfo().getBackupSourceVersionCode() - == ShortcutInfo.VERSION_CODE_UNKNOWN)) { - failed = true; - Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() - + " RESTORED_VERSION_LOWER with no backup source version code."); - } if (s.isDummyMainActivity(si.getActivity())) { failed = true; Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() diff --git a/com/android/server/pm/ShortcutPackageInfo.java b/com/android/server/pm/ShortcutPackageInfo.java index 3a9bbc89..e5a2f5ac 100644 --- a/com/android/server/pm/ShortcutPackageInfo.java +++ b/com/android/server/pm/ShortcutPackageInfo.java @@ -18,7 +18,6 @@ package com.android.server.pm; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.pm.PackageInfo; -import android.content.pm.ShortcutInfo; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -46,45 +45,32 @@ class ShortcutPackageInfo { static final String TAG_ROOT = "package-info"; private static final String ATTR_VERSION = "version"; private static final String ATTR_LAST_UPDATE_TIME = "last_udpate_time"; - private static final String ATTR_BACKUP_SOURCE_VERSION = "bk_src_version"; - private static final String ATTR_BACKUP_ALLOWED = "allow-backup"; - private static final String ATTR_BACKUP_SOURCE_BACKUP_ALLOWED = "bk_src_backup-allowed"; private static final String ATTR_SHADOW = "shadow"; private static final String TAG_SIGNATURE = "signature"; private static final String ATTR_SIGNATURE_HASH = "hash"; + private static final int VERSION_UNKNOWN = -1; + /** * When true, this package information was restored from the previous device, and the app hasn't * been installed yet. */ private boolean mIsShadow; - private int mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN; - private int mBackupSourceVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN; + private int mVersionCode = VERSION_UNKNOWN; private long mLastUpdateTime; private ArrayList<byte[]> mSigHashes; - // mBackupAllowed didn't used to be parsisted, so we don't restore it from a file. - // mBackupAllowed will always start with false, and will have been updated before making a - // backup next time, which works file. - // We just don't want to print an uninitialzied mBackupAlldowed value on dumpsys, so - // we use this boolean to control dumpsys. - private boolean mBackupAllowedInitialized; - private boolean mBackupAllowed; - private boolean mBackupSourceBackupAllowed; - private ShortcutPackageInfo(int versionCode, long lastUpdateTime, ArrayList<byte[]> sigHashes, boolean isShadow) { mVersionCode = versionCode; mLastUpdateTime = lastUpdateTime; mIsShadow = isShadow; mSigHashes = sigHashes; - mBackupAllowed = false; // By default, we assume false. - mBackupSourceBackupAllowed = false; } public static ShortcutPackageInfo newEmpty() { - return new ShortcutPackageInfo(ShortcutInfo.VERSION_CODE_UNKNOWN, /* last update time =*/ 0, + return new ShortcutPackageInfo(VERSION_UNKNOWN, /* last update time =*/ 0, new ArrayList<>(0), /* isShadow */ false); } @@ -100,33 +86,15 @@ class ShortcutPackageInfo { return mVersionCode; } - public int getBackupSourceVersionCode() { - return mBackupSourceVersionCode; - } - - @VisibleForTesting - public boolean isBackupSourceBackupAllowed() { - return mBackupSourceBackupAllowed; - } - public long getLastUpdateTime() { return mLastUpdateTime; } - public boolean isBackupAllowed() { - return mBackupAllowed; - } - - /** - * Set {@link #mVersionCode}, {@link #mLastUpdateTime} and {@link #mBackupAllowed} - * from a {@link PackageInfo}. - */ - public void updateFromPackageInfo(@NonNull PackageInfo pi) { + /** Set {@link #mVersionCode} and {@link #mLastUpdateTime} from a {@link PackageInfo}. */ + public void updateVersionInfo(@NonNull PackageInfo pi) { if (pi != null) { mVersionCode = pi.versionCode; mLastUpdateTime = pi.lastUpdateTime; - mBackupAllowed = ShortcutService.shouldBackupApp(pi); - mBackupAllowedInitialized = true; } } @@ -134,24 +102,23 @@ class ShortcutPackageInfo { return mSigHashes.size() > 0; } - //@DisabledReason - public int canRestoreTo(ShortcutService s, PackageInfo currentPackage, boolean anyVersionOkay) { - if (!BackupUtils.signaturesMatch(mSigHashes, currentPackage)) { - Slog.w(TAG, "Can't restore: Package signature mismatch"); - return ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH; - } - if (!ShortcutService.shouldBackupApp(currentPackage) || !mBackupSourceBackupAllowed) { + public boolean canRestoreTo(ShortcutService s, PackageInfo target) { + if (!s.shouldBackupApp(target)) { // "allowBackup" was true when backed up, but now false. - Slog.w(TAG, "Can't restore: package didn't or doesn't allow backup"); - return ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED; + Slog.w(TAG, "Can't restore: package no longer allows backup"); + return false; } - if (!anyVersionOkay && (currentPackage.versionCode < mBackupSourceVersionCode)) { + if (target.versionCode < mVersionCode) { Slog.w(TAG, String.format( "Can't restore: package current version %d < backed up version %d", - currentPackage.versionCode, mBackupSourceVersionCode)); - return ShortcutInfo.DISABLED_REASON_VERSION_LOWER; + target.versionCode, mVersionCode)); + return false; + } + if (!BackupUtils.signaturesMatch(mSigHashes, target)) { + Slog.w(TAG, "Can't restore: Package signature mismatch"); + return false; } - return ShortcutInfo.DISABLED_REASON_NOT_DISABLED; + return true; } @VisibleForTesting @@ -165,8 +132,6 @@ class ShortcutPackageInfo { final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode, pi.lastUpdateTime, BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false); - ret.mBackupSourceBackupAllowed = s.shouldBackupApp(pi); - ret.mBackupSourceVersionCode = pi.versionCode; return ret; } @@ -186,19 +151,13 @@ class ShortcutPackageInfo { mSigHashes = BackupUtils.hashSignatureArray(pi.signatures); } - public void saveToXml(XmlSerializer out, boolean forBackup) throws IOException { + public void saveToXml(XmlSerializer out) throws IOException { out.startTag(null, TAG_ROOT); ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode); ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime); ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow); - ShortcutService.writeAttr(out, ATTR_BACKUP_ALLOWED, mBackupAllowed); - - ShortcutService.writeAttr(out, ATTR_BACKUP_SOURCE_VERSION, mBackupSourceVersionCode); - ShortcutService.writeAttr(out, - ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, mBackupSourceBackupAllowed); - for (int i = 0; i < mSigHashes.size(); i++) { out.startTag(null, TAG_SIGNATURE); @@ -212,9 +171,7 @@ class ShortcutPackageInfo { public void loadFromXml(XmlPullParser parser, boolean fromBackup) throws IOException, XmlPullParserException { - // Don't use the version code from the backup file. - final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION, - ShortcutInfo.VERSION_CODE_UNKNOWN); + final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION); final long lastUpdateTime = ShortcutService.parseLongAttribute( parser, ATTR_LAST_UPDATE_TIME); @@ -223,20 +180,6 @@ class ShortcutPackageInfo { final boolean shadow = fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW); - // We didn't used to save these attributes, and all backed up shortcuts were from - // apps that support backups, so the default values take this fact into consideration. - final int backupSourceVersion = ShortcutService.parseIntAttribute(parser, - ATTR_BACKUP_SOURCE_VERSION, ShortcutInfo.VERSION_CODE_UNKNOWN); - - // Note the only time these "true" default value is used is when restoring from an old - // build that didn't save ATTR_BACKUP_ALLOWED, and that means all the data included in - // a backup file were from apps that support backup, so we can just use "true" as the - // default. - final boolean backupAllowed = ShortcutService.parseBooleanAttribute( - parser, ATTR_BACKUP_ALLOWED, true); - final boolean backupSourceBackupAllowed = ShortcutService.parseBooleanAttribute( - parser, ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, true); - final ArrayList<byte[]> hashes = new ArrayList<>(); final int outerDepth = parser.getDepth(); @@ -264,28 +207,11 @@ class ShortcutPackageInfo { ShortcutService.warnForInvalidTag(depth, tag); } - // Successfully loaded; replace the fields. - if (fromBackup) { - mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN; - mBackupSourceVersionCode = versionCode; - mBackupSourceBackupAllowed = backupAllowed; - } else { - mVersionCode = versionCode; - mBackupSourceVersionCode = backupSourceVersion; - mBackupSourceBackupAllowed = backupSourceBackupAllowed; - } + // Successfully loaded; replace the feilds. + mVersionCode = versionCode; mLastUpdateTime = lastUpdateTime; mIsShadow = shadow; mSigHashes = hashes; - - // Note we don't restore it from the file because it didn't used to be saved. - // We always start by assuming backup is disabled for the current package, - // and this field will have been updated before we actually create a backup, at the same - // time when we update the version code. - // Until then, the value of mBackupAllowed shouldn't matter, but we don't want to print - // a false flag on dumpsys, so set mBackupAllowedInitialized to false. - mBackupAllowed = false; - mBackupAllowedInitialized = false; } public void dump(PrintWriter pw, String prefix) { @@ -297,7 +223,6 @@ class ShortcutPackageInfo { pw.print(prefix); pw.print(" IsShadow: "); pw.print(mIsShadow); - pw.print(mIsShadow ? " (not installed)" : " (installed)"); pw.println(); pw.print(prefix); @@ -305,25 +230,6 @@ class ShortcutPackageInfo { pw.print(mVersionCode); pw.println(); - if (mBackupAllowedInitialized) { - pw.print(prefix); - pw.print(" Backup Allowed: "); - pw.print(mBackupAllowed); - pw.println(); - } - - if (mBackupSourceVersionCode != ShortcutInfo.VERSION_CODE_UNKNOWN) { - pw.print(prefix); - pw.print(" Backup source version: "); - pw.print(mBackupSourceVersionCode); - pw.println(); - - pw.print(prefix); - pw.print(" Backup source backup allowed: "); - pw.print(mBackupSourceBackupAllowed); - pw.println(); - } - pw.print(prefix); pw.print(" Last package update time: "); pw.print(mLastUpdateTime); diff --git a/com/android/server/pm/ShortcutPackageItem.java b/com/android/server/pm/ShortcutPackageItem.java index 689099cf..e59d69f4 100644 --- a/com/android/server/pm/ShortcutPackageItem.java +++ b/com/android/server/pm/ShortcutPackageItem.java @@ -17,7 +17,6 @@ package com.android.server.pm; import android.annotation.NonNull; import android.content.pm.PackageInfo; -import android.content.pm.ShortcutInfo; import android.util.Slog; import com.android.internal.util.Preconditions; @@ -102,42 +101,51 @@ abstract class ShortcutPackageItem { final ShortcutService s = mShortcutUser.mService; if (!s.isPackageInstalled(mPackageName, mPackageUserId)) { if (ShortcutService.DEBUG) { - Slog.d(TAG, String.format("Package still not installed: %s/u%d", + Slog.d(TAG, String.format("Package still not installed: %s user=%d", mPackageName, mPackageUserId)); } return; // Not installed, no need to restore yet. } - int restoreBlockReason; - int currentVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN; - + boolean blockRestore = false; if (!mPackageInfo.hasSignatures()) { - s.wtf("Attempted to restore package " + mPackageName + "/u" + mPackageUserId + s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId + " but signatures not found in the restore data."); - restoreBlockReason = ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH; - } else { + blockRestore = true; + } + if (!blockRestore) { final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId); - currentVersionCode = pi.versionCode; - restoreBlockReason = mPackageInfo.canRestoreTo(s, pi, canRestoreAnyVersion()); + if (!mPackageInfo.canRestoreTo(s, pi)) { + // Package is now installed, but can't restore. Let the subclass do the cleanup. + blockRestore = true; + } } + if (blockRestore) { + onRestoreBlocked(); + } else { + if (ShortcutService.DEBUG) { + Slog.d(TAG, String.format("Restored package: %s/%d on user %d", mPackageName, + mPackageUserId, getOwnerUserId())); + } - if (ShortcutService.DEBUG) { - Slog.d(TAG, String.format("Restoring package: %s/u%d (version=%d) %s for u%d", - mPackageName, mPackageUserId, currentVersionCode, - ShortcutInfo.getDisabledReasonDebugString(restoreBlockReason), - getOwnerUserId())); + onRestored(); } - onRestored(restoreBlockReason); - // Either way, it's no longer a shadow. mPackageInfo.setShadow(false); s.scheduleSaveUser(mPackageUserId); } - protected abstract boolean canRestoreAnyVersion(); + /** + * Called when the new package can't be restored because it has a lower version number + * or different signatures. + */ + protected abstract void onRestoreBlocked(); - protected abstract void onRestored(int restoreBlockReason); + /** + * Called when the new package is successfully restored. + */ + protected abstract void onRestored(); public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup) throws IOException, XmlPullParserException; diff --git a/com/android/server/pm/ShortcutParser.java b/com/android/server/pm/ShortcutParser.java index 866c46c6..3cf4200b 100644 --- a/com/android/server/pm/ShortcutParser.java +++ b/com/android/server/pm/ShortcutParser.java @@ -337,9 +337,6 @@ public class ShortcutParser { (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED) | ShortcutInfo.FLAG_IMMUTABLE | ((iconResId != 0) ? ShortcutInfo.FLAG_HAS_ICON_RES : 0); - final int disabledReason = - enabled ? ShortcutInfo.DISABLED_REASON_NOT_DISABLED - : ShortcutInfo.DISABLED_REASON_BY_APP; // Note we don't need to set resource names here yet. They'll be set when they're about // to be published. @@ -366,7 +363,6 @@ public class ShortcutParser { flags, iconResId, null, // icon res name - null, // bitmap path - disabledReason); + null); // bitmap path } } diff --git a/com/android/server/pm/ShortcutRequestPinProcessor.java b/com/android/server/pm/ShortcutRequestPinProcessor.java index 3e44de98..8a8128db 100644 --- a/com/android/server/pm/ShortcutRequestPinProcessor.java +++ b/com/android/server/pm/ShortcutRequestPinProcessor.java @@ -300,12 +300,10 @@ class ShortcutRequestPinProcessor { final ShortcutInfo existing = ps.findShortcutById(inShortcut.getId()); final boolean existsAlready = existing != null; - final boolean existingIsVisible = existsAlready && existing.isVisibleToPublisher(); if (DEBUG) { Slog.d(TAG, "requestPinnedShortcut: package=" + inShortcut.getPackage() + " existsAlready=" + existsAlready - + " existingIsVisible=" + existingIsVisible + " shortcut=" + inShortcut.toInsecureString()); } @@ -380,6 +378,7 @@ class ShortcutRequestPinProcessor { // manifest shortcut.) Preconditions.checkArgument(shortcutInfo.isEnabled(), "Shortcut ID=" + shortcutInfo + " already exists but disabled."); + } private boolean startRequestConfirmActivity(ComponentName activity, int launcherUserId, @@ -464,7 +463,7 @@ class ShortcutRequestPinProcessor { launcher.attemptToRestoreIfNeededAndSave(); if (launcher.hasPinned(original)) { if (DEBUG) { - Slog.d(TAG, "Shortcut " + original + " already pinned."); // This too. + Slog.d(TAG, "Shortcut " + original + " already pinned."); } return true; } @@ -498,7 +497,7 @@ class ShortcutRequestPinProcessor { if (original.getActivity() == null) { original.setActivity(mService.getDummyMainActivity(appPackageName)); } - ps.addOrReplaceDynamicShortcut(original); + ps.addOrUpdateDynamicShortcut(original); } // Pin the shortcut. @@ -506,14 +505,13 @@ class ShortcutRequestPinProcessor { Slog.d(TAG, "Pinning " + shortcutId); } - launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId, - /*forPinRequest=*/ true); + launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId); if (current == null) { if (DEBUG) { Slog.d(TAG, "Removing " + shortcutId + " as dynamic"); } - ps.deleteDynamicWithId(shortcutId, /*ignoreInvisible=*/ false); + ps.deleteDynamicWithId(shortcutId); } ps.adjustRanks(); // Shouldn't be needed, but just in case. diff --git a/com/android/server/pm/ShortcutService.java b/com/android/server/pm/ShortcutService.java index 1c002aa4..27560c5f 100644 --- a/com/android/server/pm/ShortcutService.java +++ b/com/android/server/pm/ShortcutService.java @@ -553,9 +553,6 @@ public class ShortcutService extends IShortcutService.Stub { public Lifecycle(Context context) { super(context); - if (DEBUG) { - Binder.LOG_RUNTIME_EXCEPTION = true; - } mService = new ShortcutService(context); } @@ -741,10 +738,6 @@ public class ShortcutService extends IShortcutService.Stub { return parseLongAttribute(parser, attribute) == 1; } - static boolean parseBooleanAttribute(XmlPullParser parser, String attribute, boolean def) { - return parseLongAttribute(parser, attribute, (def ? 1 : 0)) == 1; - } - static int parseIntAttribute(XmlPullParser parser, String attribute) { return (int) parseLongAttribute(parser, attribute); } @@ -842,8 +835,6 @@ public class ShortcutService extends IShortcutService.Stub { static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException { if (value) { writeAttr(out, name, "1"); - } else { - writeAttr(out, name, "0"); } } @@ -1698,7 +1689,7 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); - ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); + ps.ensureImmutableShortcutsNotIncluded(newShortcuts); fillInDefaultActivity(newShortcuts); @@ -1718,12 +1709,12 @@ public class ShortcutService extends IShortcutService.Stub { } // First, remove all un-pinned; dynamic shortcuts - ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); + ps.deleteAllDynamicShortcuts(); // Then, add/update all. We need to make sure to take over "pinned" flag. for (int i = 0; i < size; i++) { final ShortcutInfo newShortcut = newShortcuts.get(i); - ps.addOrReplaceDynamicShortcut(newShortcut); + ps.addOrUpdateDynamicShortcut(newShortcut); } // Lastly, adjust the ranks. @@ -1749,7 +1740,7 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); - ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); + ps.ensureImmutableShortcutsNotIncluded(newShortcuts); // For update, don't fill in the default activity. Having null activity means // "don't update the activity" here. @@ -1770,9 +1761,7 @@ public class ShortcutService extends IShortcutService.Stub { fixUpIncomingShortcutInfo(source, /* forUpdate= */ true); final ShortcutInfo target = ps.findShortcutById(source.getId()); - - // Invisible shortcuts can't be updated. - if (target == null || !target.isVisibleToPublisher()) { + if (target == null) { continue; } @@ -1819,7 +1808,7 @@ public class ShortcutService extends IShortcutService.Stub { } @Override - public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, + public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, @UserIdInt int userId) { verifyCaller(packageName, userId); @@ -1831,7 +1820,7 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); - ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); + ps.ensureImmutableShortcutsNotIncluded(newShortcuts); fillInDefaultActivity(newShortcuts); @@ -1856,7 +1845,7 @@ public class ShortcutService extends IShortcutService.Stub { newShortcut.setRankChanged(); // Add it. - ps.addOrReplaceDynamicShortcut(newShortcut); + ps.addOrUpdateDynamicShortcut(newShortcut); } // Lastly, adjust the ranks. @@ -1912,22 +1901,6 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkState(isUidForegroundLocked(injectBinderCallingUid()), "Calling application must have a foreground activity or a foreground service"); - // If it's a pin shortcut request, and there's already a shortcut with the same ID - // that's not visible to the caller (i.e. restore-blocked; meaning it's pinned by - // someone already), then we just replace the existing one with this new one, - // and then proceed the rest of the process. - if (shortcut != null) { - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked( - packageName, userId); - final String id = shortcut.getId(); - if (ps.isShortcutExistsAndInvisibleToPublisher(id)) { - - ps.updateInvisibleShortcutForPinRequestWith(shortcut); - - packageShortcutsChanged(packageName, userId); - } - } - // Send request to the launcher, if supported. ret = mShortcutRequestPinProcessor.requestPinItemLocked(shortcut, appWidget, extras, userId, resultIntent); @@ -1949,21 +1922,15 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); - ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, - /*ignoreInvisible=*/ true); + ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds); final String disabledMessageString = (disabledMessage == null) ? null : disabledMessage.toString(); for (int i = shortcutIds.size() - 1; i >= 0; i--) { - final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)); - if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { - continue; - } - ps.disableWithId(id, + ps.disableWithId(Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)), disabledMessageString, disabledMessageResId, - /* overrideImmutable=*/ false, /*ignoreInvisible=*/ true, - ShortcutInfo.DISABLED_REASON_BY_APP); + /* overrideImmutable=*/ false); } // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks. @@ -1984,15 +1951,10 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); - ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, - /*ignoreInvisible=*/ true); + ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds); for (int i = shortcutIds.size() - 1; i >= 0; i--) { - final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)); - if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { - continue; - } - ps.enableWithId(id); + ps.enableWithId((String) shortcutIds.get(i)); } } packageShortcutsChanged(packageName, userId); @@ -2011,15 +1973,11 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); - ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, - /*ignoreInvisible=*/ true); + ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds); for (int i = shortcutIds.size() - 1; i >= 0; i--) { - final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)); - if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { - continue; - } - ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true); + ps.deleteDynamicWithId( + Preconditions.checkStringNotEmpty((String) shortcutIds.get(i))); } // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks. @@ -2038,7 +1996,7 @@ public class ShortcutService extends IShortcutService.Stub { throwIfUserLockedL(userId); final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); - ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); + ps.deleteAllDynamicShortcuts(); } packageShortcutsChanged(packageName, userId); @@ -2055,7 +2013,7 @@ public class ShortcutService extends IShortcutService.Stub { return getShortcutsWithQueryLocked( packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, - ShortcutInfo::isDynamicVisible); + ShortcutInfo::isDynamic); } } @@ -2069,7 +2027,7 @@ public class ShortcutService extends IShortcutService.Stub { return getShortcutsWithQueryLocked( packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, - ShortcutInfo::isManifestVisible); + ShortcutInfo::isManifestShortcut); } } @@ -2083,7 +2041,7 @@ public class ShortcutService extends IShortcutService.Stub { return getShortcutsWithQueryLocked( packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, - ShortcutInfo::isPinnedVisible); + ShortcutInfo::isPinned); } } @@ -2232,11 +2190,7 @@ public class ShortcutService extends IShortcutService.Stub { } // We override this method in unit tests to do a simpler check. - boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId, - int callingPid, int callingUid) { - if (injectCheckAccessShortcutsPermission(callingPid, callingUid)) { - return true; - } + boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) { final long start = injectElapsedRealtime(); try { return hasShortcutHostPermissionInner(callingPackage, userId); @@ -2245,14 +2199,6 @@ public class ShortcutService extends IShortcutService.Stub { } } - /** - * Returns true if the caller has the "ACCESS_SHORTCUTS" permission. - */ - boolean injectCheckAccessShortcutsPermission(int callingPid, int callingUid) { - return mContext.checkPermission(android.Manifest.permission.ACCESS_SHORTCUTS, - callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; - } - // This method is extracted so we can directly call this method from unit tests, // even when hasShortcutPermission() is overridden. @VisibleForTesting @@ -2433,7 +2379,7 @@ public class ShortcutService extends IShortcutService.Stub { @NonNull String callingPackage, long changedSince, @Nullable String packageName, @Nullable List<String> shortcutIds, @Nullable ComponentName componentName, - int queryFlags, int userId, int callingPid, int callingUid) { + int queryFlags, int userId) { final ArrayList<ShortcutInfo> ret = new ArrayList<>(); final boolean cloneKeyFieldOnly = @@ -2454,15 +2400,13 @@ public class ShortcutService extends IShortcutService.Stub { if (packageName != null) { getShortcutsInnerLocked(launcherUserId, callingPackage, packageName, shortcutIds, changedSince, - componentName, queryFlags, userId, ret, cloneFlag, - callingPid, callingUid); + componentName, queryFlags, userId, ret, cloneFlag); } else { final List<String> shortcutIdsF = shortcutIds; getUserShortcutsLocked(userId).forAllPackages(p -> { getShortcutsInnerLocked(launcherUserId, callingPackage, p.getPackageName(), shortcutIdsF, changedSince, - componentName, queryFlags, userId, ret, cloneFlag, - callingPid, callingUid); + componentName, queryFlags, userId, ret, cloneFlag); }); } } @@ -2472,8 +2416,7 @@ public class ShortcutService extends IShortcutService.Stub { private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage, @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince, @Nullable ComponentName componentName, int queryFlags, - int userId, ArrayList<ShortcutInfo> ret, int cloneFlag, - int callingPid, int callingUid) { + int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) { final ArraySet<String> ids = shortcutIds == null ? null : new ArraySet<>(shortcutIds); @@ -2482,13 +2425,6 @@ public class ShortcutService extends IShortcutService.Stub { if (p == null) { return; // No need to instantiate ShortcutPackage. } - final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0; - final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0; - final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0; - - final boolean getPinnedByAnyLauncher = - ((queryFlags & ShortcutQuery.FLAG_MATCH_ALL_PINNED) != 0) - && injectCheckAccessShortcutsPermission(callingPid, callingUid); p.findAll(ret, (ShortcutInfo si) -> { @@ -2504,17 +2440,20 @@ public class ShortcutService extends IShortcutService.Stub { return false; } } - if (matchDynamic && si.isDynamic()) { + if (((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0) + && si.isDynamic()) { return true; } - if ((matchPinned && si.isPinned()) || getPinnedByAnyLauncher) { + if (((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0) + && si.isPinned()) { return true; } - if (matchManifest && si.isDeclaredInManifest()) { + if (((queryFlags & ShortcutQuery.FLAG_GET_MANIFEST) != 0) + && si.isManifestShortcut()) { return true; } return false; - }, cloneFlag, callingPackage, launcherUserId, getPinnedByAnyLauncher); + }, cloneFlag, callingPackage, launcherUserId); } @Override @@ -2531,16 +2470,14 @@ public class ShortcutService extends IShortcutService.Stub { .attemptToRestoreIfNeededAndSave(); final ShortcutInfo si = getShortcutInfoLocked( - launcherUserId, callingPackage, packageName, shortcutId, userId, - /*getPinnedByAnyLauncher=*/ false); + launcherUserId, callingPackage, packageName, shortcutId, userId); return si != null && si.isPinned(); } } private ShortcutInfo getShortcutInfoLocked( int launcherUserId, @NonNull String callingPackage, - @NonNull String packageName, @NonNull String shortcutId, int userId, - boolean getPinnedByAnyLauncher) { + @NonNull String packageName, @NonNull String shortcutId, int userId) { Preconditions.checkStringNotEmpty(packageName, "packageName"); Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); @@ -2556,7 +2493,7 @@ public class ShortcutService extends IShortcutService.Stub { final ArrayList<ShortcutInfo> list = new ArrayList<>(1); p.findAll(list, (ShortcutInfo si) -> shortcutId.equals(si.getId()), - /* clone flags=*/ 0, callingPackage, launcherUserId, getPinnedByAnyLauncher); + /* clone flags=*/ 0, callingPackage, launcherUserId); return list.size() == 0 ? null : list.get(0); } @@ -2576,7 +2513,7 @@ public class ShortcutService extends IShortcutService.Stub { getLauncherShortcutsLocked(callingPackage, userId, launcherUserId); launcher.attemptToRestoreIfNeededAndSave(); - launcher.pinShortcuts(userId, packageName, shortcutIds, /*forPinRequest=*/ false); + launcher.pinShortcuts(userId, packageName, shortcutIds); } packageShortcutsChanged(packageName, userId); @@ -2586,8 +2523,7 @@ public class ShortcutService extends IShortcutService.Stub { @Override public Intent[] createShortcutIntents(int launcherUserId, @NonNull String callingPackage, - @NonNull String packageName, @NonNull String shortcutId, int userId, - int callingPid, int callingUid) { + @NonNull String packageName, @NonNull String shortcutId, int userId) { // Calling permission must be checked by LauncherAppsImpl. Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty"); Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty"); @@ -2599,13 +2535,9 @@ public class ShortcutService extends IShortcutService.Stub { getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) .attemptToRestoreIfNeededAndSave(); - final boolean getPinnedByAnyLauncher = - injectCheckAccessShortcutsPermission(callingPid, callingUid); - // Make sure the shortcut is actually visible to the launcher. final ShortcutInfo si = getShortcutInfoLocked( - launcherUserId, callingPackage, packageName, shortcutId, userId, - getPinnedByAnyLauncher); + launcherUserId, callingPackage, packageName, shortcutId, userId); // "si == null" should suffice here, but check the flags too just to make sure. if (si == null || !si.isEnabled() || !si.isAlive()) { Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled"); @@ -2691,9 +2623,8 @@ public class ShortcutService extends IShortcutService.Stub { @Override public boolean hasShortcutHostPermission(int launcherUserId, - @NonNull String callingPackage, int callingPid, int callingUid) { - return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId, - callingPid, callingUid); + @NonNull String callingPackage) { + return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId); } @Override @@ -3412,7 +3343,7 @@ public class ShortcutService extends IShortcutService.Stub { return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP); } - static boolean shouldBackupApp(PackageInfo pi) { + boolean shouldBackupApp(PackageInfo pi) { return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0; } @@ -3440,7 +3371,7 @@ public class ShortcutService extends IShortcutService.Stub { // Set the version code for the launchers. // We shouldn't do this for publisher packages, because we don't want to update the // version code without rescanning the manifest. - user.forAllLaunchers(launcher -> launcher.ensurePackageInfo()); + user.forAllLaunchers(launcher -> launcher.ensureVersionInfo()); // Save to the filesystem. scheduleSaveUser(userId); diff --git a/com/android/server/pm/ShortcutUser.java b/com/android/server/pm/ShortcutUser.java index 48eccd02..55e6d28a 100644 --- a/com/android/server/pm/ShortcutUser.java +++ b/com/android/server/pm/ShortcutUser.java @@ -364,6 +364,9 @@ class ShortcutUser { private void saveShortcutPackageItem(XmlSerializer out, ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException { if (forBackup) { + if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) { + return; // Don't save. + } if (spi.getPackageUserId() != spi.getOwnerUserId()) { return; // Don't save cross-user information. } diff --git a/com/android/server/pm/UserRestrictionsUtils.java b/com/android/server/pm/UserRestrictionsUtils.java index c18a71d3..a6b05d71 100644 --- a/com/android/server/pm/UserRestrictionsUtils.java +++ b/com/android/server/pm/UserRestrictionsUtils.java @@ -96,7 +96,6 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_SMS, UserManager.DISALLOW_FUN, UserManager.DISALLOW_CREATE_WINDOWS, - UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, UserManager.DISALLOW_OUTGOING_BEAM, UserManager.DISALLOW_WALLPAPER, @@ -157,7 +156,6 @@ public class UserRestrictionsUtils { private static final Set<String> GLOBAL_RESTRICTIONS = Sets.newArraySet( UserManager.DISALLOW_ADJUST_VOLUME, UserManager.DISALLOW_BLUETOOTH_SHARING, - UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, UserManager.DISALLOW_RUN_IN_BACKGROUND, UserManager.DISALLOW_UNMUTE_MICROPHONE, UserManager.DISALLOW_UNMUTE_DEVICE diff --git a/com/android/server/pm/permission/BasePermission.java b/com/android/server/pm/permission/BasePermission.java index 71d3202d..09a6e9c0 100644 --- a/com/android/server/pm/permission/BasePermission.java +++ b/com/android/server/pm/permission/BasePermission.java @@ -48,7 +48,6 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; -import java.util.Collection; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -78,7 +77,7 @@ public final class BasePermission { final String name; - final @PermissionType int type; + @PermissionType final int type; String sourcePackageName; @@ -253,12 +252,12 @@ public final class BasePermission { return changed; } - public void updateDynamicPermission(Collection<BasePermission> permissionTrees) { + public void updateDynamicPermission(Map<String, BasePermission> permissionTrees) { if (PackageManagerService.DEBUG_SETTINGS) Log.v(TAG, "Dynamic permission: name=" + getName() + " pkg=" + getSourcePackageName() + " info=" + pendingPermissionInfo); if (sourcePackageSetting == null && pendingPermissionInfo != null) { - final BasePermission tree = findPermissionTree(permissionTrees, name); + final BasePermission tree = findPermissionTreeLP(permissionTrees, name); if (tree != null && tree.perm != null) { sourcePackageSetting = tree.sourcePackageSetting; perm = new PackageParser.Permission(tree.perm.owner, @@ -270,8 +269,8 @@ public final class BasePermission { } } - static BasePermission createOrUpdate(@Nullable BasePermission bp, @NonNull Permission p, - @NonNull PackageParser.Package pkg, Collection<BasePermission> permissionTrees, + public static BasePermission createOrUpdate(@Nullable BasePermission bp, @NonNull Permission p, + @NonNull PackageParser.Package pkg, Map<String, BasePermission> permissionTrees, boolean chatty) { final PackageSettingBase pkgSetting = (PackageSettingBase) pkg.mExtras; // Allow system apps to redefine non-system permissions @@ -301,7 +300,7 @@ public final class BasePermission { if (bp.perm == null) { if (bp.sourcePackageName == null || bp.sourcePackageName.equals(p.info.packageName)) { - final BasePermission tree = findPermissionTree(permissionTrees, p.info.name); + final BasePermission tree = findPermissionTreeLP(permissionTrees, p.info.name); if (tree == null || tree.sourcePackageName.equals(p.info.packageName)) { bp.sourcePackageSetting = pkgSetting; @@ -346,12 +345,12 @@ public final class BasePermission { return bp; } - static BasePermission enforcePermissionTree( - Collection<BasePermission> permissionTrees, String permName, int callingUid) { + public static BasePermission enforcePermissionTreeLP( + Map<String, BasePermission> permissionTrees, String permName, int callingUid) { if (permName != null) { - BasePermission bp = findPermissionTree(permissionTrees, permName); + BasePermission bp = findPermissionTreeLP(permissionTrees, permName); if (bp != null) { - if (bp.uid == UserHandle.getAppId(callingUid)) { + if (bp.uid == UserHandle.getAppId(callingUid)) {//UserHandle.getAppId(Binder.getCallingUid())) { return bp; } throw new SecurityException("Calling uid " + callingUid @@ -374,9 +373,9 @@ public final class BasePermission { } } - private static BasePermission findPermissionTree( - Collection<BasePermission> permissionTrees, String permName) { - for (BasePermission bp : permissionTrees) { + private static BasePermission findPermissionTreeLP( + Map<String, BasePermission> permissionTrees, String permName) { + for (BasePermission bp : permissionTrees.values()) { if (permName.startsWith(bp.name) && permName.length() > bp.name.length() && permName.charAt(bp.name.length()) == '.') { diff --git a/com/android/server/pm/permission/PermissionManagerInternal.java b/com/android/server/pm/permission/PermissionManagerInternal.java index 8aac52ae..3b20b42b 100644 --- a/com/android/server/pm/permission/PermissionManagerInternal.java +++ b/com/android/server/pm/permission/PermissionManagerInternal.java @@ -31,7 +31,6 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; /** * Internal interfaces to be used by other components within the system server. @@ -82,26 +81,11 @@ public abstract class PermissionManagerInternal { @NonNull int[] allUserIds); - /** - * Add all permissions in the given package. - * <p> - * NOTE: argument {@code groupTEMP} is temporary until mPermissionGroups is moved to - * the permission settings. - */ - public abstract void addAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty); - public abstract void removeAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty); - public abstract boolean addDynamicPermission(@NonNull PermissionInfo info, boolean async, + public abstract boolean addPermission(@NonNull PermissionInfo info, boolean async, int callingUid, @Nullable PermissionCallback callback); - public abstract void removeDynamicPermission(@NonNull String permName, int callingUid, + public abstract void removePermission(@NonNull String permName, int callingUid, @Nullable PermissionCallback callback); - public abstract int updatePermissions(@Nullable String changingPkg, - @Nullable PackageParser.Package pkgInfo, int flags); - public abstract int updatePermissionTrees(@Nullable String changingPkg, - @Nullable PackageParser.Package pkgInfo, int flags); - - public abstract @Nullable String[] getAppOpPermissionPackages(@NonNull String permName); - public abstract int getPermissionFlags(@NonNull String permName, @NonNull String packageName, int callingUid, int userId); /** @@ -114,6 +98,8 @@ public abstract class PermissionManagerInternal { */ public abstract @Nullable List<PermissionInfo> getPermissionInfoByGroup(@NonNull String group, @PermissionInfoFlags int flags, int callingUid); + public abstract boolean isPermissionAppOp(@NonNull String permName); + public abstract boolean isPermissionInstant(@NonNull String permName); /** * Updates the flags associated with a permission by replacing the flags in diff --git a/com/android/server/pm/permission/PermissionManagerService.java b/com/android/server/pm/permission/PermissionManagerService.java index d2d857ca..6c031a6a 100644 --- a/com/android/server/pm/permission/PermissionManagerService.java +++ b/com/android/server/pm/permission/PermissionManagerService.java @@ -69,7 +69,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; -import java.util.Set; /** * Manages all permissions and handles permissions related tasks. @@ -261,7 +260,7 @@ public class PermissionManagerService { // } final ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10); - for (BasePermission bp : mSettings.mPermissions.values()) { + for (BasePermission bp : mSettings.getAllPermissionsLocked()) { final PermissionInfo pi = bp.generatePermissionInfo(groupName, flags); if (pi != null) { out.add(pi); @@ -306,98 +305,7 @@ public class PermissionManagerService { return protectionLevel; } - private void addAllPermissions(PackageParser.Package pkg, boolean chatty) { - final int N = pkg.permissions.size(); - for (int i=0; i<N; i++) { - PackageParser.Permission p = pkg.permissions.get(i); - - // Assume by default that we did not install this permission into the system. - p.info.flags &= ~PermissionInfo.FLAG_INSTALLED; - - // Now that permission groups have a special meaning, we ignore permission - // groups for legacy apps to prevent unexpected behavior. In particular, - // permissions for one app being granted to someone just because they happen - // to be in a group defined by another app (before this had no implications). - if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { - p.group = mPackageManagerInt.getPermissionGroupTEMP(p.info.group); - // Warn for a permission in an unknown group. - if (PackageManagerService.DEBUG_PERMISSIONS - && p.info.group != null && p.group == null) { - Slog.i(TAG, "Permission " + p.info.name + " from package " - + p.info.packageName + " in an unknown group " + p.info.group); - } - } - - synchronized (PermissionManagerService.this.mLock) { - if (p.tree) { - final BasePermission bp = BasePermission.createOrUpdate( - mSettings.getPermissionTreeLocked(p.info.name), p, pkg, - mSettings.getAllPermissionTreesLocked(), chatty); - mSettings.putPermissionTreeLocked(p.info.name, bp); - } else { - final BasePermission bp = BasePermission.createOrUpdate( - mSettings.getPermissionLocked(p.info.name), - p, pkg, mSettings.getAllPermissionTreesLocked(), chatty); - mSettings.putPermissionLocked(p.info.name, bp); - } - } - } - } - - private void removeAllPermissions(PackageParser.Package pkg, boolean chatty) { - synchronized (mLock) { - int N = pkg.permissions.size(); - StringBuilder r = null; - for (int i=0; i<N; i++) { - PackageParser.Permission p = pkg.permissions.get(i); - BasePermission bp = (BasePermission) mSettings.mPermissions.get(p.info.name); - if (bp == null) { - bp = mSettings.mPermissionTrees.get(p.info.name); - } - if (bp != null && bp.isPermission(p)) { - bp.setPermission(null); - if (PackageManagerService.DEBUG_REMOVE && chatty) { - if (r == null) { - r = new StringBuilder(256); - } else { - r.append(' '); - } - r.append(p.info.name); - } - } - if (p.isAppOp()) { - ArraySet<String> appOpPkgs = - mSettings.mAppOpPermissionPackages.get(p.info.name); - if (appOpPkgs != null) { - appOpPkgs.remove(pkg.packageName); - } - } - } - if (r != null) { - if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); - } - - N = pkg.requestedPermissions.size(); - r = null; - for (int i=0; i<N; i++) { - String perm = pkg.requestedPermissions.get(i); - if (mSettings.isPermissionAppOp(perm)) { - ArraySet<String> appOpPkgs = mSettings.mAppOpPermissionPackages.get(perm); - if (appOpPkgs != null) { - appOpPkgs.remove(pkg.packageName); - if (appOpPkgs.isEmpty()) { - mSettings.mAppOpPermissionPackages.remove(perm); - } - } - } - } - if (r != null) { - if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); - } - } - } - - private boolean addDynamicPermission( + private boolean addPermission( PermissionInfo info, int callingUid, PermissionCallback callback) { if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { throw new SecurityException("Instant apps can't add permissions"); @@ -405,7 +313,8 @@ public class PermissionManagerService { if (info.labelRes == 0 && info.nonLocalizedLabel == null) { throw new SecurityException("Label must be specified in permission"); } - final BasePermission tree = mSettings.enforcePermissionTree(info.name, callingUid); + final BasePermission tree = (BasePermission) mPackageManagerInt.enforcePermissionTreeTEMP( + info.name, callingUid); final boolean added; final boolean changed; synchronized (mLock) { @@ -417,8 +326,8 @@ public class PermissionManagerService { bp = new BasePermission(info.name, tree.getSourcePackageName(), BasePermission.TYPE_DYNAMIC); } else if (bp.isDynamic()) { - // TODO: switch this back to SecurityException - Slog.wtf(TAG, "Not allowed to modify non-dynamic permission " + throw new SecurityException( + "Not allowed to modify non-dynamic permission " + info.name); } changed = bp.addToTree(fixedLevel, info, tree); @@ -432,20 +341,21 @@ public class PermissionManagerService { return added; } - private void removeDynamicPermission( + private void removePermission( String permName, int callingUid, PermissionCallback callback) { if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { throw new SecurityException("Instant applications don't have access to this method"); } - final BasePermission tree = mSettings.enforcePermissionTree(permName, callingUid); + final BasePermission tree = (BasePermission) mPackageManagerInt.enforcePermissionTreeTEMP( + permName, callingUid); synchronized (mLock) { final BasePermission bp = mSettings.getPermissionLocked(permName); if (bp == null) { return; } if (bp.isDynamic()) { - // TODO: switch this back to SecurityException - Slog.wtf(TAG, "Not allowed to modify non-dynamic permission " + throw new SecurityException( + "Not allowed to modify non-dynamic permission " + permName); } mSettings.removePermissionLocked(permName); @@ -803,21 +713,7 @@ public class PermissionManagerService { return runtimePermissionChangedUserIds; } - private String[] getAppOpPermissionPackages(String permName) { - if (mPackageManagerInt.getInstantAppPackageName(Binder.getCallingUid()) != null) { - return null; - } - synchronized (mLock) { - final ArraySet<String> pkgs = mSettings.mAppOpPermissionPackages.get(permName); - if (pkgs == null) { - return null; - } - return pkgs.toArray(new String[pkgs.size()]); - } - } - - private int getPermissionFlags( - String permName, String packageName, int callingUid, int userId) { + private int getPermissionFlags(String permName, String packageName, int callingUid, int userId) { if (!mUserManagerInt.exists(userId)) { return 0; } @@ -845,96 +741,6 @@ public class PermissionManagerService { return permissionsState.getPermissionFlags(permName, userId); } - private int updatePermissions(String packageName, PackageParser.Package pkgInfo, int flags) { - Set<BasePermission> needsUpdate = null; - synchronized (mLock) { - final Iterator<BasePermission> it = mSettings.mPermissions.values().iterator(); - while (it.hasNext()) { - final BasePermission bp = it.next(); - if (bp.isDynamic()) { - bp.updateDynamicPermission(mSettings.mPermissionTrees.values()); - } - if (bp.getSourcePackageSetting() != null) { - if (packageName != null && packageName.equals(bp.getSourcePackageName()) - && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) { - Slog.i(TAG, "Removing old permission tree: " + bp.getName() - + " from package " + bp.getSourcePackageName()); - flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL; - it.remove(); - } - continue; - } - if (needsUpdate == null) { - needsUpdate = new ArraySet<>(mSettings.mPermissions.size()); - } - needsUpdate.add(bp); - } - } - if (needsUpdate != null) { - for (final BasePermission bp : needsUpdate) { - final PackageParser.Package pkg = - mPackageManagerInt.getPackage(bp.getSourcePackageName()); - synchronized (mLock) { - if (pkg != null && pkg.mExtras != null) { - final PackageSetting ps = (PackageSetting) pkg.mExtras; - if (bp.getSourcePackageSetting() == null) { - bp.setSourcePackageSetting(ps); - } - continue; - } - Slog.w(TAG, "Removing dangling permission: " + bp.getName() - + " from package " + bp.getSourcePackageName()); - mSettings.removePermissionLocked(bp.getName()); - } - } - } - return flags; - } - - private int updatePermissionTrees(String packageName, PackageParser.Package pkgInfo, - int flags) { - Set<BasePermission> needsUpdate = null; - synchronized (mLock) { - final Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator(); - while (it.hasNext()) { - final BasePermission bp = it.next(); - if (bp.getSourcePackageSetting() != null) { - if (packageName != null && packageName.equals(bp.getSourcePackageName()) - && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) { - Slog.i(TAG, "Removing old permission tree: " + bp.getName() - + " from package " + bp.getSourcePackageName()); - flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL; - it.remove(); - } - continue; - } - if (needsUpdate == null) { - needsUpdate = new ArraySet<>(mSettings.mPermissionTrees.size()); - } - needsUpdate.add(bp); - } - } - if (needsUpdate != null) { - for (final BasePermission bp : needsUpdate) { - final PackageParser.Package pkg = - mPackageManagerInt.getPackage(bp.getSourcePackageName()); - synchronized (mLock) { - if (pkg != null && pkg.mExtras != null) { - final PackageSetting ps = (PackageSetting) pkg.mExtras; - if (bp.getSourcePackageSetting() == null) { - bp.setSourcePackageSetting(ps); - } - continue; - } - Slog.w(TAG, "Removing dangling permission tree: " + bp.getName() - + " from package " + bp.getSourcePackageName()); - mSettings.removePermissionLocked(bp.getName()); - } - } - } - return flags; - } - private void updatePermissionFlags(String permName, String packageName, int flagMask, int flagValues, int callingUid, int userId, PermissionCallback callback) { if (!mUserManagerInt.exists(userId)) { @@ -1066,7 +872,7 @@ public class PermissionManagerService { private int calculateCurrentPermissionFootprintLocked(BasePermission tree) { int size = 0; - for (BasePermission perm : mSettings.mPermissions.values()) { + for (BasePermission perm : mSettings.getAllPermissionsLocked()) { size += tree.calculateFootprint(perm); } return size; @@ -1083,15 +889,6 @@ public class PermissionManagerService { } } - private static boolean hasPermission(PackageParser.Package pkgInfo, String permName) { - for (int i=pkgInfo.permissions.size()-1; i>=0; i--) { - if (pkgInfo.permissions.get(i).info.name.equals(permName)) { - return true; - } - } - return false; - } - /** * Get the first event id for the permission. * @@ -1154,22 +951,14 @@ public class PermissionManagerService { private class PermissionManagerInternalImpl extends PermissionManagerInternal { @Override - public void addAllPermissions(Package pkg, boolean chatty) { - PermissionManagerService.this.addAllPermissions(pkg, chatty); - } - @Override - public void removeAllPermissions(Package pkg, boolean chatty) { - PermissionManagerService.this.removeAllPermissions(pkg, chatty); - } - @Override - public boolean addDynamicPermission(PermissionInfo info, boolean async, int callingUid, + public boolean addPermission(PermissionInfo info, boolean async, int callingUid, PermissionCallback callback) { - return PermissionManagerService.this.addDynamicPermission(info, callingUid, callback); + return PermissionManagerService.this.addPermission(info, callingUid, callback); } @Override - public void removeDynamicPermission(String permName, int callingUid, + public void removePermission(String permName, int callingUid, PermissionCallback callback) { - PermissionManagerService.this.removeDynamicPermission(permName, callingUid, callback); + PermissionManagerService.this.removePermission(permName, callingUid, callback); } @Override public void grantRuntimePermission(String permName, String packageName, @@ -1204,26 +993,12 @@ public class PermissionManagerService { (SharedUserSetting) suSetting, allUserIds); } @Override - public String[] getAppOpPermissionPackages(String permName) { - return PermissionManagerService.this.getAppOpPermissionPackages(permName); - } - @Override public int getPermissionFlags(String permName, String packageName, int callingUid, int userId) { return PermissionManagerService.this.getPermissionFlags(permName, packageName, callingUid, userId); } @Override - public int updatePermissions(String packageName, - PackageParser.Package pkgInfo, int flags) { - return PermissionManagerService.this.updatePermissions(packageName, pkgInfo, flags); - } - @Override - public int updatePermissionTrees(String packageName, - PackageParser.Package pkgInfo, int flags) { - return PermissionManagerService.this.updatePermissionTrees(packageName, pkgInfo, flags); - } - @Override public void updatePermissionFlags(String permName, String packageName, int flagMask, int flagValues, int callingUid, int userId, PermissionCallback callback) { PermissionManagerService.this.updatePermissionFlags( @@ -1263,6 +1038,20 @@ public class PermissionManagerService { return PermissionManagerService.this.getPermissionInfoByGroup(group, flags, callingUid); } @Override + public boolean isPermissionInstant(String permName) { + synchronized (PermissionManagerService.this.mLock) { + final BasePermission bp = mSettings.getPermissionLocked(permName); + return (bp != null && bp.isInstant()); + } + } + @Override + public boolean isPermissionAppOp(String permName) { + synchronized (PermissionManagerService.this.mLock) { + final BasePermission bp = mSettings.getPermissionLocked(permName); + return (bp != null && bp.isAppOp()); + } + } + @Override public PermissionSettings getPermissionSettings() { return mSettings; } diff --git a/com/android/server/pm/permission/PermissionSettings.java b/com/android/server/pm/permission/PermissionSettings.java index 7d125c9e..7a2e5ecc 100644 --- a/com/android/server/pm/permission/PermissionSettings.java +++ b/com/android/server/pm/permission/PermissionSettings.java @@ -24,7 +24,6 @@ import android.util.ArraySet; import android.util.Log; import com.android.internal.R; -import com.android.internal.annotations.GuardedBy; import com.android.internal.util.XmlUtils; import com.android.server.pm.DumpState; import com.android.server.pm.PackageManagerService; @@ -36,7 +35,6 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.PrintWriter; import java.util.Collection; -import java.util.Set; /** * Permissions and other related data. This class is not meant for @@ -51,25 +49,8 @@ public class PermissionSettings { * All of the permissions known to the system. The mapping is from permission * name to permission object. */ - @GuardedBy("mLock") - final ArrayMap<String, BasePermission> mPermissions = + private final ArrayMap<String, BasePermission> mPermissions = new ArrayMap<String, BasePermission>(); - - /** - * All permission trees known to the system. The mapping is from permission tree - * name to permission object. - */ - @GuardedBy("mLock") - final ArrayMap<String, BasePermission> mPermissionTrees = - new ArrayMap<String, BasePermission>(); - - /** - * Set of packages that request a particular app op. The mapping is from permission - * name to package names. - */ - @GuardedBy("mLock") - final ArrayMap<String, ArraySet<String>> mAppOpPermissionPackages = new ArrayMap<>(); - private final Object mLock; PermissionSettings(@NonNull Context context, @NonNull Object lock) { @@ -84,23 +65,15 @@ public class PermissionSettings { } } - public void addAppOpPackage(String permName, String packageName) { - ArraySet<String> pkgs = mAppOpPermissionPackages.get(permName); - if (pkgs == null) { - pkgs = new ArraySet<>(); - mAppOpPermissionPackages.put(permName, pkgs); - } - pkgs.add(packageName); - } - /** * Transfers ownership of permissions from one package to another. */ - public void transferPermissions(String origPackageName, String newPackageName) { + public void transferPermissions(String origPackageName, String newPackageName, + ArrayMap<String, BasePermission> permissionTrees) { synchronized (mLock) { for (int i=0; i<2; i++) { ArrayMap<String, BasePermission> permissions = - i == 0 ? mPermissionTrees : mPermissions; + i == 0 ? permissionTrees : mPermissions; for (BasePermission bp : permissions.values()) { bp.transfer(origPackageName, newPackageName); } @@ -121,26 +94,9 @@ public class PermissionSettings { } } - public void readPermissionTrees(XmlPullParser parser) - throws IOException, XmlPullParserException { - synchronized (mLock) { - readPermissions(mPermissionTrees, parser); - } - } - public void writePermissions(XmlSerializer serializer) throws IOException { - synchronized (mLock) { - for (BasePermission bp : mPermissions.values()) { - bp.writeLPr(serializer); - } - } - } - - public void writePermissionTrees(XmlSerializer serializer) throws IOException { - synchronized (mLock) { - for (BasePermission bp : mPermissionTrees.values()) { - bp.writeLPr(serializer); - } + for (BasePermission bp : mPermissions.values()) { + bp.writeLPr(serializer); } } @@ -172,22 +128,6 @@ public class PermissionSettings { printedSomething = bp.dumpPermissionsLPr(pw, packageName, permissionNames, externalStorageEnforced, printedSomething, dumpState); } - if (packageName == null && permissionNames == null) { - for (int iperm = 0; iperm<mAppOpPermissionPackages.size(); iperm++) { - if (iperm == 0) { - if (dumpState.onTitlePrinted()) - pw.println(); - pw.println("AppOp Permissions:"); - } - pw.print(" AppOp Permission "); - pw.print(mAppOpPermissionPackages.keyAt(iperm)); - pw.println(":"); - ArraySet<String> pkgs = mAppOpPermissionPackages.valueAt(iperm); - for (int ipkg=0; ipkg<pkgs.size(); ipkg++) { - pw.print(" "); pw.println(pkgs.valueAt(ipkg)); - } - } - } } } @@ -195,58 +135,15 @@ public class PermissionSettings { return mPermissions.get(permName); } - @Nullable BasePermission getPermissionTreeLocked(@NonNull String permName) { - return mPermissionTrees.get(permName); - } - void putPermissionLocked(@NonNull String permName, @NonNull BasePermission permission) { mPermissions.put(permName, permission); } - void putPermissionTreeLocked(@NonNull String permName, @NonNull BasePermission permission) { - mPermissionTrees.put(permName, permission); - } - void removePermissionLocked(@NonNull String permName) { mPermissions.remove(permName); } - void removePermissionTreeLocked(@NonNull String permName) { - mPermissionTrees.remove(permName); - } - - @NonNull Collection<BasePermission> getAllPermissionsLocked() { + Collection<BasePermission> getAllPermissionsLocked() { return mPermissions.values(); } - - @NonNull Collection<BasePermission> getAllPermissionTreesLocked() { - return mPermissionTrees.values(); - } - - /** - * Returns the permission tree for the given permission. - * @throws SecurityException If the calling UID is not allowed to add permissions to the - * found permission tree. - */ - @Nullable BasePermission enforcePermissionTree(@NonNull String permName, int callingUid) { - synchronized (mLock) { - return BasePermission.enforcePermissionTree( - mPermissionTrees.values(), permName, callingUid); - } - } - - public boolean isPermissionInstant(String permName) { - synchronized (mLock) { - final BasePermission bp = mPermissions.get(permName); - return (bp != null && bp.isInstant()); - } - } - - boolean isPermissionAppOp(String permName) { - synchronized (mLock) { - final BasePermission bp = mPermissions.get(permName); - return (bp != null && bp.isAppOp()); - } - } - } diff --git a/com/android/server/policy/GlobalActions.java b/com/android/server/policy/GlobalActions.java index 7a2e630c..342ec4b7 100644 --- a/com/android/server/policy/GlobalActions.java +++ b/com/android/server/policy/GlobalActions.java @@ -58,9 +58,6 @@ class GlobalActions implements GlobalActionsListener { public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) { if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned); - if (mStatusBarInternal.isGlobalActionsDisabled()) { - return; - } mKeyguardShowing = keyguardShowing; mDeviceProvisioned = deviceProvisioned; mShowing = true; diff --git a/com/android/server/policy/PhoneWindowManager.java b/com/android/server/policy/PhoneWindowManager.java index ceb0ad07..db7817ec 100644 --- a/com/android/server/policy/PhoneWindowManager.java +++ b/com/android/server/policy/PhoneWindowManager.java @@ -18,14 +18,13 @@ package com.android.server.policy; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.Manifest.permission.SYSTEM_ALERT_WINDOW; +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.AppOpsManager.OP_TOAST_WINDOW; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Context.CONTEXT_RESTRICTED; import static android.content.Context.DISPLAY_SERVICE; @@ -553,6 +552,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE; int mUserRotation = Surface.ROTATION_0; + boolean mAccelerometerDefault; boolean mSupportAutoRotation; int mAllowAllRotations = -1; @@ -707,6 +707,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { Intent mVrHeadsetHomeIntent; boolean mSearchKeyShortcutPending; boolean mConsumeSearchKeyUp; + boolean mAssistKeyLongPressed; boolean mPendingMetaAction; boolean mPendingCapsLockToggle; int mMetaState; @@ -837,8 +838,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_DISPATCH_BACK_KEY_TO_AUTOFILL = 24; private static final int MSG_SYSTEM_KEY_PRESS = 25; private static final int MSG_HANDLE_ALL_APPS = 26; - private static final int MSG_LAUNCH_ASSIST = 27; - private static final int MSG_LAUNCH_ASSIST_LONG_PRESS = 28; private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0; private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1; @@ -880,16 +879,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { case MSG_HIDE_BOOT_MESSAGE: handleHideBootMessage(); break; - case MSG_LAUNCH_ASSIST: - final int deviceId = msg.arg1; - final String hint = (String) msg.obj; - launchAssistAction(hint, deviceId); - break; - case MSG_LAUNCH_ASSIST_LONG_PRESS: - launchAssistLongPressAction(); - break; case MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK: - launchVoiceAssistWithWakeLock(); + launchVoiceAssistWithWakeLock(msg.arg1 != 0); break; case MSG_POWER_DELAYED_PRESS: powerPress((Long)msg.obj, msg.arg1 != 0, msg.arg2); @@ -919,7 +910,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { disposeInputConsumer((InputConsumer) msg.obj); break; case MSG_BACK_DELAYED_PRESS: - backMultiPressAction(msg.arg1); + backMultiPressAction((Long) msg.obj, msg.arg1); finishBackKeyPress(); break; case MSG_ACCESSIBILITY_SHORTCUT: @@ -1423,7 +1414,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void backMultiPressAction(int count) { + private void backMultiPressAction(long eventTime, int count) { if (count >= PANIC_PRESS_BACK_COUNT) { switch (mPanicPressOnBackBehavior) { case PANIC_PRESS_BACK_NOTHING: @@ -1592,7 +1583,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void sleepPress() { + private void sleepPress(long eventTime) { if (mShortPressOnSleepBehavior == SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME) { launchHomeFromHotKey(false /* awakenDreams */, true /*respectKeyguard*/); } @@ -2279,11 +2270,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Only force the default orientation if the screen is xlarge, at least 960dp x 720dp, per // http://developer.android.com/guide/practices/screens_support.html#range - // For car, ignore the dp limitation. It's physically impossible to rotate the car's screen - // so if the orientation is forced, we need to respect that no matter what. - boolean isCar = mContext.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_AUTOMOTIVE); - mForceDefaultOrientation = ((longSizeDp >= 960 && shortSizeDp >= 720) || isCar) && + mForceDefaultOrientation = longSizeDp >= 960 && shortSizeDp >= 720 && res.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation) && // For debug purposes the next line turns this feature off with: // $ adb shell setprop config.override_forced_orient true @@ -2857,7 +2844,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean keyguardLocked = isKeyguardLocked(); boolean hideDockDivider = attrs.type == TYPE_DOCK_DIVIDER - && !mWindowManagerInternal.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + && !mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID); return (keyguardLocked && !allowWhenLocked && win.getDisplayId() == DEFAULT_DISPLAY) || hideDockDivider; } @@ -3550,11 +3537,44 @@ public class PhoneWindowManager implements WindowManagerPolicy { toggleKeyboardShortcutsMenu(event.getDeviceId()); } } else if (keyCode == KeyEvent.KEYCODE_ASSIST) { - Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing"); + if (down) { + if (repeatCount == 0) { + mAssistKeyLongPressed = false; + } else if (repeatCount == 1) { + mAssistKeyLongPressed = true; + if (!keyguardOn) { + launchAssistLongPressAction(); + } + } + } else { + if (mAssistKeyLongPressed) { + mAssistKeyLongPressed = false; + } else { + if (!keyguardOn) { + launchAssistAction(null, event.getDeviceId()); + } + } + } return -1; } else if (keyCode == KeyEvent.KEYCODE_VOICE_ASSIST) { - Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in interceptKeyBeforeQueueing"); - return -1; + if (!down) { + Intent voiceIntent; + if (!keyguardOn) { + voiceIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); + } else { + IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); + if (dic != null) { + try { + dic.exitIdle("voice-search"); + } catch (RemoteException e) { + } + } + voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); + voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, true); + } + startActivityAsUser(voiceIntent, UserHandle.CURRENT_OR_SELF); + } } else if (keyCode == KeyEvent.KEYCODE_SYSRQ) { if (down && repeatCount == 0) { mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); @@ -5425,10 +5445,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW && attrs.type < FIRST_SYSTEM_WINDOW; - final int windowingMode = win.getWindowingMode(); - final boolean inFullScreenOrSplitScreenSecondaryWindowingMode = - windowingMode == WINDOWING_MODE_FULLSCREEN - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; + final int stackId = win.getStackId(); if (mTopFullscreenOpaqueWindowState == null && affectsSystemUi) { if ((fl & FLAG_FORCE_NOT_FULLSCREEN) != 0) { mForceStatusBar = true; @@ -5447,7 +5464,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // represent should be hidden or if we should hide the lockscreen. For attached app // windows we defer the decision to the window it is attached to. if (appWindow && attached == null) { - if (attrs.isFullscreen() && inFullScreenOrSplitScreenSecondaryWindowingMode) { + if (attrs.isFullscreen() && StackId.normallyFullscreenWindows(stackId)) { if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win); mTopFullscreenOpaqueWindowState = win; if (mTopFullscreenOpaqueOrDimmingWindowState == null) { @@ -5478,7 +5495,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Keep track of the window if it's dimming but not necessarily fullscreen. if (mTopFullscreenOpaqueOrDimmingWindowState == null && affectsSystemUi - && win.isDimming() && inFullScreenOrSplitScreenSecondaryWindowingMode) { + && win.isDimming() && StackId.normallyFullscreenWindows(stackId)) { mTopFullscreenOpaqueOrDimmingWindowState = win; } @@ -5486,7 +5503,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // separately, because both the "real fullscreen" opaque window and the one for the docked // stack can control View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR. if (mTopDockedOpaqueWindowState == null && affectsSystemUi && appWindow && attached == null - && attrs.isFullscreen() && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { + && attrs.isFullscreen() && stackId == DOCKED_STACK_ID) { mTopDockedOpaqueWindowState = win; if (mTopDockedOpaqueOrDimmingWindowState == null) { mTopDockedOpaqueOrDimmingWindowState = win; @@ -5496,7 +5513,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Also keep track of any windows that are dimming but not necessarily fullscreen in the // docked stack. if (mTopDockedOpaqueOrDimmingWindowState == null && affectsSystemUi && win.isDimming() - && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { + && stackId == DOCKED_STACK_ID) { mTopDockedOpaqueOrDimmingWindowState = win; } @@ -5591,9 +5608,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { changes |= FINISH_LAYOUT_REDO_LAYOUT; } } else if (topIsFullscreen - && !mWindowManagerInternal.isStackVisible(WINDOWING_MODE_FREEFORM) - && !mWindowManagerInternal.isStackVisible( - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) { + && !mWindowManagerInternal.isStackVisible(FREEFORM_WORKSPACE_STACK_ID) + && !mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID)) { if (DEBUG_LAYOUT) Slog.v(TAG, "** HIDING status bar"); if (mStatusBarController.setBarShowingLw(false)) { changes |= FINISH_LAYOUT_REDO_LAYOUT; @@ -6175,7 +6191,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { useHapticFeedback = false; // suppress feedback if already non-interactive } if (down) { - sleepPress(); + sleepPress(event.getEventTime()); } else { sleepRelease(event.getEventTime()); } @@ -6246,30 +6262,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { } break; } - case KeyEvent.KEYCODE_ASSIST: { - final boolean longPressed = event.getRepeatCount() > 0; - if (down && longPressed) { - Message msg = mHandler.obtainMessage(MSG_LAUNCH_ASSIST_LONG_PRESS); - msg.setAsynchronous(true); - msg.sendToTarget(); - } - if (!down && !longPressed) { - Message msg = mHandler.obtainMessage(MSG_LAUNCH_ASSIST, event.getDeviceId(), - 0 /* unused */, null /* hint */); - msg.setAsynchronous(true); - msg.sendToTarget(); - } - result &= ~ACTION_PASS_TO_USER; - break; - } case KeyEvent.KEYCODE_VOICE_ASSIST: { - if (!down) { + // Only do this if we would otherwise not pass it to the user. In that case, + // interceptKeyBeforeDispatching would apply a similar but different policy in + // order to invoke voice assist actions. Note that we need to make a copy of the + // key event here because the original key event will be recycled when we return. + if ((result & ACTION_PASS_TO_USER) == 0 && !down) { mBroadcastWakeLock.acquire(); - Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK); + Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK, + keyguardActive ? 1 : 0, 0); msg.setAsynchronous(true); msg.sendToTarget(); } - result &= ~ACTION_PASS_TO_USER; break; } case KeyEvent.KEYCODE_WINDOW: { @@ -6538,22 +6542,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - void launchVoiceAssistWithWakeLock() { - final Intent voiceIntent; - if (!keyguardOn()) { - voiceIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); - } else { - IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface( - ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); - if (dic != null) { - try { - dic.exitIdle("voice-search"); - } catch (RemoteException e) { - } + void launchVoiceAssistWithWakeLock(boolean keyguardActive) { + IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); + if (dic != null) { + try { + dic.exitIdle("voice-search"); + } catch (RemoteException e) { } - voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); - voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, true); } + Intent voiceIntent = + new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); + voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, keyguardActive); startActivityAsUser(voiceIntent, UserHandle.CURRENT_OR_SELF); mBroadcastWakeLock.release(); } @@ -8009,10 +8009,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private int updateSystemBarsLw(WindowState win, int oldVis, int vis) { - final boolean dockedStackVisible = - mWindowManagerInternal.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + final boolean dockedStackVisible = mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID); final boolean freeformStackVisible = - mWindowManagerInternal.isStackVisible(WINDOWING_MODE_FREEFORM); + mWindowManagerInternal.isStackVisible(FREEFORM_WORKSPACE_STACK_ID); final boolean resizing = mWindowManagerInternal.isDockedDividerResizing(); // We need to force system bars when the docked stack is visible, when the freeform stack diff --git a/com/android/server/stats/StatsCompanionService.java b/com/android/server/stats/StatsCompanionService.java index ca3dd058..f1fb3e7b 100644 --- a/com/android/server/stats/StatsCompanionService.java +++ b/com/android/server/stats/StatsCompanionService.java @@ -20,25 +20,15 @@ import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.UserInfo; -import android.content.IntentFilter; import android.os.Binder; -import android.os.Bundle; import android.os.IBinder; import android.os.IStatsCompanionService; import android.os.IStatsManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.UserHandle; -import android.os.UserManager; import android.util.Slog; -import java.util.ArrayList; -import java.util.List; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.KernelWakelockReader; import com.android.internal.os.KernelWakelockStats; @@ -63,8 +53,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private final PendingIntent mAnomalyAlarmIntent; private final PendingIntent mPollingAlarmIntent; - private final BroadcastReceiver mAppUpdateReceiver; - private final BroadcastReceiver mUserUpdateReceiver; public StatsCompanionService(Context context) { super(); @@ -75,113 +63,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { new Intent(mContext, AnomalyAlarmReceiver.class), 0); mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(mContext, PollingAlarmReceiver.class), 0); - mAppUpdateReceiver = new AppUpdateReceiver(); - mUserUpdateReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - synchronized (sStatsdLock) { - sStatsd = fetchStatsdService(); - if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd"); - return; - } - try { - // Pull the latest state of UID->app name, version mapping. - // Needed since the new user basically has a version of every app. - informAllUidsLocked(context); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e); - forgetEverything(); - } - } - } - }; - Slog.w(TAG, "Registered receiver for ACTION_PACKAGE_REPLACE AND ADDED."); } - private final static int[] toIntArray(List<Integer> list){ - int[] ret = new int[list.size()]; - for(int i = 0;i < ret.length;i++) { - ret[i] = list.get(i); - } - return ret; - } - - // Assumes that sStatsdLock is held. - private final void informAllUidsLocked(Context context) throws RemoteException { - UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); - PackageManager pm = context.getPackageManager(); - final List<UserInfo> users = um.getUsers(true); - if (DEBUG) { - Slog.w(TAG, "Iterating over "+users.size() + " profiles."); - } - - List<Integer> uids = new ArrayList(); - List<Integer> versions = new ArrayList(); - List<String> apps = new ArrayList(); - - // Add in all the apps for every user/profile. - for (UserInfo profile : users) { - List<PackageInfo> pi = pm.getInstalledPackagesAsUser(0, profile.id); - for (int j = 0; j < pi.size(); j++) { - if (pi.get(j).applicationInfo != null) { - uids.add(pi.get(j).applicationInfo.uid); - versions.add(pi.get(j).versionCode); - apps.add(pi.get(j).packageName); - } - } - } - sStatsd.informAllUidData(toIntArray(uids), toIntArray(versions), apps.toArray(new - String[apps.size()])); - if (DEBUG) { - Slog.w(TAG, "Sent data for "+uids.size() +" apps"); - } - } - - public final static class AppUpdateReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - Slog.i(TAG, "StatsCompanionService noticed an app was updated."); - /** - * App updates actually consist of REMOVE, ADD, and then REPLACE broadcasts. To avoid - * waste, we ignore the REMOVE and ADD broadcasts that contain the replacing flag. - */ - if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED) && - intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { - return; // Keep only replacing or normal add and remove. - } - synchronized (sStatsdLock) { - if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing"); - return; - } - try { - if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)) { - Bundle b = intent.getExtras(); - int uid = b.getInt(Intent.EXTRA_UID); - boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); - if (!replacing) { - // Don't bother sending an update if we're right about to get another - // intent for the new version that's added. - PackageManager pm = context.getPackageManager(); - String app = intent.getData().getSchemeSpecificPart(); - sStatsd.informOnePackageRemoved(app, uid); - } - } else { - PackageManager pm = context.getPackageManager(); - Bundle b = intent.getExtras(); - int uid = b.getInt(Intent.EXTRA_UID); - String app = intent.getData().getSchemeSpecificPart(); - PackageInfo pi = pm.getPackageInfo(app, PackageManager.MATCH_ANY_USER); - sStatsd.informOnePackage(app, uid, pi.versionCode); - } - } catch (Exception e) { - Slog.w(TAG, "Failed to inform statsd of an app update", e); - } - } - } - }; - public final static class AnomalyAlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -392,23 +275,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e); forgetEverything(); } - // Setup broadcast receiver for updates - IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REPLACED); - filter.addAction(Intent.ACTION_PACKAGE_ADDED); - filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addDataScheme("package"); - mContext.registerReceiverAsUser(mAppUpdateReceiver, UserHandle.ALL, filter, null, - null); - - // Setup receiver for user initialize (which happens once for a new user) and - // if a user is removed. - filter = new IntentFilter(Intent.ACTION_USER_INITIALIZE); - filter.addAction(Intent.ACTION_USER_REMOVED); - mContext.registerReceiverAsUser(mUserUpdateReceiver, UserHandle.ALL, - filter, null, null); - - // Pull the latest state of UID->app name, version mapping when statsd starts. - informAllUidsLocked(mContext); } catch (RemoteException e) { Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e); forgetEverything(); @@ -427,8 +293,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private void forgetEverything() { synchronized (sStatsdLock) { sStatsd = null; - mContext.unregisterReceiver(mAppUpdateReceiver); - mContext.unregisterReceiver(mUserUpdateReceiver); cancelAnomalyAlarm(); cancelPollingAlarms(); } diff --git a/com/android/server/statusbar/StatusBarManagerInternal.java b/com/android/server/statusbar/StatusBarManagerInternal.java index b07fe98d..08846784 100644 --- a/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/com/android/server/statusbar/StatusBarManagerInternal.java @@ -77,7 +77,6 @@ public interface StatusBarManagerInternal { void setCurrentUser(int newUserId); - boolean isGlobalActionsDisabled(); void setGlobalActionsListener(GlobalActionsListener listener); void showGlobalActions(); diff --git a/com/android/server/statusbar/StatusBarManagerService.java b/com/android/server/statusbar/StatusBarManagerService.java index c78a3406..bdfbe481 100644 --- a/com/android/server/statusbar/StatusBarManagerService.java +++ b/com/android/server/statusbar/StatusBarManagerService.java @@ -16,8 +16,6 @@ package com.android.server.statusbar; -import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS; - import android.app.ActivityThread; import android.app.StatusBarManager; import android.content.ComponentName; @@ -365,11 +363,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } @Override - public boolean isGlobalActionsDisabled() { - return (mDisabled2 & DISABLE2_GLOBAL_ACTIONS) != 0; - } - - @Override public void setGlobalActionsListener(GlobalActionsListener listener) { mGlobalActionListener = listener; mGlobalActionListener.onStatusBarConnectedChanged(mBar != null); diff --git a/com/android/server/tv/TvInputHardwareManager.java b/com/android/server/tv/TvInputHardwareManager.java index c1607e94..6117da7b 100644 --- a/com/android/server/tv/TvInputHardwareManager.java +++ b/com/android/server/tv/TvInputHardwareManager.java @@ -1022,6 +1022,20 @@ class TvInputHardwareManager implements TvInputHal.Callback { } } + @Override + public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException { + synchronized (mImplLock) { + if (mReleased) { + throw new IllegalStateException("Device already released."); + } + } + if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { + return false; + } + // TODO(hdmi): mHdmiClient.sendKeyEvent(event); + return false; + } + private boolean startCapture(Surface surface, TvStreamConfig config) { synchronized (mImplLock) { if (mReleased) { diff --git a/com/android/server/usb/UsbAlsaManager.java b/com/android/server/usb/UsbAlsaManager.java index d359b704..acc27bee 100644 --- a/com/android/server/usb/UsbAlsaManager.java +++ b/com/android/server/usb/UsbAlsaManager.java @@ -132,9 +132,7 @@ public final class UsbAlsaManager { mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI); // initial scan - if (mCardsParser.scan() != AlsaCardsParser.SCANSTATUS_SUCCESS) { - Slog.e(TAG, "Error scanning ASLA cards file."); - } + mCardsParser.scan(); } public void systemReady() { @@ -316,7 +314,7 @@ public final class UsbAlsaManager { return null; } - if (mDevicesParser.scan() != AlsaDevicesParser.SCANSTATUS_SUCCESS) { + if (!mDevicesParser.scan()) { Slog.e(TAG, "Error parsing ALSA devices file."); return null; } @@ -532,9 +530,6 @@ public final class UsbAlsaManager { // // called by UsbService.dump public void dump(IndentingPrintWriter pw) { - pw.println("Parsers Scan Status:"); - pw.println(" Cards Parser: " + mCardsParser.getScanStatus()); - pw.println(" Devices Parser: " + mDevicesParser.getScanStatus()); pw.println("USB Audio Devices:"); for (UsbDevice device : mAudioDevices.keySet()) { pw.println(" " + device.getDeviceName() + ": " + mAudioDevices.get(device)); diff --git a/com/android/server/usb/UsbHostManager.java b/com/android/server/usb/UsbHostManager.java index 095fdc63..c657a1b4 100644 --- a/com/android/server/usb/UsbHostManager.java +++ b/com/android/server/usb/UsbHostManager.java @@ -376,8 +376,6 @@ public class UsbHostManager { } } } - - mUsbAlsaManager.dump(pw); } private native void monitorUsbHostBus(); diff --git a/com/android/server/wifi/SelfRecovery.java b/com/android/server/wifi/SelfRecovery.java index e39e0d5b..21a3e0ac 100644 --- a/com/android/server/wifi/SelfRecovery.java +++ b/com/android/server/wifi/SelfRecovery.java @@ -72,7 +72,7 @@ public class SelfRecovery { Log.e(TAG, "Invalid trigger reason. Ignoring..."); return; } - Log.e(TAG, "Triggering recovery for reason: " + REASON_STRINGS[reason]); + Log.wtf(TAG, "Triggering recovery for reason: " + REASON_STRINGS[reason]); if (reason == REASON_WIFICOND_CRASH || reason == REASON_HAL_CRASH) { trimPastRestartTimes(); // Ensure there haven't been too many restarts within MAX_RESTARTS_TIME_WINDOW diff --git a/com/android/server/wifi/WifiNative.java b/com/android/server/wifi/WifiNative.java index 35dec2e7..0b1719db 100644 --- a/com/android/server/wifi/WifiNative.java +++ b/com/android/server/wifi/WifiNative.java @@ -16,7 +16,6 @@ package com.android.server.wifi; -import android.annotation.NonNull; import android.annotation.Nullable; import android.net.apf.ApfCapabilities; import android.net.wifi.IApInterface; @@ -110,12 +109,12 @@ public class WifiNative { * @return Pair of <Integer, IClientInterface> to indicate the status and the associated wificond * client interface binder handler (will be null on failure). */ - public Pair<Integer, IClientInterface> setupForClientMode(@NonNull String ifaceName) { + public Pair<Integer, IClientInterface> setupForClientMode() { if (!startHalIfNecessary(true)) { Log.e(mTAG, "Failed to start HAL for client mode"); return Pair.create(SETUP_FAILURE_HAL, null); } - IClientInterface iClientInterface = mWificondControl.setupDriverForClientMode(ifaceName); + IClientInterface iClientInterface = mWificondControl.setupDriverForClientMode(); if (iClientInterface == null) { return Pair.create(SETUP_FAILURE_WIFICOND, null); } @@ -131,12 +130,12 @@ public class WifiNative { * @return Pair of <Integer, IApInterface> to indicate the status and the associated wificond * AP interface binder handler (will be null on failure). */ - public Pair<Integer, IApInterface> setupForSoftApMode(@NonNull String ifaceName) { + public Pair<Integer, IApInterface> setupForSoftApMode() { if (!startHalIfNecessary(false)) { Log.e(mTAG, "Failed to start HAL for AP mode"); return Pair.create(SETUP_FAILURE_HAL, null); } - IApInterface iApInterface = mWificondControl.setupDriverForSoftApMode(ifaceName); + IApInterface iApInterface = mWificondControl.setupDriverForSoftApMode(); if (iApInterface == null) { return Pair.create(SETUP_FAILURE_WIFICOND, null); } diff --git a/com/android/server/wifi/WifiStateMachine.java b/com/android/server/wifi/WifiStateMachine.java index 0c2cc32f..1f6cada7 100644 --- a/com/android/server/wifi/WifiStateMachine.java +++ b/com/android/server/wifi/WifiStateMachine.java @@ -4165,7 +4165,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss switch (message.what) { case CMD_START_SUPPLICANT: Pair<Integer, IClientInterface> statusAndInterface = - mWifiNative.setupForClientMode(mInterfaceName); + mWifiNative.setupForClientMode(); if (statusAndInterface.first == WifiNative.SETUP_SUCCESS) { mClientInterface = statusAndInterface.second; } else { @@ -6954,8 +6954,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss mMode = config.getTargetMode(); IApInterface apInterface = null; - Pair<Integer, IApInterface> statusAndInterface = - mWifiNative.setupForSoftApMode(mInterfaceName); + Pair<Integer, IApInterface> statusAndInterface = mWifiNative.setupForSoftApMode(); if (statusAndInterface.first == WifiNative.SETUP_SUCCESS) { apInterface = statusAndInterface.second; } else { diff --git a/com/android/server/wifi/WifiStateMachinePrime.java b/com/android/server/wifi/WifiStateMachinePrime.java index cd1948f1..20068849 100644 --- a/com/android/server/wifi/WifiStateMachinePrime.java +++ b/com/android/server/wifi/WifiStateMachinePrime.java @@ -290,8 +290,7 @@ public class WifiStateMachinePrime { } try { - mApInterface = mWificond.createApInterface( - mWifiInjector.getWifiNative().getInterfaceName()); + mApInterface = mWificond.createApInterface(); } catch (RemoteException e1) { } if (mApInterface == null) { diff --git a/com/android/server/wifi/WificondControl.java b/com/android/server/wifi/WificondControl.java index df4e785b..b6104a8c 100644 --- a/com/android/server/wifi/WificondControl.java +++ b/com/android/server/wifi/WificondControl.java @@ -16,7 +16,6 @@ package com.android.server.wifi; -import android.annotation.NonNull; import android.net.wifi.IApInterface; import android.net.wifi.IClientInterface; import android.net.wifi.IPnoScanEvent; @@ -134,7 +133,7 @@ public class WificondControl { * @return An IClientInterface as wificond client interface binder handler. * Returns null on failure. */ - public IClientInterface setupDriverForClientMode(@NonNull String ifaceName) { + public IClientInterface setupDriverForClientMode() { Log.d(TAG, "Setting up driver for client mode"); mWificond = mWifiInjector.makeWificond(); if (mWificond == null) { @@ -144,7 +143,7 @@ public class WificondControl { IClientInterface clientInterface = null; try { - clientInterface = mWificond.createClientInterface(ifaceName); + clientInterface = mWificond.createClientInterface(); } catch (RemoteException e1) { Log.e(TAG, "Failed to get IClientInterface due to remote exception"); return null; @@ -182,7 +181,7 @@ public class WificondControl { * @return An IApInterface as wificond Ap interface binder handler. * Returns null on failure. */ - public IApInterface setupDriverForSoftApMode(@NonNull String ifaceName) { + public IApInterface setupDriverForSoftApMode() { Log.d(TAG, "Setting up driver for soft ap mode"); mWificond = mWifiInjector.makeWificond(); if (mWificond == null) { @@ -192,7 +191,7 @@ public class WificondControl { IApInterface apInterface = null; try { - apInterface = mWificond.createApInterface(ifaceName); + apInterface = mWificond.createApInterface(); } catch (RemoteException e1) { Log.e(TAG, "Failed to get IApInterface due to remote exception"); return null; diff --git a/com/android/server/wifi/scanner/WificondScannerImpl.java b/com/android/server/wifi/scanner/WificondScannerImpl.java index 10fc8e3e..fb878e67 100644 --- a/com/android/server/wifi/scanner/WificondScannerImpl.java +++ b/com/android/server/wifi/scanner/WificondScannerImpl.java @@ -167,12 +167,12 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call + ",eventHandler=" + eventHandler); return false; } + if (mPendingSingleScanSettings != null + || (mLastScanSettings != null && mLastScanSettings.singleScanActive)) { + Log.w(TAG, "A single scan is already running"); + return false; + } synchronized (mSettingsLock) { - if (mPendingSingleScanSettings != null - || (mLastScanSettings != null && mLastScanSettings.singleScanActive)) { - Log.w(TAG, "A single scan is already running"); - return false; - } mPendingSingleScanSettings = settings; mPendingSingleScanEventHandler = eventHandler; processPendingScans(); @@ -518,10 +518,8 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call } private boolean isHwPnoScanRequired() { - synchronized (mSettingsLock) { - if (mPnoSettings == null) return false; - return isHwPnoScanRequired(mPnoSettings.isConnected); - } + if (mPnoSettings == null) return false; + return isHwPnoScanRequired(mPnoSettings.isConnected); } @Override diff --git a/com/android/server/wifi/util/InformationElementUtil.java b/com/android/server/wifi/util/InformationElementUtil.java index 14912b5f..c8f9ca34 100644 --- a/com/android/server/wifi/util/InformationElementUtil.java +++ b/com/android/server/wifi/util/InformationElementUtil.java @@ -247,10 +247,6 @@ public class InformationElementUtil { "Bad Interworking element length: " + ie.bytes.length); } - if (ie.bytes.length == 3 || ie.bytes.length == 9) { - int venueInfo = (int) ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 2); - } - if (ie.bytes.length == 7 || ie.bytes.length == 9) { hessid = ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 6); } diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java index 5d034935..a1eeff84 100644 --- a/com/android/server/wm/AppWindowToken.java +++ b/com/android/server/wm/AppWindowToken.java @@ -823,7 +823,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // For freeform windows, we can't freeze the bounds at the moment because this would make // the resizing unresponsive. - if (task == null || task.inFreeformWindowingMode()) { + if (task == null || task.inFreeformWorkspace()) { return false; } @@ -1310,7 +1310,8 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // Notify the pinned stack upon all windows drawn. If there was an animation in // progress then this signal will resume that animation. - final TaskStack pinnedStack = mDisplayContent.getPinnedStack(); + final TaskStack pinnedStack = + mDisplayContent.getStack(WINDOWING_MODE_PINNED); if (pinnedStack != null) { pinnedStack.onAllWindowsDrawn(); } diff --git a/com/android/server/wm/BlackFrame.java b/com/android/server/wm/BlackFrame.java index d206554c..5c29a0aa 100644 --- a/com/android/server/wm/BlackFrame.java +++ b/com/android/server/wm/BlackFrame.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.graphics.PixelFormat.OPAQUE; import static android.view.SurfaceControl.FX_SURFACE_DIM; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -50,8 +51,14 @@ public class BlackFrame { int w = r-l; int h = b-t; - surface = new SurfaceControl(session, "BlackSurface", - w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN); + if (DEBUG_SURFACE_TRACE) { + surface = new WindowSurfaceController.SurfaceTrace(session, "BlackSurface(" + + l + ", " + t + ")", + w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN); + } else { + surface = new SurfaceControl(session, "BlackSurface", + w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN); + } surface.setAlpha(1); surface.setLayerStack(layerStack); diff --git a/com/android/server/wm/CircularDisplayMask.java b/com/android/server/wm/CircularDisplayMask.java index 85f468b5..ae415413 100644 --- a/com/android/server/wm/CircularDisplayMask.java +++ b/com/android/server/wm/CircularDisplayMask.java @@ -17,6 +17,7 @@ package com.android.server.wm; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -66,9 +67,14 @@ class CircularDisplayMask { SurfaceControl ctrl = null; try { - ctrl = new SurfaceControl(session, "CircularDisplayMask", mScreenSize.x, - mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); - + if (DEBUG_SURFACE_TRACE) { + ctrl = new WindowSurfaceController.SurfaceTrace(session, "CircularDisplayMask", + mScreenSize.x, mScreenSize.y, PixelFormat.TRANSLUCENT, + SurfaceControl.HIDDEN); + } else { + ctrl = new SurfaceControl(session, "CircularDisplayMask", mScreenSize.x, + mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); + } ctrl.setLayerStack(display.getLayerStack()); ctrl.setLayer(zOrder); ctrl.setPosition(0, 0); diff --git a/com/android/server/wm/ConfigurationContainer.java b/com/android/server/wm/ConfigurationContainer.java index 5bfea989..9e028d38 100644 --- a/com/android/server/wm/ConfigurationContainer.java +++ b/com/android/server/wm/ConfigurationContainer.java @@ -21,9 +21,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -184,11 +182,6 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; } - public boolean inSplitScreenPrimaryWindowingMode() { - return mFullConfiguration.windowConfiguration.getWindowingMode() - == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; - } - /** * Returns true if this container can be put in either * {@link WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or @@ -199,14 +192,6 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode(); } - public boolean inPinnedWindowingMode() { - return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED; - } - - public boolean inFreeformWindowingMode() { - return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM; - } - /** Returns the activity type associated with the the configuration container. */ /*@WindowConfiguration.ActivityType*/ public int getActivityType() { diff --git a/com/android/server/wm/DimLayer.java b/com/android/server/wm/DimLayer.java index 48181d30..708973d5 100644 --- a/com/android/server/wm/DimLayer.java +++ b/com/android/server/wm/DimLayer.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -104,10 +105,16 @@ public class DimLayer { private void constructSurface(WindowManagerService service) { service.openSurfaceTransaction(); try { - mDimSurface = new SurfaceControl(service.mFxSession, mName, + if (DEBUG_SURFACE_TRACE) { + mDimSurface = new WindowSurfaceController.SurfaceTrace(service.mFxSession, + "DimSurface", 16, 16, PixelFormat.OPAQUE, SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN); - + } else { + mDimSurface = new SurfaceControl(service.mFxSession, mName, + 16, 16, PixelFormat.OPAQUE, + SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN); + } if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG, " DIM " + mDimSurface + ": CREATE"); mDimSurface.setLayerStack(mDisplayId); diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java index 03fdc968..0e68a8f6 100644 --- a/com/android/server/wm/DisplayContent.java +++ b/com/android/server/wm/DisplayContent.java @@ -16,9 +16,10 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; @@ -117,7 +118,7 @@ import static com.android.server.wm.proto.DisplayProto.WINDOW_CONTAINER; import android.annotation.CallSuper; import android.annotation.NonNull; -import android.content.pm.PackageManager; +import android.app.ActivityManager.StackId; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; @@ -296,6 +297,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** Window tokens that are in the process of exiting, but still on screen for animations. */ final ArrayList<WindowToken> mExitingTokens = new ArrayList<>(); + /** A special TaskStack with id==HOME_STACK_ID that moves to the bottom whenever any TaskStack + * (except a future lockscreen TaskStack) moves to the top. */ + private TaskStack mHomeStack = null; + /** Detect user tapping outside of current focused task bounds .*/ TaskTapPointerEventListener mTapDetector; @@ -974,8 +979,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } // In the presence of the PINNED stack or System Alert - // windows we unfortunately can not seamlessly rotate. - if (hasPinnedStack()) { + // windows we unforuntately can not seamlessly rotate. + if (getStackById(PINNED_STACK_ID) != null) { mayRotateSeamlessly = false; } for (int i = 0; i < mService.mSessions.size(); i++) { @@ -1446,31 +1451,20 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } TaskStack getHomeStack() { - return mTaskStackContainers.getHomeStack(); - } - - /** - * @return The primary split-screen stack, but only if it is visible, and {@code null} otherwise. - */ - TaskStack getSplitScreenPrimaryStackStack() { - TaskStack stack = mTaskStackContainers.getSplitScreenPrimaryStackStack(); - return (stack != null && stack.isVisible()) ? stack : null; - } - - /** - * Like {@link #getSplitScreenPrimaryStackStack}, but also returns the stack if it's currently - * not visible. - */ - TaskStack getSplitScreenPrimaryStackStackIgnoringVisibility() { - return mTaskStackContainers.getSplitScreenPrimaryStackStack(); - } - - TaskStack getPinnedStack() { - return mTaskStackContainers.getPinnedStack(); + if (mHomeStack == null && mDisplayId == DEFAULT_DISPLAY) { + Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this); + } + return mHomeStack; } - private boolean hasPinnedStack() { - return mTaskStackContainers.getPinnedStack() != null; + TaskStack getStackById(int stackId) { + for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) { + final TaskStack stack = mTaskStackContainers.get(i); + if (stack.mStackId == stackId) { + return stack; + } + } + return null; } /** @@ -1486,16 +1480,29 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * activity type. Null is no compatible stack on the display. */ TaskStack getStack(int windowingMode, int activityType) { - return mTaskStackContainers.getStack(windowingMode, activityType); + for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) { + final TaskStack stack = mTaskStackContainers.get(i); + if (stack.isCompatible(windowingMode, activityType)) { + return stack; + } + } + return null; } @VisibleForTesting - TaskStack getTopStack() { - return mTaskStackContainers.getTopStack(); + int getStackCount() { + return mTaskStackContainers.size(); } - void onStackWindowingModeChanged(TaskStack stack) { - mTaskStackContainers.onStackWindowingModeChanged(stack); + @VisibleForTesting + int getStackPosition(int windowingMode, int activityType) { + for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) { + final TaskStack stack = mTaskStackContainers.get(i); + if (stack.isCompatible(windowingMode, activityType)) { + return i; + } + } + return -1; } @Override @@ -1516,8 +1523,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * bounds were updated. */ void updateStackBoundsAfterConfigChange(@NonNull List<Integer> changedStackList) { - for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) { - final TaskStack stack = mTaskStackContainers.getChildAt(i); + for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) { + final TaskStack stack = mTaskStackContainers.get(i); if (stack.updateBoundsAfterConfigChange()) { changedStackList.add(stack.mStackId); } @@ -1526,7 +1533,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // If there was no pinned stack, we still need to notify the controller of the display info // update as a result of the config change. We do this here to consolidate the flow between // changes when there is and is not a stack. - if (!hasPinnedStack()) { + if (getStack(WINDOWING_MODE_PINNED) == null) { mPinnedStackControllerLocked.onDisplayInfoChanged(); } } @@ -1625,8 +1632,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mDisplay.getDisplayInfo(mDisplayInfo); mDisplay.getMetrics(mDisplayMetrics); - for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) { - mTaskStackContainers.getChildAt(i).updateDisplayInfo(null); + for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) { + mTaskStackContainers.get(i).updateDisplayInfo(null); } } @@ -1747,14 +1754,26 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo out.set(mContentRect); } - TaskStack createStack(int stackId, boolean onTop, StackWindowController controller) { + TaskStack addStackToDisplay(int stackId, boolean onTop, StackWindowController controller) { if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId=" + mDisplayId); - final TaskStack stack = new TaskStack(mService, stackId, controller); - mTaskStackContainers.addStackToDisplay(stack, onTop); + TaskStack stack = getStackById(stackId); + if (stack != null) { + // It's already attached to the display...clear mDeferRemoval, set controller, and move + // stack to appropriate z-order on display as needed. + stack.mDeferRemoval = false; + stack.setController(controller); + // We're not moving the display to front when we're adding stacks, only when + // requested to change the position of stack explicitly. + mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack, + false /* includingParents */); + } else { + stack = new TaskStack(mService, stackId, controller); + mTaskStackContainers.addStackToDisplay(stack, onTop); + } - if (stack.inSplitScreenPrimaryWindowingMode()) { + if (stackId == DOCKED_STACK_ID) { mDividerControllerLocked.notifyDockedStackExistsChanged(true); } return stack; @@ -1771,7 +1790,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo + " to its current displayId=" + mDisplayId); } - prevDc.mTaskStackContainers.removeChild(stack); + prevDc.mTaskStackContainers.removeStackFromDisplay(stack); mTaskStackContainers.addStackToDisplay(stack, onTop); } @@ -1805,8 +1824,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } int taskIdFromPoint(int x, int y) { - for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx); + for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) { + final TaskStack stack = mTaskStackContainers.get(stackNdx); final int taskId = stack.taskIdFromPoint(x, y); if (taskId != -1) { return taskId; @@ -1822,8 +1841,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo Task findTaskForResizePoint(int x, int y) { final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics); mTmpTaskForResizePointSearchResult.reset(); - for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx); + for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) { + final TaskStack stack = mTaskStackContainers.get(stackNdx); if (!stack.getWindowConfiguration().canResizeTask()) { return null; } @@ -1845,8 +1864,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mTouchExcludeRegion.set(mBaseDisplayRect); final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics); mTmpRect2.setEmpty(); - for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx); + for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) { + final TaskStack stack = mTaskStackContainers.get(stackNdx); stack.setTouchExcludeRegion( focusedTask, delta, mTouchExcludeRegion, mContentRect, mTmpRect2); } @@ -1877,7 +1896,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION); } // TODO(multi-display): Support docked stacks on secondary displays. - if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStackStack() != null) { + if (mDisplayId == DEFAULT_DISPLAY && getDockedStackLocked() != null) { mDividerControllerLocked.getTouchRegion(mTmpRect); mTmpRegion.set(mTmpRect); mTouchExcludeRegion.op(mTmpRegion, Op.UNION); @@ -1894,8 +1913,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } private void resetAnimationBackgroundAnimator() { - for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - mTaskStackContainers.getChildAt(stackNdx).resetAnimationBackgroundAnimator(); + for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) { + mTaskStackContainers.get(stackNdx).resetAnimationBackgroundAnimator(); } } @@ -1966,8 +1985,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo float dividerAnimationTarget) { boolean updated = false; - for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) { - final TaskStack stack = mTaskStackContainers.getChildAt(i); + for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) { + final TaskStack stack = mTaskStackContainers.get(i); if (stack == null || !stack.isAdjustedForIme()) { continue; } @@ -1995,8 +2014,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo boolean clearImeAdjustAnimation() { boolean changed = false; - for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) { - final TaskStack stack = mTaskStackContainers.getChildAt(i); + for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) { + final TaskStack stack = mTaskStackContainers.get(i); if (stack != null && stack.isAdjustedForIme()) { stack.resetAdjustedForIme(true /* adjustBoundsNow */); changed = true; @@ -2006,8 +2025,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } void beginImeAdjustAnimation() { - for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) { - final TaskStack stack = mTaskStackContainers.getChildAt(i); + for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) { + final TaskStack stack = mTaskStackContainers.get(i); if (stack.isVisible() && stack.isAdjustedForIme()) { stack.beginImeAdjustAnimation(); } @@ -2018,7 +2037,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final WindowState imeWin = mService.mInputMethodWindow; final boolean imeVisible = imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw() && !mDividerControllerLocked.isImeHideRequested(); - final boolean dockVisible = isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + final boolean dockVisible = isStackVisible(DOCKED_STACK_ID); final TaskStack imeTargetStack = mService.getImeFocusStackLocked(); final int imeDockSide = (dockVisible && imeTargetStack != null) ? imeTargetStack.getDockSide() : DOCKED_INVALID; @@ -2036,8 +2055,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // - If IME is not visible, divider is not moved and is normal width. if (imeVisible && dockVisible && (imeOnTop || imeOnBottom) && !dockMinimized) { - for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) { - final TaskStack stack = mTaskStackContainers.getChildAt(i); + for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) { + final TaskStack stack = mTaskStackContainers.get(i); final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM; if (stack.isVisible() && (imeOnBottom || isDockedOnBottom) && stack.inSplitScreenWindowingMode()) { @@ -2049,8 +2068,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mDividerControllerLocked.setAdjustedForIme( imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin, imeHeight); } else { - for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) { - final TaskStack stack = mTaskStackContainers.getChildAt(i); + for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) { + final TaskStack stack = mTaskStackContainers.get(i); stack.resetAdjustedForIme(!dockVisible); } mDividerControllerLocked.setAdjustedForIme( @@ -2081,8 +2100,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } void prepareFreezingTaskBounds() { - for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx); + for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) { + final TaskStack stack = mTaskStackContainers.get(stackNdx); stack.prepareFreezingTaskBounds(); } } @@ -2141,22 +2160,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final long token = proto.start(fieldId); super.writeToProto(proto, WINDOW_CONTAINER); proto.write(ID, mDisplayId); - for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx); + for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) { + final TaskStack stack = mTaskStackContainers.get(stackNdx); stack.writeToProto(proto, STACKS); } mDividerControllerLocked.writeToProto(proto, DOCKED_STACK_DIVIDER_CONTROLLER); mPinnedStackControllerLocked.writeToProto(proto, PINNED_STACK_CONTROLLER); - for (int i = mAboveAppWindowsContainers.getChildCount() - 1; i >= 0; --i) { - final WindowToken windowToken = mAboveAppWindowsContainers.getChildAt(i); + for (int i = mAboveAppWindowsContainers.size() - 1; i >= 0; --i) { + final WindowToken windowToken = mAboveAppWindowsContainers.get(i); windowToken.writeToProto(proto, ABOVE_APP_WINDOWS); } - for (int i = mBelowAppWindowsContainers.getChildCount() - 1; i >= 0; --i) { - final WindowToken windowToken = mBelowAppWindowsContainers.getChildAt(i); + for (int i = mBelowAppWindowsContainers.size() - 1; i >= 0; --i) { + final WindowToken windowToken = mBelowAppWindowsContainers.get(i); windowToken.writeToProto(proto, BELOW_APP_WINDOWS); } - for (int i = mImeWindowsContainers.getChildCount() - 1; i >= 0; --i) { - final WindowToken windowToken = mImeWindowsContainers.getChildAt(i); + for (int i = mImeWindowsContainers.size() - 1; i >= 0; --i) { + final WindowToken windowToken = mImeWindowsContainers.get(i); windowToken.writeToProto(proto, IME_WINDOWS); } proto.write(DPI, mBaseDisplayDensity); @@ -2202,8 +2221,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo pw.println(); pw.println(prefix + "Application tokens in top down Z order:"); - for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx); + for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) { + final TaskStack stack = mTaskStackContainers.get(stackNdx); stack.dump(prefix + " ", pw); } @@ -2222,22 +2241,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo pw.println(); mDimLayerController.dump(prefix, pw); pw.println(); - - // Dump stack references - final TaskStack homeStack = getHomeStack(); - if (homeStack != null) { - pw.println(prefix + "homeStack=" + homeStack.getName()); - } - final TaskStack pinnedStack = getPinnedStack(); - if (pinnedStack != null) { - pw.println(prefix + "pinnedStack=" + pinnedStack.getName()); - } - final TaskStack splitScreenPrimaryStack = getSplitScreenPrimaryStackStack(); - if (splitScreenPrimaryStack != null) { - pw.println(prefix + "splitScreenPrimaryStack=" + splitScreenPrimaryStack.getName()); - } - - pw.println(); mDividerControllerLocked.dump(prefix, pw); pw.println(); mPinnedStackControllerLocked.dump(prefix, pw); @@ -2257,10 +2260,26 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return "Display " + mDisplayId + " name=\"" + mDisplayInfo.name + "\""; } - /** Returns true if the stack in the windowing mode is visible. */ - boolean isStackVisible(int windowingMode) { - final TaskStack stack = getStack(windowingMode); - return stack != null && stack.isVisible(); + /** Checks if stack with provided id is visible on this display. */ + boolean isStackVisible(int stackId) { + final TaskStack stack = getStackById(stackId); + return (stack != null && stack.isVisible()); + } + + /** + * @return The docked stack, but only if it is visible, and {@code null} otherwise. + */ + TaskStack getDockedStackLocked() { + final TaskStack stack = getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + return (stack != null && stack.isVisible()) ? stack : null; + } + + /** + * Like {@link #getDockedStackLocked}, but also returns the docked stack if it's currently not + * visible. + */ + TaskStack getDockedStackIgnoringVisibility() { + return getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); } /** Find the visible, touch-deliverable window under the given point */ @@ -3339,6 +3358,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ static class DisplayChildWindowContainer<E extends WindowContainer> extends WindowContainer<E> { + int size() { + return mChildren.size(); + } + + E get(int index) { + return mChildren.get(index); + } + @Override boolean fillsParent() { return true; @@ -3356,108 +3383,25 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> { - // Cached reference to some special stacks we tend to get a lot so we don't need to loop - // through the list to find them. - private TaskStack mHomeStack = null; - private TaskStack mPinnedStack = null; - private TaskStack mSplitScreenPrimaryStack = null; - - /** - * Returns the topmost stack on the display that is compatible with the input windowing mode - * and activity type. Null is no compatible stack on the display. - */ - TaskStack getStack(int windowingMode, int activityType) { - if (activityType == ACTIVITY_TYPE_HOME) { - return mHomeStack; - } - if (windowingMode == WINDOWING_MODE_PINNED) { - return mPinnedStack; - } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - return mSplitScreenPrimaryStack; - } - for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) { - final TaskStack stack = mTaskStackContainers.getChildAt(i); - if (stack.isCompatible(windowingMode, activityType)) { - return stack; - } - } - return null; - } - - @VisibleForTesting - TaskStack getTopStack() { - return mTaskStackContainers.getChildCount() > 0 - ? mTaskStackContainers.getChildAt(mTaskStackContainers.getChildCount() - 1) : null; - } - - TaskStack getHomeStack() { - if (mHomeStack == null && mDisplayId == DEFAULT_DISPLAY) { - Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this); - } - return mHomeStack; - } - - TaskStack getPinnedStack() { - return mPinnedStack; - } - - TaskStack getSplitScreenPrimaryStackStack() { - return mSplitScreenPrimaryStack; - } - /** * Adds the stack to this container. - * @see DisplayContent#createStack(int, boolean, StackWindowController) + * @see WindowManagerService#addStackToDisplay(int, int, boolean) */ void addStackToDisplay(TaskStack stack, boolean onTop) { - addStackReferenceIfNeeded(stack); - addChild(stack, onTop); - stack.onDisplayChanged(DisplayContent.this); - } - - void onStackWindowingModeChanged(TaskStack stack) { - removeStackReferenceIfNeeded(stack); - addStackReferenceIfNeeded(stack); - if (stack == mPinnedStack && getTopStack() != stack) { - // Looks like this stack changed windowing mode to pinned. Move it to the top. - positionChildAt(POSITION_TOP, stack, false /* includingParents */); - } - } - - private void addStackReferenceIfNeeded(TaskStack stack) { if (stack.isActivityTypeHome()) { if (mHomeStack != null) { - throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack=" - + mHomeStack + " already exist on display=" + this + " stack=" + stack); + throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first."); } mHomeStack = stack; } - final int windowingMode = stack.getWindowingMode(); - if (windowingMode == WINDOWING_MODE_PINNED) { - if (mPinnedStack != null) { - throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack=" - + mPinnedStack + " already exist on display=" + this - + " stack=" + stack); - } - mPinnedStack = stack; - } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - if (mSplitScreenPrimaryStack != null) { - throw new IllegalArgumentException("addStackReferenceIfNeeded:" - + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack - + " already exist on display=" + this + " stack=" + stack); - } - mSplitScreenPrimaryStack = stack; - } + addChild(stack, onTop); + stack.onDisplayChanged(DisplayContent.this); } - private void removeStackReferenceIfNeeded(TaskStack stack) { - if (stack == mHomeStack) { - mHomeStack = null; - } else if (stack == mPinnedStack) { - mPinnedStack = null; - } else if (stack == mSplitScreenPrimaryStack) { - mSplitScreenPrimaryStack = null; - } + /** Removes the stack from its container and prepare for changing the parent. */ + void removeStackFromDisplay(TaskStack stack) { + removeChild(stack); + stack.onRemovedFromDisplay(); } private void addChild(TaskStack stack, boolean toTop) { @@ -3467,11 +3411,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo setLayoutNeeded(); } - @Override - protected void removeChild(TaskStack stack) { - super.removeChild(stack); - removeStackReferenceIfNeeded(stack); - } @Override boolean isOnTop() { @@ -3514,7 +3453,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo : requestedPosition >= topChildPosition; int targetPosition = requestedPosition; - if (toTop && stack.getWindowingMode() != WINDOWING_MODE_PINNED && hasPinnedStack()) { + if (toTop && stack.getWindowingMode() != WINDOWING_MODE_PINNED + && getStack(WINDOWING_MODE_PINNED) != null) { // The pinned stack is always the top most stack (always-on-top) when it is present. TaskStack topStack = mChildren.get(topChildPosition); if (topStack.getWindowingMode() != WINDOWING_MODE_PINNED) { @@ -3616,8 +3556,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @Override int getOrientation() { - if (isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) - || isStackVisible(WINDOWING_MODE_FREEFORM)) { + if (isStackVisible(DOCKED_STACK_ID) || isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) { // Apps and their containers are not allowed to specify an orientation while the // docked or freeform stack is visible...except for the home stack/task if the // docked stack is minimized and it actually set something. @@ -3632,16 +3571,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } final int orientation = super.getOrientation(); - boolean isCar = mService.mContext.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_AUTOMOTIVE); - if (isCar) { - // In a car, you cannot physically rotate the screen, so it doesn't make sense to - // allow anything but the default orientation. - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, - "Forcing UNSPECIFIED orientation in car. Ignoring " + orientation); - return SCREEN_ORIENTATION_UNSPECIFIED; - } - if (orientation != SCREEN_ORIENTATION_UNSET && orientation != SCREEN_ORIENTATION_BEHIND) { if (DEBUG_ORIENTATION) Slog.v(TAG_WM, diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java index 52526e2f..6f441b98 100644 --- a/com/android/server/wm/DockedStackDividerController.java +++ b/com/android/server/wm/DockedStackDividerController.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; @@ -319,7 +322,7 @@ public class DockedStackDividerController implements DimLayerUser { if (mWindow == null) { return; } - TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility(); + TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility(); // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide final boolean visible = stack != null; @@ -359,7 +362,7 @@ public class DockedStackDividerController implements DimLayerUser { } void positionDockedStackedDivider(Rect frame) { - TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStack(); + TaskStack stack = mDisplayContent.getDockedStackLocked(); if (stack == null) { // Unfortunately we might end up with still having a divider, even though the underlying // stack was already removed. This is because we are on AM thread and the removal of the @@ -456,7 +459,7 @@ public class DockedStackDividerController implements DimLayerUser { long animDuration = 0; if (animate) { final TaskStack stack = - mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility(); + mDisplayContent.getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); final long transitionDuration = isAnimationMaximizing() ? mService.mAppTransition.getLastClipRevealTransitionDuration() : DEFAULT_APP_TRANSITION_DURATION; @@ -510,8 +513,7 @@ public class DockedStackDividerController implements DimLayerUser { void registerDockedStackListener(IDockedStackListener listener) { mDockedStackListeners.register(listener); notifyDockedDividerVisibilityChanged(wasVisible()); - notifyDockedStackExistsChanged( - mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null); + notifyDockedStackExistsChanged(mDisplayContent.getDockedStackIgnoringVisibility() != null); notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */, isHomeStackResizable()); notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */); @@ -530,7 +532,7 @@ public class DockedStackDividerController implements DimLayerUser { final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED ? mDisplayContent.getStack(targetWindowingMode) : null; - final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStackStack(); + final TaskStack dockedStack = mDisplayContent.getDockedStackLocked(); boolean visibleAndValid = visible && stack != null && dockedStack != null; if (visibleAndValid) { stack.getDimBounds(mTmpRect); @@ -586,7 +588,7 @@ public class DockedStackDividerController implements DimLayerUser { private boolean containsAppInDockedStack(ArraySet<AppWindowToken> apps) { for (int i = apps.size() - 1; i >= 0; i--) { final AppWindowToken token = apps.valueAt(i); - if (token.getTask() != null && token.inSplitScreenPrimaryWindowingMode()) { + if (token.getTask() != null && token.getTask().mStack.mStackId == DOCKED_STACK_ID) { return true; } } @@ -598,7 +600,7 @@ public class DockedStackDividerController implements DimLayerUser { } private void checkMinimizeChanged(boolean animate) { - if (mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() == null) { + if (mDisplayContent.getDockedStackIgnoringVisibility() == null) { return; } final TaskStack homeStack = mDisplayContent.getHomeStack(); @@ -760,7 +762,7 @@ public class DockedStackDividerController implements DimLayerUser { } private boolean setMinimizedDockedStack(boolean minimized) { - final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility(); + final TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility(); notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable()); return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f); } @@ -811,7 +813,8 @@ public class DockedStackDividerController implements DimLayerUser { } private boolean animateForMinimizedDockedStack(long now) { - final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility(); + final TaskStack stack = + mDisplayContent.getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); if (!mAnimationStarted) { mAnimationStarted = true; mAnimationStartTime = now; diff --git a/com/android/server/wm/EmulatorDisplayOverlay.java b/com/android/server/wm/EmulatorDisplayOverlay.java index 19bd8e9d..3186d3dc 100644 --- a/com/android/server/wm/EmulatorDisplayOverlay.java +++ b/com/android/server/wm/EmulatorDisplayOverlay.java @@ -17,6 +17,7 @@ package com.android.server.wm; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -56,8 +57,14 @@ class EmulatorDisplayOverlay { SurfaceControl ctrl = null; try { - ctrl = new SurfaceControl(session, "EmulatorDisplayOverlay", mScreenSize.x, - mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); + if (DEBUG_SURFACE_TRACE) { + ctrl = new WindowSurfaceController.SurfaceTrace(session, "EmulatorDisplayOverlay", + mScreenSize.x, mScreenSize.y, PixelFormat.TRANSLUCENT, + SurfaceControl.HIDDEN); + } else { + ctrl = new SurfaceControl(session, "EmulatorDisplayOverlay", mScreenSize.x, + mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); + } ctrl.setLayerStack(display.getLayerStack()); ctrl.setLayer(zOrder); ctrl.setPosition(0, 0); diff --git a/com/android/server/wm/InputMonitor.java b/com/android/server/wm/InputMonitor.java index 238cb9f1..5057f632 100644 --- a/com/android/server/wm/InputMonitor.java +++ b/com/android/server/wm/InputMonitor.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION; import static android.view.WindowManager.INPUT_CONSUMER_PIP; @@ -649,7 +650,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { final boolean hasFocus = w == mInputFocus; final boolean isVisible = w.isVisibleLw(); - if (w.inPinnedWindowingMode()) { + if (w.getStackId() == PINNED_STACK_ID) { if (mAddPipInputConsumerHandle && (inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer)) { // Update the bounds of the Pip input consumer to match the Pinned stack diff --git a/com/android/server/wm/PinnedStackController.java b/com/android/server/wm/PinnedStackController.java index 365366ad..ef31598f 100644 --- a/com/android/server/wm/PinnedStackController.java +++ b/com/android/server/wm/PinnedStackController.java @@ -417,7 +417,8 @@ class PinnedStackController { false /* useCurrentMinEdgeSize */); } final Rect animatingBounds = mTmpAnimatingBoundsRect; - final TaskStack pinnedStack = mDisplayContent.getPinnedStack(); + final TaskStack pinnedStack = + mDisplayContent.getStack(WINDOWING_MODE_PINNED); if (pinnedStack != null) { pinnedStack.getAnimationOrCurrentBounds(animatingBounds); } else { diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java index fd574709..7832f5de 100644 --- a/com/android/server/wm/RootWindowContainer.java +++ b/com/android/server/wm/RootWindowContainer.java @@ -411,6 +411,17 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { } } + TaskStack getStackById(int stackId) { + for (int i = mChildren.size() - 1; i >= 0; i--) { + final DisplayContent dc = mChildren.get(i); + final TaskStack stack = dc.getStackById(stackId); + if (stack != null) { + return stack; + } + } + return null; + } + TaskStack getStack(int windowingMode, int activityType) { for (int i = mChildren.size() - 1; i >= 0; i--) { final DisplayContent dc = mChildren.get(i); diff --git a/com/android/server/wm/ScreenRotationAnimation.java b/com/android/server/wm/ScreenRotationAnimation.java index 8e99be83..d5b6d246 100644 --- a/com/android/server/wm/ScreenRotationAnimation.java +++ b/com/android/server/wm/ScreenRotationAnimation.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; @@ -23,6 +24,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER; +import static com.android.server.wm.WindowSurfaceController.SurfaceTrace; import static com.android.server.wm.proto.ScreenRotationAnimationProto.ANIMATION_RUNNING; import static com.android.server.wm.proto.ScreenRotationAnimationProto.STARTED; @@ -274,10 +276,17 @@ class ScreenRotationAnimation { flags |= SurfaceControl.SECURE; } - mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface", - mWidth, mHeight, - PixelFormat.OPAQUE, flags); - + if (DEBUG_SURFACE_TRACE) { + mSurfaceControl = new SurfaceTrace(session, "ScreenshotSurface", + mWidth, mHeight, + PixelFormat.OPAQUE, flags); + Slog.w(TAG, "ScreenRotationAnimation ctor: displayOffset=" + + mOriginalDisplayRect.toShortString()); + } else { + mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface", + mWidth, mHeight, + PixelFormat.OPAQUE, flags); + } // capture a screenshot into the surface we just created Surface sur = new Surface(); sur.copyFrom(mSurfaceControl); diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java index 1fda832d..c0a4cb72 100644 --- a/com/android/server/wm/StackWindowController.java +++ b/com/android/server/wm/StackWindowController.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import static android.app.ActivityManager.StackId.PINNED_STACK_ID; + import android.content.res.Configuration; import android.graphics.Rect; import android.os.Handler; @@ -43,7 +45,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; public class StackWindowController extends WindowContainerController<TaskStack, StackWindowListener> { - private final int mStackId; + final int mStackId; private final H mHandler; @@ -72,7 +74,7 @@ public class StackWindowController + " to unknown displayId=" + displayId); } - dc.createStack(stackId, onTop, this); + dc.addStackToDisplay(stackId, onTop, this); getRawBounds(outBounds); } } @@ -278,9 +280,8 @@ public class StackWindowController if (stack.getWindowConfiguration().tasksAreFloating()) { // Floating tasks should not be resized to the screen's bounds. - if (stack.inPinnedWindowingMode() - && bounds.width() == mTmpDisplayBounds.width() - && bounds.height() == mTmpDisplayBounds.height()) { + if (mStackId == PINNED_STACK_ID && bounds.width() == mTmpDisplayBounds.width() && + bounds.height() == mTmpDisplayBounds.height()) { // If the bounds we are animating is the same as the fullscreen stack // dimensions, then apply the same inset calculations that we normally do for // the fullscreen stack, without intersecting it with the display bounds diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java index 891d637a..7e8d1308 100644 --- a/com/android/server/wm/Task.java +++ b/com/android/server/wm/Task.java @@ -17,6 +17,8 @@ package com.android.server.wm; import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; @@ -211,7 +213,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU // then we want to preserve our insets so that there will not // be a jump in the area covered by system decorations. We rely // on the pinned animation to later unset this value. - if (stack.inPinnedWindowingMode()) { + if (stack.mStackId == PINNED_STACK_ID) { mPreserveNonFloatingState = true; } else { mPreserveNonFloatingState = false; @@ -419,7 +421,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU return mFillsParent || !inSplitScreenSecondaryWindowingMode() || displayContent == null - || displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null; + || displayContent.getDockedStackIgnoringVisibility() != null; } /** Original bounds of the task if applicable, otherwise fullscreen rect. */ @@ -490,7 +492,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU final boolean dockedResizing = displayContent != null && displayContent.mDividerControllerLocked.isResizing(); if (useCurrentBounds()) { - if (inFreeformWindowingMode() && getMaxVisibleBounds(out)) { + if (inFreeformWorkspace() && getMaxVisibleBounds(out)) { return; } @@ -596,6 +598,14 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU return (tokensCount != 0) && mChildren.get(tokensCount - 1).mShowForAllUsers; } + boolean inFreeformWorkspace() { + return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID; + } + + boolean inPinnedWorkspace() { + return mStack != null && mStack.mStackId == PINNED_STACK_ID; + } + /** * When we are in a floating stack (Freeform, Pinned, ...) we calculate * insets differently. However if we are animating to the fullscreen stack diff --git a/com/android/server/wm/TaskPositioner.java b/com/android/server/wm/TaskPositioner.java index 87de1514..c58212cd 100644 --- a/com/android/server/wm/TaskPositioner.java +++ b/com/android/server/wm/TaskPositioner.java @@ -20,6 +20,7 @@ import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIG import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; import static android.app.ActivityManager.RESIZE_MODE_USER; import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; @@ -648,7 +649,7 @@ class TaskPositioner implements DimLayer.DimLayerUser { * shouldn't be shown. */ private int getDimSide(int x) { - if (!mTask.mStack.inFreeformWindowingMode() + if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID || !mTask.mStack.fillsParent() || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) { return CTRL_NONE; diff --git a/com/android/server/wm/TaskSnapshotController.java b/com/android/server/wm/TaskSnapshotController.java index 54ef0651..bff24f6e 100644 --- a/com/android/server/wm/TaskSnapshotController.java +++ b/com/android/server/wm/TaskSnapshotController.java @@ -294,9 +294,7 @@ class TaskSnapshotController { decorPainter.drawDecors(c, null /* statusBarExcludeFrame */); node.end(c); final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height); - if (hwBitmap == null) { - return null; - } + return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(), topChild.getConfiguration().orientation, mainWindow.mStableInsets, ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */); diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java index d170b6f2..65278837 100644 --- a/com/android/server/wm/TaskStack.java +++ b/com/android/server/wm/TaskStack.java @@ -18,6 +18,8 @@ package com.android.server.wm; import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; +import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; @@ -294,7 +296,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye if (mFillsParent || !inSplitScreenSecondaryWindowingMode() || mDisplayContent == null - || mDisplayContent.getSplitScreenPrimaryStackStack() != null) { + || mDisplayContent.getDockedStackLocked() != null) { return true; } return false; @@ -407,7 +409,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye return false; } - if (inPinnedWindowingMode()) { + if (mStackId == PINNED_STACK_ID) { getAnimationOrCurrentBounds(mTmpRect2); boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged( mTmpRect2, mTmpRect3); @@ -441,19 +443,21 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye mTmpRect2.set(mBounds); mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2); - if (inSplitScreenPrimaryWindowingMode()) { - repositionDockedStackAfterRotation(mTmpRect2); - snapDockedStackAfterRotation(mTmpRect2); - final int newDockSide = getDockSide(mTmpRect2); - - // Update the dock create mode and clear the dock create bounds, these - // might change after a rotation and the original values will be invalid. - mService.setDockedStackCreateStateLocked( - (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP) - ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT - : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, - null); - mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide); + switch (mStackId) { + case DOCKED_STACK_ID: + repositionDockedStackAfterRotation(mTmpRect2); + snapDockedStackAfterRotation(mTmpRect2); + final int newDockSide = getDockSide(mTmpRect2); + + // Update the dock create mode and clear the dock create bounds, these + // might change after a rotation and the original values will be invalid. + mService.setDockedStackCreateStateLocked( + (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP) + ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT + : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, + null); + mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide); + break; } mBoundsAfterRotation.set(mTmpRect2); @@ -673,16 +677,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } } - @Override - public void onConfigurationChanged(Configuration newParentConfig) { - final int prevWindowingMode = getWindowingMode(); - super.onConfigurationChanged(newParentConfig); - if (mDisplayContent != null && prevWindowingMode != getWindowingMode()) { - mDisplayContent.onStackWindowingModeChanged(this); - } - } - - @Override void onDisplayChanged(DisplayContent dc) { if (mDisplayContent != null) { throw new IllegalStateException("onDisplayChanged: Already attached"); @@ -693,8 +687,8 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye "animation background stackId=" + mStackId); Rect bounds = null; - final TaskStack dockedStack = dc.getSplitScreenPrimaryStackStackIgnoringVisibility(); - if (inSplitScreenPrimaryWindowingMode() + final TaskStack dockedStack = dc.getDockedStackIgnoringVisibility(); + if (mStackId == DOCKED_STACK_ID || (dockedStack != null && inSplitScreenSecondaryWindowingMode() && !dockedStack.fillsParent())) { // The existence of a docked stack affects the size of other static stack created since @@ -709,10 +703,10 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } final boolean dockedOnTopOrLeft = mService.mDockedStackCreateMode == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; - getStackDockedModeBounds(mTmpRect, bounds, mTmpRect2, + getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2, mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft); - } else if (inPinnedWindowingMode()) { + } else if (mStackId == PINNED_STACK_ID) { // Update the bounds based on any changes to the display info getAnimationOrCurrentBounds(mTmpRect2); boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged( @@ -772,8 +766,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye return; } - final TaskStack dockedStack = - mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility(); + final TaskStack dockedStack = mDisplayContent.getDockedStackIgnoringVisibility(); if (dockedStack == null) { // Not sure why you are calling this method when there is no docked stack... throw new IllegalStateException( @@ -798,7 +791,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye mDisplayContent.getLogicalDisplayRect(mTmpRect); dockedStack.getRawBounds(mTmpRect2); final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT; - getStackDockedModeBounds(mTmpRect, outStackBounds, mTmpRect2, + getStackDockedModeBounds(mTmpRect, outStackBounds, mStackId, mTmpRect2, mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft); } @@ -807,15 +800,16 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye * Outputs the bounds a stack should be given the presence of a docked stack on the display. * @param displayRect The bounds of the display the docked stack is on. * @param outBounds Output bounds that should be used for the stack. + * @param stackId Id of stack we are calculating the bounds for. * @param dockedBounds Bounds of the docked stack. * @param dockDividerWidth We need to know the width of the divider make to the output bounds * close to the side of the dock. * @param dockOnTopOrLeft If the docked stack is on the top or left side of the screen. */ private void getStackDockedModeBounds( - Rect displayRect, Rect outBounds, Rect dockedBounds, int dockDividerWidth, + Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int dockDividerWidth, boolean dockOnTopOrLeft) { - final boolean dockedStack = inSplitScreenPrimaryWindowingMode(); + final boolean dockedStack = stackId == DOCKED_STACK_ID; final boolean splitHorizontally = displayRect.width() > displayRect.height(); outBounds.set(displayRect); @@ -872,7 +866,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } void resetDockedStackToMiddle() { - if (inSplitScreenPrimaryWindowingMode()) { + if (mStackId != DOCKED_STACK_ID) { throw new IllegalStateException("Not a docked stack=" + this); } @@ -900,12 +894,17 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } @Override - void onParentSet() { - if (getParent() != null || mDisplayContent == null) { - return; - } + void removeImmediately() { + super.removeImmediately(); + + onRemovedFromDisplay(); + } - // Looks like the stack was removed from the display. Go ahead and clean things up. + /** + * Removes the stack it from its current parent, so it can be either destroyed completely or + * re-parented. + */ + void onRemovedFromDisplay() { mDisplayContent.mDimLayerController.removeDimLayerUser(this); EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId); @@ -914,7 +913,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye mAnimationBackgroundSurface = null; } - if (inSplitScreenPrimaryWindowingMode()) { + if (mStackId == DOCKED_STACK_ID) { mDisplayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(false); } @@ -1036,8 +1035,8 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } boolean shouldIgnoreInput() { - return isAdjustedForMinimizedDockedStack() || - (inSplitScreenPrimaryWindowingMode() && isMinimizedDockAndHomeStackResizable()); + return isAdjustedForMinimizedDockedStack() || mStackId == DOCKED_STACK_ID && + isMinimizedDockAndHomeStackResizable(); } /** @@ -1472,7 +1471,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye postExclude.set(mTmpRect); } - final boolean isFreeformed = task.inFreeformWindowingMode(); + final boolean isFreeformed = task.inFreeformWorkspace(); if (task != focusedTask || isFreeformed) { if (isFreeformed) { // If the task is freeformed, enlarge the area to account for outside @@ -1530,7 +1529,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } } - if (inPinnedWindowingMode()) { + if (mStackId == PINNED_STACK_ID) { try { mService.mActivityManager.notifyPinnedStackAnimationStarted(); } catch (RemoteException e) { @@ -1562,7 +1561,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye mService.requestTraversal(); } - if (inPinnedWindowingMode()) { + if (mStackId == PINNED_STACK_ID) { // Update to the final bounds if requested. This is done here instead of in the bounds // animator to allow us to coordinate this after we notify the PiP mode changed @@ -1596,7 +1595,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye * bounds and we have a deferred PiP mode changed callback set with the animation. */ public boolean deferScheduleMultiWindowModeChanged() { - if (inPinnedWindowingMode()) { + if (mStackId == PINNED_STACK_ID) { return (mBoundsAnimatingRequested || mBoundsAnimating); } return false; diff --git a/com/android/server/wm/WallpaperController.java b/com/android/server/wm/WallpaperController.java index 629cc868..7213c951 100644 --- a/com/android/server/wm/WallpaperController.java +++ b/com/android/server/wm/WallpaperController.java @@ -18,7 +18,7 @@ package com.android.server.wm; import com.android.internal.util.ToBooleanFunction; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; @@ -447,7 +447,7 @@ class WallpaperController { private void findWallpaperTarget(DisplayContent dc) { mFindResults.reset(); - if (dc.isStackVisible(WINDOWING_MODE_FREEFORM)) { + if (dc.isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) { // In freeform mode we set the wallpaper as its own target, so we don't need an // additional window to make it visible. mFindResults.setUseTopWallpaperAsTarget(true); diff --git a/com/android/server/wm/WindowContainer.java b/com/android/server/wm/WindowContainer.java index 1b0825e5..40923c82 100644 --- a/com/android/server/wm/WindowContainer.java +++ b/com/android/server/wm/WindowContainer.java @@ -73,12 +73,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Override - protected int getChildCount() { + final protected int getChildCount() { return mChildren.size(); } @Override - protected E getChildAt(int index) { + final protected E getChildAt(int index) { return mChildren.get(index); } diff --git a/com/android/server/wm/WindowManagerDebugConfig.java b/com/android/server/wm/WindowManagerDebugConfig.java index 9d9805ab..6d5673e2 100644 --- a/com/android/server/wm/WindowManagerDebugConfig.java +++ b/com/android/server/wm/WindowManagerDebugConfig.java @@ -60,6 +60,7 @@ public class WindowManagerDebugConfig { static final boolean DEBUG_SCREENSHOT = false; static final boolean DEBUG_BOOT = false; static final boolean DEBUG_LAYOUT_REPEATS = false; + static final boolean DEBUG_SURFACE_TRACE = false; static final boolean DEBUG_WINDOW_TRACE = false; static final boolean DEBUG_TASK_MOVEMENT = false; static final boolean DEBUG_TASK_POSITIONING = false; diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java index b133bd45..1fb21887 100644 --- a/com/android/server/wm/WindowManagerService.java +++ b/com/android/server/wm/WindowManagerService.java @@ -2384,7 +2384,7 @@ public class WindowManagerService extends IWindowManager.Stub final Rect insets = new Rect(); final Rect stableInsets = new Rect(); Rect surfaceInsets = null; - final boolean freeform = win != null && win.inFreeformWindowingMode(); + final boolean freeform = win != null && win.inFreeformWorkspace(); if (win != null) { // Containing frame will usually cover the whole screen, including dialog windows. // For freeform workspace windows it will not cover the whole screen and it also @@ -2794,7 +2794,7 @@ public class WindowManagerService extends IWindowManager.Stub for (final WindowState win : mWindowMap.values()) { final Task task = win.getTask(); if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1 - && task.inFreeformWindowingMode()) { + && task.inFreeformWorkspace()) { final AppWindowToken appToken = win.mAppToken; if (appToken != null && appToken.mAppAnimator != null) { appToken.mAppAnimator.startProlongAnimation(scaleUp ? @@ -3391,8 +3391,7 @@ public class WindowManagerService extends IWindowManager.Stub // Notify whether the docked stack exists for the current user final DisplayContent displayContent = getDefaultDisplayContentLocked(); - final TaskStack stack = - displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility(); + final TaskStack stack = displayContent.getDockedStackIgnoringVisibility(); displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged( stack != null && stack.hasTaskForUser(newUserId)); @@ -6899,6 +6898,11 @@ public class WindowManagerService extends IWindowManager.Stub dumpSessionsLocked(pw, true); } return; + } else if ("surfaces".equals(cmd)) { + synchronized(mWindowMap) { + WindowSurfaceController.SurfaceTrace.dumpAllSurfaces(pw, null); + } + return; } else if ("displays".equals(cmd) || "d".equals(cmd)) { synchronized(mWindowMap) { mRoot.dumpDisplayContents(pw); @@ -6963,6 +6967,10 @@ public class WindowManagerService extends IWindowManager.Stub if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } + WindowSurfaceController.SurfaceTrace.dumpAllSurfaces(pw, dumpAll ? + "-------------------------------------------------------------------------------" + : null); + pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } @@ -7124,7 +7132,7 @@ public class WindowManagerService extends IWindowManager.Stub public int getDockedStackSide() { synchronized (mWindowMap) { final TaskStack dockedStack = getDefaultDisplayContentLocked() - .getSplitScreenPrimaryStackStackIgnoringVisibility(); + .getDockedStackIgnoringVisibility(); return dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide(); } } @@ -7596,10 +7604,10 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public boolean isStackVisible(int windowingMode) { + public boolean isStackVisible(int stackId) { synchronized (mWindowMap) { final DisplayContent dc = getDefaultDisplayContentLocked(); - return dc.isStackVisible(windowingMode); + return dc.isStackVisible(stackId); } } diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java index e1715284..4ff0f391 100644 --- a/com/android/server/wm/WindowState.java +++ b/com/android/server/wm/WindowState.java @@ -16,7 +16,10 @@ package com.android.server.wm; +import static android.app.ActivityManager.StackId; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; @@ -80,6 +83,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RESIZE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -811,12 +815,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final WindowState imeWin = mService.mInputMethodWindow; // IME is up and obscuring this window. Adjust the window position so it is visible. if (imeWin != null && imeWin.isVisibleNow() && mService.mInputMethodTarget == this) { - if (inFreeformWindowingMode() + final int stackId = getStackId(); + if (stackId == FREEFORM_WORKSPACE_STACK_ID && mContainingFrame.bottom > contentFrame.bottom) { // In freeform we want to move the top up directly. // TODO: Investigate why this is contentFrame not parentFrame. mContainingFrame.top -= mContainingFrame.bottom - contentFrame.bottom; - } else if (!inPinnedWindowingMode() + } else if (stackId != PINNED_STACK_ID && mContainingFrame.bottom > parentFrame.bottom) { // But in docked we want to behave like fullscreen and behave as if the task // were given smaller bounds for the purposes of layout. Skip adjustments for @@ -893,7 +898,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // For pinned workspace the frame isn't limited in any particular // way since SystemUI controls the bounds. For freeform however // we want to keep things inside the content frame. - final Rect limitFrame = task.inPinnedWindowingMode() ? mFrame : mContentFrame; + final Rect limitFrame = task.inPinnedWorkspace() ? mFrame : mContentFrame; // Keep the frame out of the blocked system area, limit it in size to the content area // and make sure that there is always a minimum visible so that the user can drag it // into a usable area.. @@ -1205,7 +1210,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // application when it has finished drawing. if (getOrientationChanging() || dragResizingChanged || isResizedWhileNotDragResizing()) { - if (DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) { + if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) { Slog.v(TAG_WM, "Orientation or resize start waiting for draw" + ", mDrawState=DRAW_PENDING in " + this + ", surfaceController " + winAnimator.mSurfaceController); @@ -1657,9 +1662,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // // Anyway we don't need to synchronize position and content updates for these // windows since they aren't at the base layer and could be moved around anyway. - if (!computeDragResizing() && mAttrs.type == TYPE_BASE_APPLICATION - && !mWinAnimator.isForceScaled() && !isGoneForLayoutLw() - && !getTask().inPinnedWindowingMode()) { + if (!computeDragResizing() && mAttrs.type == TYPE_BASE_APPLICATION && + !mWinAnimator.isForceScaled() && !isGoneForLayoutLw() && + !getTask().inPinnedWorkspace()) { setResizedWhileNotDragResizing(true); } } @@ -2191,6 +2196,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } + // TODO: Strange usage of word workspace here and above. + boolean inPinnedWorkspace() { + final Task task = getTask(); + return task != null && task.inPinnedWorkspace(); + } + void applyAdjustForImeIfNeeded() { final Task task = getTask(); if (task != null && task.mStack != null && task.mStack.isAdjustedForIme()) { @@ -2224,7 +2235,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } else { getVisibleBounds(mTmpRect); } - if (inFreeformWindowingMode()) { + if (inFreeformWorkspace()) { // For freeform windows we the touch region to include the whole surface for the // shadows. final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics(); @@ -2360,8 +2371,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // just in case they have the divider at an unstable position. Better // also reset drag resizing state, because the owner can't do it // anymore. - final TaskStack stack = - dc.getSplitScreenPrimaryStackStackIgnoringVisibility(); + final TaskStack stack = dc.getDockedStackIgnoringVisibility(); if (stack != null) { stack.resetDockedStackToMiddle(); } @@ -2928,7 +2938,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return mTmpRect; } - private int getStackId() { + @Override + public int getStackId() { final TaskStack stack = getStack(); if (stack == null) { return INVALID_STACK_ID; @@ -2972,6 +2983,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } + boolean inFreeformWorkspace() { + final Task task = getTask(); + return task != null && task.inFreeformWorkspace(); + } + @Override public boolean isInMultiWindowMode() { final Task task = getTask(); @@ -3089,7 +3105,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // background. return (getDisplayContent().mDividerControllerLocked.isResizing() || mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) && - !task.inFreeformWindowingMode() && !isGoneForLayoutLw(); + !task.inFreeformWorkspace() && !isGoneForLayoutLw(); } @@ -3679,7 +3695,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Force the show in the next prepareSurfaceLocked() call. mWinAnimator.mLastAlpha = -1; - if (DEBUG_ANIM) Slog.v(TAG, + if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) Slog.v(TAG, "performShowLocked: mDrawState=HAS_DRAWN in " + this); mWinAnimator.mDrawState = HAS_DRAWN; mService.scheduleAnimationLocked(); @@ -3740,7 +3756,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP windowInfo.accessibilityIdOfAnchor = mAttrs.accessibilityIdOfAnchor; windowInfo.focused = isFocused(); Task task = getTask(); - windowInfo.inPictureInPicture = (task != null) && task.inPinnedWindowingMode(); + windowInfo.inPictureInPicture = (task != null) && task.inPinnedWorkspace(); if (mIsChildWindow) { windowInfo.parentToken = getParentWindow().mClient.asBinder(); @@ -4203,7 +4219,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // If a freeform window is animating from a position where it would be cutoff, it would be // cutoff during the animation. We don't want that, so for the duration of the animation // we ignore the decor cropping and depend on layering to position windows correctly. - final boolean cropToDecor = !(inFreeformWindowingMode() && isAnimatingLw()); + final boolean cropToDecor = !(inFreeformWorkspace() && isAnimatingLw()); if (cropToDecor) { // Intersect with the decor rect, offsetted by window position. systemDecorRect.intersect(decorRect.left - left, decorRect.top - top, @@ -4287,7 +4303,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // scale for the animation using the source hint rect // (see WindowStateAnimator#setSurfaceBoundariesLocked()). if (isDragResizeChanged() || isResizedWhileNotDragResizing() - || (surfaceInsetsChanging() && !inPinnedWindowingMode())) { + || (surfaceInsetsChanging() && !inPinnedWorkspace())) { mLastSurfaceInsets.set(mAttrs.surfaceInsets); setDragResizing(); diff --git a/com/android/server/wm/WindowStateAnimator.java b/com/android/server/wm/WindowStateAnimator.java index 52669031..1b7e5278 100644 --- a/com/android/server/wm/WindowStateAnimator.java +++ b/com/android/server/wm/WindowStateAnimator.java @@ -31,6 +31,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEAT import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_CROP; @@ -508,7 +509,7 @@ class WindowStateAnimator { boolean layoutNeeded = false; if (mDrawState == DRAW_PENDING) { - if (DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION) + if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION) Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + mWin + " in " + mSurfaceController); if (DEBUG_STARTING_WINDOW && startingWindow) { @@ -531,7 +532,7 @@ class WindowStateAnimator { if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) { return false; } - if (DEBUG_ANIM) { + if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) { Slog.i(TAG, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW " + mSurfaceController); } mDrawState = READY_TO_SHOW; @@ -1032,7 +1033,7 @@ class WindowStateAnimator { //Slog.i(TAG_WM, "Not applying alpha transform"); } - if ((DEBUG_ANIM || WindowManagerService.localLOGV) + if ((DEBUG_SURFACE_TRACE || WindowManagerService.localLOGV) && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v( TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha + " self=" + (selfTransformation ? mTransformation.getAlpha() : "null") @@ -1111,7 +1112,7 @@ class WindowStateAnimator { */ private boolean useFinalClipRect() { return (isAnimationSet() && resolveStackClip() == STACK_CLIP_AFTER_ANIM) - || mDestroyPreservedSurfaceUponRedraw || mWin.inPinnedWindowingMode(); + || mDestroyPreservedSurfaceUponRedraw || mWin.inPinnedWorkspace(); } /** @@ -1176,7 +1177,7 @@ class WindowStateAnimator { return false; } - if (w.inPinnedWindowingMode()) { + if (w.inPinnedWorkspace()) { return false; } diff --git a/com/android/server/wm/WindowSurfaceController.java b/com/android/server/wm/WindowSurfaceController.java index d56df55d..2e1e3f76 100644 --- a/com/android/server/wm/WindowSurfaceController.java +++ b/com/android/server/wm/WindowSurfaceController.java @@ -22,6 +22,7 @@ import static android.view.Surface.SCALING_MODE_SCALE_TO_WINDOW; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -564,4 +565,262 @@ class WindowSurfaceController { public String toString() { return mSurfaceControl.toString(); } + + static class SurfaceTrace extends SurfaceControl { + private final static String SURFACE_TAG = TAG_WITH_CLASS_NAME ? "SurfaceTrace" : TAG_WM; + private final static boolean LOG_SURFACE_TRACE = DEBUG_SURFACE_TRACE; + final static ArrayList<SurfaceTrace> sSurfaces = new ArrayList<SurfaceTrace>(); + + private float mSurfaceTraceAlpha = 0; + private int mLayer; + private final PointF mPosition = new PointF(); + private final Point mSize = new Point(); + private final Rect mWindowCrop = new Rect(); + private final Rect mFinalCrop = new Rect(); + private boolean mShown = false; + private int mLayerStack; + private boolean mIsOpaque; + private float mDsdx, mDtdx, mDsdy, mDtdy; + private final String mName; + + public SurfaceTrace(SurfaceSession s, String name, int w, int h, int format, int flags, + int windowType, int ownerUid) + throws OutOfResourcesException { + super(s, name, w, h, format, flags, windowType, ownerUid); + mName = name != null ? name : "Not named"; + mSize.set(w, h); + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by " + + Debug.getCallers(3)); + synchronized (sSurfaces) { + sSurfaces.add(0, this); + } + } + + public SurfaceTrace(SurfaceSession s, + String name, int w, int h, int format, int flags) { + super(s, name, w, h, format, flags); + mName = name != null ? name : "Not named"; + mSize.set(w, h); + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by " + + Debug.getCallers(3)); + synchronized (sSurfaces) { + sSurfaces.add(0, this); + } + } + + @Override + public void setAlpha(float alpha) { + if (mSurfaceTraceAlpha != alpha) { + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setAlpha(" + alpha + "): OLD:" + this + + ". Called by " + Debug.getCallers(3)); + mSurfaceTraceAlpha = alpha; + } + super.setAlpha(alpha); + } + + @Override + public void setLayer(int zorder) { + if (zorder != mLayer) { + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setLayer(" + zorder + "): OLD:" + this + + ". Called by " + Debug.getCallers(3)); + mLayer = zorder; + } + super.setLayer(zorder); + + synchronized (sSurfaces) { + sSurfaces.remove(this); + int i; + for (i = sSurfaces.size() - 1; i >= 0; i--) { + SurfaceTrace s = sSurfaces.get(i); + if (s.mLayer < zorder) { + break; + } + } + sSurfaces.add(i + 1, this); + } + } + + @Override + public void setPosition(float x, float y) { + if (x != mPosition.x || y != mPosition.y) { + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setPosition(" + x + "," + y + "): OLD:" + + this + ". Called by " + Debug.getCallers(3)); + mPosition.set(x, y); + } + super.setPosition(x, y); + } + + @Override + public void setGeometryAppliesWithResize() { + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setGeometryAppliesWithResize(): OLD: " + + this + ". Called by" + Debug.getCallers(3)); + super.setGeometryAppliesWithResize(); + } + + @Override + public void setSize(int w, int h) { + if (w != mSize.x || h != mSize.y) { + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setSize(" + w + "," + h + "): OLD:" + + this + ". Called by " + Debug.getCallers(3)); + mSize.set(w, h); + } + super.setSize(w, h); + } + + @Override + public void setWindowCrop(Rect crop) { + if (crop != null) { + if (!crop.equals(mWindowCrop)) { + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setWindowCrop(" + + crop.toShortString() + "): OLD:" + this + ". Called by " + + Debug.getCallers(3)); + mWindowCrop.set(crop); + } + } + super.setWindowCrop(crop); + } + + @Override + public void setFinalCrop(Rect crop) { + if (crop != null) { + if (!crop.equals(mFinalCrop)) { + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setFinalCrop(" + + crop.toShortString() + "): OLD:" + this + ". Called by " + + Debug.getCallers(3)); + mFinalCrop.set(crop); + } + } + super.setFinalCrop(crop); + } + + @Override + public void setLayerStack(int layerStack) { + if (layerStack != mLayerStack) { + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setLayerStack(" + layerStack + "): OLD:" + + this + ". Called by " + Debug.getCallers(3)); + mLayerStack = layerStack; + } + super.setLayerStack(layerStack); + } + + @Override + public void setOpaque(boolean isOpaque) { + if (isOpaque != mIsOpaque) { + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setOpaque(" + isOpaque + "): OLD:" + + this + ". Called by " + Debug.getCallers(3)); + mIsOpaque = isOpaque; + } + super.setOpaque(isOpaque); + } + + @Override + public void setSecure(boolean isSecure) { + super.setSecure(isSecure); + } + + @Override + public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { + if (dsdx != mDsdx || dtdx != mDtdx || dsdy != mDsdy || dtdy != mDtdy) { + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setMatrix(" + dsdx + "," + dtdx + "," + + dsdy + "," + dtdy + "): OLD:" + this + ". Called by " + + Debug.getCallers(3)); + mDsdx = dsdx; + mDtdx = dtdx; + mDsdy = dsdy; + mDtdy = dtdy; + } + super.setMatrix(dsdx, dtdx, dsdy, dtdy); + } + + @Override + public void hide() { + if (mShown) { + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "hide: OLD:" + this + ". Called by " + + Debug.getCallers(3)); + mShown = false; + } + super.hide(); + } + + @Override + public void show() { + if (!mShown) { + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "show: OLD:" + this + ". Called by " + + Debug.getCallers(3)); + mShown = true; + } + super.show(); + } + + @Override + public void destroy() { + super.destroy(); + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "destroy: " + this + ". Called by " + + Debug.getCallers(3)); + synchronized (sSurfaces) { + sSurfaces.remove(this); + } + } + + @Override + public void release() { + super.release(); + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "release: " + this + ". Called by " + + Debug.getCallers(3)); + synchronized (sSurfaces) { + sSurfaces.remove(this); + } + } + + @Override + public void setTransparentRegionHint(Region region) { + if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setTransparentRegionHint(" + region + + "): OLD: " + this + " . Called by " + Debug.getCallers(3)); + super.setTransparentRegionHint(region); + } + + static void dumpAllSurfaces(PrintWriter pw, String header) { + synchronized (sSurfaces) { + final int N = sSurfaces.size(); + if (N <= 0) { + return; + } + if (header != null) { + pw.println(header); + } + pw.println("WINDOW MANAGER SURFACES (dumpsys window surfaces)"); + for (int i = 0; i < N; i++) { + SurfaceTrace s = sSurfaces.get(i); + pw.print(" Surface #"); pw.print(i); pw.print(": #"); + pw.print(Integer.toHexString(System.identityHashCode(s))); + pw.print(" "); pw.println(s.mName); + pw.print(" mLayerStack="); pw.print(s.mLayerStack); + pw.print(" mLayer="); pw.println(s.mLayer); + pw.print(" mShown="); pw.print(s.mShown); pw.print(" mAlpha="); + pw.print(s.mSurfaceTraceAlpha); pw.print(" mIsOpaque="); + pw.println(s.mIsOpaque); + pw.print(" mPosition="); pw.print(s.mPosition.x); pw.print(","); + pw.print(s.mPosition.y); + pw.print(" mSize="); pw.print(s.mSize.x); pw.print("x"); + pw.println(s.mSize.y); + pw.print(" mCrop="); s.mWindowCrop.printShortString(pw); pw.println(); + pw.print(" mFinalCrop="); s.mFinalCrop.printShortString(pw); pw.println(); + pw.print(" Transform: ("); pw.print(s.mDsdx); pw.print(", "); + pw.print(s.mDtdx); pw.print(", "); pw.print(s.mDsdy); + pw.print(", "); pw.print(s.mDtdy); pw.println(")"); + } + } + } + + @Override + public String toString() { + return "Surface " + Integer.toHexString(System.identityHashCode(this)) + " " + + mName + " (" + mLayerStack + "): shown=" + mShown + " layer=" + mLayer + + " alpha=" + mSurfaceTraceAlpha + " " + mPosition.x + "," + mPosition.y + + " " + mSize.x + "x" + mSize.y + + " crop=" + mWindowCrop.toShortString() + + " opaque=" + mIsOpaque + + " (" + mDsdx + "," + mDtdx + "," + mDsdy + "," + mDtdy + ")"; + } + } } diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java index fa33fe8f..af1fa2fe 100644 --- a/com/android/server/wm/WindowSurfacePlacer.java +++ b/com/android/server/wm/WindowSurfacePlacer.java @@ -449,9 +449,6 @@ class WindowSurfacePlacer { // animating? wtoken.setVisibility(animLp, false, transit, false, voiceInteraction); wtoken.updateReportedVisibilityLocked(); - // setAllAppWinAnimators so the windows get onExitAnimationDone once the animation is - // done. - wtoken.setAllAppWinAnimators(); // Force the allDrawn flag, because we want to start // this guy's animations regardless of whether it's // gotten drawn. diff --git a/com/android/settingslib/CustomEditTextPreference.java b/com/android/settingslib/CustomEditTextPreference.java index 90124f1f..253ca11b 100644 --- a/com/android/settingslib/CustomEditTextPreference.java +++ b/com/android/settingslib/CustomEditTextPreference.java @@ -20,7 +20,6 @@ import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; -import android.support.annotation.CallSuper; import android.support.v14.preference.EditTextPreferenceDialogFragment; import android.support.v7.preference.EditTextPreference; import android.util.AttributeSet; @@ -31,8 +30,7 @@ public class CustomEditTextPreference extends EditTextPreference { private CustomPreferenceDialogFragment mFragment; - public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { + public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @@ -71,12 +69,7 @@ public class CustomEditTextPreference extends EditTextPreference { protected void onClick(DialogInterface dialog, int which) { } - @CallSuper protected void onBindDialogView(View view) { - final EditText editText = view.findViewById(android.R.id.edit); - if (editText != null) { - editText.requestFocus(); - } } private void setFragment(CustomPreferenceDialogFragment fragment) { diff --git a/com/android/settingslib/development/AbstractLogdSizePreferenceController.java b/com/android/settingslib/development/AbstractLogdSizePreferenceController.java index f79be7ea..7998b2ef 100644 --- a/com/android/settingslib/development/AbstractLogdSizePreferenceController.java +++ b/com/android/settingslib/development/AbstractLogdSizePreferenceController.java @@ -33,27 +33,21 @@ public abstract class AbstractLogdSizePreferenceController extends + "AbstractLogdSizePreferenceController.LOGD_SIZE_UPDATED"; public static final String EXTRA_CURRENT_LOGD_VALUE = "CURRENT_LOGD_VALUE"; - @VisibleForTesting - static final String LOW_RAM_CONFIG_PROPERTY_KEY = "ro.config.low_ram"; private static final String SELECT_LOGD_SIZE_KEY = "select_logd_size"; @VisibleForTesting static final String SELECT_LOGD_SIZE_PROPERTY = "persist.logd.size"; static final String SELECT_LOGD_TAG_PROPERTY = "persist.log.tag"; // Tricky, isLoggable only checks for first character, assumes silence static final String SELECT_LOGD_TAG_SILENCE = "Settings"; - @VisibleForTesting - static final String SELECT_LOGD_SNET_TAG_PROPERTY = "persist.log.tag.snet_event_log"; + private static final String SELECT_LOGD_SNET_TAG_PROPERTY = "persist.log.tag.snet_event_log"; private static final String SELECT_LOGD_RUNTIME_SNET_TAG_PROPERTY = "log.tag.snet_event_log"; private static final String SELECT_LOGD_DEFAULT_SIZE_PROPERTY = "ro.logd.size"; @VisibleForTesting static final String SELECT_LOGD_DEFAULT_SIZE_VALUE = "262144"; private static final String SELECT_LOGD_SVELTE_DEFAULT_SIZE_VALUE = "65536"; // 32768 is merely a menu marker, 64K is our lowest log buffer size we replace it with. - @VisibleForTesting - static final String SELECT_LOGD_MINIMUM_SIZE_VALUE = "65536"; + private static final String SELECT_LOGD_MINIMUM_SIZE_VALUE = "65536"; static final String SELECT_LOGD_OFF_SIZE_MARKER_VALUE = "32768"; - @VisibleForTesting - static final String DEFAULT_SNET_TAG = "I"; private ListPreference mLogdSize; @@ -160,7 +154,7 @@ public abstract class AbstractLogdSizePreferenceController extends if ((snetValue == null) || (snetValue.length() == 0)) { snetValue = SystemProperties.get(SELECT_LOGD_RUNTIME_SNET_TAG_PROPERTY); if ((snetValue == null) || (snetValue.length() == 0)) { - SystemProperties.set(SELECT_LOGD_SNET_TAG_PROPERTY, DEFAULT_SNET_TAG); + SystemProperties.set(SELECT_LOGD_SNET_TAG_PROPERTY, "I"); } } // Silence all log sources, security logs notwithstanding diff --git a/com/android/settingslib/development/AbstractLogpersistPreferenceController.java b/com/android/settingslib/development/AbstractLogpersistPreferenceController.java index 77b2d86c..67553adc 100644 --- a/com/android/settingslib/development/AbstractLogpersistPreferenceController.java +++ b/com/android/settingslib/development/AbstractLogpersistPreferenceController.java @@ -68,7 +68,7 @@ public abstract class AbstractLogpersistPreferenceController extends public AbstractLogpersistPreferenceController(Context context, Lifecycle lifecycle) { super(context); - if (isAvailable() && lifecycle != null) { + if (isAvailable()) { lifecycle.addObserver(this); } } diff --git a/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java deleted file mode 100644 index ba358f83..00000000 --- a/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2017 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.settingslib.deviceinfo; - -import android.annotation.SuppressLint; -import android.bluetooth.BluetoothAdapter; -import android.content.Context; -import android.support.annotation.VisibleForTesting; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceScreen; -import android.text.TextUtils; - -import com.android.settingslib.R; -import com.android.settingslib.core.lifecycle.Lifecycle; - -/** - * Preference controller for bluetooth address - */ -public abstract class AbstractBluetoothAddressPreferenceController - extends AbstractConnectivityPreferenceController { - - @VisibleForTesting - static final String KEY_BT_ADDRESS = "bt_address"; - - private static final String[] CONNECTIVITY_INTENTS = { - BluetoothAdapter.ACTION_STATE_CHANGED - }; - - private Preference mBtAddress; - - public AbstractBluetoothAddressPreferenceController(Context context, Lifecycle lifecycle) { - super(context, lifecycle); - } - - @Override - public boolean isAvailable() { - return BluetoothAdapter.getDefaultAdapter() != null; - } - - @Override - public String getPreferenceKey() { - return KEY_BT_ADDRESS; - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mBtAddress = screen.findPreference(KEY_BT_ADDRESS); - updateConnectivity(); - } - - @Override - protected String[] getConnectivityIntents() { - return CONNECTIVITY_INTENTS; - } - - @SuppressLint("HardwareIds") - @Override - protected void updateConnectivity() { - BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter(); - if (bluetooth != null && mBtAddress != null) { - String address = bluetooth.isEnabled() ? bluetooth.getAddress() : null; - if (!TextUtils.isEmpty(address)) { - // Convert the address to lowercase for consistency with the wifi MAC address. - mBtAddress.setSummary(address.toLowerCase()); - } else { - mBtAddress.setSummary(R.string.status_unavailable); - } - } - } -} diff --git a/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java deleted file mode 100644 index c6552f77..00000000 --- a/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2017 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.settingslib.deviceinfo; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Handler; -import android.os.Message; - -import com.android.internal.util.ArrayUtils; -import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.events.OnStart; -import com.android.settingslib.core.lifecycle.events.OnStop; - -import java.lang.ref.WeakReference; - -/** - * Base class for preference controllers which listen to connectivity broadcasts - */ -public abstract class AbstractConnectivityPreferenceController - extends AbstractPreferenceController implements LifecycleObserver, OnStart, OnStop { - - private final BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (ArrayUtils.contains(getConnectivityIntents(), action)) { - getHandler().sendEmptyMessage(EVENT_UPDATE_CONNECTIVITY); - } - } - }; - - private static final int EVENT_UPDATE_CONNECTIVITY = 600; - - private Handler mHandler; - - public AbstractConnectivityPreferenceController(Context context, Lifecycle lifecycle) { - super(context); - if (lifecycle != null) { - lifecycle.addObserver(this); - } - } - - @Override - public void onStop() { - mContext.unregisterReceiver(mConnectivityReceiver); - } - - @Override - public void onStart() { - final IntentFilter connectivityIntentFilter = new IntentFilter(); - final String[] intents = getConnectivityIntents(); - for (String intent : intents) { - connectivityIntentFilter.addAction(intent); - } - - mContext.registerReceiver(mConnectivityReceiver, connectivityIntentFilter, - android.Manifest.permission.CHANGE_NETWORK_STATE, null); - } - - protected abstract String[] getConnectivityIntents(); - - protected abstract void updateConnectivity(); - - private Handler getHandler() { - if (mHandler == null) { - mHandler = new ConnectivityEventHandler(this); - } - return mHandler; - } - - private static class ConnectivityEventHandler extends Handler { - private WeakReference<AbstractConnectivityPreferenceController> mPreferenceController; - - public ConnectivityEventHandler(AbstractConnectivityPreferenceController activity) { - mPreferenceController = new WeakReference<>(activity); - } - - @Override - public void handleMessage(Message msg) { - AbstractConnectivityPreferenceController preferenceController - = mPreferenceController.get(); - if (preferenceController == null) { - return; - } - - switch (msg.what) { - case EVENT_UPDATE_CONNECTIVITY: - preferenceController.updateConnectivity(); - break; - default: - throw new IllegalStateException("Unknown message " + msg.what); - } - } - } -} diff --git a/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java deleted file mode 100644 index bb8404b0..00000000 --- a/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2017 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.settingslib.deviceinfo; - -import android.bluetooth.BluetoothAdapter; -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.wifi.WifiManager; -import android.os.PersistableBundle; -import android.support.annotation.VisibleForTesting; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceScreen; -import android.telephony.CarrierConfigManager; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; - -import com.android.settingslib.R; -import com.android.settingslib.core.lifecycle.Lifecycle; - -/** - * Preference controller for IMS status - */ -public abstract class AbstractImsStatusPreferenceController - extends AbstractConnectivityPreferenceController { - - @VisibleForTesting - static final String KEY_IMS_REGISTRATION_STATE = "ims_reg_state"; - - private static final String[] CONNECTIVITY_INTENTS = { - BluetoothAdapter.ACTION_STATE_CHANGED, - ConnectivityManager.CONNECTIVITY_ACTION, - WifiManager.LINK_CONFIGURATION_CHANGED_ACTION, - WifiManager.NETWORK_STATE_CHANGED_ACTION, - }; - - private Preference mImsStatus; - - public AbstractImsStatusPreferenceController(Context context, - Lifecycle lifecycle) { - super(context, lifecycle); - } - - @Override - public boolean isAvailable() { - CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class); - int subId = SubscriptionManager.getDefaultDataSubscriptionId(); - PersistableBundle config = null; - if (configManager != null) { - config = configManager.getConfigForSubId(subId); - } - return config != null && config.getBoolean( - CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL); - } - - @Override - public String getPreferenceKey() { - return KEY_IMS_REGISTRATION_STATE; - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mImsStatus = screen.findPreference(KEY_IMS_REGISTRATION_STATE); - updateConnectivity(); - } - - @Override - protected String[] getConnectivityIntents() { - return CONNECTIVITY_INTENTS; - } - - @Override - protected void updateConnectivity() { - int subId = SubscriptionManager.getDefaultDataSubscriptionId(); - if (mImsStatus != null) { - TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); - mImsStatus.setSummary((tm != null && tm.isImsRegistered(subId)) ? - R.string.ims_reg_status_registered : R.string.ims_reg_status_not_registered); - } - } -} diff --git a/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java deleted file mode 100644 index ded30226..00000000 --- a/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2017 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.settingslib.deviceinfo; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.LinkProperties; -import android.net.wifi.WifiManager; -import android.support.annotation.VisibleForTesting; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceScreen; - -import com.android.settingslib.R; -import com.android.settingslib.core.lifecycle.Lifecycle; - -import java.net.InetAddress; -import java.util.Iterator; - -/** - * Preference controller for IP address - */ -public abstract class AbstractIpAddressPreferenceController - extends AbstractConnectivityPreferenceController { - - @VisibleForTesting - static final String KEY_IP_ADDRESS = "wifi_ip_address"; - - private static final String[] CONNECTIVITY_INTENTS = { - ConnectivityManager.CONNECTIVITY_ACTION, - WifiManager.LINK_CONFIGURATION_CHANGED_ACTION, - WifiManager.NETWORK_STATE_CHANGED_ACTION, - }; - - private Preference mIpAddress; - private final ConnectivityManager mCM; - - public AbstractIpAddressPreferenceController(Context context, Lifecycle lifecycle) { - super(context, lifecycle); - mCM = context.getSystemService(ConnectivityManager.class); - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public String getPreferenceKey() { - return KEY_IP_ADDRESS; - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mIpAddress = screen.findPreference(KEY_IP_ADDRESS); - updateConnectivity(); - } - - @Override - protected String[] getConnectivityIntents() { - return CONNECTIVITY_INTENTS; - } - - @Override - protected void updateConnectivity() { - String ipAddress = getDefaultIpAddresses(mCM); - if (ipAddress != null) { - mIpAddress.setSummary(ipAddress); - } else { - mIpAddress.setSummary(R.string.status_unavailable); - } - } - - /** - * Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style - * addresses. - * @param cm ConnectivityManager - * @return the formatted and newline-separated IP addresses, or null if none. - */ - private static String getDefaultIpAddresses(ConnectivityManager cm) { - LinkProperties prop = cm.getActiveLinkProperties(); - return formatIpAddresses(prop); - } - - private static String formatIpAddresses(LinkProperties prop) { - if (prop == null) return null; - Iterator<InetAddress> iter = prop.getAllAddresses().iterator(); - // If there are no entries, return null - if (!iter.hasNext()) return null; - // Concatenate all available addresses, newline separated - StringBuilder addresses = new StringBuilder(); - while (iter.hasNext()) { - addresses.append(iter.next().getHostAddress()); - if (iter.hasNext()) addresses.append("\n"); - } - return addresses.toString(); - } -} diff --git a/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java b/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java deleted file mode 100644 index ac61ade1..00000000 --- a/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2017 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.settingslib.deviceinfo; - -import android.content.Context; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.support.annotation.VisibleForTesting; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceScreen; -import android.text.format.DateUtils; - -import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.events.OnStart; -import com.android.settingslib.core.lifecycle.events.OnStop; - -import java.lang.ref.WeakReference; - -/** - * Preference controller for uptime - */ -public abstract class AbstractUptimePreferenceController extends AbstractPreferenceController - implements LifecycleObserver, OnStart, OnStop { - - @VisibleForTesting - static final String KEY_UPTIME = "up_time"; - private static final int EVENT_UPDATE_STATS = 500; - - private Preference mUptime; - private Handler mHandler; - - public AbstractUptimePreferenceController(Context context, Lifecycle lifecycle) { - super(context); - if (lifecycle != null) { - lifecycle.addObserver(this); - } - } - - @Override - public void onStart() { - getHandler().sendEmptyMessage(EVENT_UPDATE_STATS); - } - - @Override - public void onStop() { - getHandler().removeMessages(EVENT_UPDATE_STATS); - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public String getPreferenceKey() { - return KEY_UPTIME; - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mUptime = screen.findPreference(KEY_UPTIME); - updateTimes(); - } - - private Handler getHandler() { - if (mHandler == null) { - mHandler = new MyHandler(this); - } - return mHandler; - } - - private void updateTimes() { - mUptime.setSummary(DateUtils.formatDuration(SystemClock.elapsedRealtime())); - } - - private static class MyHandler extends Handler { - private WeakReference<AbstractUptimePreferenceController> mStatus; - - public MyHandler(AbstractUptimePreferenceController activity) { - mStatus = new WeakReference<>(activity); - } - - @Override - public void handleMessage(Message msg) { - AbstractUptimePreferenceController status = mStatus.get(); - if (status == null) { - return; - } - - switch (msg.what) { - case EVENT_UPDATE_STATS: - status.updateTimes(); - sendEmptyMessageDelayed(EVENT_UPDATE_STATS, 1000); - break; - - default: - throw new IllegalStateException("Unknown message " + msg.what); - } - } - } -} diff --git a/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java deleted file mode 100644 index d57b64f0..00000000 --- a/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2017 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.settingslib.deviceinfo; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import android.support.annotation.VisibleForTesting; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceScreen; -import android.text.TextUtils; - -import com.android.settingslib.R; -import com.android.settingslib.core.lifecycle.Lifecycle; - -/** - * Preference controller for WIFI MAC address - */ -public abstract class AbstractWifiMacAddressPreferenceController - extends AbstractConnectivityPreferenceController { - - @VisibleForTesting - static final String KEY_WIFI_MAC_ADDRESS = "wifi_mac_address"; - - private static final String[] CONNECTIVITY_INTENTS = { - ConnectivityManager.CONNECTIVITY_ACTION, - WifiManager.LINK_CONFIGURATION_CHANGED_ACTION, - WifiManager.NETWORK_STATE_CHANGED_ACTION, - }; - - private Preference mWifiMacAddress; - private final WifiManager mWifiManager; - - public AbstractWifiMacAddressPreferenceController(Context context, Lifecycle lifecycle) { - super(context, lifecycle); - mWifiManager = context.getSystemService(WifiManager.class); - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public String getPreferenceKey() { - return KEY_WIFI_MAC_ADDRESS; - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS); - updateConnectivity(); - } - - @Override - protected String[] getConnectivityIntents() { - return CONNECTIVITY_INTENTS; - } - - @SuppressLint("HardwareIds") - @Override - protected void updateConnectivity() { - WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); - String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress(); - if (!TextUtils.isEmpty(macAddress)) { - mWifiMacAddress.setSummary(macAddress); - } else { - mWifiMacAddress.setSummary(R.string.status_unavailable); - } - } -} diff --git a/com/android/settingslib/suggestions/SuggestionParser.java b/com/android/settingslib/suggestions/SuggestionParser.java index 9c347631..56b84415 100644 --- a/com/android/settingslib/suggestions/SuggestionParser.java +++ b/com/android/settingslib/suggestions/SuggestionParser.java @@ -261,7 +261,7 @@ public class SuggestionParser { if (requiredAccountType == null) { return true; } - AccountManager accountManager = mContext.getSystemService(AccountManager.class); + AccountManager accountManager = AccountManager.get(mContext); Account[] accounts = accountManager.getAccountsByType(requiredAccountType); boolean satisfiesRequiredAccount = accounts.length > 0; if (!satisfiesRequiredAccount) { diff --git a/com/android/settingslib/wifi/WifiTracker.java b/com/android/settingslib/wifi/WifiTracker.java index c56e1da1..12455d85 100644 --- a/com/android/settingslib/wifi/WifiTracker.java +++ b/com/android/settingslib/wifi/WifiTracker.java @@ -291,6 +291,15 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } /** + * Force a scan for wifi networks to happen now. + */ + public void forceScan() { + if (mWifiManager.isWifiEnabled() && mScanner != null) { + mScanner.forceScan(); + } + } + + /** * Temporarily stop scanning for wifi networks. */ public void pauseScanning() { @@ -780,6 +789,14 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } } + public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved, + boolean includeScans) { + WifiTracker tracker = new WifiTracker(context, null, includeSaved, includeScans); + tracker.forceUpdate(); + tracker.copyAndNotifyListeners(false /*notifyListeners*/); + return tracker.getAccessPoints(); + } + @VisibleForTesting final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -969,6 +986,11 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } } + void forceScan() { + removeMessages(MSG_SCAN); + sendEmptyMessage(MSG_SCAN); + } + void pause() { mRetry = 0; removeMessages(MSG_SCAN); diff --git a/com/android/setupwizardlib/test/util/DrawingTestActivity.java b/com/android/setupwizardlib/test/util/DrawingTestActivity.java index 154339a7..3d11e128 100644 --- a/com/android/setupwizardlib/test/util/DrawingTestActivity.java +++ b/com/android/setupwizardlib/test/util/DrawingTestActivity.java @@ -16,15 +16,14 @@ package com.android.setupwizardlib.test.util; -import android.support.v7.app.AppCompatActivity; +import android.app.Activity; /** * Activity to test view and drawable drawing behaviors. This is used to make sure that the drawing - * behavior tested is the same as it would when inflated as part of an {@link AppCompatActivity}, - * including custom layout inflaters and theme values that the support library injects to the - * activity. + * behavior tested is the same as it would when inflated as part of an activity, including any + * injected layout inflater factories and custom themes etc. * * @see DrawingTestHelper */ -public class DrawingTestActivity extends AppCompatActivity { +public class DrawingTestActivity extends Activity { } diff --git a/com/android/setupwizardlib/util/WizardManagerHelper.java b/com/android/setupwizardlib/util/WizardManagerHelper.java index 896c0137..a93694c1 100644 --- a/com/android/setupwizardlib/util/WizardManagerHelper.java +++ b/com/android/setupwizardlib/util/WizardManagerHelper.java @@ -45,8 +45,6 @@ public class WizardManagerHelper { static final String EXTRA_IS_FIRST_RUN = "firstRun"; @VisibleForTesting static final String EXTRA_IS_DEFERRED_SETUP = "deferredSetup"; - @VisibleForTesting - static final String EXTRA_IS_PRE_DEFERRED_SETUP = "preDeferredSetup"; public static final String EXTRA_THEME = "theme"; public static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode"; @@ -215,18 +213,6 @@ public class WizardManagerHelper { } /** - * Checks whether an intent is running in "pre-deferred" setup wizard flow. - * - * @param originalIntent The original intent that was used to start the step, usually via - * {@link android.app.Activity#getIntent()}. - * @return true if the intent passed in was running in "pre-deferred" setup wizard. - */ - public static boolean isPreDeferredSetupWizard(Intent originalIntent) { - return originalIntent != null - && originalIntent.getBooleanExtra(EXTRA_IS_PRE_DEFERRED_SETUP, false); - } - - /** * Checks the intent whether the extra indicates that the light theme should be used or not. If * the theme is not specified in the intent, or the theme specified is unknown, the value def * will be returned. diff --git a/com/android/setupwizardlib/util/WizardManagerHelperTest.java b/com/android/setupwizardlib/util/WizardManagerHelperTest.java index 6477b518..4c460c8c 100644 --- a/com/android/setupwizardlib/util/WizardManagerHelperTest.java +++ b/com/android/setupwizardlib/util/WizardManagerHelperTest.java @@ -88,14 +88,6 @@ public class WizardManagerHelperTest { } @Test - public void testIsPreDeferredSetupTrue() { - final Intent intent = new Intent(); - intent.putExtra("preDeferredSetup", true); - assertTrue("Is pre-deferred setup wizard should be true", - WizardManagerHelper.isPreDeferredSetupWizard(intent)); - } - - @Test public void testIsSetupWizardFalse() { final Intent intent = new Intent(); intent.putExtra("firstRun", false); diff --git a/com/android/setupwizardlib/view/NavigationBarButton.java b/com/android/setupwizardlib/view/NavigationBarButton.java index 5172c476..45d3737c 100644 --- a/com/android/setupwizardlib/view/NavigationBarButton.java +++ b/com/android/setupwizardlib/view/NavigationBarButton.java @@ -17,143 +17,16 @@ package com.android.setupwizardlib.view; import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.os.Build; -import android.support.annotation.NonNull; import android.util.AttributeSet; import android.widget.Button; -/** - * Button for navigation bar, which includes tinting of its compound drawables to be used for dark - * and light themes. - */ public class NavigationBarButton extends Button { public NavigationBarButton(Context context) { super(context); - init(); } public NavigationBarButton(Context context, AttributeSet attrs) { super(context, attrs); - init(); - } - - private void init() { - // Unfortunately, drawableStart and drawableEnd set through XML does not call the setter, - // so manually getting it and wrapping it in the compat drawable. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - Drawable[] drawables = getCompoundDrawablesRelative(); - for (int i = 0; i < drawables.length; i++) { - if (drawables[i] != null) { - drawables[i] = TintedDrawable.wrap(drawables[i]); - } - } - setCompoundDrawablesRelativeWithIntrinsicBounds(drawables[0], drawables[1], - drawables[2], drawables[3]); - } - } - - @Override - public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) { - if (left != null) left = TintedDrawable.wrap(left); - if (top != null) top = TintedDrawable.wrap(top); - if (right != null) right = TintedDrawable.wrap(right); - if (bottom != null) bottom = TintedDrawable.wrap(bottom); - super.setCompoundDrawables(left, top, right, bottom); - tintDrawables(); - } - - @Override - public void setCompoundDrawablesRelative(Drawable start, Drawable top, Drawable end, - Drawable bottom) { - if (start != null) start = TintedDrawable.wrap(start); - if (top != null) top = TintedDrawable.wrap(top); - if (end != null) end = TintedDrawable.wrap(end); - if (bottom != null) bottom = TintedDrawable.wrap(bottom); - super.setCompoundDrawablesRelative(start, top, end, bottom); - tintDrawables(); - } - - @Override - public void setTextColor(ColorStateList colors) { - super.setTextColor(colors); - tintDrawables(); - } - - private void tintDrawables() { - final ColorStateList textColors = getTextColors(); - if (textColors != null) { - for (Drawable drawable : getAllCompoundDrawables()) { - if (drawable instanceof TintedDrawable) { - ((TintedDrawable) drawable).setTintListCompat(textColors); - } - } - invalidate(); - } - } - - private Drawable[] getAllCompoundDrawables() { - Drawable[] drawables = new Drawable[6]; - Drawable[] compoundDrawables = getCompoundDrawables(); - drawables[0] = compoundDrawables[0]; // left - drawables[1] = compoundDrawables[1]; // top - drawables[2] = compoundDrawables[2]; // right - drawables[3] = compoundDrawables[3]; // bottom - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - Drawable[] compoundDrawablesRelative = getCompoundDrawablesRelative(); - drawables[4] = compoundDrawablesRelative[0]; // start - drawables[5] = compoundDrawablesRelative[2]; // end - } - return drawables; - } - - // TODO: Remove this class and use DrawableCompat.wrap() once we can use support library 22.1.0 - // or above - private static class TintedDrawable extends LayerDrawable { - - public static TintedDrawable wrap(Drawable drawable) { - if (drawable instanceof TintedDrawable) { - return (TintedDrawable) drawable; - } - return new TintedDrawable(drawable.mutate()); - } - - private ColorStateList mTintList = null; - - TintedDrawable(Drawable wrapped) { - super(new Drawable[] { wrapped }); - } - - @Override - public boolean isStateful() { - return true; - } - - @Override - public boolean setState(@NonNull int[] stateSet) { - boolean needsInvalidate = super.setState(stateSet); - boolean needsInvalidateForState = updateState(); - return needsInvalidate || needsInvalidateForState; - } - - public void setTintListCompat(ColorStateList colors) { - mTintList = colors; - if (updateState()) { - invalidateSelf(); - } - } - - private boolean updateState() { - if (mTintList != null) { - final int color = mTintList.getColorForState(getState(), 0); - setColorFilter(color, PorterDuff.Mode.SRC_IN); - return true; // Needs invalidate - } - return false; - } } } diff --git a/com/android/setupwizardlib/view/RichTextView.java b/com/android/setupwizardlib/view/RichTextView.java index e6bc9da0..5a78561f 100644 --- a/com/android/setupwizardlib/view/RichTextView.java +++ b/com/android/setupwizardlib/view/RichTextView.java @@ -17,11 +17,6 @@ package com.android.setupwizardlib.view; import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import android.support.v4.view.ViewCompat; -import android.support.v7.widget.AppCompatTextView; import android.text.Annotation; import android.text.SpannableString; import android.text.Spanned; @@ -30,18 +25,22 @@ import android.text.style.ClickableSpan; import android.text.style.TextAppearanceSpan; import android.util.AttributeSet; import android.util.Log; -import android.view.MotionEvent; +import android.widget.TextView; import com.android.setupwizardlib.span.LinkSpan; import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener; import com.android.setupwizardlib.span.SpanHelper; -import com.android.setupwizardlib.util.LinkAccessibilityHelper; /** * An extension of TextView that automatically replaces the annotation tags as specified in * {@link SpanHelper#replaceSpan(android.text.Spannable, Object, Object)} + * + * <p>Note: The accessibility interaction for ClickableSpans (and therefore LinkSpans) are built + * into platform in O, although the interaction paradigm is different. (See b/17726921). In this + * platform version, the links are exposed in the Local Context Menu of TalkBack instead of + * accessible directly through swiping. */ -public class RichTextView extends AppCompatTextView implements OnLinkClickListener { +public class RichTextView extends TextView implements OnLinkClickListener { /* static section */ @@ -89,22 +88,14 @@ public class RichTextView extends AppCompatTextView implements OnLinkClickListen /* non-static section */ - private LinkAccessibilityHelper mAccessibilityHelper; private OnLinkClickListener mOnLinkClickListener; public RichTextView(Context context) { super(context); - init(); } public RichTextView(Context context, AttributeSet attrs) { super(context, attrs); - init(); - } - - private void init() { - mAccessibilityHelper = new LinkAccessibilityHelper(this); - ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper); } @Override @@ -141,32 +132,6 @@ public class RichTextView extends AppCompatTextView implements OnLinkClickListen return false; } - @Override - protected boolean dispatchHoverEvent(MotionEvent event) { - if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) { - return true; - } - return super.dispatchHoverEvent(event); - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { - // b/26765507 causes drawableStart and drawableEnd to not get the right state on M. As a - // workaround, set the state on those drawables directly. - final int[] state = getDrawableState(); - for (Drawable drawable : getCompoundDrawablesRelative()) { - if (drawable != null) { - if (drawable.setState(state)) { - invalidateDrawable(drawable); - } - } - } - } - } - public void setOnLinkClickListener(OnLinkClickListener listener) { mOnLinkClickListener = listener; } diff --git a/com/android/systemui/Dependency.java b/com/android/systemui/Dependency.java index d8a47c5e..2937a250 100644 --- a/com/android/systemui/Dependency.java +++ b/com/android/systemui/Dependency.java @@ -22,8 +22,6 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Process; import android.util.ArrayMap; -import android.view.IWindowManager; -import android.view.WindowManagerGlobal; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.NightDisplayController; @@ -306,8 +304,6 @@ public class Dependency extends SystemUI { mProviders.put(LightBarController.class, () -> new LightBarController(mContext)); - mProviders.put(IWindowManager.class, () -> WindowManagerGlobal.getWindowManagerService()); - // Put all dependencies above here so the factory can override them if it wants. SystemUIFactory.getInstance().injectDependencies(mProviders, mContext); } diff --git a/com/android/systemui/ImageWallpaper.java b/com/android/systemui/ImageWallpaper.java index 593bb508..907a79e7 100644 --- a/com/android/systemui/ImageWallpaper.java +++ b/com/android/systemui/ImageWallpaper.java @@ -16,6 +16,11 @@ package com.android.systemui; +import static android.opengl.GLES20.*; + +import static javax.microedition.khronos.egl.EGL10.*; + +import android.app.ActivityManager; import android.app.WallpaperManager; import android.content.ComponentCallbacks2; import android.graphics.Bitmap; @@ -23,18 +28,31 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region.Op; +import android.opengl.GLUtils; import android.os.AsyncTask; +import android.os.SystemProperties; import android.os.Trace; +import android.renderscript.Matrix4f; import android.service.wallpaper.WallpaperService; import android.util.Log; import android.view.Display; import android.view.DisplayInfo; +import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.WindowManager; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; /** * Default built-in wallpaper that simply shows a static image. @@ -46,13 +64,24 @@ public class ImageWallpaper extends WallpaperService { private static final boolean DEBUG = false; private static final String PROPERTY_KERNEL_QEMU = "ro.kernel.qemu"; - private WallpaperManager mWallpaperManager; - private DrawableEngine mEngine; + static final boolean FIXED_SIZED_SURFACE = true; + static final boolean USE_OPENGL = true; + + WallpaperManager mWallpaperManager; + + DrawableEngine mEngine; + + boolean mIsHwAccelerated; @Override public void onCreate() { super.onCreate(); - mWallpaperManager = getSystemService(WallpaperManager.class); + mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE); + + //noinspection PointlessBooleanExpression,ConstantConditions + if (FIXED_SIZED_SURFACE && USE_OPENGL) { + mIsHwAccelerated = ActivityManager.isHighEndGfx(); + } } @Override @@ -69,12 +98,15 @@ public class ImageWallpaper extends WallpaperService { } class DrawableEngine extends Engine { + static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + static final int EGL_OPENGL_ES2_BIT = 4; + Bitmap mBackground; int mBackgroundWidth = -1, mBackgroundHeight = -1; int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; int mLastRotation = -1; - float mXOffset = 0f; - float mYOffset = 0f; + float mXOffset = 0.5f; + float mYOffset = 0.5f; float mScale = 1f; private Display mDefaultDisplay; @@ -85,6 +117,34 @@ public class ImageWallpaper extends WallpaperService { int mLastXTranslation; int mLastYTranslation; + private EGL10 mEgl; + private EGLDisplay mEglDisplay; + private EGLConfig mEglConfig; + private EGLContext mEglContext; + private EGLSurface mEglSurface; + + private static final String sSimpleVS = + "attribute vec4 position;\n" + + "attribute vec2 texCoords;\n" + + "varying vec2 outTexCoords;\n" + + "uniform mat4 projection;\n" + + "\nvoid main(void) {\n" + + " outTexCoords = texCoords;\n" + + " gl_Position = projection * position;\n" + + "}\n\n"; + private static final String sSimpleFS = + "precision mediump float;\n\n" + + "varying vec2 outTexCoords;\n" + + "uniform sampler2D texture;\n" + + "\nvoid main(void) {\n" + + " gl_FragColor = texture2D(texture, outTexCoords);\n" + + "}\n\n"; + + private static final int FLOAT_SIZE_BYTES = 4; + private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; + private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; + private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; + private int mRotationAtLastSurfaceSizeUpdate = -1; private int mDisplayWidthAtLastSurfaceSizeUpdate = -1; private int mDisplayHeightAtLastSurfaceSizeUpdate = -1; @@ -94,14 +154,13 @@ public class ImageWallpaper extends WallpaperService { private AsyncTask<Void, Void, Bitmap> mLoader; private boolean mNeedsDrawAfterLoadingWallpaper; private boolean mSurfaceValid; - private boolean mSurfaceRedrawNeeded; - DrawableEngine() { + public DrawableEngine() { super(); setFixedSizeAllowed(true); } - void trimMemory(int level) { + public void trimMemory(int level) { if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW && level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL && mBackground != null) { @@ -120,7 +179,6 @@ public class ImageWallpaper extends WallpaperService { super.onCreate(surfaceHolder); - //noinspection ConstantConditions mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay(); setOffsetNotificationsEnabled(false); @@ -141,7 +199,7 @@ public class ImageWallpaper extends WallpaperService { // Load background image dimensions, if we haven't saved them yet if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) { // Need to load the image to get dimensions - loadWallpaper(forDraw); + loadWallpaper(forDraw, false /* needsReset */); if (DEBUG) { Log.d(TAG, "Reloading, redoing updateSurfaceSize later."); } @@ -152,13 +210,16 @@ public class ImageWallpaper extends WallpaperService { int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth); int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight); - // Used a fixed size surface, because we are special. We can do - // this because we know the current design of window animations doesn't - // cause this to break. - surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight); - mLastRequestedWidth = surfaceWidth; - mLastRequestedHeight = surfaceHeight; - + if (FIXED_SIZED_SURFACE) { + // Used a fixed size surface, because we are special. We can do + // this because we know the current design of window animations doesn't + // cause this to break. + surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight); + mLastRequestedWidth = surfaceWidth; + mLastRequestedHeight = surfaceHeight; + } else { + surfaceHolder.setSizeFromLayout(); + } return hasWallpaper; } @@ -239,13 +300,6 @@ public class ImageWallpaper extends WallpaperService { Log.d(TAG, "onSurfaceRedrawNeeded"); } super.onSurfaceRedrawNeeded(holder); - // At the end of this method we should have drawn into the surface. - // This means that the bitmap should be loaded synchronously if - // it was already unloaded. - if (mBackground == null) { - updateBitmap(mWallpaperManager.getBitmap(true /* hardware */)); - } - mSurfaceRedrawNeeded = true; drawFrame(); } @@ -282,8 +336,7 @@ public class ImageWallpaper extends WallpaperService { boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth || dh != mLastSurfaceHeight; - boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation - || mSurfaceRedrawNeeded; + boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation; if (!redrawNeeded && !mOffsetsChanged) { if (DEBUG) { Log.d(TAG, "Suppressed drawFrame since redraw is not needed " @@ -292,24 +345,40 @@ public class ImageWallpaper extends WallpaperService { return; } mLastRotation = newRotation; - mSurfaceRedrawNeeded = false; // Load bitmap if it is not yet loaded if (mBackground == null) { - loadWallpaper(true); + if (DEBUG) { + Log.d(TAG, "Reloading bitmap: mBackground, bgw, bgh, dw, dh = " + + mBackground + ", " + + ((mBackground == null) ? 0 : mBackground.getWidth()) + ", " + + ((mBackground == null) ? 0 : mBackground.getHeight()) + ", " + + dw + ", " + dh); + } + loadWallpaper(true /* needDraw */, true /* needReset */); if (DEBUG) { Log.d(TAG, "Reloading, resuming draw later"); } return; } - // Left align the scaled image + // Center the scaled image mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(), dh / (float) mBackground.getHeight())); - final int availw = (int) (mBackground.getWidth() * mScale) - dw; - final int availh = (int) (mBackground.getHeight() * mScale) - dh; - int xPixels = (int) (availw * mXOffset); - int yPixels = (int) (availh * mYOffset); + final int availw = dw - (int) (mBackground.getWidth() * mScale); + final int availh = dh - (int) (mBackground.getHeight() * mScale); + int xPixels = availw / 2; + int yPixels = availh / 2; + + // Adjust the image for xOffset/yOffset values. If window manager is handling offsets, + // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels + // will remain unchanged + final int availwUnscaled = dw - mBackground.getWidth(); + final int availhUnscaled = dh - mBackground.getHeight(); + if (availwUnscaled < 0) + xPixels += (int) (availwUnscaled * (mXOffset - .5f) + .5f); + if (availhUnscaled < 0) + yPixels += (int) (availhUnscaled * (mYOffset - .5f) + .5f); mOffsetsChanged = false; if (surfaceDimensionsChanged) { @@ -330,7 +399,21 @@ public class ImageWallpaper extends WallpaperService { Log.d(TAG, "Redrawing wallpaper"); } - drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels); + if (mIsHwAccelerated) { + if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) { + drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels); + } + } else { + drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels); + if (FIXED_SIZED_SURFACE) { + // If the surface is fixed-size, we should only need to + // draw it once and then we'll let the window manager + // position it appropriately. As such, we no longer needed + // the loaded bitmap. Yay! + // hw-accelerated renderer retains bitmap for faster rotation + unloadWallpaper(false /* forgetSize */); + } + } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } @@ -345,20 +428,28 @@ public class ImageWallpaper extends WallpaperService { * * If {@param needsReset} is set also clears the cache in WallpaperManager first. */ - private void loadWallpaper(boolean needsDraw) { + private void loadWallpaper(boolean needsDraw, boolean needsReset) { mNeedsDrawAfterLoadingWallpaper |= needsDraw; if (mLoader != null) { - if (DEBUG) { - Log.d(TAG, "Skipping loadWallpaper, already in flight "); + if (needsReset) { + mLoader.cancel(false /* interrupt */); + mLoader = null; + } else { + if (DEBUG) { + Log.d(TAG, "Skipping loadWallpaper, already in flight "); + } + return; } - return; } mLoader = new AsyncTask<Void, Void, Bitmap>() { @Override protected Bitmap doInBackground(Void... params) { Throwable exception; try { - return mWallpaperManager.getBitmap(true /* hardware */); + if (needsReset) { + mWallpaperManager.forgetLoadedWallpaper(); + } + return mWallpaperManager.getBitmap(); } catch (RuntimeException | OutOfMemoryError e) { exception = e; } @@ -367,33 +458,48 @@ public class ImageWallpaper extends WallpaperService { return null; } - // Note that if we do fail at this, and the default wallpaper can't - // be loaded, we will go into a cycle. Don't do a build where the - // default wallpaper can't be loaded. - Log.w(TAG, "Unable to load wallpaper!", exception); - try { - mWallpaperManager.clear(); - } catch (IOException ex) { - // now we're really screwed. - Log.w(TAG, "Unable reset to default wallpaper!", ex); - } - - if (isCancelled()) { - return null; - } - - try { - return mWallpaperManager.getBitmap(true /* hardware */); - } catch (RuntimeException | OutOfMemoryError e) { - Log.w(TAG, "Unable to load default wallpaper!", e); + if (exception != null) { + // Note that if we do fail at this, and the default wallpaper can't + // be loaded, we will go into a cycle. Don't do a build where the + // default wallpaper can't be loaded. + Log.w(TAG, "Unable to load wallpaper!", exception); + try { + mWallpaperManager.clear(); + } catch (IOException ex) { + // now we're really screwed. + Log.w(TAG, "Unable reset to default wallpaper!", ex); + } + + if (isCancelled()) { + return null; + } + + try { + return mWallpaperManager.getBitmap(); + } catch (RuntimeException | OutOfMemoryError e) { + Log.w(TAG, "Unable to load default wallpaper!", e); + } } return null; } @Override protected void onPostExecute(Bitmap b) { - updateBitmap(b); + mBackground = null; + mBackgroundWidth = -1; + mBackgroundHeight = -1; + + if (b != null) { + mBackground = b; + mBackgroundWidth = mBackground.getWidth(); + mBackgroundHeight = mBackground.getHeight(); + } + if (DEBUG) { + Log.d(TAG, "Wallpaper loaded: " + mBackground); + } + updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(), + false /* forDraw */); if (mNeedsDrawAfterLoadingWallpaper) { drawFrame(); } @@ -404,24 +510,6 @@ public class ImageWallpaper extends WallpaperService { }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - private void updateBitmap(Bitmap bitmap) { - mBackground = null; - mBackgroundWidth = -1; - mBackgroundHeight = -1; - - if (bitmap != null) { - mBackground = bitmap; - mBackgroundWidth = mBackground.getWidth(); - mBackgroundHeight = mBackground.getHeight(); - } - - if (DEBUG) { - Log.d(TAG, "Wallpaper loaded: " + mBackground); - } - updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(), - false /* forDraw */); - } - private void unloadWallpaper(boolean forgetSize) { if (mLoader != null) { mLoader.cancel(false); @@ -476,7 +564,7 @@ public class ImageWallpaper extends WallpaperService { } private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top) { - Canvas c = sh.lockHardwareCanvas(); + Canvas c = sh.lockCanvas(); if (c != null) { try { if (DEBUG) { @@ -502,5 +590,278 @@ public class ImageWallpaper extends WallpaperService { } } } + + private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) { + if (!initGL(sh)) return false; + + final float right = left + mBackground.getWidth() * mScale; + final float bottom = top + mBackground.getHeight() * mScale; + + final Rect frame = sh.getSurfaceFrame(); + final Matrix4f ortho = new Matrix4f(); + ortho.loadOrtho(0.0f, frame.width(), frame.height(), 0.0f, -1.0f, 1.0f); + + final FloatBuffer triangleVertices = createMesh(left, top, right, bottom); + + final int texture = loadTexture(mBackground); + final int program = buildProgram(sSimpleVS, sSimpleFS); + + final int attribPosition = glGetAttribLocation(program, "position"); + final int attribTexCoords = glGetAttribLocation(program, "texCoords"); + final int uniformTexture = glGetUniformLocation(program, "texture"); + final int uniformProjection = glGetUniformLocation(program, "projection"); + + checkGlError(); + + glViewport(0, 0, frame.width(), frame.height()); + glBindTexture(GL_TEXTURE_2D, texture); + + glUseProgram(program); + glEnableVertexAttribArray(attribPosition); + glEnableVertexAttribArray(attribTexCoords); + glUniform1i(uniformTexture, 0); + glUniformMatrix4fv(uniformProjection, 1, false, ortho.getArray(), 0); + + checkGlError(); + + if (w > 0 || h > 0) { + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + } + + // drawQuad + triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); + glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false, + TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices); + + triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); + glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false, + TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + boolean status = mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); + checkEglError(); + + finishGL(texture, program); + + return status; + } + + private FloatBuffer createMesh(int left, int top, float right, float bottom) { + final float[] verticesData = { + // X, Y, Z, U, V + left, bottom, 0.0f, 0.0f, 1.0f, + right, bottom, 0.0f, 1.0f, 1.0f, + left, top, 0.0f, 0.0f, 0.0f, + right, top, 0.0f, 1.0f, 0.0f, + }; + + final int bytes = verticesData.length * FLOAT_SIZE_BYTES; + final FloatBuffer triangleVertices = ByteBuffer.allocateDirect(bytes).order( + ByteOrder.nativeOrder()).asFloatBuffer(); + triangleVertices.put(verticesData).position(0); + return triangleVertices; + } + + private int loadTexture(Bitmap bitmap) { + int[] textures = new int[1]; + + glActiveTexture(GL_TEXTURE0); + glGenTextures(1, textures, 0); + checkGlError(); + + int texture = textures[0]; + glBindTexture(GL_TEXTURE_2D, texture); + checkGlError(); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0); + checkGlError(); + + return texture; + } + + private int buildProgram(String vertex, String fragment) { + int vertexShader = buildShader(vertex, GL_VERTEX_SHADER); + if (vertexShader == 0) return 0; + + int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER); + if (fragmentShader == 0) return 0; + + int program = glCreateProgram(); + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + glLinkProgram(program); + checkGlError(); + + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + + int[] status = new int[1]; + glGetProgramiv(program, GL_LINK_STATUS, status, 0); + if (status[0] != GL_TRUE) { + String error = glGetProgramInfoLog(program); + Log.d(GL_LOG_TAG, "Error while linking program:\n" + error); + glDeleteProgram(program); + return 0; + } + + return program; + } + + private int buildShader(String source, int type) { + int shader = glCreateShader(type); + + glShaderSource(shader, source); + checkGlError(); + + glCompileShader(shader); + checkGlError(); + + int[] status = new int[1]; + glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0); + if (status[0] != GL_TRUE) { + String error = glGetShaderInfoLog(shader); + Log.d(GL_LOG_TAG, "Error while compiling shader:\n" + error); + glDeleteShader(shader); + return 0; + } + + return shader; + } + + private void checkEglError() { + int error = mEgl.eglGetError(); + if (error != EGL_SUCCESS) { + Log.w(GL_LOG_TAG, "EGL error = " + GLUtils.getEGLErrorString(error)); + } + } + + private void checkGlError() { + int error = glGetError(); + if (error != GL_NO_ERROR) { + Log.w(GL_LOG_TAG, "GL error = 0x" + Integer.toHexString(error), new Throwable()); + } + } + + private void finishGL(int texture, int program) { + int[] textures = new int[1]; + textures[0] = texture; + glDeleteTextures(1, textures, 0); + glDeleteProgram(program); + mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + mEgl.eglDestroySurface(mEglDisplay, mEglSurface); + mEgl.eglDestroyContext(mEglDisplay, mEglContext); + mEgl.eglTerminate(mEglDisplay); + } + + private boolean initGL(SurfaceHolder surfaceHolder) { + mEgl = (EGL10) EGLContext.getEGL(); + + mEglDisplay = mEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (mEglDisplay == EGL_NO_DISPLAY) { + throw new RuntimeException("eglGetDisplay failed " + + GLUtils.getEGLErrorString(mEgl.eglGetError())); + } + + int[] version = new int[2]; + if (!mEgl.eglInitialize(mEglDisplay, version)) { + throw new RuntimeException("eglInitialize failed " + + GLUtils.getEGLErrorString(mEgl.eglGetError())); + } + + mEglConfig = chooseEglConfig(); + if (mEglConfig == null) { + throw new RuntimeException("eglConfig not initialized"); + } + + mEglContext = createContext(mEgl, mEglDisplay, mEglConfig); + if (mEglContext == EGL_NO_CONTEXT) { + throw new RuntimeException("createContext failed " + + GLUtils.getEGLErrorString(mEgl.eglGetError())); + } + + int attribs[] = { + EGL_WIDTH, 1, + EGL_HEIGHT, 1, + EGL_NONE + }; + EGLSurface tmpSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs); + mEgl.eglMakeCurrent(mEglDisplay, tmpSurface, tmpSurface, mEglContext); + + int[] maxSize = new int[1]; + Rect frame = surfaceHolder.getSurfaceFrame(); + glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0); + + mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + mEgl.eglDestroySurface(mEglDisplay, tmpSurface); + + if(frame.width() > maxSize[0] || frame.height() > maxSize[0]) { + mEgl.eglDestroyContext(mEglDisplay, mEglContext); + mEgl.eglTerminate(mEglDisplay); + Log.e(GL_LOG_TAG, "requested texture size " + + frame.width() + "x" + frame.height() + " exceeds the support maximum of " + + maxSize[0] + "x" + maxSize[0]); + return false; + } + + mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null); + if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) { + int error = mEgl.eglGetError(); + if (error == EGL_BAD_NATIVE_WINDOW || error == EGL_BAD_ALLOC) { + Log.e(GL_LOG_TAG, "createWindowSurface returned " + + GLUtils.getEGLErrorString(error) + "."); + return false; + } + throw new RuntimeException("createWindowSurface failed " + + GLUtils.getEGLErrorString(error)); + } + + if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + throw new RuntimeException("eglMakeCurrent failed " + + GLUtils.getEGLErrorString(mEgl.eglGetError())); + } + + return true; + } + + + EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { + int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; + return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attrib_list); + } + + private EGLConfig chooseEglConfig() { + int[] configsCount = new int[1]; + EGLConfig[] configs = new EGLConfig[1]; + int[] configSpec = getConfig(); + if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) { + throw new IllegalArgumentException("eglChooseConfig failed " + + GLUtils.getEGLErrorString(mEgl.eglGetError())); + } else if (configsCount[0] > 0) { + return configs[0]; + } + return null; + } + + private int[] getConfig() { + return new int[] { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 0, + EGL_DEPTH_SIZE, 0, + EGL_STENCIL_SIZE, 0, + EGL_CONFIG_CAVEAT, EGL_NONE, + EGL_NONE + }; + } } } diff --git a/com/android/systemui/SwipeHelper.java b/com/android/systemui/SwipeHelper.java index 592dda07..4b377153 100644 --- a/com/android/systemui/SwipeHelper.java +++ b/com/android/systemui/SwipeHelper.java @@ -83,6 +83,7 @@ public class SwipeHelper implements Gefingerpoken { private boolean mMenuRowIntercepting; private boolean mLongPressSent; + private LongPressListener mLongPressListener; private Runnable mWatchLongPress; private final long mLongPressTimeout; @@ -114,6 +115,10 @@ public class SwipeHelper implements Gefingerpoken { mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f); } + public void setLongPressListener(LongPressListener listener) { + mLongPressListener = listener; + } + public void setDensityScale(float densityScale) { mDensityScale = densityScale; } @@ -252,7 +257,7 @@ public class SwipeHelper implements Gefingerpoken { } } - public void cancelLongPress() { + public void removeLongPressCallback() { if (mWatchLongPress != null) { mHandler.removeCallbacks(mWatchLongPress); mWatchLongPress = null; @@ -283,27 +288,33 @@ public class SwipeHelper implements Gefingerpoken { mInitialTouchPos = getPos(ev); mPerpendicularInitialTouchPos = getPerpendicularPos(ev); mTranslation = getTranslation(mCurrView); - if (mWatchLongPress == null) { - mWatchLongPress = new Runnable() { - @Override - public void run() { - if (mCurrView != null && !mLongPressSent) { - mLongPressSent = true; - mCurrView.getLocationOnScreen(mTmpPos); - final int x = (int) ev.getRawX() - mTmpPos[0]; - final int y = (int) ev.getRawY() - mTmpPos[1]; - if (mCurrView instanceof ExpandableNotificationRow) { + if (mLongPressListener != null) { + if (mWatchLongPress == null) { + mWatchLongPress = new Runnable() { + @Override + public void run() { + if (mCurrView != null && !mLongPressSent) { + mLongPressSent = true; mCurrView.sendAccessibilityEvent( AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); - ExpandableNotificationRow currRow = - (ExpandableNotificationRow) mCurrView; - currRow.doLongClickCallback(x, y); + mCurrView.getLocationOnScreen(mTmpPos); + final int x = (int) ev.getRawX() - mTmpPos[0]; + final int y = (int) ev.getRawY() - mTmpPos[1]; + MenuItem menuItem = null; + if (mCurrView instanceof ExpandableNotificationRow) { + menuItem = ((ExpandableNotificationRow) mCurrView) + .getProvider().getLongpressMenuItem(mContext); + } + if (menuItem != null) { + mLongPressListener.onLongPress(mCurrView, x, y, + menuItem); + } } } - } - }; + }; + } + mHandler.postDelayed(mWatchLongPress, mLongPressTimeout); } - mHandler.postDelayed(mWatchLongPress, mLongPressTimeout); } break; @@ -320,7 +331,7 @@ public class SwipeHelper implements Gefingerpoken { mDragging = true; mInitialTouchPos = getPos(ev); mTranslation = getTranslation(mCurrView); - cancelLongPress(); + removeLongPressCallback(); } } break; @@ -332,7 +343,7 @@ public class SwipeHelper implements Gefingerpoken { mCurrView = null; mLongPressSent = false; mMenuRowIntercepting = false; - cancelLongPress(); + removeLongPressCallback(); if (captured) return true; break; } @@ -575,7 +586,7 @@ public class SwipeHelper implements Gefingerpoken { // We are not doing anything, make sure the long press callback // is not still ticking like a bomb waiting to go off. - cancelLongPress(); + removeLongPressCallback(); return false; } } @@ -723,4 +734,15 @@ public class SwipeHelper implements Gefingerpoken { */ float getFalsingThresholdFactor(); } + + /** + * Equivalent to View.OnLongClickListener with coordinates + */ + public interface LongPressListener { + /** + * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates + * @return whether the longpress was handled + */ + boolean onLongPress(View v, int x, int y, MenuItem item); + } } diff --git a/com/android/systemui/doze/DozeUi.java b/com/android/systemui/doze/DozeUi.java index 851b78cf..dc626fb4 100644 --- a/com/android/systemui/doze/DozeUi.java +++ b/com/android/systemui/doze/DozeUi.java @@ -84,6 +84,9 @@ public class DozeUi implements DozeMachine.Part { case DOZE_REQUEST_PULSE: pulseWhileDozing(mMachine.getPulseReason()); break; + case DOZE_PULSE_DONE: + mHost.abortPulsing(); + break; case INITIALIZED: mHost.startDozing(); break; diff --git a/com/android/systemui/globalactions/GlobalActionsComponent.java b/com/android/systemui/globalactions/GlobalActionsComponent.java index f06cda0f..09a08f09 100644 --- a/com/android/systemui/globalactions/GlobalActionsComponent.java +++ b/com/android/systemui/globalactions/GlobalActionsComponent.java @@ -31,7 +31,6 @@ import android.os.ServiceManager; public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager { - private GlobalActions mPlugin; private Extension<GlobalActions> mExtension; private IStatusBarService mBarService; @@ -42,19 +41,10 @@ public class GlobalActionsComponent extends SystemUI implements Callbacks, Globa mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class) .withPlugin(GlobalActions.class) .withDefault(() -> new GlobalActionsImpl(mContext)) - .withCallback(this::onExtensionCallback) .build(); - mPlugin = mExtension.get(); SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this); } - private void onExtensionCallback(GlobalActions newPlugin) { - if (mPlugin != null) { - mPlugin.destroy(); - } - mPlugin = newPlugin; - } - @Override public void handleShowShutdownUi(boolean isReboot, String reason) { mExtension.get().showShutdownUi(isReboot, reason); diff --git a/com/android/systemui/globalactions/GlobalActionsDialog.java b/com/android/systemui/globalactions/GlobalActionsDialog.java index d82f9cd4..189badfc 100644 --- a/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -16,6 +16,27 @@ package com.android.systemui.globalactions; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; +import com.android.internal.R; +import com.android.internal.colorextraction.ColorExtractor; +import com.android.internal.colorextraction.ColorExtractor.GradientColors; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.EmergencyAffordanceManager; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.telephony.TelephonyProperties; +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.Dependency; +import com.android.systemui.HardwareUiLayout; +import com.android.systemui.Interpolators; +import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; +import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.volume.VolumeDialogMotion.LogAccelerateInterpolator; +import com.android.systemui.volume.VolumeDialogMotion.LogDecelerateInterpolator; + +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; import android.app.ActivityManager; import android.app.Dialog; import android.app.WallpaperManager; @@ -48,6 +69,7 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import android.util.MathUtils; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; @@ -64,23 +86,7 @@ import android.widget.ImageView.ScaleType; import android.widget.LinearLayout; import android.widget.TextView; -import com.android.internal.R; -import com.android.internal.colorextraction.ColorExtractor; -import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.colorextraction.drawable.GradientDrawable; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.telephony.TelephonyProperties; -import com.android.internal.util.EmergencyAffordanceManager; -import com.android.internal.widget.LockPatternUtils; -import com.android.systemui.Dependency; -import com.android.systemui.HardwareUiLayout; -import com.android.systemui.Interpolators; -import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; -import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.volume.VolumeDialogMotion.LogAccelerateInterpolator; import java.util.ArrayList; import java.util.List; @@ -90,8 +96,7 @@ import java.util.List; * may show depending on whether the keyguard is showing, and whether the device * is provisioned. */ -class GlobalActionsDialog implements DialogInterface.OnDismissListener, - DialogInterface.OnClickListener { +class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { static public final String SYSTEM_DIALOG_REASON_KEY = "reason"; static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; @@ -190,14 +195,6 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } - /** - * Dismiss the global actions dialog, if it's currently shown - */ - public void dismissDialog() { - mHandler.removeMessages(MESSAGE_DISMISS); - mHandler.sendEmptyMessage(MESSAGE_DISMISS); - } - private void awakenIfNecessary() { if (mDreamManager != null) { try { diff --git a/com/android/systemui/globalactions/GlobalActionsImpl.java b/com/android/systemui/globalactions/GlobalActionsImpl.java index 35634374..2cf230c8 100644 --- a/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -14,12 +14,12 @@ package com.android.systemui.globalactions; -import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS; - import android.app.Dialog; import android.app.KeyguardManager; +import android.app.WallpaperColors; import android.app.WallpaperManager; import android.content.Context; +import android.graphics.Color; import android.graphics.Point; import android.view.ViewGroup; import android.view.Window; @@ -32,14 +32,12 @@ import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.colorextraction.drawable.GradientDrawable; import com.android.settingslib.Utils; import com.android.systemui.Dependency; -import com.android.systemui.SysUiServiceProvider; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.GlobalActions; -import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardMonitor; -public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks { +public class GlobalActionsImpl implements GlobalActions { private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f; @@ -47,23 +45,15 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks private final KeyguardMonitor mKeyguardMonitor; private final DeviceProvisionedController mDeviceProvisionedController; private GlobalActionsDialog mGlobalActions; - private boolean mDisabled; public GlobalActionsImpl(Context context) { mContext = context; mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); - SysUiServiceProvider.getComponent(context, CommandQueue.class).addCallbacks(this); - } - - @Override - public void destroy() { - SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallbacks(this); } @Override public void showGlobalActions(GlobalActionsManager manager) { - if (mDisabled) return; if (mGlobalActions == null) { mGlobalActions = new GlobalActionsDialog(mContext, manager); } @@ -117,14 +107,4 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks d.show(); } - - @Override - public void disable(int state1, int state2, boolean animate) { - final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0; - if (disabled == mDisabled) return; - mDisabled = disabled; - if (disabled && mGlobalActions != null) { - mGlobalActions.dismissDialog(); - } - } } diff --git a/com/android/systemui/keyguard/WorkLockActivityController.java b/com/android/systemui/keyguard/WorkLockActivityController.java index 4c3d5bad..f198229a 100644 --- a/com/android/systemui/keyguard/WorkLockActivityController.java +++ b/com/android/systemui/keyguard/WorkLockActivityController.java @@ -16,6 +16,7 @@ package com.android.systemui.keyguard; +import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.IActivityManager; @@ -28,8 +29,9 @@ import android.os.RemoteException; import android.os.UserHandle; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.TaskStackChangeListener; +import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; public class WorkLockActivityController { private final Context mContext; @@ -96,7 +98,7 @@ public class WorkLockActivityController { } } - private final TaskStackChangeListener mLockListener = new TaskStackChangeListener() { + private final TaskStackListener mLockListener = new TaskStackListener() { @Override public void onTaskProfileLocked(int taskId, int userId) { startWorkChallengeInTask(taskId, userId); diff --git a/com/android/systemui/pip/phone/InputConsumerController.java b/com/android/systemui/pip/phone/InputConsumerController.java index e6d6c558..abc56672 100644 --- a/com/android/systemui/pip/phone/InputConsumerController.java +++ b/com/android/systemui/pip/phone/InputConsumerController.java @@ -28,6 +28,8 @@ import android.view.InputEvent; import android.view.IWindowManager; import android.view.MotionEvent; +import com.android.systemui.recents.misc.Utilities; + import java.io.PrintWriter; /** diff --git a/com/android/systemui/pip/phone/PipManager.java b/com/android/systemui/pip/phone/PipManager.java index 7e87666a..f8996aae 100644 --- a/com/android/systemui/pip/phone/PipManager.java +++ b/com/android/systemui/pip/phone/PipManager.java @@ -41,7 +41,8 @@ import com.android.systemui.pip.BasePipManager; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.component.ExpandPipEvent; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.TaskStackChangeListener; +import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; +import com.android.systemui.statusbar.CommandQueue; import java.io.PrintWriter; @@ -69,7 +70,7 @@ public class PipManager implements BasePipManager { /** * Handler for system task stack changes. */ - TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { + TaskStackListener mTaskStackListener = new TaskStackListener() { @Override public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { mTouchHandler.onActivityPinned(); diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java index 312b9908..e0445c16 100644 --- a/com/android/systemui/pip/tv/PipManager.java +++ b/com/android/systemui/pip/tv/PipManager.java @@ -20,6 +20,7 @@ import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.StackInfo; import android.app.IActivityManager; +import android.app.RemoteAction; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -46,7 +47,7 @@ import android.view.WindowManagerGlobal; import com.android.systemui.R; import com.android.systemui.pip.BasePipManager; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.TaskStackChangeListener; +import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; import java.io.PrintWriter; import java.util.ArrayList; @@ -620,7 +621,7 @@ public class PipManager implements BasePipManager { return false; } - private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { + private TaskStackListener mTaskStackListener = new TaskStackListener() { @Override public void onTaskStackChanged() { if (DEBUG) Log.d(TAG, "onTaskStackChanged()"); diff --git a/com/android/systemui/plugins/GlobalActions.java b/com/android/systemui/plugins/GlobalActions.java index 95ff13b4..1f633da4 100644 --- a/com/android/systemui/plugins/GlobalActions.java +++ b/com/android/systemui/plugins/GlobalActions.java @@ -29,9 +29,6 @@ public interface GlobalActions extends Plugin { default void showShutdownUi(boolean isReboot, String reason) { } - default void destroy() { - } - @ProvidesInterface(version = GlobalActionsManager.VERSION) public interface GlobalActionsManager { int VERSION = 1; diff --git a/com/android/systemui/power/PowerUI.java b/com/android/systemui/power/PowerUI.java index c1a36239..f3782685 100644 --- a/com/android/systemui/power/PowerUI.java +++ b/com/android/systemui/power/PowerUI.java @@ -28,14 +28,8 @@ import android.database.ContentObserver; import android.os.BatteryManager; import android.os.Handler; import android.os.HardwarePropertiesManager; -import android.os.IBinder; -import android.os.IThermalEventListener; -import android.os.IThermalService; import android.os.PowerManager; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemClock; -import android.os.Temperature; import android.os.UserHandle; import android.provider.Settings; import android.text.format.DateUtils; @@ -81,7 +75,6 @@ public class PowerUI extends SystemUI { private float[] mRecentTemps = new float[MAX_RECENT_TEMPS]; private int mNumTemps; private long mNextLogTime; - private IThermalService mThermalService; // We create a method reference here so that we are guaranteed that we can remove a callback // by using the same instance (method references are not guaranteed to be the same object @@ -270,7 +263,7 @@ public class PowerUI extends SystemUI { resources.getInteger(R.integer.config_warningTemperature)); if (mThresholdTemp < 0f) { - // Get the shutdown temperature, adjust for warning tolerance. + // Get the throttling temperature. No need to check if we're not throttling. float[] throttlingTemps = mHardwarePropertiesManager.getDeviceTemperatures( HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN, HardwarePropertiesManager.TEMPERATURE_SHUTDOWN); @@ -283,25 +276,6 @@ public class PowerUI extends SystemUI { resources.getInteger(R.integer.config_warningTemperatureTolerance); } - if (mThermalService == null) { - // Enable push notifications of throttling from vendor thermal - // management subsystem via thermalservice, in addition to our - // usual polling, to react to temperature jumps more quickly. - IBinder b = ServiceManager.getService("thermalservice"); - - if (b != null) { - mThermalService = IThermalService.Stub.asInterface(b); - try { - mThermalService.registerThermalEventListener( - new ThermalEventListener()); - } catch (RemoteException e) { - // Should never happen. - } - } else { - Slog.w(TAG, "cannot find thermalservice, no throttling push notifications"); - } - } - setNextLogTime(); // This initialization method may be called on a configuration change. Only one set of @@ -440,15 +414,5 @@ public class PowerUI extends SystemUI { void dump(PrintWriter pw); void userSwitched(); } - - // Thermal event received from vendor thermal management subsystem - private final class ThermalEventListener extends IThermalEventListener.Stub { - @Override public void notifyThrottling(boolean isThrottling, Temperature temp) { - // Trigger an update of the temperature warning. Only one - // callback can be enabled at a time, so remove any existing - // callback; updateTemperatureWarning will schedule another one. - mHandler.removeCallbacks(mUpdateTempCallback); - updateTemperatureWarning(); - } - } } + diff --git a/com/android/systemui/recents/Recents.java b/com/android/systemui/recents/Recents.java index ce1438a1..283ac0c4 100644 --- a/com/android/systemui/recents/Recents.java +++ b/com/android/systemui/recents/Recents.java @@ -29,13 +29,14 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManager; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.util.EventLog; @@ -52,7 +53,6 @@ import com.android.systemui.RecentsComponent; import com.android.systemui.SystemUI; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.ConfigurationChangedEvent; -import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent; import com.android.systemui.recents.events.activity.DockedTopTaskEvent; import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent; import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; @@ -62,7 +62,7 @@ import com.android.systemui.recents.events.component.SetWaitingForTransitionStar import com.android.systemui.recents.events.component.ShowUserToastEvent; import com.android.systemui.recents.events.ui.RecentsDrawnEvent; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.shared.recents.model.RecentsTaskLoader; +import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; @@ -81,15 +81,23 @@ public class Recents extends SystemUI implements RecentsComponent, CommandQueue.Callbacks { private final static String TAG = "Recents"; + private final static boolean DEBUG = false; public final static int EVENT_BUS_PRIORITY = 1; public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000; + public final static int RECENTS_GROW_TARGET_INVALID = -1; public final static Set<String> RECENTS_ACTIVITIES = new HashSet<>(); static { RECENTS_ACTIVITIES.add(RecentsImpl.RECENTS_ACTIVITY); } + // Purely for experimentation + private final static String RECENTS_OVERRIDE_SYSPROP_KEY = "persist.recents_override_pkg"; + private final static String ACTION_SHOW_RECENTS = "com.android.systemui.recents.ACTION_SHOW"; + private final static String ACTION_HIDE_RECENTS = "com.android.systemui.recents.ACTION_HIDE"; + private final static String ACTION_TOGGLE_RECENTS = "com.android.systemui.recents.ACTION_TOGGLE"; + private static final String COUNTER_WINDOW_SUPPORTED = "window_enter_supported"; private static final String COUNTER_WINDOW_UNSUPPORTED = "window_enter_unsupported"; private static final String COUNTER_WINDOW_INCOMPATIBLE = "window_enter_incompatible"; @@ -99,6 +107,11 @@ public class Recents extends SystemUI private static RecentsTaskLoader sTaskLoader; private static RecentsConfiguration sConfiguration; + // For experiments only, allows another package to handle recents if it is defined in the system + // properties. This is limited to show/toggle/hide, and does not tie into the ActivityManager, + // and does not reside in the home stack. + private String mOverrideRecentsPackageName; + private Handler mHandler; private RecentsImpl mImpl; private int mDraggingInRecentsCurrentUser; @@ -191,23 +204,21 @@ public class Recents extends SystemUI @Override public void start() { - final Resources res = mContext.getResources(); - final int defaultTaskBarBackgroundColor = - mContext.getColor(R.color.recents_task_bar_default_background_color); - final int defaultTaskViewBackgroundColor = - mContext.getColor(R.color.recents_task_view_default_background_color); - sDebugFlags = new RecentsDebugFlags(); + sDebugFlags = new RecentsDebugFlags(mContext); sSystemServicesProxy = SystemServicesProxy.getInstance(mContext); sConfiguration = new RecentsConfiguration(mContext); - sTaskLoader = new RecentsTaskLoader(mContext, - // TODO: Once we start building the AAR, move these into the loader - res.getInteger(R.integer.config_recents_max_thumbnail_count), - res.getInteger(R.integer.config_recents_max_icon_count), - res.getInteger(R.integer.recents_svelte_level)); - sTaskLoader.setDefaultColors(defaultTaskBarBackgroundColor, defaultTaskViewBackgroundColor); + sTaskLoader = new RecentsTaskLoader(mContext); mHandler = new Handler(); mImpl = new RecentsImpl(mContext); + // Check if there is a recents override package + if (Build.IS_USERDEBUG || Build.IS_ENG) { + String cnStr = SystemProperties.get(RECENTS_OVERRIDE_SYSPROP_KEY); + if (!cnStr.isEmpty()) { + mOverrideRecentsPackageName = cnStr; + } + } + // Register with the event bus EventBus.getDefault().register(this, EVENT_BUS_PRIORITY); EventBus.getDefault().register(sSystemServicesProxy, EVENT_BUS_PRIORITY); @@ -246,8 +257,16 @@ public class Recents extends SystemUI return; } - sSystemServicesProxy.sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS); + if (proxyToOverridePackage(ACTION_SHOW_RECENTS)) { + return; + } + try { + ActivityManager.getService().closeSystemDialogs(SYSTEM_DIALOG_REASON_RECENT_APPS); + } catch (RemoteException e) { + } + int recentsGrowTarget = getComponent(Divider.class).getView().growsRecents(); + int currentUser = sSystemServicesProxy.getCurrentUser(); if (sSystemServicesProxy.isSystemUser(currentUser)) { mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */, @@ -282,6 +301,10 @@ public class Recents extends SystemUI return; } + if (proxyToOverridePackage(ACTION_HIDE_RECENTS)) { + return; + } + int currentUser = sSystemServicesProxy.getCurrentUser(); if (sSystemServicesProxy.isSystemUser(currentUser)) { mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey); @@ -313,7 +336,12 @@ public class Recents extends SystemUI return; } + if (proxyToOverridePackage(ACTION_TOGGLE_RECENTS)) { + return; + } + int growTarget = getComponent(Divider.class).getView().growsRecents(); + int currentUser = sSystemServicesProxy.getCurrentUser(); if (sSystemServicesProxy.isSystemUser(currentUser)) { mImpl.toggleRecents(growTarget); @@ -606,23 +634,6 @@ public class Recents extends SystemUI } } - public final void onBusEvent(DockedFirstAnimationFrameEvent event) { - SystemServicesProxy ssp = Recents.getSystemServices(); - int processUser = ssp.getProcessUser(); - if (!ssp.isSystemUser(processUser)) { - postToSystemUser(new Runnable() { - @Override - public void run() { - try { - mUserToSystemCallbacks.sendDockedFirstAnimationFrameEvent(); - } catch (RemoteException e) { - Log.e(TAG, "Callback failed", e); - } - } - }); - } - } - /** * Handle screen pinning request. */ @@ -809,6 +820,21 @@ public class Recents extends SystemUI (Settings.Secure.getInt(cr, Settings.Secure.USER_SETUP_COMPLETE, 0) != 0); } + /** + * Attempts to proxy the following action to the override recents package. + * @return whether the proxying was successful + */ + private boolean proxyToOverridePackage(String action) { + if (mOverrideRecentsPackageName != null) { + Intent intent = new Intent(action); + intent.setPackage(mOverrideRecentsPackageName); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendBroadcast(intent); + return true; + } + return false; + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Recents"); diff --git a/com/android/systemui/recents/RecentsActivity.java b/com/android/systemui/recents/RecentsActivity.java index b75a1428..86b77900 100644 --- a/com/android/systemui/recents/RecentsActivity.java +++ b/com/android/systemui/recents/RecentsActivity.java @@ -17,6 +17,7 @@ package com.android.systemui.recents; import android.app.Activity; +import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.TaskStackBuilder; import android.app.WallpaperManager; @@ -28,10 +29,10 @@ import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.os.Looper; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.provider.Settings.Secure; import android.util.Log; import android.view.KeyEvent; import android.view.View; @@ -41,7 +42,6 @@ import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import com.android.internal.colorextraction.ColorExtractor; -import com.android.internal.content.PackageMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.keyguard.LatencyTracker; @@ -53,6 +53,7 @@ import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; import com.android.systemui.recents.events.activity.ConfigurationChangedEvent; +import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent; import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent; import com.android.systemui.recents.events.activity.DockedTopTaskEvent; @@ -60,10 +61,10 @@ import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationC import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent; import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent; import com.android.systemui.recents.events.activity.HideRecentsEvent; +import com.android.systemui.recents.events.activity.IterateRecentsEvent; import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent; import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent; import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent; -import com.android.systemui.recents.events.activity.PackagesChangedEvent; import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; import com.android.systemui.recents.events.activity.ToggleRecentsEvent; import com.android.systemui.recents.events.component.ActivityUnpinnedEvent; @@ -78,24 +79,28 @@ import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent; import com.android.systemui.recents.events.ui.StackViewScrolledEvent; import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; +import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent; import com.android.systemui.recents.events.ui.UserInteractionEvent; import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent; import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent; import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction; +import com.android.systemui.recents.misc.DozeTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.shared.recents.utilities.Utilities; -import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan; -import com.android.systemui.shared.recents.model.RecentsTaskLoader; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.RecentsPackageMonitor; +import com.android.systemui.recents.model.RecentsTaskLoadPlan; +import com.android.systemui.recents.model.RecentsTaskLoader; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.TaskStack; import com.android.systemui.recents.views.RecentsView; import com.android.systemui.recents.views.SystemBarScrimViews; import com.android.systemui.statusbar.phone.StatusBar; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.List; /** * The main Recents activity that is started from RecentsComponent. @@ -109,23 +114,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1; public final static int INCOMPATIBLE_APP_ALPHA_DURATION = 150; - private PackageMonitor mPackageMonitor = new PackageMonitor() { - @Override - public void onPackageRemoved(String packageName, int uid) { - RecentsActivity.this.onPackageChanged(packageName, getChangingUserId()); - } - - @Override - public boolean onPackageChanged(String packageName, int uid, String[] components) { - RecentsActivity.this.onPackageChanged(packageName, getChangingUserId()); - return true; - } - - @Override - public void onPackageModified(String packageName) { - RecentsActivity.this.onPackageChanged(packageName, getChangingUserId()); - } - }; + private RecentsPackageMonitor mPackageMonitor; private Handler mHandler = new Handler(); private long mLastTabKeyEventTime; private boolean mFinishedOnStartup; @@ -144,6 +133,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD // The trigger to automatically launch the current task private int mFocusTimerDuration; + private DozeTrigger mIterateTrigger; private final UserInteractionEvent mUserInteractionEvent = new UserInteractionEvent(); // Theme and colors @@ -198,6 +188,41 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { // When switching users, dismiss Recents to Home similar to screen off finish(); + } else if (action.equals(Intent.ACTION_TIME_CHANGED)) { + // If the time shifts but the currentTime >= lastStackActiveTime, then that boundary + // is still valid. Otherwise, we need to reset the lastStackactiveTime to the + // currentTime and remove the old tasks in between which would not be previously + // visible, but currently would be in the new currentTime + int currentUser = SystemServicesProxy.getInstance(RecentsActivity.this) + .getCurrentUser(); + long oldLastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(), + Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1, currentUser); + if (oldLastStackActiveTime != -1) { + long currentTime = System.currentTimeMillis(); + if (currentTime < oldLastStackActiveTime) { + // We are only removing tasks that are between the new current time + // and the old last stack active time, they were not visible and in the + // TaskStack so we don't need to remove any associated TaskViews but we do + // need to load the task id's from the system + RecentsTaskLoader loader = Recents.getTaskLoader(); + RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(ctx); + loader.preloadRawTasks(loadPlan, false /* includeFrontMostExcludedTask */); + List<ActivityManager.RecentTaskInfo> tasks = loadPlan.getRawTasks(); + for (int i = tasks.size() - 1; i >= 0; i--) { + ActivityManager.RecentTaskInfo task = tasks.get(i); + if (currentTime <= task.lastActiveTime && task.lastActiveTime < + oldLastStackActiveTime) { + Recents.getSystemServices().removeTask(task.persistentId); + } + } + Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync( + currentTime, currentUser); + + // Clear the last PiP task time, it's an edge case and we'd rather it + // not relaunch the PiP task if the user double taps + RecentsImpl.clearLastPipTime(); + } + } } } }; @@ -315,8 +340,8 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD EventBus.getDefault().register(this, EVENT_BUS_PRIORITY); // Initialize the package monitor - mPackageMonitor.register(this, Looper.getMainLooper(), UserHandle.ALL, - true /* externalStorage */); + mPackageMonitor = new RecentsPackageMonitor(); + mPackageMonitor.register(this); // Select theme based on wallpaper colors mColorExtractor = Dependency.get(SysuiColorExtractor.class); @@ -338,6 +363,13 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD } mLastConfig = new Configuration(Utilities.getAppConfiguration(this)); + mFocusTimerDuration = getResources().getInteger(R.integer.recents_auto_advance_duration); + mIterateTrigger = new DozeTrigger(mFocusTimerDuration, new Runnable() { + @Override + public void run() { + dismissRecentsToFocusedTask(MetricsEvent.OVERVIEW_SELECT_TIMEOUT); + } + }); // Set the window background mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode()); @@ -351,6 +383,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD // Register the broadcast receiver to handle messages when the screen is turned off IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_USER_SWITCHED); registerReceiver(mSystemBroadcastReceiver, filter); @@ -427,21 +460,22 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD RecentsTaskLoader loader = Recents.getTaskLoader(); RecentsTaskLoadPlan loadPlan = RecentsImpl.consumeInstanceLoadPlan(); if (loadPlan == null) { - loadPlan = new RecentsTaskLoadPlan(this); + loadPlan = loader.createLoadPlan(this); } // Start loading tasks according to the load plan RecentsConfiguration config = Recents.getConfiguration(); RecentsActivityLaunchState launchState = config.getLaunchState(); if (!loadPlan.hasTasks()) { - loader.preloadTasks(loadPlan, launchState.launchedToTaskId); + loader.preloadTasks(loadPlan, launchState.launchedToTaskId, + !launchState.launchedFromHome && !launchState.launchedViaDockGesture); } RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); loadOpts.runningTaskId = launchState.launchedToTaskId; loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks; loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails; - loader.loadTasks(loadPlan, loadOpts); + loader.loadTasks(this, loadPlan, loadOpts); TaskStack stack = loadPlan.getTaskStack(); mRecentsView.onReload(stack, mIsVisible); @@ -506,6 +540,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD super.onPause(); mIgnoreAltTabRelease = false; + mIterateTrigger.stopDozing(); } @Override @@ -608,7 +643,8 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD if (backward) { EventBus.getDefault().send(new FocusPreviousTaskViewEvent()); } else { - EventBus.getDefault().send(new FocusNextTaskViewEvent()); + EventBus.getDefault().send( + new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */)); } mLastTabKeyEventTime = SystemClock.elapsedRealtime(); @@ -666,10 +702,38 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD } } + public final void onBusEvent(IterateRecentsEvent event) { + final RecentsDebugFlags debugFlags = Recents.getDebugFlags(); + + // Start dozing after the recents button is clicked + int timerIndicatorDuration = 0; + if (debugFlags.isFastToggleRecentsEnabled()) { + timerIndicatorDuration = getResources().getInteger( + R.integer.recents_subsequent_auto_advance_duration); + + mIterateTrigger.setDozeDuration(timerIndicatorDuration); + if (!mIterateTrigger.isDozing()) { + mIterateTrigger.startDozing(); + } else { + mIterateTrigger.poke(); + } + } + + // Focus the next task + EventBus.getDefault().send(new FocusNextTaskViewEvent(timerIndicatorDuration)); + + MetricsLogger.action(this, MetricsEvent.ACTION_OVERVIEW_PAGE); + } + public final void onBusEvent(RecentsActivityStartingEvent event) { mRecentsStartRequested = true; } + public final void onBusEvent(UserInteractionEvent event) { + // Stop the fast-toggle dozer + mIterateTrigger.stopDozing(); + } + public final void onBusEvent(HideRecentsEvent event) { if (event.triggeredFromAltTab) { // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app @@ -687,11 +751,15 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD } public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) { + EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(true)); mRecentsView.getViewTreeObserver().addOnPreDrawListener(this); mRecentsView.invalidate(); } public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) { + if (mRecentsView.isLastTaskLaunchedFreeform()) { + EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(false)); + } mRecentsView.getViewTreeObserver().addOnPreDrawListener(this); mRecentsView.invalidate(); } @@ -791,6 +859,11 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD MetricsLogger.count(this, "overview_screen_pinned", 1); } + public final void onBusEvent(DebugFlagsChangedEvent event) { + // Just finish recents so that we can reload the flags anew on the next instantiation + finish(); + } + public final void onBusEvent(StackViewScrolledEvent event) { // Once the user has scrolled while holding alt-tab, then we should ignore the release of // the key @@ -815,13 +888,14 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD RecentsConfiguration config = Recents.getConfiguration(); RecentsActivityLaunchState launchState = config.getLaunchState(); RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(this); - loader.preloadTasks(loadPlan, -1 /* runningTaskId */); + RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this); + loader.preloadTasks(loadPlan, -1 /* runningTaskId */, + false /* includeFrontMostExcludedTask */); RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks; loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails; - loader.loadTasks(loadPlan, loadOpts); + loader.loadTasks(this, loadPlan, loadOpts); TaskStack stack = loadPlan.getTaskStack(); int numStackTasks = stack.getStackTaskCount(); @@ -850,11 +924,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD return true; } - public void onPackageChanged(String packageName, int userId) { - Recents.getTaskLoader().onPackageChanged(packageName); - EventBus.getDefault().send(new PackagesChangedEvent(packageName, userId)); - } - @Override public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { super.dump(prefix, fd, writer, args); @@ -862,9 +931,13 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD Recents.getTaskLoader().dump(prefix, writer); String id = Integer.toHexString(System.identityHashCode(this)); + long lastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(), + Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1, + SystemServicesProxy.getInstance(this).getCurrentUser()); writer.print(prefix); writer.print(TAG); writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N"); + writer.print(" lastStackTaskActiveTime="); writer.print(lastStackActiveTime); writer.print(" currentTime="); writer.print(System.currentTimeMillis()); writer.print(" [0x"); writer.print(id); writer.print("]"); writer.println(); diff --git a/com/android/systemui/recents/RecentsActivityLaunchState.java b/com/android/systemui/recents/RecentsActivityLaunchState.java index d2326ce2..5b8ed94d 100644 --- a/com/android/systemui/recents/RecentsActivityLaunchState.java +++ b/com/android/systemui/recents/RecentsActivityLaunchState.java @@ -33,6 +33,7 @@ public class RecentsActivityLaunchState { public boolean launchedFromPipApp; // Set if the next activity that quick-switch will launch is the PiP activity public boolean launchedWithNextPipApp; + public boolean launchedFromBlacklistedApp; public boolean launchedFromHome; public boolean launchedViaDragGesture; public boolean launchedViaDockGesture; @@ -43,6 +44,7 @@ public class RecentsActivityLaunchState { public void reset() { launchedFromHome = false; launchedFromApp = false; + launchedFromBlacklistedApp = false; launchedFromPipApp = false; launchedWithNextPipApp = false; launchedToTaskId = -1; @@ -58,6 +60,18 @@ public class RecentsActivityLaunchState { RecentsDebugFlags debugFlags = Recents.getDebugFlags(); RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); if (launchedFromApp) { + if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) { + // If fast toggling, focus the front most task so that the next tap will launch the + // task + return numTasks - 1; + } + + if (launchState.launchedFromBlacklistedApp) { + // If we are launching from a blacklisted app, focus the front most task so that the + // next tap will launch the task + return numTasks - 1; + } + if (useGridLayout) { // If coming from another app to the grid layout, focus the front most task return numTasks - 1; @@ -66,6 +80,12 @@ public class RecentsActivityLaunchState { // If coming from another app, focus the next task return Math.max(0, numTasks - 2); } else { + if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) { + // If fast toggling, defer focusing until the next tap (which will automatically + // focus the front most task) + return -1; + } + // If coming from home, focus the front most task return numTasks - 1; } diff --git a/com/android/systemui/recents/RecentsConfiguration.java b/com/android/systemui/recents/RecentsConfiguration.java index 68df1d5b..5dc6f31c 100644 --- a/com/android/systemui/recents/RecentsConfiguration.java +++ b/com/android/systemui/recents/RecentsConfiguration.java @@ -20,31 +20,31 @@ import android.app.ActivityManager; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Rect; import android.os.SystemProperties; import com.android.systemui.R; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.views.DockState; -import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.recents.model.TaskStack; /** * Represents the dock regions for each orientation. */ class DockRegion { - public static DockState[] PHONE_LANDSCAPE = { + public static TaskStack.DockState[] PHONE_LANDSCAPE = { // We only allow docking to the left in landscape for now on small devices - DockState.LEFT + TaskStack.DockState.LEFT }; - public static DockState[] PHONE_PORTRAIT = { + public static TaskStack.DockState[] PHONE_PORTRAIT = { // We only allow docking to the top for now on small devices - DockState.TOP + TaskStack.DockState.TOP }; - public static DockState[] TABLET_LANDSCAPE = { - DockState.LEFT, - DockState.RIGHT + public static TaskStack.DockState[] TABLET_LANDSCAPE = { + TaskStack.DockState.LEFT, + TaskStack.DockState.RIGHT }; - public static DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT; + public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT; } /** @@ -56,6 +56,18 @@ public class RecentsConfiguration { private static final int LARGE_SCREEN_MIN_DP = 600; private static final int XLARGE_SCREEN_MIN_DP = 720; + /** Levels of svelte in increasing severity/austerity. */ + // No svelting. + public static final int SVELTE_NONE = 0; + // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable + // caching thumbnails as you scroll. + public static final int SVELTE_LIMIT_CACHE = 1; + // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and + // evict all thumbnails when hidden. + public static final int SVELTE_DISABLE_CACHE = 2; + // Disable all thumbnail loading. + public static final int SVELTE_DISABLE_LOADING = 3; + // Launch states public RecentsActivityLaunchState mLaunchState = new RecentsActivityLaunchState(); @@ -113,7 +125,7 @@ public class RecentsConfiguration { * Returns the preferred dock states for the current orientation. * @return a list of dock states for device and its orientation */ - public DockState[] getDockStatesForCurrentOrientation() { + public TaskStack.DockState[] getDockStatesForCurrentOrientation() { boolean isLandscape = mAppContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; RecentsConfiguration config = Recents.getConfiguration(); diff --git a/com/android/systemui/recents/RecentsDebugFlags.java b/com/android/systemui/recents/RecentsDebugFlags.java index 19185939..0262a098 100644 --- a/com/android/systemui/recents/RecentsDebugFlags.java +++ b/com/android/systemui/recents/RecentsDebugFlags.java @@ -16,14 +16,75 @@ package com.android.systemui.recents; -public class RecentsDebugFlags { +import android.content.Context; + +import com.android.systemui.recents.events.EventBus; +import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent; +import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.tuner.TunerService; + +/** + * Tunable debug flags + */ +public class RecentsDebugFlags implements TunerService.Tunable { public static class Static { // Enables debug drawing for the transition thumbnail public static final boolean EnableTransitionThumbnailDebugMode = false; - + // This disables the bitmap and icon caches + public static final boolean DisableBackgroundCache = false; + // Enables the task affiliations + public static final boolean EnableAffiliatedTaskGroups = false; + // Enables the button above the stack + public static final boolean EnableStackActionButton = true; + // Overrides the Tuner flags and enables the timeout + private static final boolean EnableFastToggleTimeout = false; + // Overrides the Tuner flags and enables the paging via the Recents button + private static final boolean EnablePaging = false; // Disables enter and exit transitions for other tasks for low ram devices public static final boolean DisableRecentsLowRamEnterExitAnimation = false; + // Enables us to create mock recents tasks + public static final boolean EnableMockTasks = false; + // Defines the number of mock recents packages to create + public static final int MockTasksPackageCount = 3; + // Defines the number of mock recents tasks to create + public static final int MockTaskCount = 100; + // Enables the simulated task affiliations + public static final boolean EnableMockTaskGroups = false; + // Defines the number of mock task affiliations per group + public static final int MockTaskGroupsTaskCount = 12; + } + + /** + * We read the prefs once when we start the activity, then update them as the tuner changes + * the flags. + */ + public RecentsDebugFlags(Context context) { + // Register all our flags, this will also call onTuningChanged() for each key, which will + // initialize the current state of each flag + } + + /** + * @return whether we are enabling fast toggling. + */ + public boolean isFastToggleRecentsEnabled() { + SystemServicesProxy ssp = Recents.getSystemServices(); + if (ssp.hasFreeformWorkspaceSupport() || ssp.isTouchExplorationEnabled()) { + return false; + } + return Static.EnableFastToggleTimeout; + } + + /** + * @return whether we are enabling paging. + */ + public boolean isPagingEnabled() { + return Static.EnablePaging; + } + + @Override + public void onTuningChanged(String key, String newValue) { + EventBus.getDefault().send(new DebugFlagsChangedEvent()); } } diff --git a/com/android/systemui/recents/RecentsImpl.java b/com/android/systemui/recents/RecentsImpl.java index 868ed64b..3e2a5f3f 100644 --- a/com/android/systemui/recents/RecentsImpl.java +++ b/com/android/systemui/recents/RecentsImpl.java @@ -18,6 +18,7 @@ package com.android.systemui.recents; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.View.MeasureSpec; import android.app.ActivityManager; @@ -55,6 +56,7 @@ import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.DockedTopTaskEvent; import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent; import com.android.systemui.recents.events.activity.HideRecentsEvent; +import com.android.systemui.recents.events.activity.IterateRecentsEvent; import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent; import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent; import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; @@ -70,18 +72,20 @@ import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent; import com.android.systemui.recents.misc.DozeTrigger; import com.android.systemui.recents.misc.ForegroundThread; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.TaskStackChangeListener; -import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan; -import com.android.systemui.shared.recents.model.RecentsTaskLoader; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.model.Task.TaskKey; -import com.android.systemui.shared.recents.model.TaskStack; -import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; +import com.android.systemui.recents.model.RecentsTaskLoadPlan; +import com.android.systemui.recents.model.RecentsTaskLoader; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.Task.TaskKey; +import com.android.systemui.recents.model.TaskGrouping; +import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.recents.model.ThumbnailData; import com.android.systemui.recents.views.RecentsTransitionHelper; import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture; import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport; import com.android.systemui.recents.views.TaskStackView; +import com.android.systemui.recents.views.TaskStackViewScroller; import com.android.systemui.recents.views.TaskViewHeader; import com.android.systemui.recents.views.TaskViewTransform; import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; @@ -113,10 +117,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity"; /** - * An implementation of TaskStackChangeListener, that allows us to listen for changes to the system + * An implementation of TaskStackListener, that allows us to listen for changes to the system * task stacks and update recents accordingly. */ - class TaskStackListenerImpl extends TaskStackChangeListener { + class TaskStackListenerImpl extends TaskStackListener { @Override public void onTaskStackChangedBackground() { @@ -127,7 +131,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // Preloads the next task RecentsConfiguration config = Recents.getConfiguration(); - if (config.svelteLevel == RecentsTaskLoader.SVELTE_NONE) { + if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) { Rect windowRect = getWindowRect(null /* windowRectOverride */); if (windowRect.isEmpty()) { return; @@ -137,8 +141,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener SystemServicesProxy ssp = Recents.getSystemServices(); ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask(); RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext); - loader.preloadTasks(plan, -1); + RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); + loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */); TaskStack stack = plan.getTaskStack(); RecentsActivityLaunchState launchState = new RecentsActivityLaunchState(); RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); @@ -164,7 +168,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener launchOpts.onlyLoadPausedActivities = true; launchOpts.loadThumbnails = true; } - loader.loadTasks(plan, launchOpts); + loader.loadTasks(mContext, plan, launchOpts); } } @@ -203,7 +207,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId, - new ThumbnailData(snapshot))); + ThumbnailData.createFromTaskSnapshot(snapshot))); } } @@ -278,13 +282,13 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // When we start, preload the data associated with the previous recent tasks. // We can use a new plan since the caches will be the same. RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext); - loader.preloadTasks(plan, -1); + RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); + loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */); RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); launchOpts.numVisibleTasks = loader.getIconCacheSize(); launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize(); launchOpts.onlyLoadForCache = true; - loader.loadTasks(plan, launchOpts); + loader.loadTasks(mContext, plan, launchOpts); } public void onConfigurationChanged() { @@ -405,17 +409,22 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener RecentsConfiguration config = Recents.getConfiguration(); RecentsActivityLaunchState launchState = config.getLaunchState(); if (!launchState.launchedWithAltTab) { + // Has the user tapped quickly? + boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout(); if (Recents.getConfiguration().isGridEnabled) { - // Has the user tapped quickly? - boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout(); if (isQuickTap) { EventBus.getDefault().post(new LaunchNextTaskRequestEvent()); } else { EventBus.getDefault().post(new LaunchMostRecentTaskRequestEvent()); } } else { - // Launch the next focused task - EventBus.getDefault().post(new LaunchNextTaskRequestEvent()); + if (!debugFlags.isPagingEnabled() || isQuickTap) { + // Launch the next focused task + EventBus.getDefault().post(new LaunchNextTaskRequestEvent()); + } else { + // Notify recents to move onto the next task + EventBus.getDefault().post(new IterateRecentsEvent()); + } } } else { // If the user has toggled it too quickly, then just eat up the event here (it's @@ -464,15 +473,16 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // RecentsActivity) only if there is a task to animate to. Post this to ensure that we // don't block the touch feedback on the nav bar button which triggers this. mHandler.post(() -> { - if (!ssp.isRecentsActivityVisible(null)) { + MutableBoolean isHomeStackVisible = new MutableBoolean(true); + if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) { ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask(); if (runningTask == null) { return; } RecentsTaskLoader loader = Recents.getTaskLoader(); - sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext); - loader.preloadTasks(sInstanceLoadPlan, runningTask.id); + sInstanceLoadPlan = loader.createLoadPlan(mContext); + loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value); TaskStack stack = sInstanceLoadPlan.getTaskStack(); if (stack.getTaskCount() > 0) { // Only preload the icon (but not the thumbnail since it may not have been taken @@ -511,8 +521,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener public void showNextTask() { SystemServicesProxy ssp = Recents.getSystemServices(); RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext); - loader.preloadTasks(plan, -1); + RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); + loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */); TaskStack focusedStack = plan.getTaskStack(); // Return early if there are no tasks in the focused stack @@ -566,8 +576,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener public void showRelativeAffiliatedTask(boolean showNextTask) { SystemServicesProxy ssp = Recents.getSystemServices(); RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext); - loader.preloadTasks(plan, -1); + RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); + loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */); TaskStack focusedStack = plan.getTaskStack(); // Return early if there are no tasks in the focused stack @@ -585,38 +595,43 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener Task toTask = null; ActivityOptions launchOpts = null; int taskCount = tasks.size(); + int numAffiliatedTasks = 0; for (int i = 0; i < taskCount; i++) { Task task = tasks.get(i); if (task.key.id == runningTask.id) { + TaskGrouping group = task.group; + Task.TaskKey toTaskKey; if (showNextTask) { - if ((i + 1) < taskCount) { - toTask = tasks.get(i + 1); - launchOpts = ActivityOptions.makeCustomAnimation(mContext, - R.anim.recents_launch_next_affiliated_task_target, - R.anim.recents_launch_next_affiliated_task_source); - } + toTaskKey = group.getNextTaskInGroup(task); + launchOpts = ActivityOptions.makeCustomAnimation(mContext, + R.anim.recents_launch_next_affiliated_task_target, + R.anim.recents_launch_next_affiliated_task_source); } else { - if ((i - 1) >= 0) { - toTask = tasks.get(i - 1); - launchOpts = ActivityOptions.makeCustomAnimation(mContext, - R.anim.recents_launch_prev_affiliated_task_target, - R.anim.recents_launch_prev_affiliated_task_source); - } + toTaskKey = group.getPrevTaskInGroup(task); + launchOpts = ActivityOptions.makeCustomAnimation(mContext, + R.anim.recents_launch_prev_affiliated_task_target, + R.anim.recents_launch_prev_affiliated_task_source); + } + if (toTaskKey != null) { + toTask = focusedStack.findTaskWithId(toTaskKey.id); } + numAffiliatedTasks = group.getTaskCount(); break; } } // Return early if there is no next task if (toTask == null) { - if (showNextTask) { - ssp.startInPlaceAnimationOnFrontMostApplication( - ActivityOptions.makeCustomInPlaceAnimation(mContext, - R.anim.recents_launch_next_affiliated_task_bounce)); - } else { - ssp.startInPlaceAnimationOnFrontMostApplication( - ActivityOptions.makeCustomInPlaceAnimation(mContext, - R.anim.recents_launch_prev_affiliated_task_bounce)); + if (numAffiliatedTasks > 1) { + if (showNextTask) { + ssp.startInPlaceAnimationOnFrontMostApplication( + ActivityOptions.makeCustomInPlaceAnimation(mContext, + R.anim.recents_launch_next_affiliated_task_bounce)); + } else { + ssp.startInPlaceAnimationOnFrontMostApplication( + ActivityOptions.makeCustomInPlaceAnimation(mContext, + R.anim.recents_launch_prev_affiliated_task_bounce)); + } } return; } @@ -738,7 +753,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top, systemInsets.left, systemInsets.right, mTmpBounds); stackLayout.reset(); - stackLayout.initialize(displayRect, windowRect, mTmpBounds); + stackLayout.initialize(displayRect, windowRect, mTmpBounds, + TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack)); } } @@ -827,7 +843,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener launchOpts.runningTaskId = runningTaskId; launchOpts.loadThumbnails = false; launchOpts.onlyLoadForCache = true; - Recents.getTaskLoader().loadTasks(sInstanceLoadPlan, launchOpts); + Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts); } /** @@ -857,29 +873,61 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask, Rect windowOverrideRect) { final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice; - - // Update the destination rect - Task toTask = new Task(); - TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask, - windowOverrideRect); - - RectF toTaskRect = toTransform.rect; - AppTransitionAnimationSpecsFuture future = - new RecentsTransitionHelper(mContext).getAppTransitionFuture( - () -> { - Rect rect = new Rect(); - toTaskRect.round(rect); - GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask, - toTransform); - return Lists.newArrayList(new AppTransitionAnimationSpec( - toTask.key.id, thumbnail, rect)); - }); - - // For low end ram devices, wait for transition flag is reset when Recents entrance - // animation is complete instead of when the transition animation starts - return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext, - mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener, - false /* scaleUp */), future); + if (runningTask != null + && runningTask.configuration.windowConfiguration.getWindowingMode() + == WINDOWING_MODE_FREEFORM) { + ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>(); + ArrayList<Task> tasks = mDummyStackView.getStack().getStackTasks(); + TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm(); + TaskStackViewScroller stackScroller = mDummyStackView.getScroller(); + + mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */); + mDummyStackView.updateToInitialState(); + + for (int i = tasks.size() - 1; i >= 0; i--) { + Task task = tasks.get(i); + if (task.isFreeformTask()) { + mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task, + stackScroller.getStackScroll(), mTmpTransform, null, + windowOverrideRect); + GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform); + Rect toTaskRect = new Rect(); + mTmpTransform.rect.round(toTaskRect); + specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect)); + } + } + AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()]; + specs.toArray(specsArray); + + // For low end ram devices, wait for transition flag is reset when Recents entrance + // animation is complete instead of when the transition animation starts + return new Pair<>(ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, + specsArray, mHandler, isLowRamDevice ? null : mResetToggleFlagListener, this), + null); + } else { + // Update the destination rect + Task toTask = new Task(); + TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask, + windowOverrideRect); + + RectF toTaskRect = toTransform.rect; + AppTransitionAnimationSpecsFuture future = + new RecentsTransitionHelper(mContext).getAppTransitionFuture( + () -> { + Rect rect = new Rect(); + toTaskRect.round(rect); + GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask, + toTransform); + return Lists.newArrayList(new AppTransitionAnimationSpec( + toTask.key.id, thumbnail, rect)); + }); + + // For low end ram devices, wait for transition flag is reset when Recents entrance + // animation is complete instead of when the transition animation starts + return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext, + mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener, + false /* scaleUp */), future); + } } /** @@ -894,7 +942,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener runningTaskOut.copyFrom(launchTask); } else { // If no task is specified or we can not find the task just use the front most one - launchTask = stack.getStackFrontMostTask(); + launchTask = stack.getStackFrontMostTask(true /* includeFreeform */); runningTaskOut.copyFrom(launchTask); } @@ -947,8 +995,12 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener boolean isHomeStackVisible, boolean animate, int growTarget) { RecentsTaskLoader loader = Recents.getTaskLoader(); RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); + SystemServicesProxy ssp = Recents.getSystemServices(); + boolean isBlacklisted = (runningTask != null) + ? ssp.isBlackListedActivity(runningTask.baseActivity.getClassName()) + : false; - int runningTaskId = !mLaunchedWhileDocking && (runningTask != null) + int runningTaskId = !mLaunchedWhileDocking && !isBlacklisted && (runningTask != null) ? runningTask.id : -1; @@ -957,10 +1009,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // the stacks might have changed. if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) { // Create a new load plan if preloadRecents() was never triggered - sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext); + sInstanceLoadPlan = loader.createLoadPlan(mContext); } if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) { - loader.preloadTasks(sInstanceLoadPlan, runningTaskId); + loader.preloadTasks(sInstanceLoadPlan, runningTaskId, !isHomeStackVisible); } TaskStack stack = sInstanceLoadPlan.getTaskStack(); @@ -971,6 +1023,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // Update the launch state that we need in updateHeaderBarLayout() launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking; launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking; + launchState.launchedFromBlacklistedApp = launchState.launchedFromApp && isBlacklisted; launchState.launchedFromPipApp = false; launchState.launchedWithNextPipApp = stack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime()); @@ -1006,7 +1059,9 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> pair; - if (useThumbnailTransition) { + if (isBlacklisted) { + pair = new Pair<>(getUnknownTransitionActivityOptions(), null); + } else if (useThumbnailTransition) { // Try starting with a thumbnail transition pair = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect); } else { diff --git a/com/android/systemui/recents/RecentsSystemUser.java b/com/android/systemui/recents/RecentsSystemUser.java index ff1f7dc5..12856260 100644 --- a/com/android/systemui/recents/RecentsSystemUser.java +++ b/com/android/systemui/recents/RecentsSystemUser.java @@ -27,7 +27,6 @@ import android.util.SparseArray; import com.android.systemui.EventLogConstants; import com.android.systemui.EventLogTags; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent; import com.android.systemui.recents.events.activity.DockedTopTaskEvent; import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent; @@ -109,11 +108,6 @@ public class RecentsSystemUser extends IRecentsSystemUserCallbacks.Stub { } @Override - public void sendDockedFirstAnimationFrameEvent() throws RemoteException { - EventBus.getDefault().post(new DockedFirstAnimationFrameEvent()); - } - - @Override public void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart) { EventBus.getDefault().post(new SetWaitingForTransitionStartEvent( waitingForTransitionStart)); diff --git a/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java b/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java index fec34e3c..7604de1d 100644 --- a/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java +++ b/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.activity; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.recents.model.Task; /** * This is sent when we want to cancel the enter-recents window animation for the launch task. diff --git a/com/android/systemui/shared/recents/model/TaskFilter.java b/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java index 9a1ff544..fe3bf263 100644 --- a/com/android/systemui/shared/recents/model/TaskFilter.java +++ b/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2015 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. @@ -11,17 +11,16 @@ * 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 + * limitations under the License. */ -package com.android.systemui.shared.recents.model; +package com.android.systemui.recents.events.activity; -import android.util.SparseArray; +import com.android.systemui.recents.events.EventBus; /** - * An interface for a task filter to query whether a particular task should show in a stack. + * This is sent when the SystemUI tuner changes a flag. */ -interface TaskFilter { - /** Returns whether the filter accepts the specified task */ - boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index); +public class DebugFlagsChangedEvent extends EventBus.Event { + // Simple event } diff --git a/com/android/systemui/recents/events/activity/IterateRecentsEvent.java b/com/android/systemui/recents/events/activity/IterateRecentsEvent.java new file mode 100644 index 00000000..f7b2706b --- /dev/null +++ b/com/android/systemui/recents/events/activity/IterateRecentsEvent.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 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.systemui.recents.events.activity; + +import com.android.systemui.recents.events.EventBus; + +/** + * This is sent when the user taps on the Overview button to iterate to the next item in the + * Recents list. + */ +public class IterateRecentsEvent extends EventBus.Event { + // Simple event +} diff --git a/com/android/systemui/recents/events/activity/LaunchTaskEvent.java b/com/android/systemui/recents/events/activity/LaunchTaskEvent.java index 2409f39d..862a1eee 100644 --- a/com/android/systemui/recents/events/activity/LaunchTaskEvent.java +++ b/com/android/systemui/recents/events/activity/LaunchTaskEvent.java @@ -22,7 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.graphics.Rect; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.recents.model.Task; import com.android.systemui.recents.views.TaskView; /** diff --git a/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java b/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java index e4972b1f..64eeafa1 100644 --- a/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java +++ b/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.activity; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.recents.model.TaskStack; /** * This is sent by the activity whenever the multi-window state has changed. diff --git a/com/android/systemui/recents/events/activity/PackagesChangedEvent.java b/com/android/systemui/recents/events/activity/PackagesChangedEvent.java index 47670e03..3b68574c 100644 --- a/com/android/systemui/recents/events/activity/PackagesChangedEvent.java +++ b/com/android/systemui/recents/events/activity/PackagesChangedEvent.java @@ -17,20 +17,22 @@ package com.android.systemui.recents.events.activity; import com.android.systemui.recents.events.EventBus; +import com.android.systemui.recents.model.RecentsPackageMonitor; import com.android.systemui.recents.views.TaskStackView; -import com.android.systemui.recents.RecentsActivity; /** - * This event is sent by {@link RecentsActivity} when a package on the the system changes. + * This event is sent by {@link RecentsPackageMonitor} when a package on the the system changes. * {@link TaskStackView}s listen for this event, and remove the tasks associated with the removed * packages. */ public class PackagesChangedEvent extends EventBus.Event { + public final RecentsPackageMonitor monitor; public final String packageName; public final int userId; - public PackagesChangedEvent(String packageName, int userId) { + public PackagesChangedEvent(RecentsPackageMonitor monitor, String packageName, int userId) { + this.monitor = monitor; this.packageName = packageName; this.userId = userId; } diff --git a/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java b/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java index 51d02b5b..0d614e8c 100644 --- a/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java +++ b/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.activity; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.recents.model.TaskStack; /** * This is sent by the activity whenever the task stach has changed. diff --git a/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java b/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java index b52e83b8..4ed02708 100644 --- a/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java +++ b/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.ui; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.recents.model.Task; /** * This is sent when the data associated with a given {@link Task} should be deleted from the diff --git a/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java b/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java index da19384a..40c30b88 100644 --- a/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java +++ b/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.ui; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.recents.model.Task; /** * This is sent when a user wants to show the application info for a {@link Task}. diff --git a/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java b/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java index f0829280..e0ed7a9e 100644 --- a/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java +++ b/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.ui; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.systemui.recents.model.ThumbnailData; /** * Sent when a task snapshot has changed. diff --git a/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java b/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java index 881a64af..0628c501 100644 --- a/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java +++ b/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java @@ -17,8 +17,8 @@ package com.android.systemui.recents.events.ui; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.utilities.AnimationProps; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.views.AnimationProps; import com.android.systemui.recents.views.TaskView; /** diff --git a/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java b/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java new file mode 100644 index 00000000..b42da9c7 --- /dev/null +++ b/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015 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.systemui.recents.events.ui; + +import com.android.systemui.recents.events.EventBus; + +/** + * This is sent to update the visibility of all visible freeform task views. + */ +public class UpdateFreeformTaskViewVisibilityEvent extends EventBus.Event { + + public final boolean visible; + + public UpdateFreeformTaskViewVisibilityEvent(boolean visible) { + this.visible = visible; + } +} diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java index cf61b1ef..216be612 100644 --- a/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java +++ b/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.ui.dragndrop; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.recents.model.Task; import com.android.systemui.recents.views.DropTarget; /** diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java index 297afc53..edd79959 100644 --- a/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java +++ b/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java @@ -17,8 +17,9 @@ package com.android.systemui.recents.events.ui.dragndrop; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.recents.views.DropTarget; import com.android.systemui.recents.views.TaskView; /** diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java index 73cbde99..73c282fe 100644 --- a/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java +++ b/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.ui.dragndrop; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.recents.model.Task; import com.android.systemui.recents.views.DropTarget; import com.android.systemui.recents.views.TaskView; diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java index 021be77b..e57fa2d8 100644 --- a/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java +++ b/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java @@ -19,7 +19,7 @@ package com.android.systemui.recents.events.ui.dragndrop; import android.graphics.Point; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.recents.model.Task; import com.android.systemui.recents.views.TaskView; /** diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java index 64ba5748..7030729d 100644 --- a/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java +++ b/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java @@ -17,7 +17,7 @@ package com.android.systemui.recents.events.ui.dragndrop; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.recents.model.Task; import com.android.systemui.recents.views.RecentsViewTouchHandler; import com.android.systemui.recents.views.TaskView; diff --git a/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java b/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java index 171ab5e8..a1e4957a 100644 --- a/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java +++ b/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java @@ -22,5 +22,10 @@ import com.android.systemui.recents.events.EventBus; * Focuses the next task view in the stack. */ public class FocusNextTaskViewEvent extends EventBus.Event { - // Simple event + + public final int timerIndicatorDuration; + + public FocusNextTaskViewEvent(int timerIndicatorDuration) { + this.timerIndicatorDuration = timerIndicatorDuration; + } } diff --git a/java/lang/invoke/ArrayElementVarHandle.java b/com/android/systemui/recents/misc/NamedCounter.java index e315ba77..ec3c39cc 100644 --- a/java/lang/invoke/ArrayElementVarHandle.java +++ b/com/android/systemui/recents/misc/NamedCounter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2014 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. @@ -14,22 +14,26 @@ * limitations under the License. */ -package java.lang.invoke; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; +package com.android.systemui.recents.misc; /** - * A VarHandle to access array elements. - * @hide + * Used to generate successive incremented names. */ -final class ArrayElementVarHandle extends VarHandle { - private ArrayElementVarHandle(Class<?> arrayClass) { - super(arrayClass.getComponentType(), arrayClass, false /* isFinal */, - arrayClass, int.class); +public class NamedCounter { + + int mCount; + String mPrefix = ""; + String mSuffix = ""; + + public NamedCounter(String prefix, String suffix) { + mPrefix = prefix; + mSuffix = suffix; } - static ArrayElementVarHandle create(Class<?> arrayClass) { - return new ArrayElementVarHandle(arrayClass); + /** Returns the next name. */ + public String nextName() { + String name = mPrefix + mCount + mSuffix; + mCount++; + return name; } } diff --git a/com/android/systemui/shared/recents/utilities/RectFEvaluator.java b/com/android/systemui/recents/misc/RectFEvaluator.java index 51c1b5aa..72511de9 100644 --- a/com/android/systemui/shared/recents/utilities/RectFEvaluator.java +++ b/com/android/systemui/recents/misc/RectFEvaluator.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.shared.recents.utilities; +package com.android.systemui.recents.misc; import android.animation.TypeEvaluator; import android.graphics.RectF; @@ -23,7 +23,7 @@ import android.graphics.RectF; */ public class RectFEvaluator implements TypeEvaluator<RectF> { - private final RectF mRect = new RectF(); + private RectF mRect = new RectF(); /** * This function returns the result of linearly interpolating the start and diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java index 87f24fdb..bddf9a59 100644 --- a/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -25,20 +25,27 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.StackInfo; +import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.IActivityManager; +import android.app.KeyguardManager; import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -48,18 +55,24 @@ import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.IRemoteCallback; -import android.os.Looper; +import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.provider.Settings.Secure; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; +import android.util.ArraySet; +import android.util.IconDrawableFactory; import android.util.Log; import android.util.MutableBoolean; import android.view.Display; @@ -76,12 +89,19 @@ import com.android.internal.os.BackgroundThread; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.UiOffloadThread; +import com.android.systemui.pip.tv.PipMenuActivity; import com.android.systemui.recents.Recents; +import com.android.systemui.recents.RecentsDebugFlags; import com.android.systemui.recents.RecentsImpl; -import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.ThumbnailData; import com.android.systemui.statusbar.policy.UserInfoController; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Random; /** * Acts as a shim around the real system services that we need to access data from, and provides @@ -97,32 +117,42 @@ public class SystemServicesProxy { sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; } + final static List<String> sRecentsBlacklist; + static { + sRecentsBlacklist = new ArrayList<>(); + sRecentsBlacklist.add(PipMenuActivity.class.getName()); + } + private static SystemServicesProxy sSystemServicesProxy; AccessibilityManager mAccm; ActivityManager mAm; IActivityManager mIam; PackageManager mPm; + IconDrawableFactory mDrawableFactory; IPackageManager mIpm; private final IDreamManager mDreamManager; private final Context mContext; AssistUtils mAssistUtils; WindowManager mWm; IWindowManager mIwm; + KeyguardManager mKgm; UserManager mUm; Display mDisplay; String mRecentsPackage; - private TaskStackChangeListeners mTaskStackChangeListeners; + ComponentName mAssistComponent; private int mCurrentUserId; boolean mIsSafeMode; + boolean mHasFreeformWorkspaceSupport; + Bitmap mDummyIcon; int mDummyThumbnailWidth; int mDummyThumbnailHeight; Paint mBgProtectionPaint; Canvas mBgProtectionCanvas; - private final Handler mHandler = new Handler(); + private final Handler mHandler = new H(); private final Runnable mGcRunnable = new Runnable() { @Override public void run() { @@ -133,10 +163,144 @@ public class SystemServicesProxy { private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class); + /** + * An abstract class to track task stack changes. + * Classes should implement this instead of {@link android.app.ITaskStackListener} + * to reduce IPC calls from system services. These callbacks will be called on the main thread. + */ + public abstract static class TaskStackListener { + /** + * NOTE: This call is made of the thread that the binder call comes in on. + */ + public void onTaskStackChangedBackground() { } + public void onTaskStackChanged() { } + public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { } + public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { } + public void onActivityUnpinned() { } + public void onPinnedActivityRestartAttempt(boolean clearedTask) { } + public void onPinnedStackAnimationStarted() { } + public void onPinnedStackAnimationEnded() { } + public void onActivityForcedResizable(String packageName, int taskId, int reason) { } + public void onActivityDismissingDockedStack() { } + public void onActivityLaunchOnSecondaryDisplayFailed() { } + public void onTaskProfileLocked(int taskId, int userId) { } + + /** + * Checks that the current user matches the user's SystemUI process. Since + * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of + * TaskStackListener should make this call to verify that we don't act on events from other + * user's processes. + */ + protected final boolean checkCurrentUserId(Context context, boolean debug) { + int processUserId = UserHandle.myUserId(); + int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser(); + if (processUserId != currentUserId) { + if (debug) { + Log.d(TAG, "UID mismatch. SystemUI is running uid=" + processUserId + + " and the current user is uid=" + currentUserId); + } + return false; + } + return true; + } + } + + /** + * Implementation of {@link android.app.ITaskStackListener} to listen task stack changes from + * ActivityManagerService. + * This simply passes callbacks to listeners through {@link H}. + * */ + private android.app.TaskStackListener mTaskStackListener = new android.app.TaskStackListener() { + + private final List<SystemServicesProxy.TaskStackListener> mTmpListeners = new ArrayList<>(); + + @Override + public void onTaskStackChanged() throws RemoteException { + // Call the task changed callback for the non-ui thread listeners first + synchronized (mTaskStackListeners) { + mTmpListeners.clear(); + mTmpListeners.addAll(mTaskStackListeners); + } + for (int i = mTmpListeners.size() - 1; i >= 0; i--) { + mTmpListeners.get(i).onTaskStackChangedBackground(); + } + + mHandler.removeMessages(H.ON_TASK_STACK_CHANGED); + mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED); + } + + @Override + public void onActivityPinned(String packageName, int userId, int taskId, int stackId) + throws RemoteException { + mHandler.removeMessages(H.ON_ACTIVITY_PINNED); + mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, + new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget(); + } + + @Override + public void onActivityUnpinned() throws RemoteException { + mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED); + mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED); + } + + @Override + public void onPinnedActivityRestartAttempt(boolean clearedTask) + throws RemoteException{ + mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT); + mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, clearedTask ? 1 : 0, 0) + .sendToTarget(); + } + + @Override + public void onPinnedStackAnimationStarted() throws RemoteException { + mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED); + mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED); + } + + @Override + public void onPinnedStackAnimationEnded() throws RemoteException { + mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED); + mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED); + } + + @Override + public void onActivityForcedResizable(String packageName, int taskId, int reason) + throws RemoteException { + mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName) + .sendToTarget(); + } + + @Override + public void onActivityDismissingDockedStack() throws RemoteException { + mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK); + } + + @Override + public void onActivityLaunchOnSecondaryDisplayFailed() throws RemoteException { + mHandler.sendEmptyMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED); + } + + @Override + public void onTaskProfileLocked(int taskId, int userId) { + mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget(); + } + + @Override + public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) + throws RemoteException { + mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget(); + } + }; + private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener = (String name, Drawable picture, String userAccount) -> mCurrentUserId = mAm.getCurrentUser(); + /** + * List of {@link TaskStackListener} registered from {@link #registerTaskStackListener}. + */ + private List<TaskStackListener> mTaskStackListeners = new ArrayList<>(); + /** Private constructor */ private SystemServicesProxy(Context context) { mContext = context.getApplicationContext(); @@ -144,18 +308,23 @@ public class SystemServicesProxy { mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); mIam = ActivityManager.getService(); mPm = context.getPackageManager(); + mDrawableFactory = IconDrawableFactory.newInstance(context); mIpm = AppGlobals.getPackageManager(); mAssistUtils = new AssistUtils(context); mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mIwm = WindowManagerGlobal.getWindowManagerService(); + mKgm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); mUm = UserManager.get(context); mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.checkService(DreamService.DREAM_SERVICE)); mDisplay = mWm.getDefaultDisplay(); mRecentsPackage = context.getPackageName(); + mHasFreeformWorkspaceSupport = + mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT) || + Settings.Global.getInt(context.getContentResolver(), + DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0; mIsSafeMode = mPm.isSafeMode(); mCurrentUserId = mAm.getCurrentUser(); - mTaskStackChangeListeners = new TaskStackChangeListeners(Looper.getMainLooper()); // Get the dummy thumbnail width/heights Resources res = context.getResources(); @@ -170,11 +339,23 @@ public class SystemServicesProxy { mBgProtectionPaint.setColor(0xFFffffff); mBgProtectionCanvas = new Canvas(); + // Resolve the assist intent + mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.myUserId()); + // Since SystemServicesProxy can be accessed from a per-SysUI process component, create a // per-process listener to keep track of the current user id to reduce the number of binder // calls to fetch it. UserInfoController userInfoController = Dependency.get(UserInfoController.class); userInfoController.addCallback(mOnUserInfoChangedListener); + + if (RecentsDebugFlags.Static.EnableMockTasks) { + // Create a dummy icon + mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + mDummyIcon.eraseColor(0xFF999999); + } + + Collections.addAll(sRecentsBlacklist, + res.getStringArray(R.array.recents_blacklist_array)); } /** @@ -196,6 +377,110 @@ public class SystemServicesProxy { } /** + * @return whether the provided {@param className} is blacklisted + */ + public boolean isBlackListedActivity(String className) { + return sRecentsBlacklist.contains(className); + } + + /** + * Returns a list of the recents tasks. + * + * @param includeFrontMostExcludedTask if set, will ensure that the front most excluded task + * will be visible, otherwise no excluded tasks will be + * visible. + */ + public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId, + boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) { + if (mAm == null) return null; + + // If we are mocking, then create some recent tasks + if (RecentsDebugFlags.Static.EnableMockTasks) { + ArrayList<ActivityManager.RecentTaskInfo> tasks = + new ArrayList<ActivityManager.RecentTaskInfo>(); + int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.MockTaskCount); + for (int i = 0; i < count; i++) { + // Create a dummy component name + int packageIndex = i % RecentsDebugFlags.Static.MockTasksPackageCount; + ComponentName cn = new ComponentName("com.android.test" + packageIndex, + "com.android.test" + i + ".Activity"); + String description = "" + i + " - " + + Long.toString(Math.abs(new Random().nextLong()), 36); + // Create the recent task info + ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); + rti.id = rti.persistentId = rti.affiliatedTaskId = i; + rti.baseIntent = new Intent(); + rti.baseIntent.setComponent(cn); + rti.description = description; + rti.firstActiveTime = rti.lastActiveTime = i; + if (i % 2 == 0) { + rti.taskDescription = new ActivityManager.TaskDescription(description, + Bitmap.createBitmap(mDummyIcon), null, + 0xFF000000 | (0xFFFFFF & new Random().nextInt()), + 0xFF000000 | (0xFFFFFF & new Random().nextInt()), + 0, 0); + } else { + rti.taskDescription = new ActivityManager.TaskDescription(); + } + tasks.add(rti); + } + return tasks; + } + + // Remove home/recents/excluded tasks + int minNumTasksToQuery = 10; + int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks); + int flags = ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS | + ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK | + ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS | + ActivityManager.RECENT_IGNORE_UNAVAILABLE | + ActivityManager.RECENT_INCLUDE_PROFILES; + if (includeFrontMostExcludedTask) { + flags |= ActivityManager.RECENT_WITH_EXCLUDED; + } + List<ActivityManager.RecentTaskInfo> tasks = null; + try { + tasks = mAm.getRecentTasksForUser(numTasksToQuery, flags, userId); + } catch (Exception e) { + Log.e(TAG, "Failed to get recent tasks", e); + } + + // Break early if we can't get a valid set of tasks + if (tasks == null) { + return new ArrayList<>(); + } + + boolean isFirstValidTask = true; + Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator(); + while (iter.hasNext()) { + ActivityManager.RecentTaskInfo t = iter.next(); + + // NOTE: The order of these checks happens in the expected order of the traversal of the + // tasks + + // Remove the task if it or it's package are blacklsited + if (sRecentsBlacklist.contains(t.realActivity.getClassName()) || + sRecentsBlacklist.contains(t.realActivity.getPackageName())) { + iter.remove(); + continue; + } + + // Remove the task if it is marked as excluded, unless it is the first most task and we + // are requested to include it + boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; + isExcluded |= quietProfileIds.contains(t.userId); + if (isExcluded && (!isFirstValidTask || !includeFrontMostExcludedTask)) { + iter.remove(); + } + + isFirstValidTask = false; + } + + return tasks.subList(0, Math.min(tasks.size(), numLatestTasks)); + } + + /** * Returns the top running task. */ public ActivityManager.RunningTaskInfo getRunningTask() { @@ -287,6 +572,13 @@ public class SystemServicesProxy { } /** + * Returns whether this device has freeform workspaces. + */ + public boolean hasFreeformWorkspaceSupport() { + return mHasFreeformWorkspaceSupport; + } + + /** * Returns whether this device is in the safe mode. */ public boolean isInSafeMode() { @@ -354,7 +646,7 @@ public class SystemServicesProxy { */ public boolean hasSoftNavigationBar() { try { - return mIwm.hasNavigationBar(); + return WindowManagerGlobal.getWindowManagerService().hasNavigationBar(); } catch (RemoteException e) { e.printStackTrace(); } @@ -397,6 +689,43 @@ public class SystemServicesProxy { } } + /** Returns the top task thumbnail for the given task id */ + public ThumbnailData getTaskThumbnail(int taskId, boolean reduced) { + if (mAm == null) return null; + + // If we are mocking, then just return a dummy thumbnail + if (RecentsDebugFlags.Static.EnableMockTasks) { + ThumbnailData thumbnailData = new ThumbnailData(); + thumbnailData.thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth, + mDummyThumbnailHeight, Bitmap.Config.ARGB_8888); + thumbnailData.thumbnail.eraseColor(0xff333333); + return thumbnailData; + } + + return getThumbnail(taskId, reduced); + } + + /** + * Returns a task thumbnail from the activity manager + */ + public @NonNull ThumbnailData getThumbnail(int taskId, boolean reducedResolution) { + if (mAm == null) { + return new ThumbnailData(); + } + + ActivityManager.TaskSnapshot snapshot = null; + try { + snapshot = ActivityManager.getService().getTaskSnapshot(taskId, reducedResolution); + } catch (RemoteException e) { + Log.w(TAG, "Failed to retrieve snapshot", e); + } + if (snapshot != null) { + return ThumbnailData.createFromTaskSnapshot(snapshot); + } else { + return new ThumbnailData(); + } + } + /** Set the task's windowing mode. */ public void setTaskWindowingMode(int taskId, int windowingMode) { if (mIam == null) return; @@ -411,14 +740,11 @@ public class SystemServicesProxy { /** Removes the task */ public void removeTask(final int taskId) { if (mAm == null) return; + if (RecentsDebugFlags.Static.EnableMockTasks) return; // Remove the task. mUiOffloadThread.submit(() -> { - try { - mIam.removeTask(taskId); - } catch (RemoteException e) { - e.printStackTrace(); - } + mAm.removeTask(taskId); }); } @@ -434,6 +760,145 @@ public class SystemServicesProxy { }); } + /** + * Returns the activity info for a given component name. + * + * @param cn The component name of the activity. + * @param userId The userId of the user that this is for. + */ + public ActivityInfo getActivityInfo(ComponentName cn, int userId) { + if (mIpm == null) return null; + if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo(); + + try { + return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId); + } catch (RemoteException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Returns the activity info for a given component name. + * + * @param cn The component name of the activity. + */ + public ActivityInfo getActivityInfo(ComponentName cn) { + if (mPm == null) return null; + if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo(); + + try { + return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Returns the activity label, badging if necessary. + */ + public String getBadgedActivityLabel(ActivityInfo info, int userId) { + if (mPm == null) return null; + + // If we are mocking, then return a mock label + if (RecentsDebugFlags.Static.EnableMockTasks) { + return "Recent Task: " + userId; + } + + return getBadgedLabel(info.loadLabel(mPm).toString(), userId); + } + + /** + * Returns the application label, badging if necessary. + */ + public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) { + if (mPm == null) return null; + + // If we are mocking, then return a mock label + if (RecentsDebugFlags.Static.EnableMockTasks) { + return "Recent Task App: " + userId; + } + + return getBadgedLabel(appInfo.loadLabel(mPm).toString(), userId); + } + + /** + * Returns the content description for a given task, badging it if necessary. The content + * description joins the app and activity labels. + */ + public String getBadgedContentDescription(ActivityInfo info, int userId, + ActivityManager.TaskDescription td, Resources res) { + // If we are mocking, then return a mock label + if (RecentsDebugFlags.Static.EnableMockTasks) { + return "Recent Task Content Description: " + userId; + } + + String activityLabel; + if (td != null && td.getLabel() != null) { + activityLabel = td.getLabel(); + } else { + activityLabel = info.loadLabel(mPm).toString(); + } + String applicationLabel = info.applicationInfo.loadLabel(mPm).toString(); + String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId); + return applicationLabel.equals(activityLabel) ? badgedApplicationLabel + : res.getString(R.string.accessibility_recents_task_header, + badgedApplicationLabel, activityLabel); + } + + /** + * Returns the activity icon for the ActivityInfo for a user, badging if + * necessary. + */ + public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) { + if (mPm == null) return null; + + // If we are mocking, then return a mock label + if (RecentsDebugFlags.Static.EnableMockTasks) { + return new ColorDrawable(0xFF666666); + } + + return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId); + } + + /** + * Returns the application icon for the ApplicationInfo for a user, badging if + * necessary. + */ + public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) { + if (mPm == null) return null; + + // If we are mocking, then return a mock label + if (RecentsDebugFlags.Static.EnableMockTasks) { + return new ColorDrawable(0xFF666666); + } + + return mDrawableFactory.getBadgedIcon(appInfo, userId); + } + + /** + * Returns the task description icon, loading and badging it if it necessary. + */ + public Drawable getBadgedTaskDescriptionIcon(ActivityManager.TaskDescription taskDescription, + int userId, Resources res) { + + // If we are mocking, then return a mock label + if (RecentsDebugFlags.Static.EnableMockTasks) { + return new ColorDrawable(0xFF666666); + } + + Bitmap tdIcon = taskDescription.getInMemoryIcon(); + if (tdIcon == null) { + tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon( + taskDescription.getIconFilename(), userId); + } + if (tdIcon != null) { + return getBadgedIcon(new BitmapDrawable(res, tdIcon), userId); + } + return null; + } + public ActivityManager.TaskDescription getTaskDescription(int taskId) { try { return mIam.getTaskDescription(taskId); @@ -443,6 +908,85 @@ public class SystemServicesProxy { } /** + * Returns the given icon for a user, badging if necessary. + */ + private Drawable getBadgedIcon(Drawable icon, int userId) { + if (userId != UserHandle.myUserId()) { + icon = mPm.getUserBadgedIcon(icon, new UserHandle(userId)); + } + return icon; + } + + /** + * Returns a banner used on TV for the specified Activity. + */ + public Drawable getActivityBanner(ActivityInfo info) { + if (mPm == null) return null; + + // If we are mocking, then return a mock banner + if (RecentsDebugFlags.Static.EnableMockTasks) { + return new ColorDrawable(0xFF666666); + } + + Drawable banner = info.loadBanner(mPm); + return banner; + } + + /** + * Returns a logo used on TV for the specified Activity. + */ + public Drawable getActivityLogo(ActivityInfo info) { + if (mPm == null) return null; + + // If we are mocking, then return a mock logo + if (RecentsDebugFlags.Static.EnableMockTasks) { + return new ColorDrawable(0xFF666666); + } + + Drawable logo = info.loadLogo(mPm); + return logo; + } + + + /** + * Returns the given label for a user, badging if necessary. + */ + private String getBadgedLabel(String label, int userId) { + if (userId != UserHandle.myUserId()) { + label = mPm.getUserBadgedLabel(label, new UserHandle(userId)).toString(); + } + return label; + } + + /** + * Returns whether the provided {@param userId} is currently locked (and showing Keyguard). + */ + public boolean isDeviceLocked(int userId) { + if (mKgm == null) { + return false; + } + return mKgm.isDeviceLocked(userId); + } + + /** Returns the package name of the home activity. */ + public String getHomeActivityPackageName() { + if (mPm == null) return null; + if (RecentsDebugFlags.Static.EnableMockTasks) return null; + + ArrayList<ResolveInfo> homeActivities = new ArrayList<>(); + ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities); + if (defaultHomeActivity != null) { + return defaultHomeActivity.getPackageName(); + } else if (homeActivities.size() == 1) { + ResolveInfo info = homeActivities.get(0); + if (info.activityInfo != null) { + return info.activityInfo.packageName; + } + } + return null; + } + + /** * Returns whether the provided {@param userId} represents the system user. */ public boolean isSystemUser(int userId) { @@ -547,7 +1091,7 @@ public class SystemServicesProxy { ActivityManager.StackInfo stackInfo = mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS); if (stackInfo == null) { - stackInfo = mIam.getStackInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + stackInfo = mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD); } if (stackInfo != null) { windowRect.set(stackInfo.bounds); @@ -630,11 +1174,19 @@ public class SystemServicesProxy { * Registers a task stack listener with the system. * This should be called on the main thread. */ - public void registerTaskStackListener(TaskStackChangeListener listener) { + public void registerTaskStackListener(TaskStackListener listener) { if (mIam == null) return; - synchronized (mTaskStackChangeListeners) { - mTaskStackChangeListeners.addListener(mIam, listener); + synchronized (mTaskStackListeners) { + mTaskStackListeners.add(listener); + if (mTaskStackListeners.size() == 1) { + // Register mTaskStackListener to IActivityManager only once if needed. + try { + mIam.registerTaskStackListener(mTaskStackListener); + } catch (Exception e) { + Log.w(TAG, "Failed to call registerTaskStackListener", e); + } + } } } @@ -643,7 +1195,7 @@ public class SystemServicesProxy { return; } try { - mIwm.endProlongedAnimations(); + WindowManagerGlobal.getWindowManagerService().endProlongedAnimations(); } catch (Exception e) { e.printStackTrace(); } @@ -653,7 +1205,7 @@ public class SystemServicesProxy { if (mWm == null) return; try { - mIwm.registerDockedStackListener(listener); + WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(listener); } catch (Exception e) { e.printStackTrace(); } @@ -680,7 +1232,8 @@ public class SystemServicesProxy { if (mWm == null) return; try { - mIwm.getStableInsets(Display.DEFAULT_DISPLAY, outStableInsets); + WindowManagerGlobal.getWindowManagerService().getStableInsets(Display.DEFAULT_DISPLAY, + outStableInsets); } catch (Exception e) { e.printStackTrace(); } @@ -690,7 +1243,9 @@ public class SystemServicesProxy { IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener, boolean scaleUp) { try { - mIwm.overridePendingAppTransitionMultiThumbFuture(future, animStartedListener, scaleUp); + WindowManagerGlobal.getWindowManagerService() + .overridePendingAppTransitionMultiThumbFuture(future, animStartedListener, + scaleUp); } catch (RemoteException e) { Log.w(TAG, "Failed to override transition: " + e); } @@ -699,27 +1254,23 @@ public class SystemServicesProxy { /** * Updates the visibility of recents. */ - public void setRecentsVisibility(final boolean visible) { - mUiOffloadThread.submit(() -> { - try { - mIwm.setRecentsVisibility(visible); - } catch (RemoteException e) { - Log.e(TAG, "Unable to reach window manager", e); - } - }); + public void setRecentsVisibility(boolean visible) { + try { + mIwm.setRecentsVisibility(visible); + } catch (RemoteException e) { + Log.e(TAG, "Unable to reach window manager", e); + } } /** * Updates the visibility of the picture-in-picture. */ - public void setPipVisibility(final boolean visible) { - mUiOffloadThread.submit(() -> { - try { - mIwm.setPipVisibility(visible); - } catch (RemoteException e) { - Log.e(TAG, "Unable to reach window manager", e); - } - }); + public void setPipVisibility(boolean visible) { + try { + mIwm.setPipVisibility(visible); + } catch (RemoteException e) { + Log.e(TAG, "Unable to reach window manager", e); + } } public boolean isDreaming() { @@ -741,7 +1292,126 @@ public class SystemServicesProxy { }); } + public void updateOverviewLastStackActiveTimeAsync(long newLastStackActiveTime, + int currentUserId) { + mUiOffloadThread.submit(() -> { + Settings.Secure.putLongForUser(mContext.getContentResolver(), + Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, newLastStackActiveTime, currentUserId); + }); + } + public interface StartActivityFromRecentsResultListener { void onStartActivityResult(boolean succeeded); } + + private class PinnedActivityInfo { + final String mPackageName; + final int mUserId; + final int mTaskId; + final int mStackId; + + PinnedActivityInfo(String packageName, int userId, int taskId, int stackId) { + mPackageName = packageName; + mUserId = userId; + mTaskId = taskId; + mStackId = stackId; + } + } + + private final class H extends Handler { + private static final int ON_TASK_STACK_CHANGED = 1; + private static final int ON_TASK_SNAPSHOT_CHANGED = 2; + private static final int ON_ACTIVITY_PINNED = 3; + private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 4; + private static final int ON_PINNED_STACK_ANIMATION_ENDED = 5; + private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6; + private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7; + private static final int ON_TASK_PROFILE_LOCKED = 8; + private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9; + private static final int ON_ACTIVITY_UNPINNED = 10; + private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11; + + @Override + public void handleMessage(Message msg) { + synchronized (mTaskStackListeners) { + switch (msg.what) { + case ON_TASK_STACK_CHANGED: { + Trace.beginSection("onTaskStackChanged"); + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskStackChanged(); + } + Trace.endSection(); + break; + } + case ON_TASK_SNAPSHOT_CHANGED: { + Trace.beginSection("onTaskSnapshotChanged"); + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1, + (TaskSnapshot) msg.obj); + } + Trace.endSection(); + break; + } + case ON_ACTIVITY_PINNED: { + final PinnedActivityInfo info = (PinnedActivityInfo) msg.obj; + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityPinned( + info.mPackageName, info.mUserId, info.mTaskId, info.mStackId); + } + break; + } + case ON_ACTIVITY_UNPINNED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityUnpinned(); + } + break; + } + case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onPinnedActivityRestartAttempt( + msg.arg1 != 0); + } + break; + } + case ON_PINNED_STACK_ANIMATION_STARTED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onPinnedStackAnimationStarted(); + } + break; + } + case ON_PINNED_STACK_ANIMATION_ENDED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onPinnedStackAnimationEnded(); + } + break; + } + case ON_ACTIVITY_FORCED_RESIZABLE: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityForcedResizable( + (String) msg.obj, msg.arg1, msg.arg2); + } + break; + } + case ON_ACTIVITY_DISMISSING_DOCKED_STACK: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityDismissingDockedStack(); + } + break; + } + case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityLaunchOnSecondaryDisplayFailed(); + } + break; + } + case ON_TASK_PROFILE_LOCKED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2); + } + break; + } + } + } + } + } } diff --git a/com/android/systemui/recents/misc/TaskStackChangeListener.java b/com/android/systemui/recents/misc/TaskStackChangeListener.java deleted file mode 100644 index 6d0952ab..00000000 --- a/com/android/systemui/recents/misc/TaskStackChangeListener.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2017 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.systemui.recents.misc; - -import android.app.ActivityManager.TaskSnapshot; -import android.content.Context; -import android.os.UserHandle; -import android.util.Log; - -/** - * An abstract class to track task stack changes. - * Classes should implement this instead of {@link android.app.ITaskStackListener} - * to reduce IPC calls from system services. These callbacks will be called on the main thread. - */ -public abstract class TaskStackChangeListener { - - /** - * NOTE: This call is made of the thread that the binder call comes in on. - */ - public void onTaskStackChangedBackground() { } - public void onTaskStackChanged() { } - public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { } - public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { } - public void onActivityUnpinned() { } - public void onPinnedActivityRestartAttempt(boolean clearedTask) { } - public void onPinnedStackAnimationStarted() { } - public void onPinnedStackAnimationEnded() { } - public void onActivityForcedResizable(String packageName, int taskId, int reason) { } - public void onActivityDismissingDockedStack() { } - public void onActivityLaunchOnSecondaryDisplayFailed() { } - public void onTaskProfileLocked(int taskId, int userId) { } - - /** - * Checks that the current user matches the user's SystemUI process. Since - * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of - * TaskStackChangeListener should make this call to verify that we don't act on events from other - * user's processes. - */ - protected final boolean checkCurrentUserId(Context context, boolean debug) { - int processUserId = UserHandle.myUserId(); - int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser(); - if (processUserId != currentUserId) { - if (debug) { - Log.d(SystemServicesProxy.TAG, "UID mismatch. SystemUI is running uid=" + processUserId - + " and the current user is uid=" + currentUserId); - } - return false; - } - return true; - } -} diff --git a/com/android/systemui/recents/misc/TaskStackChangeListeners.java b/com/android/systemui/recents/misc/TaskStackChangeListeners.java deleted file mode 100644 index 8eb70f04..00000000 --- a/com/android/systemui/recents/misc/TaskStackChangeListeners.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright (C) 2017 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.systemui.recents.misc; - -import android.app.ActivityManager.TaskSnapshot; -import android.app.IActivityManager; -import android.app.TaskStackListener; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.os.Trace; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -/** - * Tracks all the task stack listeners - */ -public class TaskStackChangeListeners extends TaskStackListener { - - private static final String TAG = TaskStackChangeListeners.class.getSimpleName(); - - /** - * List of {@link TaskStackChangeListener} registered from {@link #addListener}. - */ - private final List<TaskStackChangeListener> mTaskStackListeners = new ArrayList<>(); - private final List<TaskStackChangeListener> mTmpListeners = new ArrayList<>(); - - private final Handler mHandler; - - public TaskStackChangeListeners(Looper looper) { - mHandler = new H(looper); - } - - public void addListener(IActivityManager am, TaskStackChangeListener listener) { - mTaskStackListeners.add(listener); - if (mTaskStackListeners.size() == 1) { - // Register mTaskStackListener to IActivityManager only once if needed. - try { - am.registerTaskStackListener(this); - } catch (Exception e) { - Log.w(TAG, "Failed to call registerTaskStackListener", e); - } - } - } - - @Override - public void onTaskStackChanged() throws RemoteException { - // Call the task changed callback for the non-ui thread listeners first - synchronized (mTaskStackListeners) { - mTmpListeners.clear(); - mTmpListeners.addAll(mTaskStackListeners); - } - for (int i = mTmpListeners.size() - 1; i >= 0; i--) { - mTmpListeners.get(i).onTaskStackChangedBackground(); - } - - mHandler.removeMessages(H.ON_TASK_STACK_CHANGED); - mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED); - } - - @Override - public void onActivityPinned(String packageName, int userId, int taskId, int stackId) - throws RemoteException { - mHandler.removeMessages(H.ON_ACTIVITY_PINNED); - mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, - new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget(); - } - - @Override - public void onActivityUnpinned() throws RemoteException { - mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED); - mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED); - } - - @Override - public void onPinnedActivityRestartAttempt(boolean clearedTask) - throws RemoteException{ - mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT); - mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, clearedTask ? 1 : 0, 0) - .sendToTarget(); - } - - @Override - public void onPinnedStackAnimationStarted() throws RemoteException { - mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED); - mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED); - } - - @Override - public void onPinnedStackAnimationEnded() throws RemoteException { - mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED); - mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED); - } - - @Override - public void onActivityForcedResizable(String packageName, int taskId, int reason) - throws RemoteException { - mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName) - .sendToTarget(); - } - - @Override - public void onActivityDismissingDockedStack() throws RemoteException { - mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK); - } - - @Override - public void onActivityLaunchOnSecondaryDisplayFailed() throws RemoteException { - mHandler.sendEmptyMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED); - } - - @Override - public void onTaskProfileLocked(int taskId, int userId) throws RemoteException { - mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget(); - } - - @Override - public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) - throws RemoteException { - mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget(); - } - - private final class H extends Handler { - private static final int ON_TASK_STACK_CHANGED = 1; - private static final int ON_TASK_SNAPSHOT_CHANGED = 2; - private static final int ON_ACTIVITY_PINNED = 3; - private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 4; - private static final int ON_PINNED_STACK_ANIMATION_ENDED = 5; - private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6; - private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7; - private static final int ON_TASK_PROFILE_LOCKED = 8; - private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9; - private static final int ON_ACTIVITY_UNPINNED = 10; - private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11; - - public H(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - synchronized (mTaskStackListeners) { - switch (msg.what) { - case ON_TASK_STACK_CHANGED: { - Trace.beginSection("onTaskStackChanged"); - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onTaskStackChanged(); - } - Trace.endSection(); - break; - } - case ON_TASK_SNAPSHOT_CHANGED: { - Trace.beginSection("onTaskSnapshotChanged"); - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1, - (TaskSnapshot) msg.obj); - } - Trace.endSection(); - break; - } - case ON_ACTIVITY_PINNED: { - final PinnedActivityInfo info = (PinnedActivityInfo) msg.obj; - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onActivityPinned( - info.mPackageName, info.mUserId, info.mTaskId, info.mStackId); - } - break; - } - case ON_ACTIVITY_UNPINNED: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onActivityUnpinned(); - } - break; - } - case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onPinnedActivityRestartAttempt( - msg.arg1 != 0); - } - break; - } - case ON_PINNED_STACK_ANIMATION_STARTED: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onPinnedStackAnimationStarted(); - } - break; - } - case ON_PINNED_STACK_ANIMATION_ENDED: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onPinnedStackAnimationEnded(); - } - break; - } - case ON_ACTIVITY_FORCED_RESIZABLE: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onActivityForcedResizable( - (String) msg.obj, msg.arg1, msg.arg2); - } - break; - } - case ON_ACTIVITY_DISMISSING_DOCKED_STACK: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onActivityDismissingDockedStack(); - } - break; - } - case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onActivityLaunchOnSecondaryDisplayFailed(); - } - break; - } - case ON_TASK_PROFILE_LOCKED: { - for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2); - } - break; - } - } - } - } - } - - private static class PinnedActivityInfo { - final String mPackageName; - final int mUserId; - final int mTaskId; - final int mStackId; - - PinnedActivityInfo(String packageName, int userId, int taskId, int stackId) { - mPackageName = packageName; - mUserId = userId; - mTaskId = taskId; - mStackId = stackId; - } - } -} diff --git a/com/android/systemui/shared/recents/utilities/Utilities.java b/com/android/systemui/recents/misc/Utilities.java index a5d19639..4349e30f 100644 --- a/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/com/android/systemui/recents/misc/Utilities.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.shared.recents.utilities; +package com.android.systemui.recents.misc; import android.animation.Animator; import android.animation.AnimatorSet; @@ -38,8 +38,12 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewStub; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.views.TaskViewTransform; + import java.util.ArrayList; import java.util.Collections; +import java.util.List; /* Common code */ public class Utilities { @@ -72,6 +76,7 @@ public class Utilities { public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator(); public static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect()); + public static final Rect EMPTY_RECT = new Rect(); /** * @return the first parent walking up the view hierarchy that has the given class type. @@ -248,6 +253,24 @@ public class Utilities { } /** + * Updates {@param transforms} to be the same size as {@param tasks}. + */ + public static void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) { + // We can reuse the task transforms where possible to reduce object allocation + int taskTransformCount = transforms.size(); + int taskCount = tasks.size(); + if (taskTransformCount < taskCount) { + // If there are less transforms than tasks, then add as many transforms as necessary + for (int i = taskTransformCount; i < taskCount; i++) { + transforms.add(new TaskViewTransform()); + } + } else if (taskTransformCount > taskCount) { + // If there are more transforms than tasks, then just subset the transform list + transforms.subList(taskCount, taskTransformCount).clear(); + } + } + + /** * Used for debugging, converts DP to PX. */ public static float dpToPx(Resources res, float dp) { diff --git a/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java b/com/android/systemui/recents/model/HighResThumbnailLoader.java index 24ba9984..6414ea1e 100644 --- a/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java +++ b/com/android/systemui/recents/model/HighResThumbnailLoader.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.systemui.shared.recents.model; +package com.android.systemui.recents.model; import static android.os.Process.setThreadPriority; @@ -25,8 +25,10 @@ import android.util.ArraySet; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.shared.recents.model.Task.TaskCallbacks; -import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.recents.Recents; +import com.android.systemui.recents.RecentsConfiguration; +import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.model.Task.TaskCallbacks; import java.util.ArrayDeque; import java.util.ArrayList; @@ -36,8 +38,6 @@ import java.util.ArrayList; */ public class HighResThumbnailLoader implements TaskCallbacks { - private final ActivityManagerWrapper mActivityManager; - @GuardedBy("mLoadQueue") private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>(); @GuardedBy("mLoadQueue") @@ -46,21 +46,20 @@ public class HighResThumbnailLoader implements TaskCallbacks { private boolean mLoaderIdling; private final ArrayList<Task> mVisibleTasks = new ArrayList<>(); - private final Thread mLoadThread; private final Handler mMainThreadHandler; + private final SystemServicesProxy mSystemServicesProxy; private final boolean mIsLowRamDevice; private boolean mLoading; private boolean mVisible; private boolean mFlingingFast; private boolean mTaskLoadQueueIdle; - public HighResThumbnailLoader(ActivityManagerWrapper activityManager, Looper looper, - boolean isLowRamDevice) { - mActivityManager = activityManager; + public HighResThumbnailLoader(SystemServicesProxy ssp, Looper looper, boolean isLowRamDevice) { mMainThreadHandler = new Handler(looper); mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader"); mLoadThread.start(); + mSystemServicesProxy = ssp; mIsLowRamDevice = isLowRamDevice; } @@ -221,7 +220,7 @@ public class HighResThumbnailLoader implements TaskCallbacks { } private void loadTask(Task t) { - ThumbnailData thumbnail = mActivityManager.getTaskThumbnail(t.key.id, + ThumbnailData thumbnail = mSystemServicesProxy.getTaskThumbnail(t.key.id, false /* reducedResolution */); mMainThreadHandler.post(() -> { synchronized (mLoadQueue) { diff --git a/com/android/systemui/recents/model/RecentsPackageMonitor.java b/com/android/systemui/recents/model/RecentsPackageMonitor.java new file mode 100644 index 00000000..308cece1 --- /dev/null +++ b/com/android/systemui/recents/model/RecentsPackageMonitor.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 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.systemui.recents.model; + +import android.content.Context; +import android.os.UserHandle; + +import com.android.internal.content.PackageMonitor; +import com.android.systemui.recents.events.EventBus; +import com.android.systemui.recents.events.activity.PackagesChangedEvent; +import com.android.systemui.recents.misc.ForegroundThread; + +/** + * The package monitor listens for changes from PackageManager to update the contents of the + * Recents list. + */ +public class RecentsPackageMonitor extends PackageMonitor { + + /** Registers the broadcast receivers with the specified callbacks. */ + public void register(Context context) { + try { + // We register for events from all users, but will cross-reference them with + // packages for the current user and any profiles they have. Ensure that events are + // handled in a background thread. + register(context, ForegroundThread.get().getLooper(), UserHandle.ALL, true); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + + /** Unregisters the broadcast receivers. */ + @Override + public void unregister() { + try { + super.unregister(); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + // Notify callbacks on the main thread that a package has changed + final int eventUserId = getChangingUserId(); + EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId)); + } + + @Override + public boolean onPackageChanged(String packageName, int uid, String[] components) { + onPackageModified(packageName); + return true; + } + + @Override + public void onPackageModified(String packageName) { + // Notify callbacks on the main thread that a package has changed + final int eventUserId = getChangingUserId(); + EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId)); + } +} diff --git a/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/recents/model/RecentsTaskLoadPlan.java new file mode 100644 index 00000000..d5e03135 --- /dev/null +++ b/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2014 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.systemui.recents.model; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.UserInfo; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.provider.Settings.Secure; +import android.util.ArraySet; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; + +import com.android.systemui.Prefs; +import com.android.systemui.R; +import com.android.systemui.recents.Recents; +import com.android.systemui.recents.RecentsDebugFlags; +import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm; +import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +/** + * This class stores the loading state as it goes through multiple stages of loading: + * 1) preloadRawTasks() will load the raw set of recents tasks from the system + * 2) preloadPlan() will construct a new task stack with all metadata and only icons and + * thumbnails that are currently in the cache + * 3) executePlan() will actually load and fill in the icons and thumbnails according to the load + * options specified, such that we can transition into the Recents activity seamlessly + */ +public class RecentsTaskLoadPlan { + + private static int MIN_NUM_TASKS = 5; + private static int SESSION_BEGIN_TIME = 1000 /* ms/s */ * 60 /* s/min */ * 60 /* min/hr */ * + 6 /* hrs */; + + /** The set of conditions to load tasks. */ + public static class Options { + public int runningTaskId = -1; + public boolean loadIcons = true; + public boolean loadThumbnails = false; + public boolean onlyLoadForCache = false; + public boolean onlyLoadPausedActivities = false; + public int numVisibleTasks = 0; + public int numVisibleTaskThumbnails = 0; + } + + Context mContext; + + int mPreloadedUserId; + List<ActivityManager.RecentTaskInfo> mRawTasks; + TaskStack mStack; + ArraySet<Integer> mCurrentQuietProfiles = new ArraySet<Integer>(); + + /** Package level ctor */ + RecentsTaskLoadPlan(Context context) { + mContext = context; + } + + private void updateCurrentQuietProfilesCache(int currentUserId) { + mCurrentQuietProfiles.clear(); + + UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + List<UserInfo> profiles = userManager.getProfiles(currentUserId); + if (profiles != null) { + for (int i = 0; i < profiles.size(); i++) { + UserInfo user = profiles.get(i); + if (user.isManagedProfile() && user.isQuietModeEnabled()) { + mCurrentQuietProfiles.add(user.id); + } + } + } + } + + /** + * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent + * to most-recent order. + * + * Note: Do not lock, callers should synchronize on the loader before making this call. + */ + void preloadRawTasks(boolean includeFrontMostExcludedTask) { + SystemServicesProxy ssp = Recents.getSystemServices(); + int currentUserId = ssp.getCurrentUser(); + updateCurrentQuietProfilesCache(currentUserId); + mPreloadedUserId = currentUserId; + mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(), + currentUserId, includeFrontMostExcludedTask, mCurrentQuietProfiles); + + // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it + Collections.reverse(mRawTasks); + } + + /** + * Preloads the list of recent tasks from the system. After this call, the TaskStack will + * have a list of all the recent tasks with their metadata, not including icons or + * thumbnails which were not cached and have to be loaded. + * + * The tasks will be ordered by: + * - least-recent to most-recent stack tasks + * - least-recent to most-recent freeform tasks + * + * Note: Do not lock, since this can be calling back to the loader, which separately also drives + * this call (callers should synchronize on the loader before making this call). + */ + void preloadPlan(RecentsTaskLoader loader, int runningTaskId, + boolean includeFrontMostExcludedTask) { + Resources res = mContext.getResources(); + ArrayList<Task> allTasks = new ArrayList<>(); + if (mRawTasks == null) { + preloadRawTasks(includeFrontMostExcludedTask); + } + + SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>(); + SparseIntArray affiliatedTaskCounts = new SparseIntArray(); + SparseBooleanArray lockedUsers = new SparseBooleanArray(); + String dismissDescFormat = mContext.getString( + R.string.accessibility_recents_item_will_be_dismissed); + String appInfoDescFormat = mContext.getString( + R.string.accessibility_recents_item_open_app_info); + int currentUserId = mPreloadedUserId; + long legacyLastStackActiveTime = migrateLegacyLastStackActiveTime(currentUserId); + long lastStackActiveTime = Settings.Secure.getLongForUser(mContext.getContentResolver(), + Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime, currentUserId); + if (RecentsDebugFlags.Static.EnableMockTasks) { + lastStackActiveTime = 0; + } + long newLastStackActiveTime = -1; + int taskCount = mRawTasks.size(); + for (int i = 0; i < taskCount; i++) { + ActivityManager.RecentTaskInfo t = mRawTasks.get(i); + + // Compose the task key + final int windowingMode = t.configuration.windowConfiguration.getWindowingMode(); + Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, windowingMode, t.baseIntent, + t.userId, t.firstActiveTime, t.lastActiveTime); + + // This task is only shown in the stack if it satisfies the historical time or min + // number of tasks constraints. Freeform tasks are also always shown. + boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM; + boolean isStackTask; + if (Recents.getConfiguration().isGridEnabled) { + // When grid layout is enabled, we only show the first + // TaskGridLayoutAlgorithm.MAX_LAYOUT_FROM_HOME_TASK_COUNT} tasks. + isStackTask = t.lastActiveTime >= lastStackActiveTime && + i >= taskCount - TaskGridLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT; + } else if (Recents.getConfiguration().isLowRamDevice) { + // Show a max of 3 items + isStackTask = t.lastActiveTime >= lastStackActiveTime && + i >= taskCount - TaskStackLowRamLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT; + } else { + isStackTask = isFreeformTask || !isHistoricalTask(t) || + (t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS)); + } + boolean isLaunchTarget = taskKey.id == runningTaskId; + + // The last stack active time is the baseline for which we show visible tasks. Since + // the system will store all the tasks, we don't want to show the tasks prior to the + // last visible ones, otherwise, as you dismiss them, the previous tasks may satisfy + // the other stack-task constraints. + if (isStackTask && newLastStackActiveTime < 0) { + newLastStackActiveTime = t.lastActiveTime; + } + + // Load the title, icon, and color + ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey); + String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription); + String titleDescription = loader.getAndUpdateContentDescription(taskKey, + t.taskDescription, res); + String dismissDescription = String.format(dismissDescFormat, titleDescription); + String appInfoDescription = String.format(appInfoDescFormat, titleDescription); + Drawable icon = isStackTask + ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false) + : null; + ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey, + false /* loadIfNotCached */, false /* storeInCache */); + int activityColor = loader.getActivityPrimaryColor(t.taskDescription); + int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription); + boolean isSystemApp = (info != null) && + ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); + if (lockedUsers.indexOfKey(t.userId) < 0) { + lockedUsers.put(t.userId, Recents.getSystemServices().isDeviceLocked(t.userId)); + } + boolean isLocked = lockedUsers.get(t.userId); + + // Add the task to the stack + Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon, + thumbnail, title, titleDescription, dismissDescription, appInfoDescription, + activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp, + t.supportsSplitScreenMultiWindow, t.bounds, t.taskDescription, t.resizeMode, t.topActivity, + isLocked); + + allTasks.add(task); + affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1); + affiliatedTasks.put(taskKey.id, taskKey); + } + if (newLastStackActiveTime != -1) { + Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync( + newLastStackActiveTime, currentUserId); + } + + // Initialize the stacks + mStack = new TaskStack(); + mStack.setTasks(mContext, allTasks, false /* notifyStackChanges */); + } + + /** + * Called to apply the actual loading based on the specified conditions. + * + * Note: Do not lock, since this can be calling back to the loader, which separately also drives + * this call (callers should synchronize on the loader before making this call). + */ + void executePlan(Options opts, RecentsTaskLoader loader) { + Resources res = mContext.getResources(); + + // Iterate through each of the tasks and load them according to the load conditions. + ArrayList<Task> tasks = mStack.getStackTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + Task.TaskKey taskKey = task.key; + + boolean isRunningTask = (task.key.id == opts.runningTaskId); + boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks); + boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails); + + // If requested, skip the running task + if (opts.onlyLoadPausedActivities && isRunningTask) { + continue; + } + + if (opts.loadIcons && (isRunningTask || isVisibleTask)) { + if (task.icon == null) { + task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, res, + true); + } + } + if (opts.loadThumbnails && isVisibleThumbnail) { + task.thumbnail = loader.getAndUpdateThumbnail(taskKey, + true /* loadIfNotCached */, true /* storeInCache */); + } + } + } + + /** + * Returns the TaskStack from the preloaded list of recent tasks. + */ + public TaskStack getTaskStack() { + return mStack; + } + + /** + * Returns the raw list of recent tasks. + */ + public List<ActivityManager.RecentTaskInfo> getRawTasks() { + return mRawTasks; + } + + /** Returns whether there are any tasks in any stacks. */ + public boolean hasTasks() { + if (mStack != null) { + return mStack.getTaskCount() > 0; + } + return false; + } + + /** + * Returns whether this task is too old to be shown. + */ + private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) { + return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME); + } + + + /** + * Migrate the last active time from the prefs to the secure settings. + * + * The first time this runs, it will: + * 1) fetch the last stack active time from the prefs + * 2) set the prefs to the last stack active time for all users + * 3) clear the pref + * 4) return the last stack active time + * + * Subsequent calls to this will return zero. + */ + private long migrateLegacyLastStackActiveTime(int currentUserId) { + long legacyLastStackActiveTime = Prefs.getLong(mContext, + Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1); + if (legacyLastStackActiveTime != -1) { + Prefs.remove(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME); + UserManager userMgr = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + List<UserInfo> users = userMgr.getUsers(); + for (int i = 0; i < users.size(); i++) { + int userId = users.get(i).id; + if (userId != currentUserId) { + Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync( + legacyLastStackActiveTime, userId); + } + } + return legacyLastStackActiveTime; + } + return 0; + } +} diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java b/com/android/systemui/recents/model/RecentsTaskLoader.java index de4c72c2..1b893862 100644 --- a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java +++ b/com/android/systemui/recents/model/RecentsTaskLoader.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.shared.recents.model; +package com.android.systemui.recents.model; import android.app.ActivityManager; import android.content.ComponentCallbacks2; @@ -25,47 +25,238 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.Trace; import android.util.Log; import android.util.LruCache; import com.android.internal.annotations.GuardedBy; -import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.Options; -import com.android.systemui.shared.recents.model.Task.TaskKey; -import com.android.systemui.shared.recents.model.TaskKeyLruCache.EvictionCallback; -import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.PackageManagerWrapper; +import com.android.systemui.R; +import com.android.systemui.recents.Recents; +import com.android.systemui.recents.RecentsConfiguration; +import com.android.systemui.recents.RecentsDebugFlags; +import com.android.systemui.recents.events.activity.PackagesChangedEvent; +import com.android.systemui.recents.misc.SystemServicesProxy; import java.io.PrintWriter; import java.util.Map; +import java.util.concurrent.ConcurrentLinkedQueue; /** + * A Task load queue + */ +class TaskResourceLoadQueue { + + ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>(); + + /** Adds a new task to the load queue */ + void addTask(Task t) { + if (!mQueue.contains(t)) { + mQueue.add(t); + } + synchronized(this) { + notifyAll(); + } + } + + /** + * Retrieves the next task from the load queue, as well as whether we want that task to be + * force reloaded. + */ + Task nextTask() { + return mQueue.poll(); + } + + /** Removes a task from the load queue */ + void removeTask(Task t) { + mQueue.remove(t); + } + + /** Clears all the tasks from the load queue */ + void clearTasks() { + mQueue.clear(); + } + + /** Returns whether the load queue is empty */ + boolean isEmpty() { + return mQueue.isEmpty(); + } +} + +/** + * Task resource loader + */ +class BackgroundTaskLoader implements Runnable { + static String TAG = "TaskResourceLoader"; + static boolean DEBUG = false; + + Context mContext; + HandlerThread mLoadThread; + Handler mLoadThreadHandler; + Handler mMainThreadHandler; + + TaskResourceLoadQueue mLoadQueue; + TaskKeyLruCache<Drawable> mIconCache; + BitmapDrawable mDefaultIcon; + + boolean mStarted; + boolean mCancelled; + boolean mWaitingOnLoadQueue; + + private final OnIdleChangedListener mOnIdleChangedListener; + + /** Constructor, creates a new loading thread that loads task resources in the background */ + public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue, + TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon, + OnIdleChangedListener onIdleChangedListener) { + mLoadQueue = loadQueue; + mIconCache = iconCache; + mDefaultIcon = defaultIcon; + mMainThreadHandler = new Handler(); + mOnIdleChangedListener = onIdleChangedListener; + mLoadThread = new HandlerThread("Recents-TaskResourceLoader", + android.os.Process.THREAD_PRIORITY_BACKGROUND); + mLoadThread.start(); + mLoadThreadHandler = new Handler(mLoadThread.getLooper()); + } + + /** Restarts the loader thread */ + void start(Context context) { + mContext = context; + mCancelled = false; + if (!mStarted) { + // Start loading on the load thread + mStarted = true; + mLoadThreadHandler.post(this); + } else { + // Notify the load thread to start loading again + synchronized (mLoadThread) { + mLoadThread.notifyAll(); + } + } + } + + /** Requests the loader thread to stop after the current iteration */ + void stop() { + // Mark as cancelled for the thread to pick up + mCancelled = true; + // If we are waiting for the load queue for more tasks, then we can just reset the + // Context now, since nothing is using it + if (mWaitingOnLoadQueue) { + mContext = null; + } + } + + @Override + public void run() { + while (true) { + if (mCancelled) { + // We have to unset the context here, since the background thread may be using it + // when we call stop() + mContext = null; + // If we are cancelled, then wait until we are started again + synchronized(mLoadThread) { + try { + mLoadThread.wait(); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } else { + SystemServicesProxy ssp = Recents.getSystemServices(); + // If we've stopped the loader, then fall through to the above logic to wait on + // the load thread + if (ssp != null) { + processLoadQueueItem(ssp); + } + + // If there are no other items in the list, then just wait until something is added + if (!mCancelled && mLoadQueue.isEmpty()) { + synchronized(mLoadQueue) { + try { + mWaitingOnLoadQueue = true; + mMainThreadHandler.post( + () -> mOnIdleChangedListener.onIdleChanged(true)); + mLoadQueue.wait(); + mMainThreadHandler.post( + () -> mOnIdleChangedListener.onIdleChanged(false)); + mWaitingOnLoadQueue = false; + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } + } + } + } + + /** + * This needs to be in a separate method to work around an surprising interpreter behavior: + * The register will keep the local reference to cachedThumbnailData even if it falls out of + * scope. Putting it into a method fixes this issue. + */ + private void processLoadQueueItem(SystemServicesProxy ssp) { + // Load the next item from the queue + final Task t = mLoadQueue.nextTask(); + if (t != null) { + Drawable cachedIcon = mIconCache.get(t.key); + + // Load the icon if it is stale or we haven't cached one yet + if (cachedIcon == null) { + cachedIcon = ssp.getBadgedTaskDescriptionIcon(t.taskDescription, + t.key.userId, mContext.getResources()); + + if (cachedIcon == null) { + ActivityInfo info = ssp.getActivityInfo( + t.key.getComponent(), t.key.userId); + if (info != null) { + if (DEBUG) Log.d(TAG, "Loading icon: " + t.key); + cachedIcon = ssp.getBadgedActivityIcon(info, t.key.userId); + } + } + + if (cachedIcon == null) { + cachedIcon = mDefaultIcon; + } + + // At this point, even if we can't load the icon, we will set the + // default icon. + mIconCache.put(t.key, cachedIcon); + } + + if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key); + final ThumbnailData thumbnailData = ssp.getTaskThumbnail(t.key.id, + true /* reducedResolution */); + + if (!mCancelled) { + // Notify that the task data has changed + final Drawable finalIcon = cachedIcon; + mMainThreadHandler.post( + () -> t.notifyTaskDataLoaded(thumbnailData, finalIcon)); + } + } + } + + interface OnIdleChangedListener { + void onIdleChanged(boolean idle); + } +} + +/** * Recents task loader */ public class RecentsTaskLoader { + private static final String TAG = "RecentsTaskLoader"; private static final boolean DEBUG = false; - /** Levels of svelte in increasing severity/austerity. */ - // No svelting. - public static final int SVELTE_NONE = 0; - // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable - // caching thumbnails as you scroll. - public static final int SVELTE_LIMIT_CACHE = 1; - // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and - // evict all thumbnails when hidden. - public static final int SVELTE_DISABLE_CACHE = 2; - // Disable all thumbnail loading. - public static final int SVELTE_DISABLE_LOADING = 3; - - private final Context mContext; - // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos // for many tasks, which we use to get the activity labels and icons. Unlike the other caches // below, this is per-package so we can't invalidate the items in the cache based on the last - // active time. Instead, we rely on the PackageMonitor to keep us informed whenever a + // active time. Instead, we rely on the RecentsPackageMonitor to keep us informed whenever a // package in the cache has been updated, so that we may remove it. private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache; private final TaskKeyLruCache<Drawable> mIconCache; @@ -81,27 +272,31 @@ public class RecentsTaskLoader { private final int mMaxThumbnailCacheSize; private final int mMaxIconCacheSize; private int mNumVisibleTasksLoaded; - private int mSvelteLevel; - private int mDefaultTaskBarBackgroundColor; - private int mDefaultTaskViewBackgroundColor; - private final BitmapDrawable mDefaultIcon; + int mDefaultTaskBarBackgroundColor; + int mDefaultTaskViewBackgroundColor; + BitmapDrawable mDefaultIcon; - private EvictionCallback mClearActivityInfoOnEviction = new EvictionCallback() { + private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction = + new TaskKeyLruCache.EvictionCallback() { @Override - public void onEntryEvicted(TaskKey key) { + public void onEntryEvicted(Task.TaskKey key) { if (key != null) { mActivityInfoCache.remove(key.getComponent()); } } }; - public RecentsTaskLoader(Context context, int maxThumbnailCacheSize, int maxIconCacheSize, - int svelteLevel) { - mContext = context; - mMaxThumbnailCacheSize = maxThumbnailCacheSize; - mMaxIconCacheSize = maxIconCacheSize; - mSvelteLevel = svelteLevel; + public RecentsTaskLoader(Context context) { + Resources res = context.getResources(); + mDefaultTaskBarBackgroundColor = + context.getColor(R.color.recents_task_bar_default_background_color); + mDefaultTaskViewBackgroundColor = + context.getColor(R.color.recents_task_view_default_background_color); + mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count); + mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count); + int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 : + mMaxIconCacheSize; // Create the default assets Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); @@ -110,27 +305,18 @@ public class RecentsTaskLoader { // Initialize the proxy, cache and loaders int numRecentTasks = ActivityManager.getMaxRecentTasksStatic(); - mHighResThumbnailLoader = new HighResThumbnailLoader(ActivityManagerWrapper.getInstance(), - Looper.getMainLooper(), ActivityManager.isLowRamDeviceStatic()); + mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(), + Looper.getMainLooper(), Recents.getConfiguration().isLowRamDevice); mLoadQueue = new TaskResourceLoadQueue(); - mIconCache = new TaskKeyLruCache<>(mMaxIconCacheSize, mClearActivityInfoOnEviction); + mIconCache = new TaskKeyLruCache<>(iconCacheSize, mClearActivityInfoOnEviction); mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction); mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction); - mActivityInfoCache = new LruCache<>(numRecentTasks); + mActivityInfoCache = new LruCache(numRecentTasks); mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultIcon, mHighResThumbnailLoader::setTaskLoadQueueIdle); } - /** - * Sets the default task bar/view colors if none are provided by the app. - */ - public void setDefaultColors(int defaultTaskBarBackgroundColor, - int defaultTaskViewBackgroundColor) { - mDefaultTaskBarBackgroundColor = defaultTaskBarBackgroundColor; - mDefaultTaskViewBackgroundColor = defaultTaskViewBackgroundColor; - } - /** Returns the size of the app icon cache. */ public int getIconCacheSize() { return mMaxIconCacheSize; @@ -145,22 +331,37 @@ public class RecentsTaskLoader { return mHighResThumbnailLoader; } + /** Creates a new plan for loading the recent tasks. */ + public RecentsTaskLoadPlan createLoadPlan(Context context) { + RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context); + return plan; + } + + /** Preloads raw recents tasks using the specified plan to store the output. */ + public synchronized void preloadRawTasks(RecentsTaskLoadPlan plan, + boolean includeFrontMostExcludedTask) { + plan.preloadRawTasks(includeFrontMostExcludedTask); + } + /** Preloads recents tasks using the specified plan to store the output. */ - public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId) { + public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId, + boolean includeFrontMostExcludedTask) { try { Trace.beginSection("preloadPlan"); - plan.preloadPlan(this, runningTaskId); + plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask); } finally { Trace.endSection(); } } /** Begins loading the heavy task data according to the specified options. */ - public synchronized void loadTasks(RecentsTaskLoadPlan plan, Options opts) { + public synchronized void loadTasks(Context context, RecentsTaskLoadPlan plan, + RecentsTaskLoadPlan.Options opts) { if (opts == null) { throw new RuntimeException("Requires load options"); } if (opts.onlyLoadForCache && opts.loadThumbnails) { + // If we are loading for the cache, we'd like to have the real cache only include the // visible thumbnails. However, we also don't want to reload already cached thumbnails. // Thus, we copy over the current entries into a second cache, and clear the real cache, @@ -243,25 +444,12 @@ public class RecentsTaskLoader { } } - public void onPackageChanged(String packageName) { - // Remove all the cached activity infos for this package. The other caches do not need to - // be pruned at this time, as the TaskKey expiration checks will flush them next time their - // cached contents are requested - Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot(); - for (ComponentName cn : activityInfoCache.keySet()) { - if (cn.getPackageName().equals(packageName)) { - if (DEBUG) { - Log.d(TAG, "Removing activity info from cache: " + cn); - } - mActivityInfoCache.remove(cn); - } - } - } - /** * Returns the cached task label if the task key is not expired, updating the cache if it is. */ - String getAndUpdateActivityTitle(TaskKey taskKey, ActivityManager.TaskDescription td) { + String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) { + SystemServicesProxy ssp = Recents.getSystemServices(); + // Return the task description label if it exists if (td != null && td.getLabel() != null) { return td.getLabel(); @@ -274,8 +462,7 @@ public class RecentsTaskLoader { // All short paths failed, load the label from the activity info and cache it ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); if (activityInfo != null) { - label = ActivityManagerWrapper.getInstance().getBadgedActivityLabel(activityInfo, - taskKey.userId); + label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId); mActivityLabelCache.put(taskKey, label); return label; } @@ -288,7 +475,10 @@ public class RecentsTaskLoader { * Returns the cached task content description if the task key is not expired, updating the * cache if it is. */ - String getAndUpdateContentDescription(TaskKey taskKey, ActivityManager.TaskDescription td) { + String getAndUpdateContentDescription(Task.TaskKey taskKey, ActivityManager.TaskDescription td, + Resources res) { + SystemServicesProxy ssp = Recents.getSystemServices(); + // Return the cached content description if it exists String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey); if (label != null) { @@ -298,8 +488,7 @@ public class RecentsTaskLoader { // All short paths failed, load the label from the activity info and cache it ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); if (activityInfo != null) { - label = ActivityManagerWrapper.getInstance().getBadgedContentDescription( - activityInfo, taskKey.userId, td); + label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, td, res); if (td == null) { // Only add to the cache if the task description is null, otherwise, it is possible // for the task description to change between calls without the last active time @@ -318,8 +507,10 @@ public class RecentsTaskLoader { /** * Returns the cached task icon if the task key is not expired, updating the cache if it is. */ - Drawable getAndUpdateActivityIcon(TaskKey taskKey, ActivityManager.TaskDescription td, + Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td, Resources res, boolean loadIfNotCached) { + SystemServicesProxy ssp = Recents.getSystemServices(); + // Return the cached activity icon if it exists Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey); if (icon != null) { @@ -328,8 +519,7 @@ public class RecentsTaskLoader { if (loadIfNotCached) { // Return and cache the task description icon if it exists - icon = ActivityManagerWrapper.getInstance().getBadgedTaskDescriptionIcon(mContext, td, - taskKey.userId, res); + icon = ssp.getBadgedTaskDescriptionIcon(td, taskKey.userId, res); if (icon != null) { mIconCache.put(taskKey, icon); return icon; @@ -338,8 +528,7 @@ public class RecentsTaskLoader { // Load the icon from the activity info and cache it ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); if (activityInfo != null) { - icon = ActivityManagerWrapper.getInstance().getBadgedActivityIcon(activityInfo, - taskKey.userId); + icon = ssp.getBadgedActivityIcon(activityInfo, taskKey.userId); if (icon != null) { mIconCache.put(taskKey, icon); return icon; @@ -353,8 +542,10 @@ public class RecentsTaskLoader { /** * Returns the cached thumbnail if the task key is not expired, updating the cache if it is. */ - synchronized ThumbnailData getAndUpdateThumbnail(TaskKey taskKey, boolean loadIfNotCached, + synchronized ThumbnailData getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached, boolean storeInCache) { + SystemServicesProxy ssp = Recents.getSystemServices(); + ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey); if (cached != null) { return cached; @@ -367,10 +558,11 @@ public class RecentsTaskLoader { } if (loadIfNotCached) { - if (mSvelteLevel < SVELTE_DISABLE_LOADING) { + RecentsConfiguration config = Recents.getConfiguration(); + if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) { // Load the thumbnail from the system - ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance().getTaskThumbnail( - taskKey.id, true /* reducedResolution */); + ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id, + true /* reducedResolution */); if (thumbnailData.thumbnail != null) { if (storeInCache) { mThumbnailCache.put(taskKey, thumbnailData); @@ -409,11 +601,12 @@ public class RecentsTaskLoader { * Returns the activity info for the given task key, retrieving one from the system if the * task key is expired. */ - ActivityInfo getAndUpdateActivityInfo(TaskKey taskKey) { + ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) { + SystemServicesProxy ssp = Recents.getSystemServices(); ComponentName cn = taskKey.getComponent(); ActivityInfo activityInfo = mActivityInfoCache.get(cn); if (activityInfo == null) { - activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(cn, taskKey.userId); + activityInfo = ssp.getActivityInfo(cn, taskKey.userId); if (cn == null || activityInfo == null) { Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " + activityInfo); @@ -439,6 +632,23 @@ public class RecentsTaskLoader { mLoadQueue.clearTasks(); } + /**** Event Bus Events ****/ + + public final void onBusEvent(PackagesChangedEvent event) { + // Remove all the cached activity infos for this package. The other caches do not need to + // be pruned at this time, as the TaskKey expiration checks will flush them next time their + // cached contents are requested + Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot(); + for (ComponentName cn : activityInfoCache.keySet()) { + if (cn.getPackageName().equals(event.packageName)) { + if (DEBUG) { + Log.d(TAG, "Removing activity info from cache: " + cn); + } + mActivityInfoCache.remove(cn); + } + } + } + public synchronized void dump(String prefix, PrintWriter writer) { String innerPrefix = prefix + " "; diff --git a/com/android/systemui/shared/recents/model/Task.java b/com/android/systemui/recents/model/Task.java index 6bddbe01..abdb5cb8 100644 --- a/com/android/systemui/shared/recents/model/Task.java +++ b/com/android/systemui/recents/model/Task.java @@ -14,17 +14,22 @@ * limitations under the License. */ -package com.android.systemui.shared.recents.model; +package com.android.systemui.recents.model; -import android.app.ActivityManager.TaskDescription; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; + +import android.app.ActivityManager; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Color; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.ViewDebug; -import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.recents.Recents; +import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.misc.Utilities; import java.io.PrintWriter; import java.util.ArrayList; @@ -40,11 +45,11 @@ public class Task { /* Task callbacks */ public interface TaskCallbacks { /* Notifies when a task has been bound */ - void onTaskDataLoaded(Task task, ThumbnailData thumbnailData); + public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData); /* Notifies when a task has been unbound */ - void onTaskDataUnloaded(); + public void onTaskDataUnloaded(); /* Notifies when a task's windowing mode has changed. */ - void onTaskWindowingModeChanged(); + public void onTaskWindowingModeChanged(); } /* The Task Key represents the unique primary key for the task */ @@ -58,15 +63,19 @@ public class Task { @ViewDebug.ExportedProperty(category="recents") public final int userId; @ViewDebug.ExportedProperty(category="recents") + public long firstActiveTime; + @ViewDebug.ExportedProperty(category="recents") public long lastActiveTime; private int mHashCode; - public TaskKey(int id, int windowingMode, Intent intent, int userId, long lastActiveTime) { + public TaskKey(int id, int windowingMode, Intent intent, int userId, long firstActiveTime, + long lastActiveTime) { this.id = id; this.windowingMode = windowingMode; this.baseIntent = intent; this.userId = userId; + this.firstActiveTime = firstActiveTime; this.lastActiveTime = lastActiveTime; updateHashCode(); } @@ -116,6 +125,20 @@ public class Task { public int temporarySortIndexInStack; /** + * The group will be computed separately from the initialization of the task + */ + @ViewDebug.ExportedProperty(deepExport=true, prefix="group_") + public TaskGrouping group; + /** + * The affiliationTaskId is the task id of the parent task or itself if it is not affiliated + * with any task. + */ + @ViewDebug.ExportedProperty(category="recents") + public int affiliationTaskId; + @ViewDebug.ExportedProperty(category="recents") + public int affiliationColor; + + /** * The icon is the task description icon (if provided), which falls back to the activity icon, * which can then fall back to the application icon. */ @@ -126,6 +149,10 @@ public class Task { @ViewDebug.ExportedProperty(category="recents") public String titleDescription; @ViewDebug.ExportedProperty(category="recents") + public String dismissDescription; + @ViewDebug.ExportedProperty(category="recents") + public String appInfoDescription; + @ViewDebug.ExportedProperty(category="recents") public int colorPrimary; @ViewDebug.ExportedProperty(category="recents") public int colorBackground; @@ -133,9 +160,15 @@ public class Task { public boolean useLightOnPrimaryColor; /** + * The bounds of the task, used only if it is a freeform task. + */ + @ViewDebug.ExportedProperty(category="recents") + public Rect bounds; + + /** * The task description for this task, only used to reload task icons. */ - public TaskDescription taskDescription; + public ActivityManager.TaskDescription taskDescription; /** * The state isLaunchTarget will be set for the correct task upon launching Recents. @@ -167,20 +200,28 @@ public class Task { // Do nothing } - public Task(TaskKey key, Drawable icon, ThumbnailData thumbnail, String title, - String titleDescription, int colorPrimary, int colorBackground, boolean isLaunchTarget, - boolean isStackTask, boolean isSystemApp, boolean isDockable, - TaskDescription taskDescription, int resizeMode, ComponentName topActivity, - boolean isLocked) { + public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon, + ThumbnailData thumbnail, String title, String titleDescription, + String dismissDescription, String appInfoDescription, int colorPrimary, + int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp, + boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription, + int resizeMode, ComponentName topActivity, boolean isLocked) { + boolean isInAffiliationGroup = (affiliationTaskId != key.id); + boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0); this.key = key; + this.affiliationTaskId = affiliationTaskId; + this.affiliationColor = affiliationColor; this.icon = icon; this.thumbnail = thumbnail; this.title = title; this.titleDescription = titleDescription; - this.colorPrimary = colorPrimary; + this.dismissDescription = dismissDescription; + this.appInfoDescription = appInfoDescription; + this.colorPrimary = hasAffiliationGroupColor ? affiliationColor : colorPrimary; this.colorBackground = colorBackground; this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary, Color.WHITE) > 3f; + this.bounds = bounds; this.taskDescription = taskDescription; this.isLaunchTarget = isLaunchTarget; this.isStackTask = isStackTask; @@ -196,13 +237,19 @@ public class Task { */ public void copyFrom(Task o) { this.key = o.key; + this.group = o.group; + this.affiliationTaskId = o.affiliationTaskId; + this.affiliationColor = o.affiliationColor; this.icon = o.icon; this.thumbnail = o.thumbnail; this.title = o.title; this.titleDescription = o.titleDescription; + this.dismissDescription = o.dismissDescription; + this.appInfoDescription = o.appInfoDescription; this.colorPrimary = o.colorPrimary; this.colorBackground = o.colorBackground; this.useLightOnPrimaryColor = o.useLightOnPrimaryColor; + this.bounds = o.bounds; this.taskDescription = o.taskDescription; this.isLaunchTarget = o.isLaunchTarget; this.isStackTask = o.isStackTask; @@ -229,6 +276,11 @@ public class Task { mCallbacks.remove(cb); } + /** Set the grouping */ + public void setGroup(TaskGrouping group) { + this.group = group; + } + /** Updates the task's windowing mode. */ public void setWindowingMode(int windowingMode) { key.setWindowingMode(windowingMode); @@ -238,6 +290,14 @@ public class Task { } } + /** + * Returns whether this task is on the freeform task stack. + */ + public boolean isFreeformTask() { + SystemServicesProxy ssp = Recents.getSystemServices(); + return ssp.hasFreeformWorkspaceSupport() && key.windowingMode == WINDOWING_MODE_FREEFORM; + } + /** Notifies the callback listeners that this task has been loaded */ public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) { this.icon = applicationIcon; @@ -258,6 +318,13 @@ public class Task { } /** + * Returns whether this task is affiliated with another task. + */ + public boolean isAffiliatedTask() { + return key.id != affiliationTaskId; + } + + /** * Returns the top activity component. */ public ComponentName getTopComponent() { @@ -280,12 +347,18 @@ public class Task { public void dump(String prefix, PrintWriter writer) { writer.print(prefix); writer.print(key); + if (isAffiliatedTask()) { + writer.print(" "); writer.print("affTaskId=" + affiliationTaskId); + } if (!isDockable) { writer.print(" dockable=N"); } if (isLaunchTarget) { writer.print(" launchTarget=Y"); } + if (isFreeformTask()) { + writer.print(" freeform=Y"); + } if (isLocked) { writer.print(" locked=Y"); } diff --git a/com/android/systemui/recents/model/TaskGrouping.java b/com/android/systemui/recents/model/TaskGrouping.java new file mode 100644 index 00000000..2109376d --- /dev/null +++ b/com/android/systemui/recents/model/TaskGrouping.java @@ -0,0 +1,106 @@ +package com.android.systemui.recents.model; + +import android.util.ArrayMap; + +import java.util.ArrayList; + +/** Represents a grouping of tasks witihin a stack. */ +public class TaskGrouping { + + int affiliation; + long latestActiveTimeInGroup; + + Task.TaskKey mFrontMostTaskKey; + ArrayList<Task.TaskKey> mTaskKeys = new ArrayList<Task.TaskKey>(); + ArrayMap<Task.TaskKey, Integer> mTaskKeyIndices = new ArrayMap<>(); + + /** Creates a group with a specified affiliation. */ + public TaskGrouping(int affiliation) { + this.affiliation = affiliation; + } + + /** Adds a new task to this group. */ + void addTask(Task t) { + mTaskKeys.add(t.key); + if (t.key.lastActiveTime > latestActiveTimeInGroup) { + latestActiveTimeInGroup = t.key.lastActiveTime; + } + t.setGroup(this); + updateTaskIndices(); + } + + /** Removes a task from this group. */ + void removeTask(Task t) { + mTaskKeys.remove(t.key); + latestActiveTimeInGroup = 0; + int taskCount = mTaskKeys.size(); + for (int i = 0; i < taskCount; i++) { + long lastActiveTime = mTaskKeys.get(i).lastActiveTime; + if (lastActiveTime > latestActiveTimeInGroup) { + latestActiveTimeInGroup = lastActiveTime; + } + } + t.setGroup(null); + updateTaskIndices(); + } + + /** Returns the key of the next task in the group. */ + public Task.TaskKey getNextTaskInGroup(Task t) { + int i = indexOf(t); + if ((i + 1) < getTaskCount()) { + return mTaskKeys.get(i + 1); + } + return null; + } + + /** Returns the key of the previous task in the group. */ + public Task.TaskKey getPrevTaskInGroup(Task t) { + int i = indexOf(t); + if ((i - 1) >= 0) { + return mTaskKeys.get(i - 1); + } + return null; + } + + /** Gets the front task */ + public boolean isFrontMostTask(Task t) { + return (t.key == mFrontMostTaskKey); + } + + /** Finds the index of a given task in a group. */ + public int indexOf(Task t) { + return mTaskKeyIndices.get(t.key); + } + + /** Returns whether a task is in this grouping. */ + public boolean containsTask(Task t) { + return mTaskKeyIndices.containsKey(t.key); + } + + /** Returns whether one task is above another in the group. If they are not in the same group, + * this returns false. */ + public boolean isTaskAboveTask(Task t, Task below) { + return mTaskKeyIndices.containsKey(t.key) && mTaskKeyIndices.containsKey(below.key) && + mTaskKeyIndices.get(t.key) > mTaskKeyIndices.get(below.key); + } + + /** Returns the number of tasks in this group. */ + public int getTaskCount() { return mTaskKeys.size(); } + + /** Updates the mapping of tasks to indices. */ + private void updateTaskIndices() { + if (mTaskKeys.isEmpty()) { + mFrontMostTaskKey = null; + mTaskKeyIndices.clear(); + return; + } + + int taskCount = mTaskKeys.size(); + mFrontMostTaskKey = mTaskKeys.get(mTaskKeys.size() - 1); + mTaskKeyIndices.clear(); + for (int i = 0; i < taskCount; i++) { + Task.TaskKey k = mTaskKeys.get(i); + mTaskKeyIndices.put(k, i); + } + } +} diff --git a/com/android/systemui/shared/recents/model/TaskKeyCache.java b/com/android/systemui/recents/model/TaskKeyCache.java index 4bf3500a..247a6542 100644 --- a/com/android/systemui/shared/recents/model/TaskKeyCache.java +++ b/com/android/systemui/recents/model/TaskKeyCache.java @@ -14,12 +14,12 @@ * limitations under the License */ -package com.android.systemui.shared.recents.model; +package com.android.systemui.recents.model; import android.util.Log; import android.util.SparseArray; -import com.android.systemui.shared.recents.model.Task.TaskKey; +import com.android.systemui.recents.model.Task.TaskKey; /** * Base class for both strong and LRU task key cache. @@ -34,7 +34,7 @@ public abstract class TaskKeyCache<V> { * Gets a specific entry in the cache with the specified key, regardless of whether the cached * value is valid or not. */ - final V get(TaskKey key) { + final V get(Task.TaskKey key) { return getCacheEntry(key.id); } @@ -42,8 +42,8 @@ public abstract class TaskKeyCache<V> { * Returns the value only if the key is valid (has not been updated since the last time it was * in the cache) */ - final V getAndInvalidateIfModified(TaskKey key) { - TaskKey lastKey = mKeys.get(key.id); + final V getAndInvalidateIfModified(Task.TaskKey key) { + Task.TaskKey lastKey = mKeys.get(key.id); if (lastKey != null) { if ((lastKey.windowingMode != key.windowingMode) || (lastKey.lastActiveTime != key.lastActiveTime)) { @@ -59,7 +59,7 @@ public abstract class TaskKeyCache<V> { } /** Puts an entry in the cache for a specific key. */ - final void put(TaskKey key, V value) { + final void put(Task.TaskKey key, V value) { if (key == null || value == null) { Log.e(TAG, "Unexpected null key or value: " + key + ", " + value); return; @@ -70,7 +70,7 @@ public abstract class TaskKeyCache<V> { /** Removes a cache entry for a specific key. */ - final void remove(TaskKey key) { + final void remove(Task.TaskKey key) { // Remove the key after the cache value because we need it to make the callback removeCacheEntry(key.id); mKeys.remove(key.id); diff --git a/com/android/systemui/shared/recents/model/TaskKeyLruCache.java b/com/android/systemui/recents/model/TaskKeyLruCache.java index 0ba2c3bf..778df6be 100644 --- a/com/android/systemui/shared/recents/model/TaskKeyLruCache.java +++ b/com/android/systemui/recents/model/TaskKeyLruCache.java @@ -14,16 +14,14 @@ * limitations under the License. */ -package com.android.systemui.shared.recents.model; +package com.android.systemui.recents.model; import android.util.LruCache; -import com.android.systemui.shared.recents.model.Task.TaskKey; - import java.io.PrintWriter; /** - * A mapping of {@link TaskKey} to value, with additional LRU functionality where the least + * A mapping of {@link Task.TaskKey} to value, with additional LRU functionality where the least * recently referenced key/values will be evicted as more values than the given cache size are * inserted. * @@ -33,7 +31,7 @@ import java.io.PrintWriter; public class TaskKeyLruCache<V> extends TaskKeyCache<V> { public interface EvictionCallback { - void onEntryEvicted(TaskKey key); + public void onEntryEvicted(Task.TaskKey key); } private final LruCache<Integer, V> mCache; diff --git a/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java b/com/android/systemui/recents/model/TaskKeyStrongCache.java index 4408eced..c84df8a1 100644 --- a/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java +++ b/com/android/systemui/recents/model/TaskKeyStrongCache.java @@ -14,11 +14,13 @@ * limitations under the License */ -package com.android.systemui.shared.recents.model; +package com.android.systemui.recents.model; import android.util.ArrayMap; +import android.util.Log; +import android.util.SparseArray; -import com.android.systemui.shared.recents.model.Task.TaskKey; +import com.android.systemui.recents.model.Task.TaskKey; import java.io.PrintWriter; diff --git a/com/android/systemui/recents/model/TaskStack.java b/com/android/systemui/recents/model/TaskStack.java new file mode 100644 index 00000000..fdae917c --- /dev/null +++ b/com/android/systemui/recents/model/TaskStack.java @@ -0,0 +1,1140 @@ +/* + * Copyright (C) 2014 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.systemui.recents.model; + +import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; +import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.DOCKED_BOTTOM; +import static android.view.WindowManager.DOCKED_INVALID; +import static android.view.WindowManager.DOCKED_LEFT; +import static android.view.WindowManager.DOCKED_RIGHT; +import static android.view.WindowManager.DOCKED_TOP; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.annotation.IntDef; +import android.content.ComponentName; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.ColorDrawable; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.IntProperty; +import android.util.SparseArray; +import android.view.animation.Interpolator; + +import com.android.internal.policy.DockedDividerUtils; +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.recents.Recents; +import com.android.systemui.recents.RecentsDebugFlags; +import com.android.systemui.recents.misc.NamedCounter; +import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.views.AnimationProps; +import com.android.systemui.recents.views.DropTarget; +import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; + +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Random; + + +/** + * An interface for a task filter to query whether a particular task should show in a stack. + */ +interface TaskFilter { + /** Returns whether the filter accepts the specified task */ + public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index); +} + +/** + * A list of filtered tasks. + */ +class FilteredTaskList { + + ArrayList<Task> mTasks = new ArrayList<>(); + ArrayList<Task> mFilteredTasks = new ArrayList<>(); + ArrayMap<Task.TaskKey, Integer> mTaskIndices = new ArrayMap<>(); + TaskFilter mFilter; + + /** Sets the task filter, saving the current touch state */ + boolean setFilter(TaskFilter filter) { + ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks); + mFilter = filter; + updateFilteredTasks(); + if (!prevFilteredTasks.equals(mFilteredTasks)) { + return true; + } else { + return false; + } + } + + /** Removes the task filter and returns the previous touch state */ + void removeFilter() { + mFilter = null; + updateFilteredTasks(); + } + + /** Adds a new task to the task list */ + void add(Task t) { + mTasks.add(t); + updateFilteredTasks(); + } + + /** + * Moves the given task. + */ + public void setTaskWindowingMode(Task task, int insertIndex, int windowingMode) { + int taskIndex = indexOf(task); + if (taskIndex != insertIndex) { + mTasks.remove(taskIndex); + if (taskIndex < insertIndex) { + insertIndex--; + } + mTasks.add(insertIndex, task); + } + + // Update the stack id now, after we've moved the task, and before we update the + // filtered tasks + task.setWindowingMode(windowingMode); + updateFilteredTasks(); + } + + /** Sets the list of tasks */ + void set(List<Task> tasks) { + mTasks.clear(); + mTasks.addAll(tasks); + updateFilteredTasks(); + } + + /** Removes a task from the base list only if it is in the filtered list */ + boolean remove(Task t) { + if (mFilteredTasks.contains(t)) { + boolean removed = mTasks.remove(t); + updateFilteredTasks(); + return removed; + } + return false; + } + + /** Returns the index of this task in the list of filtered tasks */ + int indexOf(Task t) { + if (t != null && mTaskIndices.containsKey(t.key)) { + return mTaskIndices.get(t.key); + } + return -1; + } + + /** Returns the size of the list of filtered tasks */ + int size() { + return mFilteredTasks.size(); + } + + /** Returns whether the filtered list contains this task */ + boolean contains(Task t) { + return mTaskIndices.containsKey(t.key); + } + + /** Updates the list of filtered tasks whenever the base task list changes */ + private void updateFilteredTasks() { + mFilteredTasks.clear(); + if (mFilter != null) { + // Create a sparse array from task id to Task + SparseArray<Task> taskIdMap = new SparseArray<>(); + int taskCount = mTasks.size(); + for (int i = 0; i < taskCount; i++) { + Task t = mTasks.get(i); + taskIdMap.put(t.key.id, t); + } + + for (int i = 0; i < taskCount; i++) { + Task t = mTasks.get(i); + if (mFilter.acceptTask(taskIdMap, t, i)) { + mFilteredTasks.add(t); + } + } + } else { + mFilteredTasks.addAll(mTasks); + } + updateFilteredTaskIndices(); + } + + /** Updates the mapping of tasks to indices. */ + private void updateFilteredTaskIndices() { + int taskCount = mFilteredTasks.size(); + mTaskIndices.clear(); + for (int i = 0; i < taskCount; i++) { + Task t = mFilteredTasks.get(i); + mTaskIndices.put(t.key, i); + } + } + + /** Returns whether this task list is filtered */ + boolean hasFilter() { + return (mFilter != null); + } + + /** Returns the list of filtered tasks */ + ArrayList<Task> getTasks() { + return mFilteredTasks; + } +} + +/** + * The task stack contains a list of multiple tasks. + */ +public class TaskStack { + + private static final String TAG = "TaskStack"; + + /** Task stack callbacks */ + public interface TaskStackCallbacks { + /** + * Notifies when a new task has been added to the stack. + */ + void onStackTaskAdded(TaskStack stack, Task newTask); + + /** + * Notifies when a task has been removed from the stack. + */ + void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask, + AnimationProps animation, boolean fromDockGesture, + boolean dismissRecentsIfAllRemoved); + + /** + * Notifies when all tasks have been removed from the stack. + */ + void onStackTasksRemoved(TaskStack stack); + + /** + * Notifies when tasks in the stack have been updated. + */ + void onStackTasksUpdated(TaskStack stack); + } + + /** + * The various possible dock states when dragging and dropping a task. + */ + public static class DockState implements DropTarget { + + public static final int DOCK_AREA_BG_COLOR = 0xFFffffff; + public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000; + + // The rotation to apply to the hint text + @Retention(RetentionPolicy.SOURCE) + @IntDef({HORIZONTAL, VERTICAL}) + public @interface TextOrientation {} + private static final int HORIZONTAL = 0; + private static final int VERTICAL = 1; + + private static final int DOCK_AREA_ALPHA = 80; + public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL, + null, null, null); + public static final DockState LEFT = new DockState(DOCKED_LEFT, + DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL, + new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1), + new RectF(0, 0, 0.5f, 1)); + public static final DockState TOP = new DockState(DOCKED_TOP, + DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL, + new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f), + new RectF(0, 0, 1, 0.5f)); + public static final DockState RIGHT = new DockState(DOCKED_RIGHT, + DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL, + new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1), + new RectF(0.5f, 0, 1, 1)); + public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM, + DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL, + new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1), + new RectF(0, 0.5f, 1, 1)); + + @Override + public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, + boolean isCurrentTarget) { + if (isCurrentTarget) { + getMappedRect(expandedTouchDockArea, width, height, mTmpRect); + return mTmpRect.contains(x, y); + } else { + getMappedRect(touchArea, width, height, mTmpRect); + updateBoundsWithSystemInsets(mTmpRect, insets); + return mTmpRect.contains(x, y); + } + } + + // Represents the view state of this dock state + public static class ViewState { + private static final IntProperty<ViewState> HINT_ALPHA = + new IntProperty<ViewState>("drawableAlpha") { + @Override + public void setValue(ViewState object, int alpha) { + object.mHintTextAlpha = alpha; + object.dockAreaOverlay.invalidateSelf(); + } + + @Override + public Integer get(ViewState object) { + return object.mHintTextAlpha; + } + }; + + public final int dockAreaAlpha; + public final ColorDrawable dockAreaOverlay; + public final int hintTextAlpha; + public final int hintTextOrientation; + + private final int mHintTextResId; + private String mHintText; + private Paint mHintTextPaint; + private Point mHintTextBounds = new Point(); + private int mHintTextAlpha = 255; + private AnimatorSet mDockAreaOverlayAnimator; + private Rect mTmpRect = new Rect(); + + private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation, + int hintTextResId) { + dockAreaAlpha = areaAlpha; + dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled + ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR); + dockAreaOverlay.setAlpha(0); + hintTextAlpha = hintAlpha; + hintTextOrientation = hintOrientation; + mHintTextResId = hintTextResId; + mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mHintTextPaint.setColor(Color.WHITE); + } + + /** + * Updates the view state with the given context. + */ + public void update(Context context) { + Resources res = context.getResources(); + mHintText = context.getString(mHintTextResId); + mHintTextPaint.setTextSize(res.getDimensionPixelSize( + R.dimen.recents_drag_hint_text_size)); + mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect); + mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height()); + } + + /** + * Draws the current view state. + */ + public void draw(Canvas canvas) { + // Draw the overlay background + if (dockAreaOverlay.getAlpha() > 0) { + dockAreaOverlay.draw(canvas); + } + + // Draw the hint text + if (mHintTextAlpha > 0) { + Rect bounds = dockAreaOverlay.getBounds(); + int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2; + int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2; + mHintTextPaint.setAlpha(mHintTextAlpha); + if (hintTextOrientation == VERTICAL) { + canvas.save(); + canvas.rotate(-90f, bounds.centerX(), bounds.centerY()); + } + canvas.drawText(mHintText, x, y, mHintTextPaint); + if (hintTextOrientation == VERTICAL) { + canvas.restore(); + } + } + } + + /** + * Creates a new bounds and alpha animation. + */ + public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration, + Interpolator interpolator, boolean animateAlpha, boolean animateBounds) { + if (mDockAreaOverlayAnimator != null) { + mDockAreaOverlayAnimator.cancel(); + } + + ObjectAnimator anim; + ArrayList<Animator> animators = new ArrayList<>(); + if (dockAreaOverlay.getAlpha() != areaAlpha) { + if (animateAlpha) { + anim = ObjectAnimator.ofInt(dockAreaOverlay, + Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha); + anim.setDuration(duration); + anim.setInterpolator(interpolator); + animators.add(anim); + } else { + dockAreaOverlay.setAlpha(areaAlpha); + } + } + if (mHintTextAlpha != hintAlpha) { + if (animateAlpha) { + anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha, + hintAlpha); + anim.setDuration(150); + anim.setInterpolator(hintAlpha > mHintTextAlpha + ? Interpolators.ALPHA_IN + : Interpolators.ALPHA_OUT); + animators.add(anim); + } else { + mHintTextAlpha = hintAlpha; + dockAreaOverlay.invalidateSelf(); + } + } + if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) { + if (animateBounds) { + PropertyValuesHolder prop = PropertyValuesHolder.ofObject( + Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR, + new Rect(dockAreaOverlay.getBounds()), bounds); + anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop); + anim.setDuration(duration); + anim.setInterpolator(interpolator); + animators.add(anim); + } else { + dockAreaOverlay.setBounds(bounds); + } + } + if (!animators.isEmpty()) { + mDockAreaOverlayAnimator = new AnimatorSet(); + mDockAreaOverlayAnimator.playTogether(animators); + mDockAreaOverlayAnimator.start(); + } + } + } + + public final int dockSide; + public final int createMode; + public final ViewState viewState; + private final RectF touchArea; + private final RectF dockArea; + private final RectF expandedTouchDockArea; + private static final Rect mTmpRect = new Rect(); + + /** + * @param createMode used to pass to ActivityManager to dock the task + * @param touchArea the area in which touch will initiate this dock state + * @param dockArea the visible dock area + * @param expandedTouchDockArea the area in which touch will continue to dock after entering + * the initial touch area. This is also the new dock area to + * draw. + */ + DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha, + @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea, + RectF expandedTouchDockArea) { + this.dockSide = dockSide; + this.createMode = createMode; + this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation, + R.string.recents_drag_hint_message); + this.dockArea = dockArea; + this.touchArea = touchArea; + this.expandedTouchDockArea = expandedTouchDockArea; + } + + /** + * Updates the dock state with the given context. + */ + public void update(Context context) { + viewState.update(context); + } + + /** + * Returns the docked task bounds with the given {@param width} and {@param height}. + */ + public Rect getPreDockedBounds(int width, int height, Rect insets) { + getMappedRect(dockArea, width, height, mTmpRect); + return updateBoundsWithSystemInsets(mTmpRect, insets); + } + + /** + * Returns the expanded docked task bounds with the given {@param width} and + * {@param height}. + */ + public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets, + Resources res) { + // Calculate the docked task bounds + boolean isHorizontalDivision = + res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; + int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision, + insets, width, height, dividerSize); + Rect newWindowBounds = new Rect(); + DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds, + width, height, dividerSize); + return newWindowBounds; + } + + /** + * Returns the task stack bounds with the given {@param width} and + * {@param height}. + */ + public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height, + int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm, + Resources res, Rect windowRectOut) { + // Calculate the inverse docked task bounds + boolean isHorizontalDivision = + res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; + int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision, + insets, width, height, dividerSize); + DockedDividerUtils.calculateBoundsForPosition(position, + DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height, + dividerSize); + + // Calculate the task stack bounds from the new window bounds + Rect taskStackBounds = new Rect(); + // If the task stack bounds is specifically under the dock area, then ignore the top + // inset + int top = dockArea.bottom < 1f + ? 0 + : insets.top; + // For now, ignore the left insets since we always dock on the left and show Recents + // on the right + layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right, + taskStackBounds); + return taskStackBounds; + } + + /** + * Returns the expanded bounds in certain dock sides such that the bounds account for the + * system insets (namely the vertical nav bar). This call modifies and returns the given + * {@param bounds}. + */ + private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) { + if (dockSide == DOCKED_LEFT) { + bounds.right += insets.left; + } else if (dockSide == DOCKED_RIGHT) { + bounds.left -= insets.right; + } + return bounds; + } + + /** + * Returns the mapped rect to the given dimensions. + */ + private void getMappedRect(RectF bounds, int width, int height, Rect out) { + out.set((int) (bounds.left * width), (int) (bounds.top * height), + (int) (bounds.right * width), (int) (bounds.bottom * height)); + } + } + + // A comparator that sorts tasks by their freeform state + private Comparator<Task> FREEFORM_COMPARATOR = new Comparator<Task>() { + @Override + public int compare(Task o1, Task o2) { + if (o1.isFreeformTask() && !o2.isFreeformTask()) { + return 1; + } else if (o2.isFreeformTask() && !o1.isFreeformTask()) { + return -1; + } + return Long.compare(o1.temporarySortIndexInStack, o2.temporarySortIndexInStack); + } + }; + + + // The task offset to apply to a task id as a group affiliation + static final int IndividualTaskIdOffset = 1 << 16; + + ArrayList<Task> mRawTaskList = new ArrayList<>(); + FilteredTaskList mStackTaskList = new FilteredTaskList(); + TaskStackCallbacks mCb; + + ArrayList<TaskGrouping> mGroups = new ArrayList<>(); + ArrayMap<Integer, TaskGrouping> mAffinitiesGroups = new ArrayMap<>(); + + public TaskStack() { + // Ensure that we only show non-docked tasks + mStackTaskList.setFilter(new TaskFilter() { + @Override + public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) { + if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) { + if (t.isAffiliatedTask()) { + // If this task is affiliated with another parent in the stack, then the + // historical state of this task depends on the state of the parent task + Task parentTask = taskIdMap.get(t.affiliationTaskId); + if (parentTask != null) { + t = parentTask; + } + } + } + return t.isStackTask; + } + }); + } + + /** Sets the callbacks for this task stack. */ + public void setCallbacks(TaskStackCallbacks cb) { + mCb = cb; + } + + /** Sets the windowing mode for a given task. */ + public void setTaskWindowingMode(Task task, int windowingMode) { + // Find the index to insert into + ArrayList<Task> taskList = mStackTaskList.getTasks(); + int taskCount = taskList.size(); + if (!task.isFreeformTask() && (windowingMode == WINDOWING_MODE_FREEFORM)) { + // Insert freeform tasks at the front + mStackTaskList.setTaskWindowingMode(task, taskCount, windowingMode); + } else if (task.isFreeformTask() && (windowingMode == WINDOWING_MODE_FULLSCREEN)) { + // Insert after the first stacked task + int insertIndex = 0; + for (int i = taskCount - 1; i >= 0; i--) { + if (!taskList.get(i).isFreeformTask()) { + insertIndex = i + 1; + break; + } + } + mStackTaskList.setTaskWindowingMode(task, insertIndex, windowingMode); + } + } + + /** Does the actual work associated with removing the task. */ + void removeTaskImpl(FilteredTaskList taskList, Task t) { + // Remove the task from the list + taskList.remove(t); + // Remove it from the group as well, and if it is empty, remove the group + TaskGrouping group = t.group; + if (group != null) { + group.removeTask(t); + if (group.getTaskCount() == 0) { + removeGroup(group); + } + } + } + + /** + * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on + * how they should update themselves. + */ + public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) { + removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */); + } + + /** + * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on + * how they should update themselves. + */ + public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture, + boolean dismissRecentsIfAllRemoved) { + if (mStackTaskList.contains(t)) { + removeTaskImpl(mStackTaskList, t); + Task newFrontMostTask = getStackFrontMostTask(false /* includeFreeform */); + if (mCb != null) { + // Notify that a task has been removed + mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation, + fromDockGesture, dismissRecentsIfAllRemoved); + } + } + mRawTaskList.remove(t); + } + + /** + * Removes all tasks from the stack. + */ + public void removeAllTasks(boolean notifyStackChanges) { + ArrayList<Task> tasks = mStackTaskList.getTasks(); + for (int i = tasks.size() - 1; i >= 0; i--) { + Task t = tasks.get(i); + removeTaskImpl(mStackTaskList, t); + mRawTaskList.remove(t); + } + if (mCb != null && notifyStackChanges) { + // Notify that all tasks have been removed + mCb.onStackTasksRemoved(this); + } + } + + + /** + * @see #setTasks(Context, List, boolean, boolean) + */ + public void setTasks(Context context, TaskStack stack, boolean notifyStackChanges) { + setTasks(context, stack.mRawTaskList, notifyStackChanges); + } + + /** + * Sets a few tasks in one go, without calling any callbacks. + * + * @param tasks the new set of tasks to replace the current set. + * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks. + */ + public void setTasks(Context context, List<Task> tasks, boolean notifyStackChanges) { + // Compute a has set for each of the tasks + ArrayMap<Task.TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList); + ArrayMap<Task.TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks); + ArrayList<Task> addedTasks = new ArrayList<>(); + ArrayList<Task> removedTasks = new ArrayList<>(); + ArrayList<Task> allTasks = new ArrayList<>(); + + // Disable notifications if there are no callbacks + if (mCb == null) { + notifyStackChanges = false; + } + + // Remove any tasks that no longer exist + int taskCount = mRawTaskList.size(); + for (int i = taskCount - 1; i >= 0; i--) { + Task task = mRawTaskList.get(i); + if (!newTasksMap.containsKey(task.key)) { + if (notifyStackChanges) { + removedTasks.add(task); + } + } + task.setGroup(null); + } + + // Add any new tasks + taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task newTask = tasks.get(i); + Task currentTask = currentTasksMap.get(newTask.key); + if (currentTask == null && notifyStackChanges) { + addedTasks.add(newTask); + } else if (currentTask != null) { + // The current task has bound callbacks, so just copy the data from the new task + // state and add it back into the list + currentTask.copyFrom(newTask); + newTask = currentTask; + } + allTasks.add(newTask); + } + + // Sort all the tasks to ensure they are ordered correctly + for (int i = allTasks.size() - 1; i >= 0; i--) { + allTasks.get(i).temporarySortIndexInStack = i; + } + Collections.sort(allTasks, FREEFORM_COMPARATOR); + + mStackTaskList.set(allTasks); + mRawTaskList = allTasks; + + // Update the affiliated groupings + createAffiliatedGroupings(context); + + // Only callback for the removed tasks after the stack has updated + int removedTaskCount = removedTasks.size(); + Task newFrontMostTask = getStackFrontMostTask(false); + for (int i = 0; i < removedTaskCount; i++) { + mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask, + AnimationProps.IMMEDIATE, false /* fromDockGesture */, + true /* dismissRecentsIfAllRemoved */); + } + + // Only callback for the newly added tasks after this stack has been updated + int addedTaskCount = addedTasks.size(); + for (int i = 0; i < addedTaskCount; i++) { + mCb.onStackTaskAdded(this, addedTasks.get(i)); + } + + // Notify that the task stack has been updated + if (notifyStackChanges) { + mCb.onStackTasksUpdated(this); + } + } + + /** + * Gets the front-most task in the stack. + */ + public Task getStackFrontMostTask(boolean includeFreeformTasks) { + ArrayList<Task> stackTasks = mStackTaskList.getTasks(); + if (stackTasks.isEmpty()) { + return null; + } + for (int i = stackTasks.size() - 1; i >= 0; i--) { + Task task = stackTasks.get(i); + if (!task.isFreeformTask() || includeFreeformTasks) { + return task; + } + } + return null; + } + + /** Gets the task keys */ + public ArrayList<Task.TaskKey> getTaskKeys() { + ArrayList<Task.TaskKey> taskKeys = new ArrayList<>(); + ArrayList<Task> tasks = computeAllTasksList(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + taskKeys.add(task.key); + } + return taskKeys; + } + + /** + * Returns the set of "active" (non-historical) tasks in the stack that have been used recently. + */ + public ArrayList<Task> getStackTasks() { + return mStackTaskList.getTasks(); + } + + /** + * Returns the set of "freeform" tasks in the stack. + */ + public ArrayList<Task> getFreeformTasks() { + ArrayList<Task> freeformTasks = new ArrayList<>(); + ArrayList<Task> tasks = mStackTaskList.getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + if (task.isFreeformTask()) { + freeformTasks.add(task); + } + } + return freeformTasks; + } + + /** + * Computes a set of all the active and historical tasks. + */ + public ArrayList<Task> computeAllTasksList() { + ArrayList<Task> tasks = new ArrayList<>(); + tasks.addAll(mStackTaskList.getTasks()); + return tasks; + } + + /** + * Returns the number of stack and freeform tasks. + */ + public int getTaskCount() { + return mStackTaskList.size(); + } + + /** + * Returns the number of stack tasks. + */ + public int getStackTaskCount() { + ArrayList<Task> tasks = mStackTaskList.getTasks(); + int stackCount = 0; + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + if (!task.isFreeformTask()) { + stackCount++; + } + } + return stackCount; + } + + /** + * Returns the number of freeform tasks. + */ + public int getFreeformTaskCount() { + ArrayList<Task> tasks = mStackTaskList.getTasks(); + int freeformCount = 0; + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + if (task.isFreeformTask()) { + freeformCount++; + } + } + return freeformCount; + } + + /** + * Returns the task in stack tasks which is the launch target. + */ + public Task getLaunchTarget() { + ArrayList<Task> tasks = mStackTaskList.getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + if (task.isLaunchTarget) { + return task; + } + } + return null; + } + + /** + * Returns whether the next launch target should actually be the PiP task. + */ + public boolean isNextLaunchTargetPip(long lastPipTime) { + Task launchTarget = getLaunchTarget(); + Task nextLaunchTarget = getNextLaunchTargetRaw(); + if (nextLaunchTarget != null && lastPipTime > 0) { + // If the PiP time is more recent than the next launch target, then launch the PiP task + return lastPipTime > nextLaunchTarget.key.lastActiveTime; + } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) { + // Otherwise, if there is no next launch target, but there is a PiP, then launch + // the PiP task + return true; + } + return false; + } + + /** + * Returns the task in stack tasks which should be launched next if Recents are toggled + * again, or null if there is no task to be launched. Callers should check + * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the + * stack. + */ + public Task getNextLaunchTarget() { + Task nextLaunchTarget = getNextLaunchTargetRaw(); + if (nextLaunchTarget != null) { + return nextLaunchTarget; + } + return getStackTasks().get(getTaskCount() - 1); + } + + private Task getNextLaunchTargetRaw() { + int taskCount = getTaskCount(); + if (taskCount == 0) { + return null; + } + int launchTaskIndex = indexOfStackTask(getLaunchTarget()); + if (launchTaskIndex != -1 && launchTaskIndex > 0) { + return getStackTasks().get(launchTaskIndex - 1); + } + return null; + } + + /** Returns the index of this task in this current task stack */ + public int indexOfStackTask(Task t) { + return mStackTaskList.indexOf(t); + } + + /** Finds the task with the specified task id. */ + public Task findTaskWithId(int taskId) { + ArrayList<Task> tasks = computeAllTasksList(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + if (task.key.id == taskId) { + return task; + } + } + return null; + } + + /******** Grouping ********/ + + /** Adds a group to the set */ + public void addGroup(TaskGrouping group) { + mGroups.add(group); + mAffinitiesGroups.put(group.affiliation, group); + } + + public void removeGroup(TaskGrouping group) { + mGroups.remove(group); + mAffinitiesGroups.remove(group.affiliation); + } + + /** Returns the group with the specified affiliation. */ + public TaskGrouping getGroupWithAffiliation(int affiliation) { + return mAffinitiesGroups.get(affiliation); + } + + /** + * Temporary: This method will simulate affiliation groups + */ + void createAffiliatedGroupings(Context context) { + mGroups.clear(); + mAffinitiesGroups.clear(); + + if (RecentsDebugFlags.Static.EnableMockTaskGroups) { + ArrayMap<Task.TaskKey, Task> taskMap = new ArrayMap<>(); + // Sort all tasks by increasing firstActiveTime of the task + ArrayList<Task> tasks = mStackTaskList.getTasks(); + Collections.sort(tasks, new Comparator<Task>() { + @Override + public int compare(Task task, Task task2) { + return Long.compare(task.key.firstActiveTime, task2.key.firstActiveTime); + } + }); + // Create groups when sequential packages are the same + NamedCounter counter = new NamedCounter("task-group", ""); + int taskCount = tasks.size(); + String prevPackage = ""; + int prevAffiliation = -1; + Random r = new Random(); + int groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount; + for (int i = 0; i < taskCount; i++) { + Task t = tasks.get(i); + String packageName = t.key.getComponent().getPackageName(); + packageName = "pkg"; + TaskGrouping group; + if (packageName.equals(prevPackage) && groupCountDown > 0) { + group = getGroupWithAffiliation(prevAffiliation); + groupCountDown--; + } else { + int affiliation = IndividualTaskIdOffset + t.key.id; + group = new TaskGrouping(affiliation); + addGroup(group); + prevAffiliation = affiliation; + prevPackage = packageName; + groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount; + } + group.addTask(t); + taskMap.put(t.key, t); + } + // Sort groups by increasing latestActiveTime of the group + Collections.sort(mGroups, new Comparator<TaskGrouping>() { + @Override + public int compare(TaskGrouping taskGrouping, TaskGrouping taskGrouping2) { + return Long.compare(taskGrouping.latestActiveTimeInGroup, + taskGrouping2.latestActiveTimeInGroup); + } + }); + // Sort group tasks by increasing firstActiveTime of the task, and also build a new list + // of tasks + int taskIndex = 0; + int groupCount = mGroups.size(); + for (int i = 0; i < groupCount; i++) { + TaskGrouping group = mGroups.get(i); + Collections.sort(group.mTaskKeys, new Comparator<Task.TaskKey>() { + @Override + public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) { + return Long.compare(taskKey.firstActiveTime, taskKey2.firstActiveTime); + } + }); + ArrayList<Task.TaskKey> groupTasks = group.mTaskKeys; + int groupTaskCount = groupTasks.size(); + for (int j = 0; j < groupTaskCount; j++) { + tasks.set(taskIndex, taskMap.get(groupTasks.get(j))); + taskIndex++; + } + } + mStackTaskList.set(tasks); + } else { + // Create the task groups + ArrayMap<Task.TaskKey, Task> tasksMap = new ArrayMap<>(); + ArrayList<Task> tasks = mStackTaskList.getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task t = tasks.get(i); + TaskGrouping group; + if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) { + int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId : + IndividualTaskIdOffset + t.key.id; + if (mAffinitiesGroups.containsKey(affiliation)) { + group = getGroupWithAffiliation(affiliation); + } else { + group = new TaskGrouping(affiliation); + addGroup(group); + } + } else { + group = new TaskGrouping(t.key.id); + addGroup(group); + } + group.addTask(t); + tasksMap.put(t.key, t); + } + // Update the task colors for each of the groups + float minAlpha = context.getResources().getFloat( + R.dimen.recents_task_affiliation_color_min_alpha_percentage); + int taskGroupCount = mGroups.size(); + for (int i = 0; i < taskGroupCount; i++) { + TaskGrouping group = mGroups.get(i); + taskCount = group.getTaskCount(); + // Ignore the groups that only have one task + if (taskCount <= 1) continue; + // Calculate the group color distribution + int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).affiliationColor; + float alphaStep = (1f - minAlpha) / taskCount; + float alpha = 1f; + for (int j = 0; j < taskCount; j++) { + Task t = tasksMap.get(group.mTaskKeys.get(j)); + t.colorPrimary = Utilities.getColorWithOverlay(affiliationColor, Color.WHITE, + alpha); + alpha -= alphaStep; + } + } + } + } + + /** + * Computes the components of tasks in this stack that have been removed as a result of a change + * in the specified package. + */ + public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) { + // Identify all the tasks that should be removed as a result of the package being removed. + // Using a set to ensure that we callback once per unique component. + SystemServicesProxy ssp = Recents.getSystemServices(); + ArraySet<ComponentName> existingComponents = new ArraySet<>(); + ArraySet<ComponentName> removedComponents = new ArraySet<>(); + ArrayList<Task.TaskKey> taskKeys = getTaskKeys(); + int taskKeyCount = taskKeys.size(); + for (int i = 0; i < taskKeyCount; i++) { + Task.TaskKey t = taskKeys.get(i); + + // Skip if this doesn't apply to the current user + if (t.userId != userId) continue; + + ComponentName cn = t.getComponent(); + if (cn.getPackageName().equals(packageName)) { + if (existingComponents.contains(cn)) { + // If we know that the component still exists in the package, then skip + continue; + } + if (ssp.getActivityInfo(cn, userId) != null) { + existingComponents.add(cn); + } else { + removedComponents.add(cn); + } + } + } + return removedComponents; + } + + @Override + public String toString() { + String str = "Stack Tasks (" + mStackTaskList.size() + "):\n"; + ArrayList<Task> tasks = mStackTaskList.getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + str += " " + tasks.get(i).toString() + "\n"; + } + return str; + } + + /** + * Given a list of tasks, returns a map of each task's key to the task. + */ + private ArrayMap<Task.TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) { + ArrayMap<Task.TaskKey, Task> map = new ArrayMap<>(tasks.size()); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + map.put(task.key, task); + } + return map; + } + + public void dump(String prefix, PrintWriter writer) { + String innerPrefix = prefix + " "; + + writer.print(prefix); writer.print(TAG); + writer.print(" numStackTasks="); writer.print(mStackTaskList.size()); + writer.println(); + ArrayList<Task> tasks = mStackTaskList.getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + tasks.get(i).dump(innerPrefix, writer); + } + } +} diff --git a/com/android/systemui/shared/recents/model/ThumbnailData.java b/com/android/systemui/recents/model/ThumbnailData.java index dd1763bb..33ff1b63 100644 --- a/com/android/systemui/shared/recents/model/ThumbnailData.java +++ b/com/android/systemui/recents/model/ThumbnailData.java @@ -14,9 +14,7 @@ * limitations under the License. */ -package com.android.systemui.shared.recents.model; - -import static android.content.res.Configuration.ORIENTATION_UNDEFINED; +package com.android.systemui.recents.model; import android.app.ActivityManager.TaskSnapshot; import android.graphics.Bitmap; @@ -27,25 +25,20 @@ import android.graphics.Rect; */ public class ThumbnailData { - public final Bitmap thumbnail; + // TODO: Make these final once the non-snapshot path is removed. + public Bitmap thumbnail; public int orientation; - public Rect insets; + public final Rect insets = new Rect(); public boolean reducedResolution; public float scale; - public ThumbnailData() { - thumbnail = null; - orientation = ORIENTATION_UNDEFINED; - insets = new Rect(); - reducedResolution = false; - scale = 1f; - } - - public ThumbnailData(TaskSnapshot snapshot) { - thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot()); - insets = new Rect(snapshot.getContentInsets()); - orientation = snapshot.getOrientation(); - reducedResolution = snapshot.isReducedResolution(); - scale = snapshot.getScale(); + public static ThumbnailData createFromTaskSnapshot(TaskSnapshot snapshot) { + ThumbnailData out = new ThumbnailData(); + out.thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot()); + out.insets.set(snapshot.getContentInsets()); + out.orientation = snapshot.getOrientation(); + out.reducedResolution = snapshot.isReducedResolution(); + out.scale = snapshot.getScale(); + return out; } } diff --git a/com/android/systemui/recents/views/AnimateableViewBounds.java b/com/android/systemui/recents/views/AnimateableViewBounds.java index b598ec6f..7998ecb4 100644 --- a/com/android/systemui/recents/views/AnimateableViewBounds.java +++ b/com/android/systemui/recents/views/AnimateableViewBounds.java @@ -22,7 +22,7 @@ import android.view.View; import android.view.ViewDebug; import android.view.ViewOutlineProvider; -import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.recents.misc.Utilities; /* An outline provider that has a clip and outline that can be animated. */ public class AnimateableViewBounds extends ViewOutlineProvider { diff --git a/com/android/systemui/shared/recents/utilities/AnimationProps.java b/com/android/systemui/recents/views/AnimationProps.java index 2de7f74b..716d1bcf 100644 --- a/com/android/systemui/shared/recents/utilities/AnimationProps.java +++ b/com/android/systemui/recents/views/AnimationProps.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.shared.recents.utilities; +package com.android.systemui.recents.views; import android.animation.Animator; import android.animation.AnimatorSet; @@ -24,7 +24,8 @@ import android.util.SparseArray; import android.util.SparseLongArray; import android.view.View; import android.view.animation.Interpolator; -import android.view.animation.LinearInterpolator; + +import com.android.systemui.Interpolators; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -36,8 +37,7 @@ import java.util.List; */ public class AnimationProps { - private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); - public static final AnimationProps IMMEDIATE = new AnimationProps(0, LINEAR_INTERPOLATOR); + public static final AnimationProps IMMEDIATE = new AnimationProps(0, Interpolators.LINEAR); @Retention(RetentionPolicy.SOURCE) @IntDef({ALL, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, ALPHA, SCALE, BOUNDS}) @@ -51,6 +51,7 @@ public class AnimationProps { public static final int SCALE = 5; public static final int BOUNDS = 6; public static final int DIM_ALPHA = 7; + public static final int FOCUS_STATE = 8; private SparseLongArray mPropStartDelay; private SparseLongArray mPropDuration; @@ -194,9 +195,9 @@ public class AnimationProps { if (interp != null) { return interp; } - return mPropInterpolators.get(ALL, LINEAR_INTERPOLATOR); + return mPropInterpolators.get(ALL, Interpolators.LINEAR); } - return LINEAR_INTERPOLATOR; + return Interpolators.LINEAR; } /** diff --git a/com/android/systemui/recents/views/DockState.java b/com/android/systemui/recents/views/DockState.java deleted file mode 100644 index 59f28680..00000000 --- a/com/android/systemui/recents/views/DockState.java +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright (C) 2017 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.systemui.recents.views; - -import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; -import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; -import static android.view.WindowManager.DOCKED_BOTTOM; -import static android.view.WindowManager.DOCKED_INVALID; -import static android.view.WindowManager.DOCKED_LEFT; -import static android.view.WindowManager.DOCKED_RIGHT; -import static android.view.WindowManager.DOCKED_TOP; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.annotation.IntDef; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.ColorDrawable; -import android.util.IntProperty; -import android.view.animation.Interpolator; - -import com.android.internal.policy.DockedDividerUtils; -import com.android.systemui.Interpolators; -import com.android.systemui.R; -import com.android.systemui.recents.Recents; -import com.android.systemui.shared.recents.utilities.Utilities; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; - -/** - * The various possible dock states when dragging and dropping a task. - */ -public class DockState implements DropTarget { - - public static final int DOCK_AREA_BG_COLOR = 0xFFffffff; - public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000; - - // The rotation to apply to the hint text - @Retention(RetentionPolicy.SOURCE) - @IntDef({HORIZONTAL, VERTICAL}) - public @interface TextOrientation {} - private static final int HORIZONTAL = 0; - private static final int VERTICAL = 1; - - private static final int DOCK_AREA_ALPHA = 80; - public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL, - null, null, null); - public static final DockState LEFT = new DockState(DOCKED_LEFT, - DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL, - new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1), - new RectF(0, 0, 0.5f, 1)); - public static final DockState TOP = new DockState(DOCKED_TOP, - DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL, - new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f), - new RectF(0, 0, 1, 0.5f)); - public static final DockState RIGHT = new DockState(DOCKED_RIGHT, - DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL, - new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1), - new RectF(0.5f, 0, 1, 1)); - public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM, - DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL, - new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1), - new RectF(0, 0.5f, 1, 1)); - - @Override - public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, - boolean isCurrentTarget) { - if (isCurrentTarget) { - getMappedRect(expandedTouchDockArea, width, height, mTmpRect); - return mTmpRect.contains(x, y); - } else { - getMappedRect(touchArea, width, height, mTmpRect); - updateBoundsWithSystemInsets(mTmpRect, insets); - return mTmpRect.contains(x, y); - } - } - - // Represents the view state of this dock state - public static class ViewState { - private static final IntProperty<ViewState> HINT_ALPHA = - new IntProperty<ViewState>("drawableAlpha") { - @Override - public void setValue(ViewState object, int alpha) { - object.mHintTextAlpha = alpha; - object.dockAreaOverlay.invalidateSelf(); - } - - @Override - public Integer get(ViewState object) { - return object.mHintTextAlpha; - } - }; - - public final int dockAreaAlpha; - public final ColorDrawable dockAreaOverlay; - public final int hintTextAlpha; - public final int hintTextOrientation; - - private final int mHintTextResId; - private String mHintText; - private Paint mHintTextPaint; - private Point mHintTextBounds = new Point(); - private int mHintTextAlpha = 255; - private AnimatorSet mDockAreaOverlayAnimator; - private Rect mTmpRect = new Rect(); - - private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation, - int hintTextResId) { - dockAreaAlpha = areaAlpha; - dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled - ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR); - dockAreaOverlay.setAlpha(0); - hintTextAlpha = hintAlpha; - hintTextOrientation = hintOrientation; - mHintTextResId = hintTextResId; - mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mHintTextPaint.setColor(Color.WHITE); - } - - /** - * Updates the view state with the given context. - */ - public void update(Context context) { - Resources res = context.getResources(); - mHintText = context.getString(mHintTextResId); - mHintTextPaint.setTextSize(res.getDimensionPixelSize( - R.dimen.recents_drag_hint_text_size)); - mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect); - mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height()); - } - - /** - * Draws the current view state. - */ - public void draw(Canvas canvas) { - // Draw the overlay background - if (dockAreaOverlay.getAlpha() > 0) { - dockAreaOverlay.draw(canvas); - } - - // Draw the hint text - if (mHintTextAlpha > 0) { - Rect bounds = dockAreaOverlay.getBounds(); - int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2; - int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2; - mHintTextPaint.setAlpha(mHintTextAlpha); - if (hintTextOrientation == VERTICAL) { - canvas.save(); - canvas.rotate(-90f, bounds.centerX(), bounds.centerY()); - } - canvas.drawText(mHintText, x, y, mHintTextPaint); - if (hintTextOrientation == VERTICAL) { - canvas.restore(); - } - } - } - - /** - * Creates a new bounds and alpha animation. - */ - public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration, - Interpolator interpolator, boolean animateAlpha, boolean animateBounds) { - if (mDockAreaOverlayAnimator != null) { - mDockAreaOverlayAnimator.cancel(); - } - - ObjectAnimator anim; - ArrayList<Animator> animators = new ArrayList<>(); - if (dockAreaOverlay.getAlpha() != areaAlpha) { - if (animateAlpha) { - anim = ObjectAnimator.ofInt(dockAreaOverlay, - Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha); - anim.setDuration(duration); - anim.setInterpolator(interpolator); - animators.add(anim); - } else { - dockAreaOverlay.setAlpha(areaAlpha); - } - } - if (mHintTextAlpha != hintAlpha) { - if (animateAlpha) { - anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha, - hintAlpha); - anim.setDuration(150); - anim.setInterpolator(hintAlpha > mHintTextAlpha - ? Interpolators.ALPHA_IN - : Interpolators.ALPHA_OUT); - animators.add(anim); - } else { - mHintTextAlpha = hintAlpha; - dockAreaOverlay.invalidateSelf(); - } - } - if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) { - if (animateBounds) { - PropertyValuesHolder prop = PropertyValuesHolder.ofObject( - Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR, - new Rect(dockAreaOverlay.getBounds()), bounds); - anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop); - anim.setDuration(duration); - anim.setInterpolator(interpolator); - animators.add(anim); - } else { - dockAreaOverlay.setBounds(bounds); - } - } - if (!animators.isEmpty()) { - mDockAreaOverlayAnimator = new AnimatorSet(); - mDockAreaOverlayAnimator.playTogether(animators); - mDockAreaOverlayAnimator.start(); - } - } - } - - public final int dockSide; - public final int createMode; - public final ViewState viewState; - private final RectF touchArea; - private final RectF dockArea; - private final RectF expandedTouchDockArea; - private static final Rect mTmpRect = new Rect(); - - /** - * @param createMode used to pass to ActivityManager to dock the task - * @param touchArea the area in which touch will initiate this dock state - * @param dockArea the visible dock area - * @param expandedTouchDockArea the area in which touch will continue to dock after entering - * the initial touch area. This is also the new dock area to - * draw. - */ - DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha, - @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea, - RectF expandedTouchDockArea) { - this.dockSide = dockSide; - this.createMode = createMode; - this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation, - R.string.recents_drag_hint_message); - this.dockArea = dockArea; - this.touchArea = touchArea; - this.expandedTouchDockArea = expandedTouchDockArea; - } - - /** - * Updates the dock state with the given context. - */ - public void update(Context context) { - viewState.update(context); - } - - /** - * Returns the docked task bounds with the given {@param width} and {@param height}. - */ - public Rect getPreDockedBounds(int width, int height, Rect insets) { - getMappedRect(dockArea, width, height, mTmpRect); - return updateBoundsWithSystemInsets(mTmpRect, insets); - } - - /** - * Returns the expanded docked task bounds with the given {@param width} and - * {@param height}. - */ - public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets, - Resources res) { - // Calculate the docked task bounds - boolean isHorizontalDivision = - res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; - int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision, - insets, width, height, dividerSize); - Rect newWindowBounds = new Rect(); - DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds, - width, height, dividerSize); - return newWindowBounds; - } - - /** - * Returns the task stack bounds with the given {@param width} and - * {@param height}. - */ - public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height, - int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm, - Resources res, Rect windowRectOut) { - // Calculate the inverse docked task bounds - boolean isHorizontalDivision = - res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; - int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision, - insets, width, height, dividerSize); - DockedDividerUtils.calculateBoundsForPosition(position, - DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height, - dividerSize); - - // Calculate the task stack bounds from the new window bounds - Rect taskStackBounds = new Rect(); - // If the task stack bounds is specifically under the dock area, then ignore the top - // inset - int top = dockArea.bottom < 1f - ? 0 - : insets.top; - // For now, ignore the left insets since we always dock on the left and show Recents - // on the right - layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right, - taskStackBounds); - return taskStackBounds; - } - - /** - * Returns the expanded bounds in certain dock sides such that the bounds account for the - * system insets (namely the vertical nav bar). This call modifies and returns the given - * {@param bounds}. - */ - private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) { - if (dockSide == DOCKED_LEFT) { - bounds.right += insets.left; - } else if (dockSide == DOCKED_RIGHT) { - bounds.left -= insets.right; - } - return bounds; - } - - /** - * Returns the mapped rect to the given dimensions. - */ - private void getMappedRect(RectF bounds, int width, int height, Rect out) { - out.set((int) (bounds.left * width), (int) (bounds.top * height), - (int) (bounds.right * width), (int) (bounds.bottom * height)); - } -}
\ No newline at end of file diff --git a/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java new file mode 100644 index 00000000..035c058c --- /dev/null +++ b/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2014 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.systemui.recents.views; + +import android.content.Context; +import android.graphics.RectF; +import android.util.ArrayMap; + +import com.android.systemui.R; +import com.android.systemui.recents.model.Task; + +import java.util.Collections; +import java.util.List; + +/** + * The layout logic for the contents of the freeform workspace. + */ +public class FreeformWorkspaceLayoutAlgorithm { + + // Optimization, allows for quick lookup of task -> rect + private ArrayMap<Task.TaskKey, RectF> mTaskRectMap = new ArrayMap<>(); + + private int mTaskPadding; + + public FreeformWorkspaceLayoutAlgorithm(Context context) { + reloadOnConfigurationChange(context); + } + + /** + * Reloads the layout for the current configuration. + */ + public void reloadOnConfigurationChange(Context context) { + // This is applied to the edges of each task + mTaskPadding = context.getResources().getDimensionPixelSize( + R.dimen.recents_freeform_layout_task_padding) / 2; + } + + /** + * Updates the layout for each of the freeform workspace tasks. This is called after the stack + * layout is updated. + */ + public void update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) { + Collections.reverse(freeformTasks); + mTaskRectMap.clear(); + + int numFreeformTasks = stackLayout.mNumFreeformTasks; + if (!freeformTasks.isEmpty()) { + + // Normalize the widths so that we can calculate the best layout below + int workspaceWidth = stackLayout.mFreeformRect.width(); + int workspaceHeight = stackLayout.mFreeformRect.height(); + float normalizedWorkspaceWidth = (float) workspaceWidth / workspaceHeight; + float normalizedWorkspaceHeight = 1f; + float[] normalizedTaskWidths = new float[numFreeformTasks]; + for (int i = 0; i < numFreeformTasks; i++) { + Task task = freeformTasks.get(i); + float rowTaskWidth; + if (task.bounds != null) { + rowTaskWidth = (float) task.bounds.width() / task.bounds.height(); + } else { + // If this is a stack task that was dragged into the freeform workspace, then + // the task will not yet have an associated bounds, so assume the full workspace + // width for the time being + rowTaskWidth = normalizedWorkspaceWidth; + } + // Bound the task width to the workspace width so that at the worst case, it will + // fit its own row + normalizedTaskWidths[i] = Math.min(rowTaskWidth, normalizedWorkspaceWidth); + } + + // Determine the scale to best fit each of the tasks in the workspace + float rowScale = 0.85f; + float rowWidth = 0f; + float maxRowWidth = 0f; + int rowCount = 1; + for (int i = 0; i < numFreeformTasks;) { + float width = normalizedTaskWidths[i] * rowScale; + if (rowWidth + width > normalizedWorkspaceWidth) { + // That is too long for this row, create new row + if ((rowCount + 1) * rowScale > normalizedWorkspaceHeight) { + // The new row is too high, so we need to try fitting again. Update the + // scale to be the smaller of the scale needed to fit the task in the + // previous row, or the scale needed to fit the new row + rowScale = Math.min(normalizedWorkspaceWidth / (rowWidth + width), + normalizedWorkspaceHeight / (rowCount + 1)); + rowCount = 1; + rowWidth = 0; + i = 0; + } else { + // The new row fits, so continue + rowWidth = width; + rowCount++; + i++; + } + } else { + // Task is OK in this row + rowWidth += width; + i++; + } + maxRowWidth = Math.max(rowWidth, maxRowWidth); + } + + // Normalize each of the actual rects to that scale + float defaultRowLeft = ((1f - (maxRowWidth / normalizedWorkspaceWidth)) * + workspaceWidth) / 2f; + float rowLeft = defaultRowLeft; + float rowTop = ((1f - (rowScale * rowCount)) * workspaceHeight) / 2f; + float rowHeight = rowScale * workspaceHeight; + for (int i = 0; i < numFreeformTasks; i++) { + Task task = freeformTasks.get(i); + float width = rowHeight * normalizedTaskWidths[i]; + if (rowLeft + width > workspaceWidth) { + // This goes on the next line + rowTop += rowHeight; + rowLeft = defaultRowLeft; + } + RectF rect = new RectF(rowLeft, rowTop, rowLeft + width, rowTop + rowHeight); + rect.inset(mTaskPadding, mTaskPadding); + rowLeft += width; + mTaskRectMap.put(task.key, rect); + } + } + } + + /** + * Returns whether the transform is available for the given task. + */ + public boolean isTransformAvailable(Task task, TaskStackLayoutAlgorithm stackLayout) { + if (stackLayout.mNumFreeformTasks == 0 || task == null) { + return false; + } + return mTaskRectMap.containsKey(task.key); + } + + /** + * Returns the transform for the given task. Any rect returned will be offset by the actual + * transform for the freeform workspace. + */ + public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut, + TaskStackLayoutAlgorithm stackLayout) { + if (mTaskRectMap.containsKey(task.key)) { + final RectF ffRect = mTaskRectMap.get(task.key); + + transformOut.scale = 1f; + transformOut.alpha = 1f; + transformOut.translationZ = stackLayout.mMaxTranslationZ; + transformOut.dimAlpha = 0f; + transformOut.viewOutlineAlpha = TaskStackLayoutAlgorithm.OUTLINE_ALPHA_MAX_VALUE; + transformOut.rect.set(ffRect); + transformOut.rect.offset(stackLayout.mFreeformRect.left, stackLayout.mFreeformRect.top); + transformOut.visible = true; + return transformOut; + } + return null; + } +} diff --git a/com/android/systemui/recents/views/RecentsTransitionHelper.java b/com/android/systemui/recents/views/RecentsTransitionHelper.java index 25c2fc97..ee05d81c 100644 --- a/com/android/systemui/recents/views/RecentsTransitionHelper.java +++ b/com/android/systemui/recents/views/RecentsTransitionHelper.java @@ -19,6 +19,7 @@ package com.android.systemui.recents.views; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; @@ -30,6 +31,8 @@ import android.app.ActivityOptions; import android.app.ActivityOptions.OnAnimationStartedListener; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.GraphicBuffer; import android.graphics.Rect; import android.os.Bundle; @@ -56,8 +59,8 @@ import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent; import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.TaskStack; import com.android.systemui.statusbar.phone.StatusBar; import java.util.ArrayList; @@ -185,9 +188,20 @@ public class RecentsTransitionHelper { } else { LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView, screenPinningRequested); - EventBus.getDefault().send(launchStartedEvent); - startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode, - activityType); + if (task.group != null && !task.group.isFrontMostTask(task)) { + launchStartedEvent.addPostAnimationCallback(new Runnable() { + @Override + public void run() { + startTaskActivity(stack, task, taskView, opts, transitionFuture, + windowingMode, activityType); + } + }); + EventBus.getDefault().send(launchStartedEvent); + } else { + EventBus.getDefault().send(launchStartedEvent); + startTaskActivity(stack, task, taskView, opts, transitionFuture, + windowingMode, activityType); + } } Recents.getSystemServices().sendCloseSystemWindows( StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY); @@ -315,6 +329,7 @@ public class RecentsTransitionHelper { // If this is a full screen stack, the transition will be towards the single, full screen // task. We only need the transition spec for this task. + List<AppTransitionAnimationSpec> specs = new ArrayList<>(); // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED) @@ -323,7 +338,6 @@ public class RecentsTransitionHelper { || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY || activityType == ACTIVITY_TYPE_ASSISTANT || windowingMode == WINDOWING_MODE_UNDEFINED) { - List<AppTransitionAnimationSpec> specs = new ArrayList<>(); if (taskView == null) { specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect)); } else { @@ -337,7 +351,34 @@ public class RecentsTransitionHelper { } return specs; } - return Collections.emptyList(); + + // Otherwise, for freeform tasks, create a new animation spec for each task we have to + // launch + TaskStack stack = stackView.getStack(); + ArrayList<Task> tasks = stack.getStackTasks(); + int taskCount = tasks.size(); + for (int i = taskCount - 1; i >= 0; i--) { + Task t = tasks.get(i); + if (t.isFreeformTask() || windowingMode == WINDOWING_MODE_FREEFORM) { + TaskView tv = stackView.getChildViewForTask(t); + if (tv == null) { + // TODO: Create a different animation task rect for this case (though it should + // never happen) + specs.add(composeOffscreenAnimationSpec(t, offscreenTaskRect)); + } else { + mTmpTransform.fillIn(taskView); + stackLayout.transformToScreenCoordinates(mTmpTransform, + null /* windowOverrideRect */); + AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, tv, + mTmpTransform, true /* addHeaderBitmap */); + if (spec != null) { + specs.add(spec); + } + } + } + } + + return specs; } /** @@ -421,7 +462,7 @@ public class RecentsTransitionHelper { // force the task thumbnail to full stackView height immediately causing the transition // jarring. if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() != - stackView.getStack().getStackFrontMostTask()) { + stackView.getStack().getStackFrontMostTask(false /* includeFreeformTasks */)) { taskRect.bottom = taskRect.top + stackView.getMeasuredHeight(); } return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect); diff --git a/com/android/systemui/recents/views/RecentsView.java b/com/android/systemui/recents/views/RecentsView.java index 5f12a04b..c7edb9ae 100644 --- a/com/android/systemui/recents/views/RecentsView.java +++ b/com/android/systemui/recents/views/RecentsView.java @@ -16,6 +16,10 @@ package com.android.systemui.recents.views; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; + +import android.animation.Animator; +import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.app.ActivityOptions.OnAnimationStartedListener; @@ -53,6 +57,7 @@ import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivity; import com.android.systemui.recents.RecentsActivityLaunchState; import com.android.systemui.recents.RecentsConfiguration; +import com.android.systemui.recents.RecentsDebugFlags; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent; @@ -73,9 +78,9 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.shared.recents.utilities.Utilities; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.TaskStack; import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer; import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture; import com.android.systemui.stackdivider.WindowManagerProxy; @@ -109,6 +114,7 @@ public class RecentsView extends FrameLayout { private final int mStackButtonShadowColor; private boolean mAwaitingFirstLayout = true; + private boolean mLastTaskLaunchedWasFreeform; @ViewDebug.ExportedProperty(category="recents") Rect mSystemInsets = new Rect(); @@ -159,20 +165,22 @@ public class RecentsView extends FrameLayout { mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false); addView(mEmptyView); - if (mStackActionButton != null) { - removeView(mStackActionButton); - } - mStackActionButton = (TextView) inflater.inflate(Recents.getConfiguration() - .isLowRamDevice - ? R.layout.recents_low_ram_stack_action_button - : R.layout.recents_stack_action_button, - this, false); + if (RecentsDebugFlags.Static.EnableStackActionButton) { + if (mStackActionButton != null) { + removeView(mStackActionButton); + } + mStackActionButton = (TextView) inflater.inflate(Recents.getConfiguration() + .isLowRamDevice + ? R.layout.recents_low_ram_stack_action_button + : R.layout.recents_stack_action_button, + this, false); - mStackButtonShadowRadius = mStackActionButton.getShadowRadius(); - mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(), - mStackActionButton.getShadowDy()); - mStackButtonShadowColor = mStackActionButton.getShadowColor(); - addView(mStackActionButton); + mStackButtonShadowRadius = mStackActionButton.getShadowRadius(); + mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(), + mStackActionButton.getShadowDy()); + mStackButtonShadowColor = mStackActionButton.getShadowColor(); + addView(mStackActionButton); + } reevaluateStyles(); } @@ -224,6 +232,7 @@ public class RecentsView extends FrameLayout { // Reset the state mAwaitingFirstLayout = !isResumingFromVisible; + mLastTaskLaunchedWasFreeform = false; // Update the stack mTaskStackView.onReload(isResumingFromVisible); @@ -310,6 +319,13 @@ public class RecentsView extends FrameLayout { } } + /** + * Returns whether the last task launched was in the freeform stack or not. + */ + public boolean isLastTaskLaunchedFreeform() { + return mLastTaskLaunchedWasFreeform; + } + /** Launches the focused task from the first stack if possible */ public boolean launchFocusedTask(int logEvent) { if (mTaskStackView != null) { @@ -355,7 +371,9 @@ public class RecentsView extends FrameLayout { mEmptyView.setText(msgResId); mEmptyView.setVisibility(View.VISIBLE); mEmptyView.bringToFront(); - mStackActionButton.bringToFront(); + if (RecentsDebugFlags.Static.EnableStackActionButton) { + mStackActionButton.bringToFront(); + } } /** @@ -365,7 +383,9 @@ public class RecentsView extends FrameLayout { mEmptyView.setVisibility(View.INVISIBLE); mTaskStackView.setVisibility(View.VISIBLE); mTaskStackView.bringToFront(); - mStackActionButton.bringToFront(); + if (RecentsDebugFlags.Static.EnableStackActionButton) { + mStackActionButton.bringToFront(); + } } /** @@ -413,11 +433,13 @@ public class RecentsView extends FrameLayout { MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); } - // Measure the stack action button within the constraints of the space above the stack - Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect(); - measureChild(mStackActionButton, - MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST), - MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST)); + if (RecentsDebugFlags.Static.EnableStackActionButton) { + // Measure the stack action button within the constraints of the space above the stack + Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect(); + measureChild(mStackActionButton, + MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST)); + } setMeasuredDimension(width, height); } @@ -451,11 +473,13 @@ public class RecentsView extends FrameLayout { mBackgroundScrim.setBounds(left, top, right, bottom); mMultiWindowBackgroundScrim.setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); - // Layout the stack action button such that its drawable is start-aligned with the - // stack, vertically centered in the available space above the stack - Rect buttonBounds = getStackActionButtonBoundsFromStackLayout(); - mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right, - buttonBounds.bottom); + if (RecentsDebugFlags.Static.EnableStackActionButton) { + // Layout the stack action button such that its drawable is start-aligned with the + // stack, vertically centered in the available space above the stack + Rect buttonBounds = getStackActionButtonBoundsFromStackLayout(); + mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right, + buttonBounds.bottom); + } if (mAwaitingFirstLayout) { mAwaitingFirstLayout = false; @@ -497,7 +521,7 @@ public class RecentsView extends FrameLayout { public void onDrawForeground(Canvas canvas) { super.onDrawForeground(canvas); - ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates(); + ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates(); for (int i = visDockStates.size() - 1; i >= 0; i--) { visDockStates.get(i).viewState.draw(canvas); } @@ -505,7 +529,7 @@ public class RecentsView extends FrameLayout { @Override protected boolean verifyDrawable(Drawable who) { - ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates(); + ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates(); for (int i = visDockStates.size() - 1; i >= 0; i--) { Drawable d = visDockStates.get(i).viewState.dockAreaOverlay; if (d == who) { @@ -518,6 +542,7 @@ public class RecentsView extends FrameLayout { /**** EventBus Events ****/ public final void onBusEvent(LaunchTaskEvent event) { + mLastTaskLaunchedWasFreeform = event.task.isFreeformTask(); mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView, event.taskView, event.screenPinningRequested, event.targetWindowingMode, event.targetActivityType); @@ -528,8 +553,10 @@ public class RecentsView extends FrameLayout { public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) { int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION; - // Hide the stack action button - EventBus.getDefault().send(new HideStackActionButtonEvent()); + if (RecentsDebugFlags.Static.EnableStackActionButton) { + // Hide the stack action button + EventBus.getDefault().send(new HideStackActionButtonEvent()); + } animateBackgroundScrim(0f, taskViewExitToHomeDuration); if (Recents.getConfiguration().isLowRamDevice) { @@ -539,8 +566,8 @@ public class RecentsView extends FrameLayout { public final void onBusEvent(DragStartEvent event) { updateVisibleDockRegions(Recents.getConfiguration().getDockStatesForCurrentOrientation(), - true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha, - DockState.NONE.viewState.hintTextAlpha, + true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha, + TaskStack.DockState.NONE.viewState.hintTextAlpha, true /* animateAlpha */, false /* animateBounds */); // Temporarily hide the stack action button without changing visibility @@ -554,15 +581,15 @@ public class RecentsView extends FrameLayout { } public final void onBusEvent(DragDropTargetChangedEvent event) { - if (event.dropTarget == null || !(event.dropTarget instanceof DockState)) { + if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) { updateVisibleDockRegions( Recents.getConfiguration().getDockStatesForCurrentOrientation(), - true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha, - DockState.NONE.viewState.hintTextAlpha, + true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha, + TaskStack.DockState.NONE.viewState.hintTextAlpha, true /* animateAlpha */, true /* animateBounds */); } else { - final DockState dockState = (DockState) event.dropTarget; - updateVisibleDockRegions(new DockState[] {dockState}, + final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; + updateVisibleDockRegions(new TaskStack.DockState[] {dockState}, false /* isDefaultDockState */, -1, -1, true /* animateAlpha */, true /* animateBounds */); } @@ -581,8 +608,8 @@ public class RecentsView extends FrameLayout { public final void onBusEvent(final DragEndEvent event) { // Handle the case where we drop onto a dock region - if (event.dropTarget instanceof DockState) { - final DockState dockState = (DockState) event.dropTarget; + if (event.dropTarget instanceof TaskStack.DockState) { + final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; // Hide the dock region updateVisibleDockRegions(null, false /* isDefaultDockState */, -1, -1, @@ -704,10 +731,18 @@ public class RecentsView extends FrameLayout { } public final void onBusEvent(ShowStackActionButtonEvent event) { + if (!RecentsDebugFlags.Static.EnableStackActionButton) { + return; + } + showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate); } public final void onBusEvent(HideStackActionButtonEvent event) { + if (!RecentsDebugFlags.Static.EnableStackActionButton) { + return; + } + hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */); } @@ -723,6 +758,10 @@ public class RecentsView extends FrameLayout { * Shows the stack action button. */ private void showStackActionButton(final int duration, final boolean translate) { + if (!RecentsDebugFlags.Static.EnableStackActionButton) { + return; + } + final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(); if (mStackActionButton.getVisibility() == View.INVISIBLE) { mStackActionButton.setVisibility(View.VISIBLE); @@ -755,6 +794,10 @@ public class RecentsView extends FrameLayout { * Hides the stack action button. */ private void hideStackActionButton(int duration, boolean translate) { + if (!RecentsDebugFlags.Static.EnableStackActionButton) { + return; + } + final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(); hideStackActionButton(duration, translate, postAnimationTrigger); postAnimationTrigger.flushLastDecrementRunnables(); @@ -765,6 +808,10 @@ public class RecentsView extends FrameLayout { */ private void hideStackActionButton(int duration, boolean translate, final ReferenceCountedTrigger postAnimationTrigger) { + if (!RecentsDebugFlags.Static.EnableStackActionButton) { + return; + } + if (mStackActionButton.getVisibility() == View.VISIBLE) { if (translate) { mStackActionButton.animate().translationY(mStackActionButton.getMeasuredHeight() @@ -811,15 +858,15 @@ public class RecentsView extends FrameLayout { /** * Updates the dock region to match the specified dock state. */ - private void updateVisibleDockRegions(DockState[] newDockStates, + private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates, boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha, boolean animateAlpha, boolean animateBounds) { - ArraySet<DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates, - new ArraySet<DockState>()); - ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates(); + ArraySet<TaskStack.DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates, + new ArraySet<TaskStack.DockState>()); + ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates(); for (int i = visDockStates.size() - 1; i >= 0; i--) { - DockState dockState = visDockStates.get(i); - DockState.ViewState viewState = dockState.viewState; + TaskStack.DockState dockState = visDockStates.get(i); + TaskStack.DockState.ViewState viewState = dockState.viewState; if (newDockStates == null || !newDockStatesSet.contains(dockState)) { // This is no longer visible, so hide it viewState.startAnimation(null, 0, 0, TaskStackView.SLOW_SYNC_STACK_DURATION, diff --git a/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/com/android/systemui/recents/views/RecentsViewTouchHandler.java index 0cfdbdec..b6b24bcd 100644 --- a/com/android/systemui/recents/views/RecentsViewTouchHandler.java +++ b/com/android/systemui/recents/views/RecentsViewTouchHandler.java @@ -17,6 +17,7 @@ package com.android.systemui.recents.views; import android.app.ActivityManager; +import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.view.InputDevice; @@ -31,6 +32,7 @@ import com.android.systemui.recents.Recents; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.ConfigurationChangedEvent; import com.android.systemui.recents.events.activity.HideRecentsEvent; +import com.android.systemui.recents.events.activity.HideStackActionButtonEvent; import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent; import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent; @@ -39,8 +41,8 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.TaskStack; import java.util.ArrayList; @@ -70,7 +72,7 @@ public class RecentsViewTouchHandler { private DropTarget mLastDropTarget; private DividerSnapAlgorithm mDividerSnapAlgorithm; private ArrayList<DropTarget> mDropTargets = new ArrayList<>(); - private ArrayList<DockState> mVisibleDockStates = new ArrayList<>(); + private ArrayList<TaskStack.DockState> mVisibleDockStates = new ArrayList<>(); public RecentsViewTouchHandler(RecentsView rv) { mRv = rv; @@ -94,7 +96,7 @@ public class RecentsViewTouchHandler { /** * Returns the set of visible dock states for this current drag. */ - public ArrayList<DockState> getVisibleDockStates() { + public ArrayList<TaskStack.DockState> getVisibleDockStates() { return mVisibleDockStates; } @@ -146,9 +148,9 @@ public class RecentsViewTouchHandler { EventBus.getDefault().send(new ShowIncompatibleAppOverlayEvent()); } else { // Add the dock state drop targets (these take priority) - DockState[] dockStates = Recents.getConfiguration() + TaskStack.DockState[] dockStates = Recents.getConfiguration() .getDockStatesForCurrentOrientation(); - for (DockState dockState : dockStates) { + for (TaskStack.DockState dockState : dockStates) { registerDropTargetForCurrentDrag(dockState); dockState.update(mRv.getContext()); mVisibleDockStates.add(dockState); diff --git a/com/android/systemui/recents/views/SystemBarScrimViews.java b/com/android/systemui/recents/views/SystemBarScrimViews.java index 7827c590..8f784b83 100644 --- a/com/android/systemui/recents/views/SystemBarScrimViews.java +++ b/com/android/systemui/recents/views/SystemBarScrimViews.java @@ -30,7 +30,7 @@ import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; -import com.android.systemui.shared.recents.utilities.AnimationProps; +import com.android.systemui.recents.model.TaskStack; /** Manages the scrims for the various system bars. */ public class SystemBarScrimViews { @@ -159,7 +159,7 @@ public class SystemBarScrimViews { public final void onBusEvent(final DragEndEvent event) { // Hide the nav bar scrims once we drop to a dock region - if (event.dropTarget instanceof DockState) { + if (event.dropTarget instanceof TaskStack.DockState) { animateScrimToCurrentNavBarState(false /* hasStackTasks */); } } diff --git a/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/com/android/systemui/recents/views/TaskStackAnimationHelper.java index 26db26fa..81bf6aff 100644 --- a/com/android/systemui/recents/views/TaskStackAnimationHelper.java +++ b/com/android/systemui/recents/views/TaskStackAnimationHelper.java @@ -18,11 +18,13 @@ package com.android.systemui.recents.views; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.util.Log; +import android.view.View; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; @@ -35,10 +37,9 @@ import com.android.systemui.recents.RecentsDebugFlags; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent; import com.android.systemui.recents.misc.ReferenceCountedTrigger; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.TaskStack; import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm; -import com.android.systemui.shared.recents.utilities.AnimationProps; import java.util.ArrayList; import java.util.List; @@ -160,12 +161,20 @@ public class TaskStackAnimationHelper { for (int i = taskViews.size() - 1; i >= 0; i--) { TaskView tv = taskViews.get(i); Task task = tv.getTask(); + boolean currentTaskOccludesLaunchTarget = launchTargetTask != null && + launchTargetTask.group != null && + launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); + boolean hideTask = launchTargetTask != null && + launchTargetTask.isFreeformTask() && + task.isFreeformTask(); // Get the current transform for the task, which will be used to position it offscreen stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, null); - if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) { + if (hideTask) { + tv.setVisibility(View.INVISIBLE); + } else if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) { if (task.isLaunchTarget) { tv.onPrepareLaunchTargetForEnterAnimation(); } else if (isLowRamDevice && i >= taskViews.size() - @@ -186,6 +195,13 @@ public class TaskStackAnimationHelper { // com.android.server.wm.AppTransition#DEFAULT_APP_TRANSITION_DURATION} mStackView.updateTaskViewToTransform(tv, mTmpTransform, new AnimationProps(336, Interpolators.FAST_OUT_SLOW_IN)); + } else if (currentTaskOccludesLaunchTarget) { + // Move the task view slightly lower so we can animate it in + mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset); + mTmpTransform.alpha = 0f; + mStackView.updateTaskViewToTransform(tv, mTmpTransform, + AnimationProps.IMMEDIATE); + tv.setClipViewInStack(false); } } else if (launchState.launchedFromHome) { if (isLowRamDevice) { @@ -250,6 +266,9 @@ public class TaskStackAnimationHelper { int taskIndexFromBack = i; final TaskView tv = taskViews.get(i); Task task = tv.getTask(); + boolean currentTaskOccludesLaunchTarget = launchTargetTask != null && + launchTargetTask.group != null && + launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); // Get the current transform for the task, which will be updated to the final transform // to animate to depending on how recents was invoked @@ -261,6 +280,21 @@ public class TaskStackAnimationHelper { tv.onStartLaunchTargetEnterAnimation(mTmpTransform, taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled, postAnimationTrigger); + } else { + // Animate the task up if it was occluding the launch target + if (currentTaskOccludesLaunchTarget) { + AnimationProps taskAnimation = new AnimationProps( + taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN, + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + postAnimationTrigger.decrement(); + tv.setClipViewInStack(true); + } + }); + postAnimationTrigger.increment(); + mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); + } } } else if (launchState.launchedFromHome) { @@ -389,6 +423,9 @@ public class TaskStackAnimationHelper { for (int i = 0; i < taskViewCount; i++) { TaskView tv = taskViews.get(i); Task task = tv.getTask(); + boolean currentTaskOccludesLaunchTarget = launchingTask != null && + launchingTask.group != null && + launchingTask.group.isTaskAboveTask(task, launchingTask); if (tv == launchingTaskView) { tv.setClipViewInStack(false); @@ -400,6 +437,17 @@ public class TaskStackAnimationHelper { }); tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration, screenPinningRequested, postAnimationTrigger); + } else if (currentTaskOccludesLaunchTarget) { + // Animate this task out of view + AnimationProps taskAnimation = new AnimationProps( + taskViewExitToAppDuration, Interpolators.ALPHA_OUT, + postAnimationTrigger.decrementOnAnimationEnd()); + postAnimationTrigger.increment(); + + mTmpTransform.fillIn(tv); + mTmpTransform.alpha = 0f; + mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset); + mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); } } } @@ -563,7 +611,7 @@ public class TaskStackAnimationHelper { false /* ignoreTaskOverrides */, mTmpFinalTaskTransforms); // Hide the front most task view until the scroll is complete - Task frontMostTask = newStack.getStackFrontMostTask(); + Task frontMostTask = newStack.getStackFrontMostTask(false /* includeFreeform */); final TaskView frontMostTaskView = mStackView.getChildViewForTask(frontMostTask); final TaskViewTransform frontMostTransform = mTmpFinalTaskTransforms.get( stackTasks.indexOf(frontMostTask)); diff --git a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index acb058ce..eaa32eef 100644 --- a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -24,6 +24,7 @@ import android.graphics.Path; import android.graphics.Rect; import android.util.ArraySet; import android.util.Log; +import android.util.MutableFloat; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.ViewDebug; @@ -35,9 +36,9 @@ import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.RecentsDebugFlags; import com.android.systemui.recents.misc.FreePathInterpolator; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.shared.recents.utilities.Utilities; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.TaskStack; import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm; import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; @@ -146,6 +147,75 @@ public class TaskStackLayoutAlgorithm { } /** + * The various stack/freeform states. + */ + public static class StackState { + + public static final StackState FREEFORM_ONLY = new StackState(1f, 255); + public static final StackState STACK_ONLY = new StackState(0f, 0); + public static final StackState SPLIT = new StackState(0.5f, 255); + + public final float freeformHeightPct; + public final int freeformBackgroundAlpha; + + /** + * @param freeformHeightPct the percentage of the stack height (not including paddings) to + * allocate to the freeform workspace + * @param freeformBackgroundAlpha the background alpha for the freeform workspace + */ + private StackState(float freeformHeightPct, int freeformBackgroundAlpha) { + this.freeformHeightPct = freeformHeightPct; + this.freeformBackgroundAlpha = freeformBackgroundAlpha; + } + + /** + * Resolves the stack state for the layout given a task stack. + */ + public static StackState getStackStateForStack(TaskStack stack) { + SystemServicesProxy ssp = Recents.getSystemServices(); + boolean hasFreeformWorkspaces = ssp.hasFreeformWorkspaceSupport(); + int freeformCount = stack.getFreeformTaskCount(); + int stackCount = stack.getStackTaskCount(); + if (hasFreeformWorkspaces && stackCount > 0 && freeformCount > 0) { + return SPLIT; + } else if (hasFreeformWorkspaces && freeformCount > 0) { + return FREEFORM_ONLY; + } else { + return STACK_ONLY; + } + } + + /** + * Computes the freeform and stack rect for this state. + * + * @param freeformRectOut the freeform rect to be written out + * @param stackRectOut the stack rect, we only write out the top of the stack + * @param taskStackBounds the full rect that the freeform rect can take up + */ + public void computeRects(Rect freeformRectOut, Rect stackRectOut, + Rect taskStackBounds, int topMargin, int freeformGap, int stackBottomOffset) { + // The freeform height is the visible height (not including system insets) - padding + // above freeform and below stack - gap between the freeform and stack + int availableHeight = taskStackBounds.height() - topMargin - stackBottomOffset; + int ffPaddedHeight = (int) (availableHeight * freeformHeightPct); + int ffHeight = Math.max(0, ffPaddedHeight - freeformGap); + freeformRectOut.set(taskStackBounds.left, + taskStackBounds.top + topMargin, + taskStackBounds.right, + taskStackBounds.top + topMargin + ffHeight); + stackRectOut.set(taskStackBounds.left, + taskStackBounds.top, + taskStackBounds.right, + taskStackBounds.bottom); + if (ffPaddedHeight > 0) { + stackRectOut.top += ffPaddedHeight; + } else { + stackRectOut.top += topMargin; + } + } + } + + /** * @return True if we should use the grid layout. */ boolean useGridLayout() { @@ -164,11 +234,15 @@ public class TaskStackLayoutAlgorithm { } Context mContext; + private StackState mState = StackState.SPLIT; private TaskStackLayoutAlgorithmCallbacks mCb; // The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot. @ViewDebug.ExportedProperty(category="recents") public Rect mTaskRect = new Rect(); + // The freeform workspace bounds, inset by the top system insets and is a fixed height + @ViewDebug.ExportedProperty(category="recents") + public Rect mFreeformRect = new Rect(); // The stack bounds, inset from the top system insets, and runs to the bottom of the screen @ViewDebug.ExportedProperty(category="recents") public Rect mStackRect = new Rect(); @@ -194,6 +268,10 @@ public class TaskStackLayoutAlgorithm { private int mBaseBottomMargin; private int mMinMargin; + // The gap between the freeform and stack layouts + @ViewDebug.ExportedProperty(category="recents") + private int mFreeformStackGap; + // The initial offset that the focused task is from the top @ViewDebug.ExportedProperty(category="recents") private int mInitialTopOffset; @@ -253,6 +331,8 @@ public class TaskStackLayoutAlgorithm { // The last computed task counts @ViewDebug.ExportedProperty(category="recents") int mNumStackTasks; + @ViewDebug.ExportedProperty(category="recents") + int mNumFreeformTasks; // The min/max z translations @ViewDebug.ExportedProperty(category="recents") @@ -264,6 +344,8 @@ public class TaskStackLayoutAlgorithm { private SparseIntArray mTaskIndexMap = new SparseIntArray(); private SparseArray<Float> mTaskIndexOverrideMap = new SparseArray<>(); + // The freeform workspace layout + FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm; TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm; TaskStackLowRamLayoutAlgorithm mTaskStackLowRamLayoutAlgorithm; @@ -274,6 +356,7 @@ public class TaskStackLayoutAlgorithm { public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) { mContext = context; mCb = cb; + mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context); mTaskGridLayoutAlgorithm = new TaskGridLayoutAlgorithm(context); mTaskStackLowRamLayoutAlgorithm = new TaskStackLowRamLayoutAlgorithm(context); reloadOnConfigurationChange(context); @@ -310,6 +393,7 @@ public class TaskStackLayoutAlgorithm { R.dimen.recents_layout_initial_bottom_offset_tablet, R.dimen.recents_layout_initial_bottom_offset_tablet, R.dimen.recents_layout_initial_bottom_offset_tablet); + mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context); mTaskGridLayoutAlgorithm.reloadOnConfigurationChange(context); mTaskStackLowRamLayoutAlgorithm.reloadOnConfigurationChange(context); mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin); @@ -324,6 +408,8 @@ public class TaskStackLayoutAlgorithm { R.dimen.recents_layout_side_margin_tablet_xlarge, R.dimen.recents_layout_side_margin_tablet); mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin); + mFreeformStackGap = + res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin); mTitleBarHeight = getDimensionForDevice(mContext, R.dimen.recents_task_view_header_height, R.dimen.recents_task_view_header_height, @@ -376,7 +462,8 @@ public class TaskStackLayoutAlgorithm { * Computes the stack and task rects. The given task stack bounds already has the top/right * insets and left/right padding already applied. */ - public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds) { + public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds, + StackState state) { Rect lastStackRect = new Rect(mStackRect); int topMargin = getScaleForExtent(windowRect, displayRect, mBaseTopMargin, mMinMargin, HEIGHT); @@ -387,9 +474,10 @@ public class TaskStackLayoutAlgorithm { mInitialBottomOffset = mBaseInitialBottomOffset; // Compute the stack bounds + mState = state; mStackBottomOffset = mSystemInsets.bottom + bottomMargin; - mStackRect.set(taskStackBounds); - mStackRect.top += topMargin; + state.computeRects(mFreeformRect, mStackRect, taskStackBounds, topMargin, + mFreeformStackGap, mStackBottomOffset); // The stack action button will take the full un-padded header space above the stack mStackActionButtonRect.set(mStackRect.left, mStackRect.top - topMargin, @@ -442,20 +530,26 @@ public class TaskStackLayoutAlgorithm { if (tasks.isEmpty()) { mFrontMostTaskP = 0; mMinScrollP = mMaxScrollP = mInitialScrollP = 0; - mNumStackTasks = 0; + mNumStackTasks = mNumFreeformTasks = 0; return; } - // Filter the set of stack tasks + // Filter the set of freeform and stack tasks + ArrayList<Task> freeformTasks = new ArrayList<>(); ArrayList<Task> stackTasks = new ArrayList<>(); for (int i = 0; i < tasks.size(); i++) { Task task = tasks.get(i); if (ignoreTasksSet.contains(task.key)) { continue; } - stackTasks.add(task); + if (task.isFreeformTask()) { + freeformTasks.add(task); + } else { + stackTasks.add(task); + } } mNumStackTasks = stackTasks.size(); + mNumFreeformTasks = freeformTasks.size(); // Put each of the tasks in the progress map at a fixed index (does not need to actually // map to a scroll position, just by index) @@ -465,6 +559,11 @@ public class TaskStackLayoutAlgorithm { mTaskIndexMap.put(task.key.id, i); } + // Update the freeform tasks + if (!freeformTasks.isEmpty()) { + mFreeformLayoutAlgorithm.update(freeformTasks, this); + } + // Calculate the min/max/initial scroll Task launchTask = stack.getLaunchTarget(); int launchTaskIndex = launchTask != null @@ -483,7 +582,7 @@ public class TaskStackLayoutAlgorithm { } else { mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP); } - } else if (mNumStackTasks == 1) { + } else if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) { // If there is one stack task, ignore the min/max/initial scroll positions mMinScrollP = 0; mMaxScrollP = 0; @@ -504,7 +603,9 @@ public class TaskStackLayoutAlgorithm { boolean scrollToFront = launchState.launchedFromHome || launchState.launchedFromPipApp || launchState.launchedWithNextPipApp || launchState.launchedViaDockGesture; - if (launchState.launchedWithAltTab) { + if (launchState.launchedFromBlacklistedApp) { + mInitialScrollP = mMaxScrollP; + } else if (launchState.launchedWithAltTab) { mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP); } else if (Recents.getConfiguration().isLowRamDevice) { mInitialScrollP = mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks, @@ -532,6 +633,7 @@ public class TaskStackLayoutAlgorithm { boolean scrollToFront = launchState.launchedFromHome || launchState.launchedFromPipApp || launchState.launchedWithNextPipApp || + launchState.launchedFromBlacklistedApp || launchState.launchedViaDockGesture; if (getInitialFocusState() == STATE_UNFOCUSED && mNumStackTasks > 1) { if (ignoreScrollToFront || (!launchState.launchedWithAltTab && !scrollToFront)) { @@ -665,7 +767,7 @@ public class TaskStackLayoutAlgorithm { public int getInitialFocusState() { RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); RecentsDebugFlags debugFlags = Recents.getDebugFlags(); - if (launchState.launchedWithAltTab) { + if (debugFlags.isPagingEnabled() || launchState.launchedWithAltTab) { return STATE_FOCUSED; } else { return STATE_UNFOCUSED; @@ -692,6 +794,13 @@ public class TaskStackLayoutAlgorithm { } /** + * Returns the current stack state. + */ + public StackState getStackState() { + return mState; + } + + /** * Returns whether this stack layout has been initialized. */ public boolean isInitialized() { @@ -716,44 +825,62 @@ public class TaskStackLayoutAlgorithm { return new VisibilityReport(1, 1); } + // Quick return when there are no stack tasks + if (mNumStackTasks == 0) { + return new VisibilityReport(mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0, + mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0); + } + // Otherwise, walk backwards in the stack and count the number of tasks and visible - // thumbnails and add that to the total task count + // thumbnails and add that to the total freeform task count TaskViewTransform tmpTransform = new TaskViewTransform(); Range currentRange = getInitialFocusState() > 0f ? mFocusedRange : mUnfocusedRange; currentRange.offset(mInitialScrollP); int taskBarHeight = mContext.getResources().getDimensionPixelSize( R.dimen.recents_task_view_header_height); - int numVisibleTasks = 0; - int numVisibleThumbnails = 0; + int numVisibleTasks = mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0; + int numVisibleThumbnails = mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 0) : 0; float prevScreenY = Integer.MAX_VALUE; for (int i = tasks.size() - 1; i >= 0; i--) { Task task = tasks.get(i); + // Skip freeform + if (task.isFreeformTask()) { + continue; + } + // Skip invisible float taskProgress = getStackScrollForTask(task); if (!currentRange.isInRange(taskProgress)) { continue; } - getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState, - tmpTransform, null, false /* ignoreSingleTaskCase */, false /* forceUpdate */); - float screenY = tmpTransform.rect.top; - boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight; - if (hasVisibleThumbnail) { - numVisibleThumbnails++; - numVisibleTasks++; - prevScreenY = screenY; - } else { - // Once we hit the next front most task that does not have a visible thumbnail, - // walk through remaining visible set - for (int j = i; j >= 0; j--) { - taskProgress = getStackScrollForTask(tasks.get(j)); - if (!currentRange.isInRange(taskProgress)) { - break; - } + boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task); + if (isFrontMostTaskInGroup) { + getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState, + tmpTransform, null, false /* ignoreSingleTaskCase */, + false /* forceUpdate */); + float screenY = tmpTransform.rect.top; + boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight; + if (hasVisibleThumbnail) { + numVisibleThumbnails++; numVisibleTasks++; + prevScreenY = screenY; + } else { + // Once we hit the next front most task that does not have a visible thumbnail, + // walk through remaining visible set + for (int j = i; j >= 0; j--) { + taskProgress = getStackScrollForTask(tasks.get(j)); + if (!currentRange.isInRange(taskProgress)) { + break; + } + numVisibleTasks++; + } + break; } - break; + } else { + // Affiliated task, no thumbnail + numVisibleTasks++; } } return new VisibilityReport(numVisibleTasks, numVisibleThumbnails); @@ -779,7 +906,10 @@ public class TaskStackLayoutAlgorithm { public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState, TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate, boolean ignoreTaskOverrides) { - if (useGridLayout()) { + if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) { + mFreeformLayoutAlgorithm.getTransform(task, transformOut, this); + return transformOut; + } else if (useGridLayout()) { int taskIndex = mTaskIndexMap.get(task.key.id); int taskCount = mTaskIndexMap.size(); mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this); @@ -894,7 +1024,7 @@ public class TaskStackLayoutAlgorithm { float z; float dimAlpha; float viewOutlineAlpha; - if (mNumStackTasks == 1 && !ignoreSingleTaskCase) { + if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) { // When there is exactly one task, then decouple the task from the stack and just move // in screen space float tmpP = (mMinScrollP - stackScroll) / mNumStackTasks; @@ -1248,6 +1378,7 @@ public class TaskStackLayoutAlgorithm { writer.print("insets="); writer.print(Utilities.dumpRect(mSystemInsets)); writer.print(" stack="); writer.print(Utilities.dumpRect(mStackRect)); writer.print(" task="); writer.print(Utilities.dumpRect(mTaskRect)); + writer.print(" freeform="); writer.print(Utilities.dumpRect(mFreeformRect)); writer.print(" actionButton="); writer.print(Utilities.dumpRect(mStackActionButtonRect)); writer.println(); diff --git a/com/android/systemui/recents/views/TaskStackView.java b/com/android/systemui/recents/views/TaskStackView.java index 428113a2..3160ee0e 100644 --- a/com/android/systemui/recents/views/TaskStackView.java +++ b/com/android/systemui/recents/views/TaskStackView.java @@ -16,15 +16,22 @@ package com.android.systemui.recents.views; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.IntDef; import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Canvas; import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; import android.os.Bundle; import android.provider.Settings; import android.util.ArrayMap; @@ -57,6 +64,7 @@ import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimatio import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent; import com.android.systemui.recents.events.activity.HideRecentsEvent; import com.android.systemui.recents.events.activity.HideStackActionButtonEvent; +import com.android.systemui.recents.events.activity.IterateRecentsEvent; import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent; import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent; import com.android.systemui.recents.events.activity.LaunchTaskEvent; @@ -75,11 +83,13 @@ import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; import com.android.systemui.recents.events.ui.DismissTaskViewEvent; import com.android.systemui.recents.events.ui.RecentsGrowingEvent; import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; +import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent; import com.android.systemui.recents.events.ui.UserInteractionEvent; import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; +import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent; import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent; import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; @@ -87,10 +97,9 @@ import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent; import com.android.systemui.recents.misc.DozeTrigger; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.shared.recents.utilities.AnimationProps; -import com.android.systemui.shared.recents.utilities.Utilities; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.TaskStack; import com.android.systemui.recents.views.grid.GridTaskView; import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; import com.android.systemui.recents.views.grid.TaskViewFocusFrame; @@ -144,6 +153,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_") private TaskStackViewTouchHandler mTouchHandler; private TaskStackAnimationHelper mAnimationHelper; + private GradientDrawable mFreeformWorkspaceBackground; + private ObjectAnimator mFreeformWorkspaceBackgroundAnimator; private ViewPool<TaskView, Task> mViewPool; private ArrayList<TaskView> mTaskViews = new ArrayList<>(); @@ -228,6 +239,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } }; + // The drop targets for a task drag + private DropTarget mFreeformWorkspaceDropTarget = new DropTarget() { + @Override + public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, + boolean isCurrentTarget) { + // This drop target has a fixed bounds and should be checked last, so just fall through + // if it is the current target + if (!isCurrentTarget) { + return mLayoutAlgorithm.mFreeformRect.contains(x, y); + } + return false; + } + }; + private DropTarget mStackDropTarget = new DropTarget() { @Override public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, @@ -287,6 +312,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } }); setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + if (ssp.hasFreeformWorkspaceSupport()) { + setWillNotDraw(false); + } + + mFreeformWorkspaceBackground = (GradientDrawable) getContext().getDrawable( + R.drawable.recents_freeform_workspace_bg); + mFreeformWorkspaceBackground.setCallback(this); + if (ssp.hasFreeformWorkspaceSupport()) { + mFreeformWorkspaceBackground.setColor( + getContext().getColor(R.color.recents_freeform_workspace_bg_color)); + } } @Override @@ -323,7 +359,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal readSystemFlags(); mTaskViewsClipDirty = true; mUIDozeTrigger.stopDozing(); - if (!isResumingFromVisible) { + if (isResumingFromVisible) { + // Animate in the freeform workspace + int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha; + animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150, + Interpolators.FAST_OUT_SLOW_IN)); + } else { mStackScroller.reset(); mStableLayoutAlgorithm.reset(); mLayoutAlgorithm.reset(); @@ -346,7 +387,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Only notify if we are already initialized, otherwise, everything will pick up all the // new and old tasks when we next layout - mStack.setTasks(stack, allowNotifyStackChanges && isInitialized); + mStack.setTasks(getContext(), stack, allowNotifyStackChanges && isInitialized); } /** Returns the task stack. */ @@ -381,13 +422,23 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** * Returns the front most task view. + * + * @param stackTasksOnly if set, will return the front most task view in the stack (by default + * the front most task view will be freeform since they are placed above + * stack tasks) */ - private TaskView getFrontMostTaskView() { + private TaskView getFrontMostTaskView(boolean stackTasksOnly) { List<TaskView> taskViews = getTaskViews(); - if (taskViews.isEmpty()) { - return null; + int taskViewCount = taskViews.size(); + for (int i = taskViewCount - 1; i >= 0; i--) { + TaskView tv = taskViews.get(i); + Task task = tv.getTask(); + if (stackTasksOnly && task.isFreeformTask()) { + continue; + } + return tv; } - return taskViews.get(taskViews.size() - 1); + return null; } /** @@ -449,6 +500,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal * visible range includes all tasks at the target stack scroll. This is useful for ensure that * all views necessary for a transition or animation will be visible at the start. * + * This call ignores freeform tasks. + * * @param taskTransforms The set of task view transforms to reuse, this list will be sized to * match the size of {@param tasks} * @param tasks The set of tasks for which to generate transforms @@ -471,7 +524,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0; // We can reuse the task transforms where possible to reduce object allocation - matchTaskListSize(tasks, taskTransforms); + Utilities.matchTaskListSize(tasks, taskTransforms); // Update the stack transforms TaskViewTransform frontTransform = null; @@ -501,6 +554,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal continue; } + // For freeform tasks, only calculate the stack transform and skip the calculation of + // the visible stack indices + if (task.isFreeformTask()) { + continue; + } + frontTransform = transform; frontTransformAtTarget = transformAtTarget; if (transform.visible) { @@ -563,7 +622,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal transform = mCurrentTaskTransforms.get(taskIndex); } - if (transform != null && transform.visible) { + if (task.isFreeformTask() || (transform != null && transform.visible)) { mTmpTaskViewMap.put(task.key, tv); } else { if (mTouchExplorationEnabled && Utilities.isDescendentAccessibilityFocused(tv)) { @@ -584,20 +643,24 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal continue; } - // Skip the invisible stack tasks - if (!transform.visible) { + // Skip the invisible non-freeform stack tasks + if (!task.isFreeformTask() && !transform.visible) { continue; } TaskView tv = mTmpTaskViewMap.get(task.key); if (tv == null) { tv = mViewPool.pickUpViewFromPool(task, task); - if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) { - updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(), - AnimationProps.IMMEDIATE); + if (task.isFreeformTask()) { + updateTaskViewToTransform(tv, transform, AnimationProps.IMMEDIATE); } else { - updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(), - AnimationProps.IMMEDIATE); + if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) { + updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(), + AnimationProps.IMMEDIATE); + } else { + updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(), + AnimationProps.IMMEDIATE); + } } } else { // Reattach it in the right z order @@ -701,7 +764,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal */ public void getCurrentTaskTransforms(ArrayList<Task> tasks, ArrayList<TaskViewTransform> transformsOut) { - matchTaskListSize(tasks, transformsOut); + Utilities.matchTaskListSize(tasks, transformsOut); int focusState = mLayoutAlgorithm.getFocusState(); for (int i = tasks.size() - 1; i >= 0; i--) { Task task = tasks.get(i); @@ -724,7 +787,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal */ public void getLayoutTaskTransforms(float stackScroll, int focusState, ArrayList<Task> tasks, boolean ignoreTaskOverrides, ArrayList<TaskViewTransform> transformsOut) { - matchTaskListSize(tasks, transformsOut); + Utilities.matchTaskListSize(tasks, transformsOut); for (int i = tasks.size() - 1; i >= 0; i--) { Task task = tasks.get(i); TaskViewTransform transform = transformsOut.get(i); @@ -824,6 +887,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Compute the min and max scroll values mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState); + // Update the freeform workspace background + SystemServicesProxy ssp = Recents.getSystemServices(); + if (ssp.hasFreeformWorkspaceSupport()) { + mTmpRect.set(mLayoutAlgorithm.mFreeformRect); + mFreeformWorkspaceBackground.setBounds(mTmpRect); + } + if (boundScrollToNewMinMax) { mStackScroller.boundScroll(); } @@ -836,7 +906,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mWindowRect.set(mStableWindowRect); mStackBounds.set(mStableStackBounds); mLayoutAlgorithm.setSystemInsets(mStableLayoutAlgorithm.mSystemInsets); - mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds); + mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds, + TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); updateLayoutAlgorithm(true /* boundScroll */); } @@ -957,10 +1028,21 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (focusedTask != null) { if (stackTasksOnly) { List<Task> tasks = mStack.getStackTasks(); - // Try the next task if it is a stack task - int tmpNewIndex = newIndex + (forward ? -1 : 1); - if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) { - newIndex = tmpNewIndex; + if (focusedTask.isFreeformTask()) { + // Try and focus the front most stack task + TaskView tv = getFrontMostTaskView(stackTasksOnly); + if (tv != null) { + newIndex = mStack.indexOfStackTask(tv.getTask()); + } + } else { + // Try the next task if it is a stack task + int tmpNewIndex = newIndex + (forward ? -1 : 1); + if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) { + Task t = tasks.get(tmpNewIndex); + if (!t.isFreeformTask()) { + newIndex = tmpNewIndex; + } + } } } else { // No restrictions, lets just move to the new task (looping forward/backwards if @@ -1045,7 +1127,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal return tv.getTask(); } } - TaskView frontTv = getFrontMostTaskView(); + TaskView frontTv = getFrontMostTaskView(true /* stackTasksOnly */); if (frontTv != null) { return frontTv.getTask(); } @@ -1196,8 +1278,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Compute the rects in the stack algorithm - mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds); - mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds); + mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds, + TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); + mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds, + TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); updateLayoutAlgorithm(false /* boundScroll */); // If this is the first layout, then scroll to the front of the stack, then update the @@ -1320,6 +1404,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Setup the view for the enter animation mAnimationHelper.prepareForEnterAnimation(); + // Animate in the freeform workspace + int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha; + animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150, + Interpolators.FAST_OUT_SLOW_IN)); + // Set the task focused state without requesting view focus, and leave the focus animations // until after the enter-animation RecentsConfiguration config = Recents.getConfiguration(); @@ -1367,6 +1456,43 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal return null; } + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // Draw the freeform workspace background + SystemServicesProxy ssp = Recents.getSystemServices(); + if (ssp.hasFreeformWorkspaceSupport()) { + if (mFreeformWorkspaceBackground.getAlpha() > 0) { + mFreeformWorkspaceBackground.draw(canvas); + } + } + } + + @Override + protected boolean verifyDrawable(Drawable who) { + if (who == mFreeformWorkspaceBackground) { + return true; + } + return super.verifyDrawable(who); + } + + /** + * Launches the freeform tasks. + */ + public boolean launchFreeformTasks() { + ArrayList<Task> tasks = mStack.getFreeformTasks(); + if (!tasks.isEmpty()) { + Task frontTask = tasks.get(tasks.size() - 1); + if (frontTask != null && frontTask.isFreeformTask()) { + EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask), + frontTask, null, false)); + return true; + } + } + return false; + } + /**** TaskStackCallbacks Implementation ****/ @Override @@ -1545,7 +1671,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Restore the action button visibility if it is the front most task view - if (mScreenPinningEnabled && tv.getTask() == mStack.getStackFrontMostTask()) { + if (mScreenPinningEnabled && tv.getTask() == + mStack.getStackFrontMostTask(false /* includeFreeform */)) { tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */); } } @@ -1561,6 +1688,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // If the doze trigger has already fired, then update the state for this task view if (mUIDozeTrigger.isAsleep() || + Recents.getSystemServices().hasFreeformWorkspaceSupport() || useGridLayout() || Recents.getConfiguration().isLowRamDevice) { tv.setNoUserInteractionState(); } @@ -1692,17 +1820,21 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public final void onBusEvent(LaunchMostRecentTaskRequestEvent event) { if (mStack.getTaskCount() > 0) { - Task mostRecentTask = mStack.getStackFrontMostTask(); + Task mostRecentTask = mStack.getStackFrontMostTask(true /* includeFreefromTasks */); launchTask(mostRecentTask); } } public final void onBusEvent(ShowStackActionButtonEvent event) { - mStackActionButtonVisible = true; + if (RecentsDebugFlags.Static.EnableStackActionButton) { + mStackActionButtonVisible = true; + } } public final void onBusEvent(HideStackActionButtonEvent event) { - mStackActionButtonVisible = false; + if (RecentsDebugFlags.Static.EnableStackActionButton) { + mStackActionButtonVisible = false; + } } public final void onBusEvent(LaunchNextTaskRequestEvent event) { @@ -1759,6 +1891,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Start the task animations mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger()); + // Dismiss the freeform workspace background + int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION; + animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration, + Interpolators.FAST_OUT_SLOW_IN)); + // Dismiss the grid task view focus frame if (mTaskViewFocusFrame != null) { mTaskViewFocusFrame.moveGridTaskViewFocus(null); @@ -1840,7 +1977,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStackScroller.stopScroller(); mStackScroller.stopBoundScrollAnimation(); - setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false, 0); + setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false, + event.timerIndicatorDuration); } public final void onBusEvent(FocusPreviousTaskViewEvent event) { @@ -1864,7 +2002,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal EventBus.getDefault().send(new FocusPreviousTaskViewEvent()); break; case DOWN: - EventBus.getDefault().send(new FocusNextTaskViewEvent()); + EventBus.getDefault().send( + new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */)); break; } } @@ -1875,7 +2014,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mUIDozeTrigger.poke(); RecentsDebugFlags debugFlags = Recents.getDebugFlags(); - if (mFocusedTask != null) { + if (debugFlags.isFastToggleRecentsEnabled() && mFocusedTask != null) { TaskView tv = getChildViewForTask(mFocusedTask); if (tv != null) { tv.getHeaderView().cancelFocusTimerIndicator(); @@ -1887,6 +2026,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Ensure that the drag task is not animated addIgnoreTask(event.task); + if (event.task.isFreeformTask()) { + // Animate to the front of the stack + mStackScroller.animateScroll(mLayoutAlgorithm.mInitialScrollP, null); + } + // Enlarge the dragged view slightly float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR; mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(), @@ -1898,14 +2042,22 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN)); } + public final void onBusEvent(DragStartInitializeDropTargetsEvent event) { + SystemServicesProxy ssp = Recents.getSystemServices(); + if (ssp.hasFreeformWorkspaceSupport()) { + event.handler.registerDropTargetForCurrentDrag(mStackDropTarget); + event.handler.registerDropTargetForCurrentDrag(mFreeformWorkspaceDropTarget); + } + } + public final void onBusEvent(DragDropTargetChangedEvent event) { AnimationProps animation = new AnimationProps(SLOW_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN); boolean ignoreTaskOverrides = false; - if (event.dropTarget instanceof DockState) { + if (event.dropTarget instanceof TaskStack.DockState) { // Calculate the new task stack bounds that matches the window size that Recents will // have after the drop - final DockState dockState = (DockState) event.dropTarget; + final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; Rect systemInsets = new Rect(mStableLayoutAlgorithm.mSystemInsets); // When docked, the nav bar insets are consumed and the activity is measured without // insets. However, the window bounds include the insets, so we need to subtract them @@ -1917,7 +2069,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal height, mDividerSize, systemInsets, mLayoutAlgorithm, getResources(), mWindowRect)); mLayoutAlgorithm.setSystemInsets(systemInsets); - mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds); + mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds, + TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); updateLayoutAlgorithm(true /* boundScroll */); ignoreTaskOverrides = true; } else { @@ -1932,13 +2085,39 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public final void onBusEvent(final DragEndEvent event) { // We don't handle drops on the dock regions - if (event.dropTarget instanceof DockState) { + if (event.dropTarget instanceof TaskStack.DockState) { // However, we do need to reset the overrides, since the last state of this task stack // view layout was ignoring task overrides (see DragDropTargetChangedEvent handler) mLayoutAlgorithm.clearUnfocusedTaskOverrides(); return; } + boolean isFreeformTask = event.task.isFreeformTask(); + boolean hasChangedWindowingMode = + (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) || + (isFreeformTask && event.dropTarget == mStackDropTarget); + + if (hasChangedWindowingMode) { + // Move the task to the right position in the stack (ie. the front of the stack if + // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks + // before we update their stack ids, otherwise, the keys will have changed. + if (event.dropTarget == mFreeformWorkspaceDropTarget) { + mStack.setTaskWindowingMode(event.task, WINDOWING_MODE_FREEFORM); + } else if (event.dropTarget == mStackDropTarget) { + mStack.setTaskWindowingMode(event.task, WINDOWING_MODE_FULLSCREEN); + } + updateLayoutAlgorithm(true /* boundScroll */); + + // Move the task to the new stack in the system after the animation completes + event.addPostAnimationCallback(new Runnable() { + @Override + public void run() { + SystemServicesProxy ssp = Recents.getSystemServices(); + ssp.setTaskWindowingMode(event.task.key.id, event.task.key.windowingMode); + } + }); + } + // Restore the task, so that relayout will apply to it below removeIgnoreTask(event.task); @@ -1973,6 +2152,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal event.getAnimationTrigger().increment(); } + public final void onBusEvent(IterateRecentsEvent event) { + if (!mEnterAnimationComplete) { + // Cancel the previous task's window transition before animating the focused state + EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null)); + } + } + public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) { mEnterAnimationComplete = true; tryStartEnterAnimation(); @@ -1991,7 +2177,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Add a runnable to the post animation ref counter to clear all the views trigger.addLastDecrementRunnable(() -> { // Start the dozer to trigger to trigger any UI that shows after a timeout - mUIDozeTrigger.startDozing(); + if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) { + mUIDozeTrigger.startDozing(); + } // Update the focused state here -- since we only set the focused task without // requesting view focus in onFirstLayout(), actually request view focus and @@ -2014,6 +2202,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStackReloaded = false; } + public final void onBusEvent(UpdateFreeformTaskViewVisibilityEvent event) { + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + TaskView tv = taskViews.get(i); + Task task = tv.getTask(); + if (task.isFreeformTask()) { + tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE); + } + } + } + public final void onBusEvent(final MultiWindowStateChangedEvent event) { if (event.inMultiWindow || !event.showDeferredAnimation) { setTasks(event.stack, true /* allowNotifyStackChanges */); @@ -2115,6 +2315,27 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** + * Starts an alpha animation on the freeform workspace background. + */ + private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha, + AnimationProps animation) { + if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) { + return; + } + + Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator); + mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground, + Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha); + mFreeformWorkspaceBackgroundAnimator.setStartDelay( + animation.getDuration(AnimationProps.ALPHA)); + mFreeformWorkspaceBackgroundAnimator.setDuration( + animation.getDuration(AnimationProps.ALPHA)); + mFreeformWorkspaceBackgroundAnimator.setInterpolator( + animation.getInterpolator(AnimationProps.ALPHA)); + mFreeformWorkspaceBackgroundAnimator.start(); + } + + /** * Returns the insert index for the task in the current set of task views. If the given task * is already in the task view list, then this method returns the insert index assuming it * is first removed at the previous index. @@ -2200,24 +2421,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } - /** - * Updates {@param transforms} to be the same size as {@param tasks}. - */ - private void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) { - // We can reuse the task transforms where possible to reduce object allocation - int taskTransformCount = transforms.size(); - int taskCount = tasks.size(); - if (taskTransformCount < taskCount) { - // If there are less transforms than tasks, then add as many transforms as necessary - for (int i = taskTransformCount; i < taskCount; i++) { - transforms.add(new TaskViewTransform()); - } - } else if (taskTransformCount > taskCount) { - // If there are more transforms than tasks, then just subset the transform list - transforms.subList(taskCount, taskTransformCount).clear(); - } - } - public void dump(String prefix, PrintWriter writer) { String innerPrefix = prefix + " "; String id = Integer.toHexString(System.identityHashCode(this)); diff --git a/com/android/systemui/recents/views/TaskStackViewScroller.java b/com/android/systemui/recents/views/TaskStackViewScroller.java index 6b239774..0b20b105 100644 --- a/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -24,6 +24,7 @@ import android.animation.ValueAnimator; import android.content.Context; import android.util.FloatProperty; import android.util.Log; +import android.util.MutableFloat; import android.util.Property; import android.view.ViewConfiguration; import android.view.ViewDebug; @@ -32,8 +33,7 @@ import android.widget.OverScroller; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.recents.Recents; -import com.android.systemui.shared.recents.utilities.AnimationProps; -import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm; import com.android.systemui.statusbar.FlingAnimationUtils; diff --git a/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index b9ca2483..32a249c2 100644 --- a/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -21,6 +21,7 @@ import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.graphics.Path; +import android.graphics.Rect; import android.util.ArrayMap; import android.util.MutableBoolean; import android.view.InputDevice; @@ -44,9 +45,9 @@ import com.android.systemui.recents.events.activity.HideRecentsEvent; import com.android.systemui.recents.events.ui.StackViewScrolledEvent; import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; import com.android.systemui.recents.misc.FreePathInterpolator; -import com.android.systemui.shared.recents.utilities.AnimationProps; -import com.android.systemui.shared.recents.utilities.Utilities; -import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.Task; import com.android.systemui.statusbar.FlingAnimationUtils; import java.util.ArrayList; @@ -402,6 +403,18 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { return; } + // If tapping on the freeform workspace background, just launch the first freeform task + SystemServicesProxy ssp = Recents.getSystemServices(); + if (ssp.hasFreeformWorkspaceSupport()) { + Rect freeformRect = mSv.mLayoutAlgorithm.mFreeformRect; + if (freeformRect.top <= y && y <= freeformRect.bottom) { + if (mSv.launchFreeformTasks()) { + // TODO: Animate Recents away as we launch the freeform tasks + return; + } + } + } + // The user intentionally tapped on the background, which is like a tap on the "desktop". // Hide recents and transition to the launcher. EventBus.getDefault().send(new HideRecentsEvent(false, true)); diff --git a/com/android/systemui/recents/views/TaskView.java b/com/android/systemui/recents/views/TaskView.java index b4408474..9d639647 100644 --- a/com/android/systemui/recents/views/TaskView.java +++ b/com/android/systemui/recents/views/TaskView.java @@ -16,6 +16,8 @@ package com.android.systemui.recents.views; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; + import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; @@ -51,10 +53,10 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.shared.recents.utilities.AnimationProps; -import com.android.systemui.shared.recents.utilities.Utilities; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.recents.model.ThumbnailData; import java.io.PrintWriter; import java.util.ArrayList; @@ -194,7 +196,9 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks * Called from RecentsActivity when it is relaunched. */ void onReload(boolean isResumingFromVisible) { - resetNoUserInteractionState(); + if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) { + resetNoUserInteractionState(); + } if (!isResumingFromVisible) { resetViewProperties(); } @@ -411,7 +415,9 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks * view. */ boolean shouldClipViewInStack() { - if (getVisibility() != View.VISIBLE || Recents.getConfiguration().isLowRamDevice) { + // Never clip for freeform tasks or if invisible + if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE || + Recents.getConfiguration().isLowRamDevice) { return false; } return mClipViewInStack; @@ -709,7 +715,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks /**** Events ****/ public final void onBusEvent(DragEndEvent event) { - if (!(event.dropTarget instanceof DockState)) { + if (!(event.dropTarget instanceof TaskStack.DockState)) { event.addPostAnimationCallback(() -> { // Reset the clip state for the drag view after the end animation completes setClipViewInStack(true); diff --git a/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java b/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java index 0fc507b9..0c6b6b84 100644 --- a/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java +++ b/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java @@ -28,10 +28,11 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import com.android.systemui.R; import com.android.systemui.recents.Recents; import com.android.systemui.recents.events.EventBus; +import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; -import com.android.systemui.shared.recents.utilities.Utilities; -import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.TaskStack; public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate { private static final String TAG = "TaskViewAccessibilityDelegate"; @@ -60,14 +61,14 @@ public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate { super.onInitializeAccessibilityNodeInfo(host, info); if (ActivityManager.supportsSplitScreenMultiWindow(mTaskView.getContext()) && !Recents.getSystemServices().hasDockedTask()) { - DockState[] dockStates = Recents.getConfiguration() + TaskStack.DockState[] dockStates = Recents.getConfiguration() .getDockStatesForCurrentOrientation(); - for (DockState dockState: dockStates) { - if (dockState == DockState.TOP) { + for (TaskStack.DockState dockState: dockStates) { + if (dockState == TaskStack.DockState.TOP) { info.addAction(mActions.get(SPLIT_TASK_TOP)); - } else if (dockState == DockState.LEFT) { + } else if (dockState == TaskStack.DockState.LEFT) { info.addAction(mActions.get(SPLIT_TASK_LEFT)); - } else if (dockState == DockState.RIGHT) { + } else if (dockState == TaskStack.DockState.RIGHT) { info.addAction(mActions.get(SPLIT_TASK_RIGHT)); } } @@ -77,11 +78,11 @@ public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate { @Override public boolean performAccessibilityAction(View host, int action, Bundle args) { if (action == SPLIT_TASK_TOP) { - simulateDragIntoMultiwindow(DockState.TOP); + simulateDragIntoMultiwindow(TaskStack.DockState.TOP); } else if (action == SPLIT_TASK_LEFT) { - simulateDragIntoMultiwindow(DockState.LEFT); + simulateDragIntoMultiwindow(TaskStack.DockState.LEFT); } else if (action == SPLIT_TASK_RIGHT) { - simulateDragIntoMultiwindow(DockState.RIGHT); + simulateDragIntoMultiwindow(TaskStack.DockState.RIGHT); } else { return super.performAccessibilityAction(host, action, args); } @@ -89,7 +90,8 @@ public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate { } /** Simulate a user drag event to split the screen to the respected side */ - private void simulateDragIntoMultiwindow(DockState dockState) { + private void simulateDragIntoMultiwindow(TaskStack.DockState dockState) { + int orientation = Utilities.getAppConfiguration(mTaskView.getContext()).orientation; EventBus.getDefault().send(new DragStartEvent(mTaskView.getTask(), mTaskView, new Point(0,0), false /* isUserTouchInitiated */)); EventBus.getDefault().send(new DragEndEvent(mTaskView.getTask(), mTaskView, dockState)); diff --git a/com/android/systemui/recents/views/TaskViewHeader.java b/com/android/systemui/recents/views/TaskViewHeader.java index 0272a903..198ecae2 100644 --- a/com/android/systemui/recents/views/TaskViewHeader.java +++ b/com/android/systemui/recents/views/TaskViewHeader.java @@ -17,6 +17,8 @@ package com.android.systemui.recents.views; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.animation.Animator; @@ -31,6 +33,7 @@ import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; @@ -56,10 +59,8 @@ import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.LaunchTaskEvent; import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.PackageManagerWrapper; -import com.android.systemui.shared.recents.utilities.Utilities; -import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.Task; /* The task bar view */ public class TaskViewHeader extends FrameLayout @@ -163,6 +164,8 @@ public class TaskViewHeader extends FrameLayout float mDimAlpha; Drawable mLightDismissDrawable; Drawable mDarkDismissDrawable; + Drawable mLightFreeformIcon; + Drawable mDarkFreeformIcon; Drawable mLightFullscreenIcon; Drawable mDarkFullscreenIcon; Drawable mLightInfoIcon; @@ -170,8 +173,6 @@ public class TaskViewHeader extends FrameLayout int mTaskBarViewLightTextColor; int mTaskBarViewDarkTextColor; int mDisabledTaskBarBackgroundColor; - String mDismissDescFormat; - String mAppInfoDescFormat; int mTaskWindowingMode = WINDOWING_MODE_UNDEFINED; // Header background @@ -214,15 +215,14 @@ public class TaskViewHeader extends FrameLayout mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight); mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color); mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color); + mLightFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_light); + mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark); mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light); mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark); mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light); mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark); mDisabledTaskBarBackgroundColor = context.getColor(R.color.recents_task_bar_disabled_background_color); - mDismissDescFormat = mContext.getString( - R.string.accessibility_recents_item_will_be_dismissed); - mAppInfoDescFormat = mContext.getString(R.string.accessibility_recents_item_open_app_info); // Configure the background and dim mBackground = new HighlightColorDrawable(); @@ -249,6 +249,9 @@ public class TaskViewHeader extends FrameLayout mIconView.setOnLongClickListener(this); mTitleView = findViewById(R.id.title); mDismissButton = findViewById(R.id.dismiss_task); + if (ssp.hasFreeformWorkspaceSupport()) { + mMoveTaskButton = findViewById(R.id.move_task); + } onConfigurationChanged(); } @@ -338,6 +341,20 @@ public class TaskViewHeader extends FrameLayout boolean showDismissIcon = true; int rightInset = width - getMeasuredWidth(); + if (mTask != null && mTask.isFreeformTask()) { + // For freeform tasks, we always show the app icon, and only show the title, move-task + // icon, and the dismiss icon if there is room + int appIconWidth = mIconView.getMeasuredWidth(); + int titleWidth = (int) mTitleView.getPaint().measureText(mTask.title); + int dismissWidth = mDismissButton.getMeasuredWidth(); + int moveTaskWidth = mMoveTaskButton != null + ? mMoveTaskButton.getMeasuredWidth() + : 0; + showTitle = width >= (appIconWidth + dismissWidth + moveTaskWidth + titleWidth); + showMoveIcon = width >= (appIconWidth + dismissWidth + moveTaskWidth); + showDismissIcon = width >= (appIconWidth + dismissWidth); + } + mTitleView.setVisibility(showTitle ? View.VISIBLE : View.INVISIBLE); if (mMoveTaskButton != null) { mMoveTaskButton.setVisibility(showMoveIcon ? View.VISIBLE : View.INVISIBLE); @@ -460,14 +477,44 @@ public class TaskViewHeader extends FrameLayout mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor); mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ? mLightDismissDrawable : mDarkDismissDrawable); - mDismissButton.setContentDescription(String.format(mDismissDescFormat, t.titleDescription)); + mDismissButton.setContentDescription(t.dismissDescription); mDismissButton.setOnClickListener(this); mDismissButton.setClickable(false); ((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true); + // When freeform workspaces are enabled, then update the move-task button depending on the + // current task + if (mMoveTaskButton != null) { + if (t.isFreeformTask()) { + mTaskWindowingMode = WINDOWING_MODE_FULLSCREEN; + mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor + ? mLightFullscreenIcon + : mDarkFullscreenIcon); + } else { + mTaskWindowingMode = WINDOWING_MODE_FREEFORM; + mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor + ? mLightFreeformIcon + : mDarkFreeformIcon); + } + mMoveTaskButton.setOnClickListener(this); + mMoveTaskButton.setClickable(false); + ((RippleDrawable) mMoveTaskButton.getBackground()).setForceSoftware(true); + } + + if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) { + if (mFocusTimerIndicator == null) { + mFocusTimerIndicator = (ProgressBar) Utilities.findViewStubById(this, + R.id.focus_timer_indicator_stub).inflate(); + } + mFocusTimerIndicator.getProgressDrawable() + .setColorFilter( + getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor), + PorterDuff.Mode.SRC_IN); + } + // In accessibility, a single click on the focused app info button will show it if (touchExplorationEnabled) { - mIconView.setContentDescription(String.format(mAppInfoDescFormat, t.titleDescription)); + mIconView.setContentDescription(t.appInfoDescription); mIconView.setOnClickListener(this); mIconView.setClickable(true); } @@ -604,7 +651,7 @@ public class TaskViewHeader extends FrameLayout SystemServicesProxy ssp = Recents.getSystemServices(); ComponentName cn = mTask.key.getComponent(); int userId = mTask.key.userId; - ActivityInfo activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(cn, userId); + ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId); if (activityInfo == null) { return; } @@ -624,12 +671,11 @@ public class TaskViewHeader extends FrameLayout } // Update the overlay contents for the current app - mAppTitleView.setText(ActivityManagerWrapper.getInstance().getBadgedApplicationLabel( - activityInfo.applicationInfo, userId)); + mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId)); mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ? mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor); - mAppIconView.setImageDrawable(ActivityManagerWrapper.getInstance().getBadgedApplicationIcon( - activityInfo.applicationInfo, userId)); + mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo, + userId)); mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor ? mLightInfoIcon : mDarkInfoIcon); diff --git a/com/android/systemui/recents/views/TaskViewThumbnail.java b/com/android/systemui/recents/views/TaskViewThumbnail.java index 4152b05a..a2190b3a 100644 --- a/com/android/systemui/recents/views/TaskViewThumbnail.java +++ b/com/android/systemui/recents/views/TaskViewThumbnail.java @@ -37,9 +37,9 @@ import android.view.ViewDebug; import com.android.systemui.R; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent; -import com.android.systemui.shared.recents.utilities.Utilities; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.ThumbnailData; import java.io.PrintWriter; @@ -245,6 +245,10 @@ public class TaskViewThumbnail extends View { public void updateThumbnailMatrix() { mThumbnailScale = 1f; if (mBitmapShader != null && mThumbnailData != null) { + // We consider this a stack task if it is not freeform (ie. has no bounds) or has been + // dragged into the stack from the freeform workspace + boolean isStackTask = !mTask.isFreeformTask() || mTask.bounds == null; + int xOffset, yOffset = 0; if (mTaskViewRect.isEmpty()) { // If we haven't measured , skip the thumbnail drawing and only draw the background // color @@ -262,7 +266,7 @@ public class TaskViewThumbnail extends View { mThumbnailScale = (float) (mTaskViewRect.height() - mTitleBarHeight) / (float) mThumbnailRect.height(); } - } else { + } else if (isStackTask) { float invThumbnailScale = 1f / mFullscreenThumbnailScale; if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) { if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) { @@ -279,6 +283,12 @@ public class TaskViewThumbnail extends View { // Otherwise, scale the screenshot to fit 1:1 in the current orientation mThumbnailScale = invThumbnailScale; } + } else { + // Otherwise, if this is a freeform task with task bounds, then scale the thumbnail + // to fit the entire bitmap into the task bounds + mThumbnailScale = Math.min( + (float) mTaskViewRect.width() / mThumbnailRect.width(), + (float) mTaskViewRect.height() / mThumbnailRect.height()); } mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale, -mThumbnailData.insets.top * mFullscreenThumbnailScale); diff --git a/com/android/systemui/recents/views/TaskViewTransform.java b/com/android/systemui/recents/views/TaskViewTransform.java index 9b717e0e..397f24eb 100644 --- a/com/android/systemui/recents/views/TaskViewTransform.java +++ b/com/android/systemui/recents/views/TaskViewTransform.java @@ -21,11 +21,11 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.graphics.Rect; import android.graphics.RectF; +import android.util.IntProperty; import android.util.Property; import android.view.View; -import com.android.systemui.shared.recents.utilities.AnimationProps; -import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.recents.misc.Utilities; import java.util.ArrayList; @@ -59,7 +59,7 @@ public class TaskViewTransform { public boolean visible = false; - // This is a window-space rect used for positioning the task in the stack + // This is a window-space rect used for positioning the task in the stack and freeform workspace public RectF rect = new RectF(); /** diff --git a/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java index ccda4b5a..c5132024 100644 --- a/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java +++ b/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java @@ -25,9 +25,10 @@ import android.graphics.Rect; import android.view.WindowManager; import com.android.systemui.R; +import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent; import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction; -import com.android.systemui.shared.recents.utilities.Utilities; -import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.Task; import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; import com.android.systemui.recents.views.TaskViewTransform; diff --git a/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java b/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java index 95f1d583..86ed583b 100644 --- a/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java +++ b/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java @@ -23,7 +23,7 @@ import android.view.View; import android.view.ViewTreeObserver.OnGlobalFocusChangeListener; import com.android.systemui.R; -import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.recents.model.TaskStack; import com.android.systemui.recents.views.TaskStackView; public class TaskViewFocusFrame extends View implements OnGlobalFocusChangeListener { diff --git a/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java b/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java index 49cac269..17e6b9e3 100644 --- a/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java +++ b/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java @@ -23,8 +23,8 @@ import android.view.ViewConfiguration; import com.android.systemui.R; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivityLaunchState; -import com.android.systemui.shared.recents.utilities.Utilities; -import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.Task; import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; import com.android.systemui.recents.views.TaskViewTransform; diff --git a/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java b/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java deleted file mode 100644 index ddd27b0b..00000000 --- a/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2017 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.systemui.shared.recents.model; - -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.HandlerThread; -import android.util.Log; - -import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.PackageManagerWrapper; - -/** - * Background task resource loader - */ -class BackgroundTaskLoader implements Runnable { - static String TAG = "BackgroundTaskLoader"; - static boolean DEBUG = false; - - private Context mContext; - private final HandlerThread mLoadThread; - private final Handler mLoadThreadHandler; - private final Handler mMainThreadHandler; - - private final TaskResourceLoadQueue mLoadQueue; - private final TaskKeyLruCache<Drawable> mIconCache; - private final BitmapDrawable mDefaultIcon; - - private boolean mStarted; - private boolean mCancelled; - private boolean mWaitingOnLoadQueue; - - private final OnIdleChangedListener mOnIdleChangedListener; - - /** Constructor, creates a new loading thread that loads task resources in the background */ - public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue, - TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon, - OnIdleChangedListener onIdleChangedListener) { - mLoadQueue = loadQueue; - mIconCache = iconCache; - mDefaultIcon = defaultIcon; - mMainThreadHandler = new Handler(); - mOnIdleChangedListener = onIdleChangedListener; - mLoadThread = new HandlerThread("Recents-TaskResourceLoader", - android.os.Process.THREAD_PRIORITY_BACKGROUND); - mLoadThread.start(); - mLoadThreadHandler = new Handler(mLoadThread.getLooper()); - } - - /** Restarts the loader thread */ - void start(Context context) { - mContext = context; - mCancelled = false; - if (!mStarted) { - // Start loading on the load thread - mStarted = true; - mLoadThreadHandler.post(this); - } else { - // Notify the load thread to start loading again - synchronized (mLoadThread) { - mLoadThread.notifyAll(); - } - } - } - - /** Requests the loader thread to stop after the current iteration */ - void stop() { - // Mark as cancelled for the thread to pick up - mCancelled = true; - // If we are waiting for the load queue for more tasks, then we can just reset the - // Context now, since nothing is using it - if (mWaitingOnLoadQueue) { - mContext = null; - } - } - - @Override - public void run() { - while (true) { - if (mCancelled) { - // We have to unset the context here, since the background thread may be using it - // when we call stop() - mContext = null; - // If we are cancelled, then wait until we are started again - synchronized(mLoadThread) { - try { - mLoadThread.wait(); - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - } - } else { - // If we've stopped the loader, then fall through to the above logic to wait on - // the load thread - processLoadQueueItem(); - - // If there are no other items in the list, then just wait until something is added - if (!mCancelled && mLoadQueue.isEmpty()) { - synchronized(mLoadQueue) { - try { - mWaitingOnLoadQueue = true; - mMainThreadHandler.post( - () -> mOnIdleChangedListener.onIdleChanged(true)); - mLoadQueue.wait(); - mMainThreadHandler.post( - () -> mOnIdleChangedListener.onIdleChanged(false)); - mWaitingOnLoadQueue = false; - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - } - } - } - } - } - - /** - * This needs to be in a separate method to work around an surprising interpreter behavior: - * The register will keep the local reference to cachedThumbnailData even if it falls out of - * scope. Putting it into a method fixes this issue. - */ - private void processLoadQueueItem() { - // Load the next item from the queue - final Task t = mLoadQueue.nextTask(); - if (t != null) { - Drawable cachedIcon = mIconCache.get(t.key); - - // Load the icon if it is stale or we haven't cached one yet - if (cachedIcon == null) { - cachedIcon = ActivityManagerWrapper.getInstance().getBadgedTaskDescriptionIcon( - mContext, t.taskDescription, t.key.userId, mContext.getResources()); - - if (cachedIcon == null) { - ActivityInfo info = PackageManagerWrapper.getInstance().getActivityInfo( - t.key.getComponent(), t.key.userId); - if (info != null) { - if (DEBUG) Log.d(TAG, "Loading icon: " + t.key); - cachedIcon = ActivityManagerWrapper.getInstance().getBadgedActivityIcon( - info, t.key.userId); - } - } - - if (cachedIcon == null) { - cachedIcon = mDefaultIcon; - } - - // At this point, even if we can't load the icon, we will set the - // default icon. - mIconCache.put(t.key, cachedIcon); - } - - if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key); - final ThumbnailData thumbnailData = - ActivityManagerWrapper.getInstance().getTaskThumbnail(t.key.id, - true /* reducedResolution */); - - if (!mCancelled) { - // Notify that the task data has changed - final Drawable finalIcon = cachedIcon; - mMainThreadHandler.post( - () -> t.notifyTaskDataLoaded(thumbnailData, finalIcon)); - } - } - } - - interface OnIdleChangedListener { - void onIdleChanged(boolean idle); - } -} diff --git a/com/android/systemui/shared/recents/model/FilteredTaskList.java b/com/android/systemui/shared/recents/model/FilteredTaskList.java deleted file mode 100644 index 898d64a1..00000000 --- a/com/android/systemui/shared/recents/model/FilteredTaskList.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2017 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.systemui.shared.recents.model; - -import android.util.ArrayMap; -import android.util.SparseArray; - -import com.android.systemui.shared.recents.model.Task.TaskKey; - -import java.util.ArrayList; -import java.util.List; - -/** - * A list of filtered tasks. - */ -class FilteredTaskList { - - private final ArrayList<Task> mTasks = new ArrayList<>(); - private final ArrayList<Task> mFilteredTasks = new ArrayList<>(); - private final ArrayMap<TaskKey, Integer> mFilteredTaskIndices = new ArrayMap<>(); - private TaskFilter mFilter; - - /** Sets the task filter, and returns whether the set of filtered tasks have changed. */ - boolean setFilter(TaskFilter filter) { - ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks); - mFilter = filter; - updateFilteredTasks(); - return !prevFilteredTasks.equals(mFilteredTasks); - } - - /** Adds a new task to the task list */ - void add(Task t) { - mTasks.add(t); - updateFilteredTasks(); - } - - /** Sets the list of tasks */ - void set(List<Task> tasks) { - mTasks.clear(); - mTasks.addAll(tasks); - updateFilteredTasks(); - } - - /** Removes a task from the base list only if it is in the filtered list */ - boolean remove(Task t) { - if (mFilteredTasks.contains(t)) { - boolean removed = mTasks.remove(t); - updateFilteredTasks(); - return removed; - } - return false; - } - - /** Returns the index of this task in the list of filtered tasks */ - int indexOf(Task t) { - if (t != null && mFilteredTaskIndices.containsKey(t.key)) { - return mFilteredTaskIndices.get(t.key); - } - return -1; - } - - /** Returns the size of the list of filtered tasks */ - int size() { - return mFilteredTasks.size(); - } - - /** Returns whether the filtered list contains this task */ - boolean contains(Task t) { - return mFilteredTaskIndices.containsKey(t.key); - } - - /** Updates the list of filtered tasks whenever the base task list changes */ - private void updateFilteredTasks() { - mFilteredTasks.clear(); - if (mFilter != null) { - // Create a sparse array from task id to Task - SparseArray<Task> taskIdMap = new SparseArray<>(); - int taskCount = mTasks.size(); - for (int i = 0; i < taskCount; i++) { - Task t = mTasks.get(i); - taskIdMap.put(t.key.id, t); - } - - for (int i = 0; i < taskCount; i++) { - Task t = mTasks.get(i); - if (mFilter.acceptTask(taskIdMap, t, i)) { - mFilteredTasks.add(t); - } - } - } else { - mFilteredTasks.addAll(mTasks); - } - updateFilteredTaskIndices(); - } - - /** Updates the mapping of tasks to indices. */ - private void updateFilteredTaskIndices() { - int taskCount = mFilteredTasks.size(); - mFilteredTaskIndices.clear(); - for (int i = 0; i < taskCount; i++) { - Task t = mFilteredTasks.get(i); - mFilteredTaskIndices.put(t.key, i); - } - } - - /** Returns the list of filtered tasks */ - ArrayList<Task> getTasks() { - return mFilteredTasks; - } -} diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java deleted file mode 100644 index c9368f3e..00000000 --- a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2014 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.systemui.shared.recents.model; - -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; - -import android.app.ActivityManager; -import android.app.KeyguardManager; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.util.SparseBooleanArray; - -import com.android.systemui.shared.recents.model.Task.TaskKey; -import com.android.systemui.shared.system.ActivityManagerWrapper; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - - -/** - * This class stores the loading state as it goes through multiple stages of loading: - * 1) preloadRawTasks() will load the raw set of recents tasks from the system - * 2) preloadPlan() will construct a new task stack with all metadata and only icons and - * thumbnails that are currently in the cache - * 3) executePlan() will actually load and fill in the icons and thumbnails according to the load - * options specified, such that we can transition into the Recents activity seamlessly - */ -public class RecentsTaskLoadPlan { - - /** The set of conditions to load tasks. */ - public static class Options { - public int runningTaskId = -1; - public boolean loadIcons = true; - public boolean loadThumbnails = false; - public boolean onlyLoadForCache = false; - public boolean onlyLoadPausedActivities = false; - public int numVisibleTasks = 0; - public int numVisibleTaskThumbnails = 0; - } - - private final Context mContext; - private final KeyguardManager mKeyguardManager; - - private List<ActivityManager.RecentTaskInfo> mRawTasks; - private TaskStack mStack; - - private final SparseBooleanArray mTmpLockedUsers = new SparseBooleanArray(); - - public RecentsTaskLoadPlan(Context context) { - mContext = context; - mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); - } - - /** - * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent - * to most-recent order. - * - * Note: Do not lock, callers should synchronize on the loader before making this call. - */ - void preloadRawTasks() { - int currentUserId = ActivityManagerWrapper.getInstance().getCurrentUserId(); - mRawTasks = ActivityManagerWrapper.getInstance().getRecentTasks( - ActivityManager.getMaxRecentTasksStatic(), currentUserId); - - // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it - Collections.reverse(mRawTasks); - } - - /** - * Preloads the list of recent tasks from the system. After this call, the TaskStack will - * have a list of all the recent tasks with their metadata, not including icons or - * thumbnails which were not cached and have to be loaded. - * - * The tasks will be ordered by: - * - least-recent to most-recent stack tasks - * - * Note: Do not lock, since this can be calling back to the loader, which separately also drives - * this call (callers should synchronize on the loader before making this call). - */ - void preloadPlan(RecentsTaskLoader loader, int runningTaskId) { - Resources res = mContext.getResources(); - ArrayList<Task> allTasks = new ArrayList<>(); - if (mRawTasks == null) { - preloadRawTasks(); - } - - int taskCount = mRawTasks.size(); - for (int i = 0; i < taskCount; i++) { - ActivityManager.RecentTaskInfo t = mRawTasks.get(i); - - // Compose the task key - final int windowingMode = t.configuration.windowConfiguration.getWindowingMode(); - TaskKey taskKey = new TaskKey(t.persistentId, windowingMode, t.baseIntent, - t.userId, t.lastActiveTime); - - boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM; - boolean isStackTask = !isFreeformTask; - boolean isLaunchTarget = taskKey.id == runningTaskId; - - // Load the title, icon, and color - ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey); - String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription); - String titleDescription = loader.getAndUpdateContentDescription(taskKey, - t.taskDescription); - Drawable icon = isStackTask - ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false) - : null; - ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey, - false /* loadIfNotCached */, false /* storeInCache */); - int activityColor = loader.getActivityPrimaryColor(t.taskDescription); - int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription); - boolean isSystemApp = (info != null) && - ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); - - // TODO: Refactor to not do this every preload - if (mTmpLockedUsers.indexOfKey(t.userId) < 0) { - mTmpLockedUsers.put(t.userId, mKeyguardManager.isDeviceLocked(t.userId)); - } - boolean isLocked = mTmpLockedUsers.get(t.userId); - - // Add the task to the stack - Task task = new Task(taskKey, icon, - thumbnail, title, titleDescription, activityColor, backgroundColor, - isLaunchTarget, isStackTask, isSystemApp, t.supportsSplitScreenMultiWindow, - t.taskDescription, t.resizeMode, t.topActivity, isLocked); - - allTasks.add(task); - } - - // Initialize the stacks - mStack = new TaskStack(); - mStack.setTasks(allTasks, false /* notifyStackChanges */); - } - - /** - * Called to apply the actual loading based on the specified conditions. - * - * Note: Do not lock, since this can be calling back to the loader, which separately also drives - * this call (callers should synchronize on the loader before making this call). - */ - void executePlan(Options opts, RecentsTaskLoader loader) { - Resources res = mContext.getResources(); - - // Iterate through each of the tasks and load them according to the load conditions. - ArrayList<Task> tasks = mStack.getStackTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - TaskKey taskKey = task.key; - - boolean isRunningTask = (task.key.id == opts.runningTaskId); - boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks); - boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails); - - // If requested, skip the running task - if (opts.onlyLoadPausedActivities && isRunningTask) { - continue; - } - - if (opts.loadIcons && (isRunningTask || isVisibleTask)) { - if (task.icon == null) { - task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, res, - true); - } - } - if (opts.loadThumbnails && isVisibleThumbnail) { - task.thumbnail = loader.getAndUpdateThumbnail(taskKey, - true /* loadIfNotCached */, true /* storeInCache */); - } - } - } - - /** - * Returns the TaskStack from the preloaded list of recent tasks. - */ - public TaskStack getTaskStack() { - return mStack; - } - - /** Returns whether there are any tasks in any stacks. */ - public boolean hasTasks() { - if (mStack != null) { - return mStack.getTaskCount() > 0; - } - return false; - } -} diff --git a/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java b/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java deleted file mode 100644 index fbb6aceb..00000000 --- a/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2017 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.systemui.shared.recents.model; - -import java.util.concurrent.ConcurrentLinkedQueue; - -/** - * A Task load queue - */ -class TaskResourceLoadQueue { - - private final ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<>(); - - /** Adds a new task to the load queue */ - void addTask(Task t) { - if (!mQueue.contains(t)) { - mQueue.add(t); - } - synchronized(this) { - notifyAll(); - } - } - - /** - * Retrieves the next task from the load queue, as well as whether we want that task to be - * force reloaded. - */ - Task nextTask() { - return mQueue.poll(); - } - - /** Removes a task from the load queue */ - void removeTask(Task t) { - mQueue.remove(t); - } - - /** Clears all the tasks from the load queue */ - void clearTasks() { - mQueue.clear(); - } - - /** Returns whether the load queue is empty */ - boolean isEmpty() { - return mQueue.isEmpty(); - } -} diff --git a/com/android/systemui/shared/recents/model/TaskStack.java b/com/android/systemui/shared/recents/model/TaskStack.java deleted file mode 100644 index 693379d3..00000000 --- a/com/android/systemui/shared/recents/model/TaskStack.java +++ /dev/null @@ -1,403 +0,0 @@ -/* - * Copyright (C) 2014 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.systemui.shared.recents.model; - -import android.content.ComponentName; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.SparseArray; - -import com.android.systemui.shared.recents.model.Task.TaskKey; -import com.android.systemui.shared.recents.utilities.AnimationProps; -import com.android.systemui.shared.system.PackageManagerWrapper; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; - - -/** - * The task stack contains a list of multiple tasks. - */ -public class TaskStack { - - private static final String TAG = "TaskStack"; - - /** Task stack callbacks */ - public interface TaskStackCallbacks { - /** - * Notifies when a new task has been added to the stack. - */ - void onStackTaskAdded(TaskStack stack, Task newTask); - - /** - * Notifies when a task has been removed from the stack. - */ - void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask, - AnimationProps animation, boolean fromDockGesture, - boolean dismissRecentsIfAllRemoved); - - /** - * Notifies when all tasks have been removed from the stack. - */ - void onStackTasksRemoved(TaskStack stack); - - /** - * Notifies when tasks in the stack have been updated. - */ - void onStackTasksUpdated(TaskStack stack); - } - - private final ArrayList<Task> mRawTaskList = new ArrayList<>(); - private final FilteredTaskList mStackTaskList = new FilteredTaskList(); - private TaskStackCallbacks mCb; - - public TaskStack() { - // Ensure that we only show stack tasks - mStackTaskList.setFilter((taskIdMap, t, index) -> t.isStackTask); - } - - /** Sets the callbacks for this task stack. */ - public void setCallbacks(TaskStackCallbacks cb) { - mCb = cb; - } - - /** - * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on - * how they should update themselves. - */ - public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) { - removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */); - } - - /** - * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on - * how they should update themselves. - */ - public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture, - boolean dismissRecentsIfAllRemoved) { - if (mStackTaskList.contains(t)) { - mStackTaskList.remove(t); - Task newFrontMostTask = getStackFrontMostTask(); - if (mCb != null) { - // Notify that a task has been removed - mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation, - fromDockGesture, dismissRecentsIfAllRemoved); - } - } - mRawTaskList.remove(t); - } - - /** - * Removes all tasks from the stack. - */ - public void removeAllTasks(boolean notifyStackChanges) { - ArrayList<Task> tasks = mStackTaskList.getTasks(); - for (int i = tasks.size() - 1; i >= 0; i--) { - Task t = tasks.get(i); - mStackTaskList.remove(t); - mRawTaskList.remove(t); - } - if (mCb != null && notifyStackChanges) { - // Notify that all tasks have been removed - mCb.onStackTasksRemoved(this); - } - } - - - /** - * @see #setTasks(List, boolean) - */ - public void setTasks(TaskStack stack, boolean notifyStackChanges) { - setTasks(stack.mRawTaskList, notifyStackChanges); - } - - /** - * Sets a few tasks in one go, without calling any callbacks. - * - * @param tasks the new set of tasks to replace the current set. - * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks. - */ - public void setTasks(List<Task> tasks, boolean notifyStackChanges) { - // Compute a has set for each of the tasks - ArrayMap<TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList); - ArrayMap<TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks); - ArrayList<Task> addedTasks = new ArrayList<>(); - ArrayList<Task> removedTasks = new ArrayList<>(); - ArrayList<Task> allTasks = new ArrayList<>(); - - // Disable notifications if there are no callbacks - if (mCb == null) { - notifyStackChanges = false; - } - - // Remove any tasks that no longer exist - int taskCount = mRawTaskList.size(); - for (int i = taskCount - 1; i >= 0; i--) { - Task task = mRawTaskList.get(i); - if (!newTasksMap.containsKey(task.key)) { - if (notifyStackChanges) { - removedTasks.add(task); - } - } - } - - // Add any new tasks - taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task newTask = tasks.get(i); - Task currentTask = currentTasksMap.get(newTask.key); - if (currentTask == null && notifyStackChanges) { - addedTasks.add(newTask); - } else if (currentTask != null) { - // The current task has bound callbacks, so just copy the data from the new task - // state and add it back into the list - currentTask.copyFrom(newTask); - newTask = currentTask; - } - allTasks.add(newTask); - } - - // Sort all the tasks to ensure they are ordered correctly - for (int i = allTasks.size() - 1; i >= 0; i--) { - allTasks.get(i).temporarySortIndexInStack = i; - } - - mStackTaskList.set(allTasks); - mRawTaskList.clear(); - mRawTaskList.addAll(allTasks); - - // Only callback for the removed tasks after the stack has updated - int removedTaskCount = removedTasks.size(); - Task newFrontMostTask = getStackFrontMostTask(); - for (int i = 0; i < removedTaskCount; i++) { - mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask, - AnimationProps.IMMEDIATE, false /* fromDockGesture */, - true /* dismissRecentsIfAllRemoved */); - } - - // Only callback for the newly added tasks after this stack has been updated - int addedTaskCount = addedTasks.size(); - for (int i = 0; i < addedTaskCount; i++) { - mCb.onStackTaskAdded(this, addedTasks.get(i)); - } - - // Notify that the task stack has been updated - if (notifyStackChanges) { - mCb.onStackTasksUpdated(this); - } - } - - /** - * Gets the front-most task in the stack. - */ - public Task getStackFrontMostTask() { - ArrayList<Task> stackTasks = mStackTaskList.getTasks(); - if (stackTasks.isEmpty()) { - return null; - } - return stackTasks.get(stackTasks.size() - 1); - } - - /** Gets the task keys */ - public ArrayList<TaskKey> getTaskKeys() { - ArrayList<TaskKey> taskKeys = new ArrayList<>(); - ArrayList<Task> tasks = computeAllTasksList(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - taskKeys.add(task.key); - } - return taskKeys; - } - - /** - * Returns the set of "active" (non-historical) tasks in the stack that have been used recently. - */ - public ArrayList<Task> getStackTasks() { - return mStackTaskList.getTasks(); - } - - /** - * Computes a set of all the active and historical tasks. - */ - public ArrayList<Task> computeAllTasksList() { - ArrayList<Task> tasks = new ArrayList<>(); - tasks.addAll(mStackTaskList.getTasks()); - return tasks; - } - - /** - * Returns the number of stacktasks. - */ - public int getTaskCount() { - return mStackTaskList.size(); - } - - /** - * Returns the number of stack tasks. - */ - public int getStackTaskCount() { - return mStackTaskList.size(); - } - - /** - * Returns the task in stack tasks which is the launch target. - */ - public Task getLaunchTarget() { - ArrayList<Task> tasks = mStackTaskList.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - if (task.isLaunchTarget) { - return task; - } - } - return null; - } - - /** - * Returns whether the next launch target should actually be the PiP task. - */ - public boolean isNextLaunchTargetPip(long lastPipTime) { - Task launchTarget = getLaunchTarget(); - Task nextLaunchTarget = getNextLaunchTargetRaw(); - if (nextLaunchTarget != null && lastPipTime > 0) { - // If the PiP time is more recent than the next launch target, then launch the PiP task - return lastPipTime > nextLaunchTarget.key.lastActiveTime; - } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) { - // Otherwise, if there is no next launch target, but there is a PiP, then launch - // the PiP task - return true; - } - return false; - } - - /** - * Returns the task in stack tasks which should be launched next if Recents are toggled - * again, or null if there is no task to be launched. Callers should check - * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the - * stack. - */ - public Task getNextLaunchTarget() { - Task nextLaunchTarget = getNextLaunchTargetRaw(); - if (nextLaunchTarget != null) { - return nextLaunchTarget; - } - return getStackTasks().get(getTaskCount() - 1); - } - - private Task getNextLaunchTargetRaw() { - int taskCount = getTaskCount(); - if (taskCount == 0) { - return null; - } - int launchTaskIndex = indexOfStackTask(getLaunchTarget()); - if (launchTaskIndex != -1 && launchTaskIndex > 0) { - return getStackTasks().get(launchTaskIndex - 1); - } - return null; - } - - /** Returns the index of this task in this current task stack */ - public int indexOfStackTask(Task t) { - return mStackTaskList.indexOf(t); - } - - /** Finds the task with the specified task id. */ - public Task findTaskWithId(int taskId) { - ArrayList<Task> tasks = computeAllTasksList(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - if (task.key.id == taskId) { - return task; - } - } - return null; - } - - /** - * Computes the components of tasks in this stack that have been removed as a result of a change - * in the specified package. - */ - public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) { - // Identify all the tasks that should be removed as a result of the package being removed. - // Using a set to ensure that we callback once per unique component. - ArraySet<ComponentName> existingComponents = new ArraySet<>(); - ArraySet<ComponentName> removedComponents = new ArraySet<>(); - ArrayList<TaskKey> taskKeys = getTaskKeys(); - int taskKeyCount = taskKeys.size(); - for (int i = 0; i < taskKeyCount; i++) { - TaskKey t = taskKeys.get(i); - - // Skip if this doesn't apply to the current user - if (t.userId != userId) continue; - - ComponentName cn = t.getComponent(); - if (cn.getPackageName().equals(packageName)) { - if (existingComponents.contains(cn)) { - // If we know that the component still exists in the package, then skip - continue; - } - if (PackageManagerWrapper.getInstance().getActivityInfo(cn, userId) != null) { - existingComponents.add(cn); - } else { - removedComponents.add(cn); - } - } - } - return removedComponents; - } - - @Override - public String toString() { - String str = "Stack Tasks (" + mStackTaskList.size() + "):\n"; - ArrayList<Task> tasks = mStackTaskList.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - str += " " + tasks.get(i).toString() + "\n"; - } - return str; - } - - /** - * Given a list of tasks, returns a map of each task's key to the task. - */ - private ArrayMap<TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) { - ArrayMap<TaskKey, Task> map = new ArrayMap<>(tasks.size()); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); - map.put(task.key, task); - } - return map; - } - - public void dump(String prefix, PrintWriter writer) { - String innerPrefix = prefix + " "; - - writer.print(prefix); writer.print(TAG); - writer.print(" numStackTasks="); writer.print(mStackTaskList.size()); - writer.println(); - ArrayList<Task> tasks = mStackTaskList.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - tasks.get(i).dump(innerPrefix, writer); - } - } -} diff --git a/com/android/systemui/shared/system/ActivityManagerWrapper.java b/com/android/systemui/shared/system/ActivityManagerWrapper.java deleted file mode 100644 index 3f93f76a..00000000 --- a/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2015 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.systemui.shared.system; - -import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; - -import android.annotation.NonNull; -import android.app.ActivityManager; -import android.app.ActivityManager.RecentTaskInfo; -import android.app.AppGlobals; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.UserInfo; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.RemoteException; -import android.os.UserHandle; -import android.util.IconDrawableFactory; -import android.util.Log; - -import com.android.systemui.shared.recents.model.ThumbnailData; - -import java.util.ArrayList; -import java.util.List; - -public class ActivityManagerWrapper { - - private static final String TAG = "ActivityManagerWrapper"; - - private static final ActivityManagerWrapper sInstance = new ActivityManagerWrapper(); - - private final PackageManager mPackageManager; - private final IconDrawableFactory mDrawableFactory; - - private ActivityManagerWrapper() { - final Context context = AppGlobals.getInitialApplication(); - mPackageManager = context.getPackageManager(); - mDrawableFactory = IconDrawableFactory.newInstance(context); - } - - public static ActivityManagerWrapper getInstance() { - return sInstance; - } - - /** - * @return the current user's id. - */ - public int getCurrentUserId() { - UserInfo ui; - try { - ui = ActivityManager.getService().getCurrentUser(); - return ui != null ? ui.id : 0; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * @return a list of the recents tasks. - */ - public List<RecentTaskInfo> getRecentTasks(int numTasks, int userId) { - try { - return ActivityManager.getService().getRecentTasks(numTasks, - RECENT_IGNORE_UNAVAILABLE, userId).getList(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get recent tasks", e); - return new ArrayList<>(); - } - } - - /** - * @return the task snapshot for the given {@param taskId}. - */ - public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean reducedResolution) { - ActivityManager.TaskSnapshot snapshot = null; - try { - snapshot = ActivityManager.getService().getTaskSnapshot(taskId, reducedResolution); - } catch (RemoteException e) { - Log.w(TAG, "Failed to retrieve task snapshot", e); - } - if (snapshot != null) { - return new ThumbnailData(snapshot); - } else { - return new ThumbnailData(); - } - } - - /** - * @return the task description icon, loading and badging it if it necessary. - */ - public Drawable getBadgedTaskDescriptionIcon(Context context, - ActivityManager.TaskDescription taskDescription, int userId, Resources res) { - Bitmap tdIcon = taskDescription.getInMemoryIcon(); - Drawable dIcon = null; - if (tdIcon != null) { - dIcon = new BitmapDrawable(res, tdIcon); - } else if (taskDescription.getIconResource() != 0) { - try { - dIcon = context.getDrawable(taskDescription.getIconResource()); - } catch (NotFoundException e) { - Log.e(TAG, "Could not find icon drawable from resource", e); - } - } else { - tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon( - taskDescription.getIconFilename(), userId); - if (tdIcon != null) { - dIcon = new BitmapDrawable(res, tdIcon); - } - } - if (dIcon != null) { - return getBadgedIcon(dIcon, userId); - } - return null; - } - - /** - * @return the given icon for a user, badging if necessary. - */ - private Drawable getBadgedIcon(Drawable icon, int userId) { - if (userId != UserHandle.myUserId()) { - icon = mPackageManager.getUserBadgedIcon(icon, new UserHandle(userId)); - } - return icon; - } - - /** - * @return the activity icon for the ActivityInfo for a user, badging if necessary. - */ - public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) { - return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId); - } - - /** - * @return the application icon for the ApplicationInfo for a user, badging if necessary. - */ - public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) { - return mDrawableFactory.getBadgedIcon(appInfo, userId); - } - - /** - * @return the activity label, badging if necessary. - */ - public String getBadgedActivityLabel(ActivityInfo info, int userId) { - return getBadgedLabel(info.loadLabel(mPackageManager).toString(), userId); - } - - /** - * @return the application label, badging if necessary. - */ - public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) { - return getBadgedLabel(appInfo.loadLabel(mPackageManager).toString(), userId); - } - - /** - * @return the content description for a given task, badging it if necessary. The content - * description joins the app and activity labels. - */ - public String getBadgedContentDescription(ActivityInfo info, int userId, - ActivityManager.TaskDescription td) { - String activityLabel; - if (td != null && td.getLabel() != null) { - activityLabel = td.getLabel(); - } else { - activityLabel = info.loadLabel(mPackageManager).toString(); - } - String applicationLabel = info.applicationInfo.loadLabel(mPackageManager).toString(); - String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId); - return applicationLabel.equals(activityLabel) - ? badgedApplicationLabel - : badgedApplicationLabel + " " + activityLabel; - } - - /** - * @return the given label for a user, badging if necessary. - */ - private String getBadgedLabel(String label, int userId) { - if (userId != UserHandle.myUserId()) { - label = mPackageManager.getUserBadgedLabel(label, new UserHandle(userId)).toString(); - } - return label; - } -} diff --git a/com/android/systemui/shared/system/PackageManagerWrapper.java b/com/android/systemui/shared/system/PackageManagerWrapper.java deleted file mode 100644 index d5e6e6ef..00000000 --- a/com/android/systemui/shared/system/PackageManagerWrapper.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2015 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.systemui.shared.system; - -import android.app.AppGlobals; -import android.content.ComponentName; -import android.content.pm.ActivityInfo; -import android.content.pm.IPackageManager; -import android.content.pm.PackageManager; -import android.os.RemoteException; - -public class PackageManagerWrapper { - - private static final String TAG = "PackageManagerWrapper"; - - private static final PackageManagerWrapper sInstance = new PackageManagerWrapper(); - - private static final IPackageManager mIPackageManager = AppGlobals.getPackageManager(); - - public static PackageManagerWrapper getInstance() { - return sInstance; - } - - /** - * @return the activity info for a given {@param componentName} and {@param userId}. - */ - public ActivityInfo getActivityInfo(ComponentName componentName, int userId) { - try { - return mIPackageManager.getActivityInfo(componentName, PackageManager.GET_META_DATA, - userId); - } catch (RemoteException e) { - e.printStackTrace(); - return null; - } - } -} diff --git a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java index 195f4d3f..7699bb90 100644 --- a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java +++ b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java @@ -16,28 +16,37 @@ package com.android.systemui.shortcut; -import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; -import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; -import static android.os.UserHandle.USER_CURRENT; - +import android.accessibilityservice.AccessibilityServiceInfo; import android.app.ActivityManager; +import android.app.IActivityManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ServiceInfo; import android.content.res.Configuration; import android.os.RemoteException; +import android.os.UserHandle; +import android.util.ArraySet; +import android.util.DisplayMetrics; import android.util.Log; import android.view.IWindowManager; import android.view.KeyEvent; import android.view.WindowManager; import android.view.WindowManagerGlobal; - +import android.view.accessibility.AccessibilityManager; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.policy.DividerSnapAlgorithm; +import com.android.settingslib.accessibility.AccessibilityUtils; +import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.recents.Recents; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.stackdivider.Divider; import com.android.systemui.stackdivider.DividerView; +import com.android.systemui.statusbar.phone.NavigationBarGestureHelper; import java.util.List; +import java.util.Set; /** * Dispatches shortcut to System UI components @@ -49,6 +58,7 @@ public class ShortcutKeyDispatcher extends SystemUI private ShortcutKeyServiceProxy mShortcutKeyServiceProxy = new ShortcutKeyServiceProxy(this); private IWindowManager mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); + private IActivityManager mActivityManager = ActivityManager.getService(); protected final long META_MASK = ((long) KeyEvent.META_META_ON) << Integer.SIZE; protected final long ALT_MASK = ((long) KeyEvent.META_ALT_ON) << Integer.SIZE; @@ -92,10 +102,11 @@ public class ShortcutKeyDispatcher extends SystemUI // If there is no window docked, we dock the top-most window. Recents recents = getComponent(Recents.class); int dockMode = (shortcutCode == SC_DOCK_LEFT) - ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT - : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; + ? ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT + : ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; List<ActivityManager.RecentTaskInfo> taskList = - ActivityManagerWrapper.getInstance().getRecentTasks(1, USER_CURRENT); + SystemServicesProxy.getInstance(mContext).getRecentTasks(1, + UserHandle.USER_CURRENT, false, new ArraySet<>()); recents.showRecentApps( false /* triggeredFromAltTab */, false /* fromHome */); diff --git a/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java index 0997983a..578a18a0 100644 --- a/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java +++ b/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java @@ -32,7 +32,7 @@ import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent; import com.android.systemui.recents.events.component.ShowUserToastEvent; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.TaskStackChangeListener; +import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; import com.android.systemui.stackdivider.events.StartedDragingEvent; import com.android.systemui.stackdivider.events.StoppedDragingEvent; @@ -76,7 +76,7 @@ public class ForcedResizableInfoActivityController { mContext = context; EventBus.getDefault().register(this); SystemServicesProxy.getInstance(context).registerTaskStackListener( - new TaskStackChangeListener() { + new TaskStackListener() { @Override public void onActivityForcedResizable(String packageName, int taskId, int reason) { diff --git a/com/android/systemui/statusbar/ExpandableNotificationRow.java b/com/android/systemui/statusbar/ExpandableNotificationRow.java index 6c5f4b23..966e7899 100644 --- a/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -36,7 +36,6 @@ import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Property; -import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.NotificationHeaderView; @@ -175,11 +174,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mShowNoBackground; private ExpandableNotificationRow mNotificationParent; private OnExpandClickListener mOnExpandClickListener; - - // Listener will be called when receiving a long click event. - // Use #setLongPressPosition to optionally assign positional data with the long press. - private LongPressListener mLongPressListener; - private boolean mGroupExpansionChanging; /** @@ -794,10 +788,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mOnExpandClickListener = onExpandClickListener; } - public void setLongPressListener(LongPressListener longPressListener) { - mLongPressListener = longPressListener; - } - @Override public void setOnClickListener(@Nullable OnClickListener l) { super.setOnClickListener(l); @@ -1348,47 +1338,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - private void doLongClickCallback() { - doLongClickCallback(getWidth() / 2, getHeight() / 2); - } - - public void doLongClickCallback(int x, int y) { - createMenu(); - MenuItem menuItem = getProvider().getLongpressMenuItem(mContext); - if (mLongPressListener != null && menuItem != null) { - mLongPressListener.onLongPress(this, x, y, menuItem); - } - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (KeyEvent.isConfirmKey(keyCode)) { - event.startTracking(); - return true; - } - return super.onKeyDown(keyCode, event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (KeyEvent.isConfirmKey(keyCode)) { - if (!event.isCanceled()) { - performClick(); - } - return true; - } - return super.onKeyUp(keyCode, event); - } - - @Override - public boolean onKeyLongPress(int keyCode, KeyEvent event) { - if (KeyEvent.isConfirmKey(keyCode)) { - doLongClickCallback(); - return true; - } - return false; - } - public void resetTranslation() { if (mTranslateAnim != null) { mTranslateAnim.cancel(); @@ -2256,7 +2205,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); if (canViewBeDismissed()) { info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); } @@ -2296,9 +2244,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView case AccessibilityNodeInfo.ACTION_EXPAND: mExpandClickListener.onClick(this); return true; - case AccessibilityNodeInfo.ACTION_LONG_CLICK: - doLongClickCallback(); - return true; } return false; } @@ -2387,15 +2332,4 @@ public class ExpandableNotificationRow extends ActivatableNotificationView protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) { mChildrenContainer = childrenContainer; } - - /** - * Equivalent to View.OnLongClickListener with coordinates - */ - public interface LongPressListener { - /** - * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates - * @return whether the longpress was handled - */ - boolean onLongPress(View v, int x, int y, MenuItem item); - } } diff --git a/com/android/systemui/statusbar/car/CarStatusBar.java b/com/android/systemui/statusbar/car/CarStatusBar.java index fed2ebe9..59d3e0a3 100644 --- a/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/com/android/systemui/statusbar/car/CarStatusBar.java @@ -36,18 +36,14 @@ import android.view.ViewStub; import android.view.WindowManager; import android.widget.LinearLayout; -import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.BatteryMeterView; import com.android.systemui.Dependency; -import com.android.systemui.Prefs; import com.android.systemui.R; -import com.android.systemui.classifier.FalsingLog; -import com.android.systemui.classifier.FalsingManager; +import com.android.systemui.SwipeHelper; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.recents.Recents; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.TaskStackChangeListener; -import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; @@ -55,6 +51,10 @@ import com.android.systemui.statusbar.phone.NavigationBarView; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.classifier.FalsingLog; +import com.android.systemui.classifier.FalsingManager; +import com.android.systemui.Prefs; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -247,12 +247,11 @@ public class CarStatusBar extends StatusBar implements } /** - * Returns the - * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will - * be triggered when a notification card is long-pressed. + * Returns the {@link com.android.systemui.SwipeHelper.LongPressListener} that will be + * triggered when a notification card is long-pressed. */ @Override - protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { + protected SwipeHelper.LongPressListener getNotificationLongClicker() { // For the automative use case, we do not want to the user to be able to interact with // a notification other than a regular click. As a result, just return null for the // long click listener. @@ -305,10 +304,10 @@ public class CarStatusBar extends StatusBar implements } /** - * An implementation of TaskStackChangeListener, that listens for changes in the system task + * An implementation of TaskStackListener, that listens for changes in the system task * stack and notifies the navigation bar. */ - private class TaskStackListenerImpl extends TaskStackChangeListener { + private class TaskStackListenerImpl extends TaskStackListener { @Override public void onTaskStackChanged() { SystemServicesProxy ssp = Recents.getSystemServices(); diff --git a/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/com/android/systemui/statusbar/phone/LockscreenWallpaper.java index 40ddf5b4..87f5ca7a 100644 --- a/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -124,8 +124,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen } else { if (selectedUser != null) { // Show the selected user's static wallpaper. - return LoaderResult.success(mWallpaperManager.getBitmapAsUser( - selectedUser.getIdentifier(), true /* hardware */)); + return LoaderResult.success( + mWallpaperManager.getBitmapAsUser(selectedUser.getIdentifier())); } else { // When there is no selected user, show the system wallpaper diff --git a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index c9500363..f3ca66ff 100644 --- a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -17,19 +17,12 @@ package com.android.systemui.statusbar.phone; import android.content.Context; -import android.os.Handler; -import android.os.RemoteException; import android.os.ServiceManager; import android.util.SparseArray; -import android.view.Display; -import android.view.IWallpaperVisibilityListener; -import android.view.IWindowManager; import android.view.MotionEvent; import android.view.View; -import android.view.WindowManagerGlobal; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.Dependency; import com.android.systemui.R; public final class NavigationBarTransitions extends BarTransitions { @@ -37,7 +30,6 @@ public final class NavigationBarTransitions extends BarTransitions { private final NavigationBarView mView; private final IStatusBarService mBarService; private final LightBarTransitionsController mLightTransitionsController; - private boolean mWallpaperVisible; private boolean mLightsOut; private boolean mAutoDim; @@ -49,21 +41,6 @@ public final class NavigationBarTransitions extends BarTransitions { ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mLightTransitionsController = new LightBarTransitionsController(view.getContext(), this::applyDarkIntensity); - - IWindowManager windowManagerService = Dependency.get(IWindowManager.class); - Handler handler = Handler.getMain(); - try { - mWallpaperVisible = windowManagerService.registerWallpaperVisibilityListener( - new IWallpaperVisibilityListener.Stub() { - @Override - public void onWallpaperVisibilityChanged(boolean newVisibility, - int displayId) throws RemoteException { - mWallpaperVisible = newVisibility; - handler.post(() -> applyLightsOut(true, false)); - } - }, Display.DEFAULT_DISPLAY); - } catch (RemoteException e) { - } } public void init() { @@ -80,7 +57,7 @@ public final class NavigationBarTransitions extends BarTransitions { @Override protected boolean isLightsOut(int mode) { - return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible); + return super.isLightsOut(mode) || mAutoDim; } public LightBarTransitionsController getLightTransitionsController() { @@ -108,7 +85,7 @@ public final class NavigationBarTransitions extends BarTransitions { // ok, everyone, stop it right there navButtons.animate().cancel(); - final float navButtonsAlpha = lightsOut ? 0.6f : 1f; + final float navButtonsAlpha = lightsOut ? 0.5f : 1f; if (!animate) { navButtons.setAlpha(navButtonsAlpha); diff --git a/com/android/systemui/statusbar/phone/NotificationPanelView.java b/com/android/systemui/statusbar/phone/NotificationPanelView.java index af034406..7b11ace8 100644 --- a/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -704,7 +704,7 @@ public class NotificationPanelView extends PanelView implements mInitialHeightOnTouch = mQsExpansionHeight; mQsTracking = true; mIntercepting = false; - mNotificationStackScroller.cancelLongPress(); + mNotificationStackScroller.removeLongPressCallback(); } break; case MotionEvent.ACTION_POINTER_UP: @@ -740,7 +740,7 @@ public class NotificationPanelView extends PanelView implements mInitialTouchY = y; mInitialTouchX = x; mIntercepting = false; - mNotificationStackScroller.cancelLongPress(); + mNotificationStackScroller.removeLongPressCallback(); return true; } break; diff --git a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index b876286b..9c837ed8 100644 --- a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -66,7 +66,7 @@ import com.android.systemui.UiOffloadThread; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.TaskStackChangeListener; +import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.policy.BluetoothController; @@ -639,17 +639,12 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, } private Intent getTaskIntent(int taskId, int userId) { - try { - final List<ActivityManager.RecentTaskInfo> tasks = - ActivityManager.getService().getRecentTasks( - NUM_TASKS_FOR_INSTANT_APP_INFO, 0, userId).getList(); - for (int i = 0; i < tasks.size(); i++) { - if (tasks.get(i).id == taskId) { - return tasks.get(i).baseIntent; - } + List<ActivityManager.RecentTaskInfo> tasks = mContext.getSystemService(ActivityManager.class) + .getRecentTasksForUser(NUM_TASKS_FOR_INSTANT_APP_INFO, 0, userId); + for (int i = 0; i < tasks.size(); i++) { + if (tasks.get(i).id == taskId) { + return tasks.get(i).baseIntent; } - } catch (RemoteException e) { - // Fall through } return null; } @@ -768,7 +763,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, mIconController.setIconVisibility(mSlotDataSaver, isDataSaving); } - private final TaskStackChangeListener mTaskListener = new TaskStackChangeListener() { + private final TaskStackListener mTaskListener = new TaskStackListener() { @Override public void onTaskStackChanged() { // Listen for changes to stacks and then check which instant apps are foreground. diff --git a/com/android/systemui/statusbar/phone/StatusBar.java b/com/android/systemui/statusbar/phone/StatusBar.java index 9f039543..54be857d 100644 --- a/com/android/systemui/statusbar/phone/StatusBar.java +++ b/com/android/systemui/statusbar/phone/StatusBar.java @@ -160,6 +160,7 @@ import com.android.systemui.Interpolators; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.RecentsComponent; +import com.android.systemui.SwipeHelper; import com.android.systemui.SystemUI; import com.android.systemui.SystemUIFactory; import com.android.systemui.UiOffloadThread; @@ -4837,7 +4838,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onTouchSlopExceeded() { - mStackScroller.cancelLongPress(); + mStackScroller.removeLongPressCallback(); mStackScroller.checkSnoozeLeavebehind(); } @@ -5469,7 +5470,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onDoubleTap(float screenX, float screenY) { - if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null + if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) { mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2); float viewX = screenX - mTmpInt2[0]; @@ -5881,7 +5882,8 @@ public class StatusBar extends SystemUI implements DemoMode, List<ActivityManager.RecentTaskInfo> recentTask = null; try { recentTask = ActivityManager.getService().getRecentTasks(1, - ActivityManager.RECENT_WITH_EXCLUDED, + ActivityManager.RECENT_WITH_EXCLUDED + | ActivityManager.RECENT_INCLUDE_PROFILES, mCurrentUserId).getList(); } catch (RemoteException e) { // Abandon hope activity manager not running. @@ -6294,15 +6296,14 @@ public class StatusBar extends SystemUI implements DemoMode, true /* removeControls */, x, y, true /* resetMenu */); } - protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { - return new ExpandableNotificationRow.LongPressListener() { + protected SwipeHelper.LongPressListener getNotificationLongClicker() { + return new SwipeHelper.LongPressListener() { @Override public boolean onLongPress(View v, final int x, final int y, MenuItem item) { if (!(v instanceof ExpandableNotificationRow)) { return false; } - if (v.getWindowToken() == null) { Log.e(TAG, "Trying to show notification guts, but not attached to window"); return false; @@ -6317,7 +6318,7 @@ public class StatusBar extends SystemUI implements DemoMode, closeAndSaveGuts(false /* removeLeavebehind */, false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */); - return true; + return false; } bindGuts(row, item); NotificationGuts guts = row.getGuts(); @@ -6597,7 +6598,6 @@ public class StatusBar extends SystemUI implements DemoMode, row.setRemoteViewClickHandler(mOnClickHandler); row.setInflationCallback(this); row.setSecureStateProvider(this::isKeyguardCurrentlySecure); - row.setLongPressListener(getNotificationLongClicker()); // Get the app name. // Note that Notification.Builder#bindHeaderAppName has similar logic diff --git a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 1e14626d..75532d9e 100644 --- a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -240,7 +240,7 @@ public class NotificationStackScrollLayout extends ViewGroup * motion. */ private int mMaxScrollAfterExpand; - private ExpandableNotificationRow.LongPressListener mLongPressListener; + private SwipeHelper.LongPressListener mLongPressListener; private NotificationMenuRowPlugin mCurrMenuRow; private View mTranslatingParentView; @@ -410,6 +410,7 @@ public class NotificationStackScrollLayout extends ViewGroup mExpandHelper.setEventSource(this); mExpandHelper.setScrollAdapter(this); mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext()); + mSwipeHelper.setLongPressListener(mLongPressListener); mStackScrollAlgorithm = createStackScrollAlgorithm(context); initView(context); mFalsingManager = FalsingManager.getInstance(context); @@ -883,7 +884,8 @@ public class NotificationStackScrollLayout extends ViewGroup return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize; } - public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) { + public void setLongPressListener(SwipeHelper.LongPressListener listener) { + mSwipeHelper.setLongPressListener(listener); mLongPressListener = listener; } @@ -1173,7 +1175,7 @@ public class NotificationStackScrollLayout extends ViewGroup if (v instanceof ExpandableNotificationRow) { ((ExpandableNotificationRow) v).setUserLocked(userLocked); } - cancelLongPress(); + removeLongPressCallback(); requestDisallowInterceptTouchEvent(true); } @@ -2579,7 +2581,7 @@ public class NotificationStackScrollLayout extends ViewGroup public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { super.requestDisallowInterceptTouchEvent(disallowIntercept); if (disallowIntercept) { - cancelLongPress(); + mSwipeHelper.removeLongPressCallback(); } } @@ -3300,7 +3302,7 @@ public class NotificationStackScrollLayout extends ViewGroup mIsBeingDragged = isDragged; if (isDragged) { requestDisallowInterceptTouchEvent(true); - cancelLongPress(); + removeLongPressCallback(); } } @@ -3308,7 +3310,7 @@ public class NotificationStackScrollLayout extends ViewGroup public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if (!hasWindowFocus) { - cancelLongPress(); + removeLongPressCallback(); } } @@ -3322,7 +3324,7 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public void requestDisallowLongPress() { - cancelLongPress(); + removeLongPressCallback(); } @Override @@ -3330,8 +3332,8 @@ public class NotificationStackScrollLayout extends ViewGroup mDisallowDismissInThisMotion = true; } - public void cancelLongPress() { - mSwipeHelper.cancelLongPress(); + public void removeLongPressCallback() { + mSwipeHelper.removeLongPressCallback(); } @Override diff --git a/com/android/systemui/util/wakelock/DelayedWakeLock.java b/com/android/systemui/util/wakelock/DelayedWakeLock.java index 5ec3dff0..b8359097 100644 --- a/com/android/systemui/util/wakelock/DelayedWakeLock.java +++ b/com/android/systemui/util/wakelock/DelayedWakeLock.java @@ -23,7 +23,7 @@ import android.os.Handler; */ public class DelayedWakeLock implements WakeLock { - private static final long RELEASE_DELAY_MS = 120; + private static final long RELEASE_DELAY_MS = 100; private final Handler mHandler; private final WakeLock mInner; diff --git a/foo/bar/ComplexDao.java b/foo/bar/ComplexDao.java index dbb54fbc..89859e50 100644 --- a/foo/bar/ComplexDao.java +++ b/foo/bar/ComplexDao.java @@ -1,433 +1,69 @@ -package foo.bar; +/* + * Copyright (C) 2016 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. + */ -import android.arch.lifecycle.ComputableLiveData; -import android.arch.lifecycle.LiveData; -import android.arch.persistence.room.InvalidationTracker.Observer; -import android.arch.persistence.room.RoomDatabase; -import android.arch.persistence.room.RoomSQLiteQuery; -import android.arch.persistence.room.util.StringUtil; -import android.database.Cursor; -import android.support.annotation.NonNull; -import java.lang.Integer; -import java.lang.Override; -import java.lang.String; -import java.lang.StringBuilder; -import java.util.ArrayList; +package foo.bar; +import android.arch.persistence.room.*; import java.util.List; -import java.util.Set; -import javax.annotation.Generated; - -@Generated("android.arch.persistence.room.RoomProcessor") -public class ComplexDao_Impl extends ComplexDao { - private final RoomDatabase __db; - - public ComplexDao_Impl(ComplexDatabase __db) { - super(__db); - this.__db = __db; - } - - @Override - public boolean transactionMethod(int i, String s, long l) { - __db.beginTransaction(); - try { - boolean _result = super.transactionMethod(i, s, l); - __db.setTransactionSuccessful(); - return _result; - } finally { - __db.endTransaction(); - } - } - - @Override - public List<ComplexDao.FullName> fullNames(int id) { - final String _sql = "SELECT name || lastName as fullName, uid as id FROM user where uid = ?"; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1); - int _argIndex = 1; - _statement.bindLong(_argIndex, id); - final Cursor _cursor = __db.query(_statement); - try { - final int _cursorIndexOfFullName = _cursor.getColumnIndexOrThrow("fullName"); - final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id"); - final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>(_cursor.getCount()); - while(_cursor.moveToNext()) { - final ComplexDao.FullName _item; - _item = new ComplexDao.FullName(); - _item.fullName = _cursor.getString(_cursorIndexOfFullName); - _item.id = _cursor.getInt(_cursorIndexOfId); - _result.add(_item); - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } +import android.arch.lifecycle.LiveData; +@Dao +abstract class ComplexDao { + static class FullName { + public int id; + public String fullName; } - @Override - public User getById(int id) { - final String _sql = "SELECT * FROM user where uid = ?"; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1); - int _argIndex = 1; - _statement.bindLong(_argIndex, id); - final Cursor _cursor = __db.query(_statement); - try { - final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid"); - final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name"); - final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName"); - final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn"); - final User _result; - if(_cursor.moveToFirst()) { - _result = new User(); - _result.uid = _cursor.getInt(_cursorIndexOfUid); - _result.name = _cursor.getString(_cursorIndexOfName); - final String _tmpLastName; - _tmpLastName = _cursor.getString(_cursorIndexOfLastName); - _result.setLastName(_tmpLastName); - _result.age = _cursor.getInt(_cursorIndexOfAge); - } else { - _result = null; - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } - } + private final ComplexDatabase mDb; - @Override - public User findByName(String name, String lastName) { - final String _sql = "SELECT * FROM user where name LIKE ? AND lastName LIKE ?"; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 2); - int _argIndex = 1; - if (name == null) { - _statement.bindNull(_argIndex); - } else { - _statement.bindString(_argIndex, name); - } - _argIndex = 2; - if (lastName == null) { - _statement.bindNull(_argIndex); - } else { - _statement.bindString(_argIndex, lastName); - } - final Cursor _cursor = __db.query(_statement); - try { - final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid"); - final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name"); - final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName"); - final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn"); - final User _result; - if(_cursor.moveToFirst()) { - _result = new User(); - _result.uid = _cursor.getInt(_cursorIndexOfUid); - _result.name = _cursor.getString(_cursorIndexOfName); - final String _tmpLastName; - _tmpLastName = _cursor.getString(_cursorIndexOfLastName); - _result.setLastName(_tmpLastName); - _result.age = _cursor.getInt(_cursorIndexOfAge); - } else { - _result = null; - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } + public ComplexDao(ComplexDatabase db) { + mDb = db; } - @Override - public List<User> loadAllByIds(int... ids) { - StringBuilder _stringBuilder = StringUtil.newStringBuilder(); - _stringBuilder.append("SELECT * FROM user where uid IN ("); - final int _inputSize = ids.length; - StringUtil.appendPlaceholders(_stringBuilder, _inputSize); - _stringBuilder.append(")"); - final String _sql = _stringBuilder.toString(); - final int _argCount = 0 + _inputSize; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount); - int _argIndex = 1; - for (int _item : ids) { - _statement.bindLong(_argIndex, _item); - _argIndex ++; - } - final Cursor _cursor = __db.query(_statement); - try { - final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid"); - final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name"); - final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName"); - final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn"); - final List<User> _result = new ArrayList<User>(_cursor.getCount()); - while(_cursor.moveToNext()) { - final User _item_1; - _item_1 = new User(); - _item_1.uid = _cursor.getInt(_cursorIndexOfUid); - _item_1.name = _cursor.getString(_cursorIndexOfName); - final String _tmpLastName; - _tmpLastName = _cursor.getString(_cursorIndexOfLastName); - _item_1.setLastName(_tmpLastName); - _item_1.age = _cursor.getInt(_cursorIndexOfAge); - _result.add(_item_1); - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } + @Transaction + public boolean transactionMethod(int i, String s, long l) { + return true; } - @Override - int getAge(int id) { - final String _sql = "SELECT ageColumn FROM user where uid = ?"; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1); - int _argIndex = 1; - _statement.bindLong(_argIndex, id); - final Cursor _cursor = __db.query(_statement); - try { - final int _result; - if(_cursor.moveToFirst()) { - _result = _cursor.getInt(0); - } else { - _result = 0; - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } - } + @Query("SELECT name || lastName as fullName, uid as id FROM user where uid = :id") + abstract public List<FullName> fullNames(int id); - @Override - public int[] getAllAges(int... ids) { - StringBuilder _stringBuilder = StringUtil.newStringBuilder(); - _stringBuilder.append("SELECT ageColumn FROM user where uid IN("); - final int _inputSize = ids.length; - StringUtil.appendPlaceholders(_stringBuilder, _inputSize); - _stringBuilder.append(")"); - final String _sql = _stringBuilder.toString(); - final int _argCount = 0 + _inputSize; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount); - int _argIndex = 1; - for (int _item : ids) { - _statement.bindLong(_argIndex, _item); - _argIndex ++; - } - final Cursor _cursor = __db.query(_statement); - try { - final int[] _result = new int[_cursor.getCount()]; - int _index = 0; - while(_cursor.moveToNext()) { - final int _item_1; - _item_1 = _cursor.getInt(0); - _result[_index] = _item_1; - _index ++; - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } - } + @Query("SELECT * FROM user where uid = :id") + abstract public User getById(int id); - @Override - public List<Integer> getAllAgesAsList(List<Integer> ids) { - StringBuilder _stringBuilder = StringUtil.newStringBuilder(); - _stringBuilder.append("SELECT ageColumn FROM user where uid IN("); - final int _inputSize = ids.size(); - StringUtil.appendPlaceholders(_stringBuilder, _inputSize); - _stringBuilder.append(")"); - final String _sql = _stringBuilder.toString(); - final int _argCount = 0 + _inputSize; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount); - int _argIndex = 1; - for (Integer _item : ids) { - if (_item == null) { - _statement.bindNull(_argIndex); - } else { - _statement.bindLong(_argIndex, _item); - } - _argIndex ++; - } - final Cursor _cursor = __db.query(_statement); - try { - final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount()); - while(_cursor.moveToNext()) { - final Integer _item_1; - if (_cursor.isNull(0)) { - _item_1 = null; - } else { - _item_1 = _cursor.getInt(0); - } - _result.add(_item_1); - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } - } + @Query("SELECT * FROM user where name LIKE :name AND lastName LIKE :lastName") + abstract public User findByName(String name, String lastName); - @Override - public LiveData<User> getByIdLive(int id) { - final String _sql = "SELECT * FROM user where uid = ?"; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1); - int _argIndex = 1; - _statement.bindLong(_argIndex, id); - return new ComputableLiveData<User>() { - private Observer _observer; + @Query("SELECT * FROM user where uid IN (:ids)") + abstract public List<User> loadAllByIds(int... ids); - @Override - protected User compute() { - if (_observer == null) { - _observer = new Observer("user") { - @Override - public void onInvalidated(@NonNull Set<String> tables) { - invalidate(); - } - }; - __db.getInvalidationTracker().addWeakObserver(_observer); - } - final Cursor _cursor = __db.query(_statement); - try { - final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid"); - final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name"); - final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName"); - final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn"); - final User _result; - if(_cursor.moveToFirst()) { - _result = new User(); - _result.uid = _cursor.getInt(_cursorIndexOfUid); - _result.name = _cursor.getString(_cursorIndexOfName); - final String _tmpLastName; - _tmpLastName = _cursor.getString(_cursorIndexOfLastName); - _result.setLastName(_tmpLastName); - _result.age = _cursor.getInt(_cursorIndexOfAge); - } else { - _result = null; - } - return _result; - } finally { - _cursor.close(); - } - } + @Query("SELECT ageColumn FROM user where uid = :id") + abstract int getAge(int id); - @Override - protected void finalize() { - _statement.release(); - } - }.getLiveData(); - } + @Query("SELECT ageColumn FROM user where uid IN(:ids)") + abstract public int[] getAllAges(int... ids); - @Override - public LiveData<List<User>> loadUsersByIdsLive(int... ids) { - StringBuilder _stringBuilder = StringUtil.newStringBuilder(); - _stringBuilder.append("SELECT * FROM user where uid IN ("); - final int _inputSize = ids.length; - StringUtil.appendPlaceholders(_stringBuilder, _inputSize); - _stringBuilder.append(")"); - final String _sql = _stringBuilder.toString(); - final int _argCount = 0 + _inputSize; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount); - int _argIndex = 1; - for (int _item : ids) { - _statement.bindLong(_argIndex, _item); - _argIndex ++; - } - return new ComputableLiveData<List<User>>() { - private Observer _observer; + @Query("SELECT ageColumn FROM user where uid IN(:ids)") + abstract public List<Integer> getAllAgesAsList(List<Integer> ids); - @Override - protected List<User> compute() { - if (_observer == null) { - _observer = new Observer("user") { - @Override - public void onInvalidated(@NonNull Set<String> tables) { - invalidate(); - } - }; - __db.getInvalidationTracker().addWeakObserver(_observer); - } - final Cursor _cursor = __db.query(_statement); - try { - final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid"); - final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name"); - final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName"); - final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn"); - final List<User> _result = new ArrayList<User>(_cursor.getCount()); - while(_cursor.moveToNext()) { - final User _item_1; - _item_1 = new User(); - _item_1.uid = _cursor.getInt(_cursorIndexOfUid); - _item_1.name = _cursor.getString(_cursorIndexOfName); - final String _tmpLastName; - _tmpLastName = _cursor.getString(_cursorIndexOfLastName); - _item_1.setLastName(_tmpLastName); - _item_1.age = _cursor.getInt(_cursorIndexOfAge); - _result.add(_item_1); - } - return _result; - } finally { - _cursor.close(); - } - } + @Query("SELECT * FROM user where uid = :id") + abstract public LiveData<User> getByIdLive(int id); - @Override - protected void finalize() { - _statement.release(); - } - }.getLiveData(); - } + @Query("SELECT * FROM user where uid IN (:ids)") + abstract public LiveData<List<User>> loadUsersByIdsLive(int... ids); - @Override - public List<Integer> getAllAgesAsList(List<Integer> ids1, int[] ids2, int... ids3) { - StringBuilder _stringBuilder = StringUtil.newStringBuilder(); - _stringBuilder.append("SELECT ageColumn FROM user where uid IN("); - final int _inputSize = ids1.size(); - StringUtil.appendPlaceholders(_stringBuilder, _inputSize); - _stringBuilder.append(") OR uid IN ("); - final int _inputSize_1 = ids2.length; - StringUtil.appendPlaceholders(_stringBuilder, _inputSize_1); - _stringBuilder.append(") OR uid IN ("); - final int _inputSize_2 = ids3.length; - StringUtil.appendPlaceholders(_stringBuilder, _inputSize_2); - _stringBuilder.append(")"); - final String _sql = _stringBuilder.toString(); - final int _argCount = 0 + _inputSize + _inputSize_1 + _inputSize_2; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount); - int _argIndex = 1; - for (Integer _item : ids1) { - if (_item == null) { - _statement.bindNull(_argIndex); - } else { - _statement.bindLong(_argIndex, _item); - } - _argIndex ++; - } - _argIndex = 1 + _inputSize; - for (int _item_1 : ids2) { - _statement.bindLong(_argIndex, _item_1); - _argIndex ++; - } - _argIndex = 1 + _inputSize + _inputSize_1; - for (int _item_2 : ids3) { - _statement.bindLong(_argIndex, _item_2); - _argIndex ++; - } - final Cursor _cursor = __db.query(_statement); - try { - final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount()); - while(_cursor.moveToNext()) { - final Integer _item_3; - if (_cursor.isNull(0)) { - _item_3 = null; - } else { - _item_3 = _cursor.getInt(0); - } - _result.add(_item_3); - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } - } + @Query("SELECT ageColumn FROM user where uid IN(:ids1) OR uid IN (:ids2) OR uid IN (:ids3)") + abstract public List<Integer> getAllAgesAsList(List<Integer> ids1, + int[] ids2, int... ids3); } diff --git a/foo/bar/ComplexDatabase.java b/foo/bar/ComplexDatabase.java index cfdc1101..f35e0b8a 100644 --- a/foo/bar/ComplexDatabase.java +++ b/foo/bar/ComplexDatabase.java @@ -1,99 +1,23 @@ -package foo.bar; - -import android.arch.persistence.db.SupportSQLiteDatabase; -import android.arch.persistence.db.SupportSQLiteOpenHelper; -import android.arch.persistence.db.SupportSQLiteOpenHelper.Callback; -import android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration; -import android.arch.persistence.room.DatabaseConfiguration; -import android.arch.persistence.room.InvalidationTracker; -import android.arch.persistence.room.RoomOpenHelper; -import android.arch.persistence.room.RoomOpenHelper.Delegate; -import android.arch.persistence.room.util.TableInfo; -import android.arch.persistence.room.util.TableInfo.Column; -import android.arch.persistence.room.util.TableInfo.ForeignKey; -import android.arch.persistence.room.util.TableInfo.Index; -import java.lang.IllegalStateException; -import java.lang.Override; -import java.lang.String; -import java.util.HashMap; -import java.util.HashSet; -import javax.annotation.Generated; - -@Generated("android.arch.persistence.room.RoomProcessor") -public class ComplexDatabase_Impl extends ComplexDatabase { - private volatile ComplexDao _complexDao; - - protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) { - final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1923) { - public void createAllTables(SupportSQLiteDatabase _db) { - _db.execSQL("CREATE TABLE IF NOT EXISTS `User` (`uid` INTEGER NOT NULL, `name` TEXT, `lastName` TEXT, `ageColumn` INTEGER NOT NULL, PRIMARY KEY(`uid`))"); - _db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)"); - _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"6773601c5bcf94c71ee4eb0de04f21a4\")"); - } - - public void dropAllTables(SupportSQLiteDatabase _db) { - _db.execSQL("DROP TABLE IF EXISTS `User`"); - } +/* + * Copyright (C) 2016 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. + */ - protected void onCreate(SupportSQLiteDatabase _db) { - if (mCallbacks != null) { - for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) { - mCallbacks.get(_i).onCreate(_db); - } - } - } - - public void onOpen(SupportSQLiteDatabase _db) { - mDatabase = _db; - internalInitInvalidationTracker(_db); - if (mCallbacks != null) { - for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) { - mCallbacks.get(_i).onOpen(_db); - } - } - } - - protected void validateMigration(SupportSQLiteDatabase _db) { - final HashMap<String, TableInfo.Column> _columnsUser = new HashMap<String, TableInfo.Column>(4); - _columnsUser.put("uid", new TableInfo.Column("uid", "INTEGER", true, 1)); - _columnsUser.put("name", new TableInfo.Column("name", "TEXT", false, 0)); - _columnsUser.put("lastName", new TableInfo.Column("lastName", "TEXT", false, 0)); - _columnsUser.put("ageColumn", new TableInfo.Column("ageColumn", "INTEGER", true, 0)); - final HashSet<TableInfo.ForeignKey> _foreignKeysUser = new HashSet<TableInfo.ForeignKey>(0); - final HashSet<TableInfo.Index> _indicesUser = new HashSet<TableInfo.Index>(0); - final TableInfo _infoUser = new TableInfo("User", _columnsUser, _foreignKeysUser, _indicesUser); - final TableInfo _existingUser = TableInfo.read(_db, "User"); - if (! _infoUser.equals(_existingUser)) { - throw new IllegalStateException("Migration didn't properly handle User(foo.bar.User).\n" - + " Expected:\n" + _infoUser + "\n" - + " Found:\n" + _existingUser); - } - } - }, "6773601c5bcf94c71ee4eb0de04f21a4"); - final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context) - .name(configuration.name) - .callback(_openCallback) - .build(); - final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig); - return _helper; - } - - @Override - protected InvalidationTracker createInvalidationTracker() { - return new InvalidationTracker(this, "User"); - } - - @Override - ComplexDao getComplexDao() { - if (_complexDao != null) { - return _complexDao; - } else { - synchronized(this) { - if(_complexDao == null) { - _complexDao = new ComplexDao_Impl(this); - } - return _complexDao; - } - } - } +package foo.bar; +import android.arch.persistence.room.*; +import java.util.List; +@Database(entities = {User.class}, version = 1923) +abstract class ComplexDatabase extends RoomDatabase { + abstract ComplexDao getComplexDao(); } diff --git a/foo/bar/DeletionDao.java b/foo/bar/DeletionDao.java index 067bf670..997f2906 100644 --- a/foo/bar/DeletionDao.java +++ b/foo/bar/DeletionDao.java @@ -1,240 +1,51 @@ -package foo.bar; +/* + * Copyright (C) 2016 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. + */ -import android.arch.persistence.db.SupportSQLiteStatement; -import android.arch.persistence.room.EntityDeletionOrUpdateAdapter; -import android.arch.persistence.room.RoomDatabase; -import android.arch.persistence.room.SharedSQLiteStatement; -import android.arch.persistence.room.util.StringUtil; -import java.lang.Override; -import java.lang.String; -import java.lang.StringBuilder; +package foo.bar; +import android.arch.persistence.room.*; import java.util.List; -import javax.annotation.Generated; - -@Generated("android.arch.persistence.room.RoomProcessor") -public class DeletionDao_Impl implements DeletionDao { - private final RoomDatabase __db; - - private final EntityDeletionOrUpdateAdapter __deletionAdapterOfUser; - - private final EntityDeletionOrUpdateAdapter __deletionAdapterOfMultiPKeyEntity; - - private final EntityDeletionOrUpdateAdapter __deletionAdapterOfBook; - - private final SharedSQLiteStatement __preparedStmtOfDeleteByUid; - - private final SharedSQLiteStatement __preparedStmtOfDeleteEverything; - - public DeletionDao_Impl(RoomDatabase __db) { - this.__db = __db; - this.__deletionAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) { - @Override - public String createQuery() { - return "DELETE FROM `User` WHERE `uid` = ?"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, User value) { - stmt.bindLong(1, value.uid); - } - }; - this.__deletionAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) { - @Override - public String createQuery() { - return "DELETE FROM `MultiPKeyEntity` WHERE `name` = ? AND `lastName` = ?"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, MultiPKeyEntity value) { - if (value.name == null) { - stmt.bindNull(1); - } else { - stmt.bindString(1, value.name); - } - if (value.lastName == null) { - stmt.bindNull(2); - } else { - stmt.bindString(2, value.lastName); - } - } - }; - this.__deletionAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) { - @Override - public String createQuery() { - return "DELETE FROM `Book` WHERE `bookId` = ?"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, Book value) { - stmt.bindLong(1, value.bookId); - } - }; - this.__preparedStmtOfDeleteByUid = new SharedSQLiteStatement(__db) { - @Override - public String createQuery() { - final String _query = "DELETE FROM user where uid = ?"; - return _query; - } - }; - this.__preparedStmtOfDeleteEverything = new SharedSQLiteStatement(__db) { - @Override - public String createQuery() { - final String _query = "DELETE FROM user"; - return _query; - } - }; - } - - @Override - public void deleteUser(User user) { - __db.beginTransaction(); - try { - __deletionAdapterOfUser.handle(user); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public void deleteUsers(User user1, List<User> others) { - __db.beginTransaction(); - try { - __deletionAdapterOfUser.handle(user1); - __deletionAdapterOfUser.handleMultiple(others); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public void deleteArrayOfUsers(User[] users) { - __db.beginTransaction(); - try { - __deletionAdapterOfUser.handleMultiple(users); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public int deleteUserAndReturnCount(User user) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__deletionAdapterOfUser.handle(user); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } - @Override - public int deleteUserAndReturnCount(User user1, List<User> others) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__deletionAdapterOfUser.handle(user1); - _total +=__deletionAdapterOfUser.handleMultiple(others); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } +@Dao +abstract interface DeletionDao { + @Delete + void deleteUser(User user); + @Delete + void deleteUsers(User user1, List<User> others); + @Delete + void deleteArrayOfUsers(User[] users); - @Override - public int deleteUserAndReturnCount(User[] users) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__deletionAdapterOfUser.handleMultiple(users); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } + @Delete + int deleteUserAndReturnCount(User user); + @Delete + int deleteUserAndReturnCount(User user1, List<User> others); + @Delete + int deleteUserAndReturnCount(User[] users); - @Override - public int multiPKey(MultiPKeyEntity entity) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__deletionAdapterOfMultiPKeyEntity.handle(entity); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } + @Delete + int multiPKey(MultiPKeyEntity entity); - @Override - public void deleteUserAndBook(User user, Book book) { - __db.beginTransaction(); - try { - __deletionAdapterOfUser.handle(user); - __deletionAdapterOfBook.handle(book); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } + @Query("DELETE FROM user where uid = :uid") + int deleteByUid(int uid); - @Override - public int deleteByUid(int uid) { - final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire(); - __db.beginTransaction(); - try { - int _argIndex = 1; - _stmt.bindLong(_argIndex, uid); - final int _result = _stmt.executeUpdateDelete(); - __db.setTransactionSuccessful(); - return _result; - } finally { - __db.endTransaction(); - __preparedStmtOfDeleteByUid.release(_stmt); - } - } + @Query("DELETE FROM user where uid IN(:uid)") + int deleteByUidList(int... uid); - @Override - public int deleteEverything() { - final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteEverything.acquire(); - __db.beginTransaction(); - try { - final int _result = _stmt.executeUpdateDelete(); - __db.setTransactionSuccessful(); - return _result; - } finally { - __db.endTransaction(); - __preparedStmtOfDeleteEverything.release(_stmt); - } - } + @Delete + void deleteUserAndBook(User user, Book book); - @Override - public int deleteByUidList(int... uid) { - StringBuilder _stringBuilder = StringUtil.newStringBuilder(); - _stringBuilder.append("DELETE FROM user where uid IN("); - final int _inputSize = uid.length; - StringUtil.appendPlaceholders(_stringBuilder, _inputSize); - _stringBuilder.append(")"); - final String _sql = _stringBuilder.toString(); - SupportSQLiteStatement _stmt = __db.compileStatement(_sql); - int _argIndex = 1; - for (int _item : uid) { - _stmt.bindLong(_argIndex, _item); - _argIndex ++; - } - __db.beginTransaction(); - try { - final int _result = _stmt.executeUpdateDelete(); - __db.setTransactionSuccessful(); - return _result; - } finally { - __db.endTransaction(); - } - } + @Query("DELETE FROM user") + int deleteEverything(); } diff --git a/foo/bar/UpdateDao.java b/foo/bar/UpdateDao.java index 1190a0df..040b5c79 100644 --- a/foo/bar/UpdateDao.java +++ b/foo/bar/UpdateDao.java @@ -1,240 +1,48 @@ -package foo.bar; +/* + * Copyright (C) 2017 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. + */ -import android.arch.persistence.db.SupportSQLiteStatement; -import android.arch.persistence.room.EntityDeletionOrUpdateAdapter; -import android.arch.persistence.room.RoomDatabase; -import android.arch.persistence.room.SharedSQLiteStatement; -import java.lang.Override; -import java.lang.String; +package foo.bar; +import android.arch.persistence.room.*; import java.util.List; -import javax.annotation.Generated; - -@Generated("android.arch.persistence.room.RoomProcessor") -public class UpdateDao_Impl implements UpdateDao { - private final RoomDatabase __db; - - private final EntityDeletionOrUpdateAdapter __updateAdapterOfUser; - - private final EntityDeletionOrUpdateAdapter __updateAdapterOfMultiPKeyEntity; - - private final EntityDeletionOrUpdateAdapter __updateAdapterOfBook; - - private final SharedSQLiteStatement __preparedStmtOfAgeUserByUid; - - private final SharedSQLiteStatement __preparedStmtOfAgeUserAll; - - public UpdateDao_Impl(RoomDatabase __db) { - this.__db = __db; - this.__updateAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) { - @Override - public String createQuery() { - return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, User value) { - stmt.bindLong(1, value.uid); - if (value.name == null) { - stmt.bindNull(2); - } else { - stmt.bindString(2, value.name); - } - if (value.getLastName() == null) { - stmt.bindNull(3); - } else { - stmt.bindString(3, value.getLastName()); - } - stmt.bindLong(4, value.age); - stmt.bindLong(5, value.uid); - } - }; - this.__updateAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) { - @Override - public String createQuery() { - return "UPDATE OR ABORT `MultiPKeyEntity` SET `name` = ?,`lastName` = ? WHERE `name` = ? AND `lastName` = ?"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, MultiPKeyEntity value) { - if (value.name == null) { - stmt.bindNull(1); - } else { - stmt.bindString(1, value.name); - } - if (value.lastName == null) { - stmt.bindNull(2); - } else { - stmt.bindString(2, value.lastName); - } - if (value.name == null) { - stmt.bindNull(3); - } else { - stmt.bindString(3, value.name); - } - if (value.lastName == null) { - stmt.bindNull(4); - } else { - stmt.bindString(4, value.lastName); - } - } - }; - this.__updateAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) { - @Override - public String createQuery() { - return "UPDATE OR ABORT `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, Book value) { - stmt.bindLong(1, value.bookId); - stmt.bindLong(2, value.uid); - stmt.bindLong(3, value.bookId); - } - }; - this.__preparedStmtOfAgeUserByUid = new SharedSQLiteStatement(__db) { - @Override - public String createQuery() { - final String _query = "UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = ?"; - return _query; - } - }; - this.__preparedStmtOfAgeUserAll = new SharedSQLiteStatement(__db) { - @Override - public String createQuery() { - final String _query = "UPDATE User SET ageColumn = ageColumn + 1"; - return _query; - } - }; - } - - @Override - public void updateUser(User user) { - __db.beginTransaction(); - try { - __updateAdapterOfUser.handle(user); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public void updateUsers(User user1, List<User> others) { - __db.beginTransaction(); - try { - __updateAdapterOfUser.handle(user1); - __updateAdapterOfUser.handleMultiple(others); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public void updateArrayOfUsers(User[] users) { - __db.beginTransaction(); - try { - __updateAdapterOfUser.handleMultiple(users); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public int updateUserAndReturnCount(User user) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__updateAdapterOfUser.handle(user); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } - - @Override - public int updateUserAndReturnCount(User user1, List<User> others) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__updateAdapterOfUser.handle(user1); - _total +=__updateAdapterOfUser.handleMultiple(others); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } - - @Override - public int updateUserAndReturnCount(User[] users) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__updateAdapterOfUser.handleMultiple(users); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } - - @Override - public int multiPKey(MultiPKeyEntity entity) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__updateAdapterOfMultiPKeyEntity.handle(entity); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } - - @Override - public void updateUserAndBook(User user, Book book) { - __db.beginTransaction(); - try { - __updateAdapterOfUser.handle(user); - __updateAdapterOfBook.handle(book); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public void ageUserByUid(String uid) { - final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserByUid.acquire(); - __db.beginTransaction(); - try { - int _argIndex = 1; - if (uid == null) { - _stmt.bindNull(_argIndex); - } else { - _stmt.bindString(_argIndex, uid); - } - _stmt.executeUpdateDelete(); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - __preparedStmtOfAgeUserByUid.release(_stmt); - } - } - @Override - public void ageUserAll() { - final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserAll.acquire(); - __db.beginTransaction(); - try { - _stmt.executeUpdateDelete(); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - __preparedStmtOfAgeUserAll.release(_stmt); - } - } +@Dao +abstract interface UpdateDao { + @Update + void updateUser(User user); + @Update + void updateUsers(User user1, List<User> others); + @Update + void updateArrayOfUsers(User[] users); + + @Update + int updateUserAndReturnCount(User user); + @Update + int updateUserAndReturnCount(User user1, List<User> others); + @Update + int updateUserAndReturnCount(User[] users); + + @Update + int multiPKey(MultiPKeyEntity entity); + + @Update + void updateUserAndBook(User user, Book book); + + @Query("UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = :uid") + void ageUserByUid(String uid); + + @Query("UPDATE User SET ageColumn = ageColumn + 1") + void ageUserAll(); } diff --git a/foo/bar/WriterDao.java b/foo/bar/WriterDao.java index cfad0469..e122479b 100644 --- a/foo/bar/WriterDao.java +++ b/foo/bar/WriterDao.java @@ -15,131 +15,17 @@ */ package foo.bar; - -import android.arch.persistence.db.SupportSQLiteStatement; -import android.arch.persistence.room.EntityInsertionAdapter; -import android.arch.persistence.room.RoomDatabase; - -import java.lang.Override; -import java.lang.String; +import android.arch.persistence.room.*; import java.util.List; -import javax.annotation.Generated; - -@Generated("android.arch.persistence.room.RoomProcessor") -public class WriterDao_Impl implements WriterDao { - private final RoomDatabase __db; - - private final EntityInsertionAdapter __insertionAdapterOfUser; - - private final EntityInsertionAdapter __insertionAdapterOfUser_1; - - private final EntityInsertionAdapter __insertionAdapterOfBook; - - public WriterDao_Impl(RoomDatabase __db) { - this.__db = __db; - this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__db) { - @Override - public String createQuery() { - return "INSERT OR ABORT INTO `User`(`uid`,`name`,`lastName`,`ageColumn`) VALUES" - + " (?,?,?,?)"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, User value) { - stmt.bindLong(1, value.uid); - if (value.name == null) { - stmt.bindNull(2); - } else { - stmt.bindString(2, value.name); - } - if (value.getLastName() == null) { - stmt.bindNull(3); - } else { - stmt.bindString(3, value.getLastName()); - } - stmt.bindLong(4, value.age); - } - }; - this.__insertionAdapterOfUser_1 = new EntityInsertionAdapter<User>(__db) { - @Override - public String createQuery() { - return "INSERT OR REPLACE INTO `User`(`uid`,`name`,`lastName`,`ageColumn`) VALUES" - + " (?,?,?,?)"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, User value) { - stmt.bindLong(1, value.uid); - if (value.name == null) { - stmt.bindNull(2); - } else { - stmt.bindString(2, value.name); - } - if (value.getLastName() == null) { - stmt.bindNull(3); - } else { - stmt.bindString(3, value.getLastName()); - } - stmt.bindLong(4, value.age); - } - }; - this.__insertionAdapterOfBook = new EntityInsertionAdapter<Book>(__db) { - @Override - public String createQuery() { - return "INSERT OR ABORT INTO `Book`(`bookId`,`uid`) VALUES (?,?)"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, Book value) { - stmt.bindLong(1, value.bookId); - stmt.bindLong(2, value.uid); - } - }; - } - - @Override - public void insertUser(User user) { - __db.beginTransaction(); - try { - __insertionAdapterOfUser.insert(user); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public void insertUsers(User user1, List<User> others) { - __db.beginTransaction(); - try { - __insertionAdapterOfUser.insert(user1); - __insertionAdapterOfUser.insert(others); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public void insertUsers(User[] users) { - __db.beginTransaction(); - try { - __insertionAdapterOfUser_1.insert(users); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - @Override - public void insertUserAndBook(User user, Book book) { - __db.beginTransaction(); - try { - __insertionAdapterOfUser.insert(user); - __insertionAdapterOfBook.insert(book); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } +@Dao +abstract interface WriterDao { + @Insert + void insertUser(User user); + @Insert + void insertUsers(User user1, List<User> others); + @Insert(onConflict=OnConflictStrategy.REPLACE) + void insertUsers(User[] users); + @Insert + void insertUserAndBook(User user, Book book); } diff --git a/java/lang/Boolean.java b/java/lang/Boolean.java index 397bf092..802d9104 100644 --- a/java/lang/Boolean.java +++ b/java/lang/Boolean.java @@ -61,8 +61,6 @@ public final class Boolean implements java.io.Serializable, * @since JDK1.1 */ @SuppressWarnings("unchecked") - // Android-changed: Avoid use of removed Class.getPrimitiveClass method. - // public static final Class<Boolean> TYPE = (Class<Boolean>) Class.getPrimitiveClass("boolean"); public static final Class<Boolean> TYPE = (Class<Boolean>) boolean[].class.getComponentType(); /** diff --git a/java/lang/Byte.java b/java/lang/Byte.java index 2333afd3..d0031d0c 100644 --- a/java/lang/Byte.java +++ b/java/lang/Byte.java @@ -60,8 +60,6 @@ public final class Byte extends Number implements Comparable<Byte> { * {@code byte}. */ @SuppressWarnings("unchecked") - // Android-changed: Avoid use of removed Class.getPrimitiveClass method. - // public static final Class<Byte> TYPE = (Class<Byte>) Class.getPrimitiveClass("byte"); public static final Class<Byte> TYPE = (Class<Byte>) byte[].class.getComponentType(); /** diff --git a/java/lang/Character.java b/java/lang/Character.java index 7615dab7..fb0d576e 100644 --- a/java/lang/Character.java +++ b/java/lang/Character.java @@ -173,8 +173,6 @@ class Character implements java.io.Serializable, Comparable<Character> { * @since 1.1 */ @SuppressWarnings("unchecked") - // Android-changed: Avoid use of removed Class.getPrimitiveClass method. - // public static final Class<Character> TYPE = (Class<Character>) Class.getPrimitiveClass("char"); public static final Class<Character> TYPE = (Class<Character>) char[].class.getComponentType(); /* diff --git a/java/lang/Double.java b/java/lang/Double.java index 2721a842..2bc2bf39 100644 --- a/java/lang/Double.java +++ b/java/lang/Double.java @@ -137,8 +137,6 @@ public final class Double extends Number implements Comparable<Double> { * @since JDK1.1 */ @SuppressWarnings("unchecked") - // Android-changed: Avoid use of removed Class.getPrimitiveClass method. - // public static final Class<Double> TYPE = (Class<Double>) Class.getPrimitiveClass("double"); public static final Class<Double> TYPE = (Class<Double>) double[].class.getComponentType(); /** diff --git a/java/lang/Float.java b/java/lang/Float.java index 6a2b9336..32bd625a 100644 --- a/java/lang/Float.java +++ b/java/lang/Float.java @@ -135,8 +135,6 @@ public final class Float extends Number implements Comparable<Float> { * @since JDK1.1 */ @SuppressWarnings("unchecked") - // Android-changed: Avoid use of removed Class.getPrimitiveClass method. - // public static final Class<Float> TYPE = (Class<Float>) Class.getPrimitiveClass("float"); public static final Class<Float> TYPE = (Class<Float>) float[].class.getComponentType(); /** diff --git a/java/lang/Integer.java b/java/lang/Integer.java index c63b0d55..f742505a 100644 --- a/java/lang/Integer.java +++ b/java/lang/Integer.java @@ -70,8 +70,6 @@ public final class Integer extends Number implements Comparable<Integer> { * @since JDK1.1 */ @SuppressWarnings("unchecked") - // Android-changed: Avoid use of removed Class.getPrimitiveClass method. - // public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int"); public static final Class<Integer> TYPE = (Class<Integer>) int[].class.getComponentType(); /** @@ -317,8 +315,7 @@ public final class Integer extends Number implements Comparable<Integer> { formatUnsignedInt(val, shift, buf, 0, chars); - // Android-changed: Use regular constructor instead of one which takes over "buf". - // return new String(buf, true); + // Use special constructor which takes over "buf". return new String(buf); } @@ -343,10 +340,8 @@ public final class Integer extends Number implements Comparable<Integer> { return charPos; } - // BEGIN Android-changed: Cache the toString() result for small values. private static final String[] SMALL_NEG_VALUES = new String[100]; private static final String[] SMALL_NONNEG_VALUES = new String[100]; - // END Android-changed: Cache the toString() result for small values. final static char [] DigitTens = { '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', @@ -407,8 +402,7 @@ public final class Integer extends Number implements Comparable<Integer> { if (i == Integer.MIN_VALUE) return "-2147483648"; - // BEGIN Android-changed: Cache the String for small values. - // int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i); + // Android-changed: cache the string literal for small values. boolean negative = i < 0; boolean small = negative ? i > -100 : i < 100; if (small) { @@ -430,12 +424,10 @@ public final class Integer extends Number implements Comparable<Integer> { } return smallValues[i]; } + int size = negative ? stringSize(-i) + 1 : stringSize(i); - // END Android-changed: Cache the String for small values. char[] buf = new char[size]; getChars(i, size, buf); - // Android-changed: Use regular constructor instead of one which takes over "buf". - // return new String(buf, true); return new String(buf); } @@ -575,7 +567,6 @@ public final class Integer extends Number implements Comparable<Integer> { */ if (s == null) { - // Android-changed: Improve exception message for parseInt. throw new NumberFormatException("s == null"); } diff --git a/java/lang/Long.java b/java/lang/Long.java index c752957a..3f383b40 100644 --- a/java/lang/Long.java +++ b/java/lang/Long.java @@ -72,8 +72,6 @@ public final class Long extends Number implements Comparable<Long> { * @since JDK1.1 */ @SuppressWarnings("unchecked") - // Android-changed: Avoid use of removed Class.getPrimitiveClass method. - // public static final Class<Long> TYPE = (Class<Long>) Class.getPrimitiveClass("long"); public static final Class<Long> TYPE = (Class<Long>) long[].class.getComponentType(); /** @@ -359,8 +357,6 @@ public final class Long extends Number implements Comparable<Long> { char[] buf = new char[chars]; formatUnsignedLong(val, shift, buf, 0, chars); - // Android-changed: Use regular constructor instead of one which takes over "buf". - // return new String(buf, true); return new String(buf); } @@ -401,8 +397,6 @@ public final class Long extends Number implements Comparable<Long> { int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i); char[] buf = new char[size]; getChars(i, size, buf); - // Android-changed: Use regular constructor instead of one which takes over "buf". - // return new String(buf, true); return new String(buf); } diff --git a/java/lang/Short.java b/java/lang/Short.java index 0f600a84..00317114 100644 --- a/java/lang/Short.java +++ b/java/lang/Short.java @@ -60,8 +60,6 @@ public final class Short extends Number implements Comparable<Short> { * {@code short}. */ @SuppressWarnings("unchecked") - // Android-changed: Avoid use of removed Class.getPrimitiveClass method. - // public static final Class<Short> TYPE = (Class<Short>) Class.getPrimitiveClass("short"); public static final Class<Short> TYPE = (Class<Short>) short[].class.getComponentType(); /** diff --git a/java/lang/String.java b/java/lang/String.java index 7e823479..4cefed81 100644 --- a/java/lang/String.java +++ b/java/lang/String.java @@ -543,10 +543,8 @@ public final class String throw new UnsupportedOperationException("Use StringFactory instead."); } - // Android-removed: Unused package-private constructor String(char[] value, boolean share). - // BEGIN Android-added: Constructor for internal use. - // Not implemented in java as all calls are intercepted by the runtime. + // BEGIN Android-changed: Deprecated & unsupported as all calls are intercepted by the runtime. /** * Package private constructor * @@ -556,7 +554,7 @@ public final class String String(int offset, int count, char[] value) { throw new UnsupportedOperationException("Use StringFactory instead."); } - // END Android-added: Constructor for internal use. + // END Android-changed: Deprecated & unsupported as all calls are intercepted by the runtime. /** * Returns the length of this string. @@ -1561,6 +1559,12 @@ public final class String } } + // BEGIN Android-added: Native method to access char storage managed by runtime. + // TODO(b/67411061): This seems to be unused, see whether we can remove it. + @FastNative + private native int fastIndexOf(int c, int start); + // END Android-added: Native method to access char storage managed by runtime. + /** * Handles (rare) calls of indexOf with a supplementary character. */ diff --git a/java/lang/Void.java b/java/lang/Void.java index 14268c78..8311ea8c 100644 --- a/java/lang/Void.java +++ b/java/lang/Void.java @@ -44,13 +44,24 @@ class Void { * The {@code Class} object representing the pseudo-type corresponding to * the keyword {@code void}. */ - // BEGIN Android-changed: Avoid use of removed Class.getPrimitiveClass method. - // public static final Class<Void> TYPE = (Class<Void>) Class.getPrimitiveClass("void"); public static final Class<Void> TYPE = lookupType(); + // Android-changed: Upstream code would use reflection to establish the value of "void.class". + // ART makes a native call instead because the reflection approach could lead to initialization + // of TYPE with the current, i.e. uninitialized, value of TYPE due to other Android changes. @dalvik.annotation.optimization.FastNative private static native Class<Void> lookupType(); - // END Android-changed: Avoid use of removed Class.getPrimitiveClass method. + /* + @SuppressWarnings("unchecked") + private static Class<Void> lookupType() { + try { + Method method = Runnable.class.getMethod("run", EmptyArray.CLASS); + return (Class<Void>) method.getReturnType(); + } catch (Exception e) { + throw new AssertionError(e); + } + } + */ /* * The Void class cannot be instantiated. diff --git a/java/lang/invoke/ByteArrayVarHandle.java b/java/lang/invoke/ByteArrayVarHandle.java deleted file mode 100644 index 4237058d..00000000 --- a/java/lang/invoke/ByteArrayVarHandle.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2017 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 java.lang.invoke; - -import java.nio.ByteOrder; - -/** - * A VarHandle to access byte array elements as an array of primitive types. - * @hide - */ -final class ByteArrayVarHandle extends VarHandle { - private ByteOrder byteOrder; - - private ByteArrayVarHandle(Class<?> arrayClass, ByteOrder byteOrder) { - super(arrayClass.getComponentType(), byte[].class, false /* isFinal */, - byte[].class, int.class); - this.byteOrder = byteOrder; - } - - static ByteArrayVarHandle create(Class<?> arrayClass, ByteOrder byteOrder) { - return new ByteArrayVarHandle(arrayClass, byteOrder); - } -} diff --git a/java/lang/invoke/ByteBufferViewVarHandle.java b/java/lang/invoke/ByteBufferViewVarHandle.java deleted file mode 100644 index 74f3b0cb..00000000 --- a/java/lang/invoke/ByteBufferViewVarHandle.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2017 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 java.lang.invoke; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -/** - * A VarHandle to access byte array elements as an array of primitive types. - * @hide - */ -final class ByteBufferViewVarHandle extends VarHandle { - private ByteOrder byteOrder; - - private ByteBufferViewVarHandle(Class<?> arrayClass, ByteOrder byteOrder) { - super(arrayClass.getComponentType(), byte[].class, false /* isFinal */, - ByteBuffer.class, int.class); - this.byteOrder = byteOrder; - } - - static ByteBufferViewVarHandle create(Class<?> arrayClass, ByteOrder byteOrder) { - return new ByteBufferViewVarHandle(arrayClass, byteOrder); - } -} diff --git a/java/lang/invoke/CallSite.java b/java/lang/invoke/CallSite.java index 1ff1eb84..85b4bb9f 100644 --- a/java/lang/invoke/CallSite.java +++ b/java/lang/invoke/CallSite.java @@ -25,15 +25,363 @@ package java.lang.invoke; +// Android-changed: Not using Empty +//import sun.invoke.empty.Empty; +import static java.lang.invoke.MethodHandleStatics.*; +import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; + +/** + * A {@code CallSite} is a holder for a variable {@link MethodHandle}, + * which is called its {@code target}. + * An {@code invokedynamic} instruction linked to a {@code CallSite} delegates + * all calls to the site's current target. + * A {@code CallSite} may be associated with several {@code invokedynamic} + * instructions, or it may be "free floating", associated with none. + * In any case, it may be invoked through an associated method handle + * called its {@linkplain #dynamicInvoker dynamic invoker}. + * <p> + * {@code CallSite} is an abstract class which does not allow + * direct subclassing by users. It has three immediate, + * concrete subclasses that may be either instantiated or subclassed. + * <ul> + * <li>If a mutable target is not required, an {@code invokedynamic} instruction + * may be permanently bound by means of a {@linkplain ConstantCallSite constant call site}. + * <li>If a mutable target is required which has volatile variable semantics, + * because updates to the target must be immediately and reliably witnessed by other threads, + * a {@linkplain VolatileCallSite volatile call site} may be used. + * <li>Otherwise, if a mutable target is required, + * a {@linkplain MutableCallSite mutable call site} may be used. + * </ul> + * <p> + * A non-constant call site may be <em>relinked</em> by changing its target. + * The new target must have the same {@linkplain MethodHandle#type() type} + * as the previous target. + * Thus, though a call site can be relinked to a series of + * successive targets, it cannot change its type. + * <p> + * Here is a sample use of call sites and bootstrap methods which links every + * dynamic call site to print its arguments: +<blockquote><pre>{@code +static void test() throws Throwable { + // THE FOLLOWING LINE IS PSEUDOCODE FOR A JVM INSTRUCTION + InvokeDynamic[#bootstrapDynamic].baz("baz arg", 2, 3.14); +} +private static void printArgs(Object... args) { + System.out.println(java.util.Arrays.deepToString(args)); +} +private static final MethodHandle printArgs; +static { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + Class thisClass = lookup.lookupClass(); // (who am I?) + printArgs = lookup.findStatic(thisClass, + "printArgs", MethodType.methodType(void.class, Object[].class)); +} +private static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) { + // ignore caller and name, but match the type: + return new ConstantCallSite(printArgs.asType(type)); +} +}</pre></blockquote> + * @author John Rose, JSR 292 EG + */ abstract public class CallSite { + // Android-changed: not used. + // static { MethodHandleImpl.initStatics(); } + + // The actual payload of this call site: + /*package-private*/ + MethodHandle target; // Note: This field is known to the JVM. Do not change. + + /** + * Make a blank call site object with the given method type. + * An initial target method is supplied which will throw + * an {@link IllegalStateException} if called. + * <p> + * Before this {@code CallSite} object is returned from a bootstrap method, + * it is usually provided with a more useful target method, + * via a call to {@link CallSite#setTarget(MethodHandle) setTarget}. + * @throws NullPointerException if the proposed type is null + */ + /*package-private*/ + CallSite(MethodType type) { + // Android-changed: No cache for these so create uninitializedCallSite target here using + // method handle transformations to create a method handle that has the expected method + // type but throws an IllegalStateException. + // target = makeUninitializedCallSite(type); + this.target = MethodHandles.throwException(type.returnType(), IllegalStateException.class); + this.target = MethodHandles.insertArguments( + this.target, 0, new IllegalStateException("uninitialized call site")); + if (type.parameterCount() > 0) { + this.target = MethodHandles.dropArguments(this.target, 0, type.ptypes()); + } + + // Android-changed: Using initializer method for GET_TARGET + // rather than complex static initializer. + initializeGetTarget(); + } + + /** + * Make a call site object equipped with an initial target method handle. + * @param target the method handle which will be the initial target of the call site + * @throws NullPointerException if the proposed target is null + */ + /*package-private*/ + CallSite(MethodHandle target) { + target.type(); // null check + this.target = target; + + // Android-changed: Using initializer method for GET_TARGET + // rather than complex static initializer. + initializeGetTarget(); + } - public MethodType type() { return null; } + /** + * Make a call site object equipped with an initial target method handle. + * @param targetType the desired type of the call site + * @param createTargetHook a hook which will bind the call site to the target method handle + * @throws WrongMethodTypeException if the hook cannot be invoked on the required arguments, + * or if the target returned by the hook is not of the given {@code targetType} + * @throws NullPointerException if the hook returns a null value + * @throws ClassCastException if the hook returns something other than a {@code MethodHandle} + * @throws Throwable anything else thrown by the hook function + */ + /*package-private*/ + CallSite(MethodType targetType, MethodHandle createTargetHook) throws Throwable { + this(targetType); + ConstantCallSite selfCCS = (ConstantCallSite) this; + MethodHandle boundTarget = (MethodHandle) createTargetHook.invokeWithArguments(selfCCS); + checkTargetChange(this.target, boundTarget); + this.target = boundTarget; + // Android-changed: Using initializer method for GET_TARGET + // rather than complex static initializer. + initializeGetTarget(); + } + + /** + * Returns the type of this call site's target. + * Although targets may change, any call site's type is permanent, and can never change to an unequal type. + * The {@code setTarget} method enforces this invariant by refusing any new target that does + * not have the previous target's type. + * @return the type of the current target, which is also the type of any future target + */ + public MethodType type() { + // warning: do not call getTarget here, because CCS.getTarget can throw IllegalStateException + return target.type(); + } + + /** + * Returns the target method of the call site, according to the + * behavior defined by this call site's specific class. + * The immediate subclasses of {@code CallSite} document the + * class-specific behaviors of this method. + * + * @return the current linkage state of the call site, its target method handle + * @see ConstantCallSite + * @see VolatileCallSite + * @see #setTarget + * @see ConstantCallSite#getTarget + * @see MutableCallSite#getTarget + * @see VolatileCallSite#getTarget + */ public abstract MethodHandle getTarget(); + /** + * Updates the target method of this call site, according to the + * behavior defined by this call site's specific class. + * The immediate subclasses of {@code CallSite} document the + * class-specific behaviors of this method. + * <p> + * The type of the new target must be {@linkplain MethodType#equals equal to} + * the type of the old target. + * + * @param newTarget the new target + * @throws NullPointerException if the proposed new target is null + * @throws WrongMethodTypeException if the proposed new target + * has a method type that differs from the previous target + * @see CallSite#getTarget + * @see ConstantCallSite#setTarget + * @see MutableCallSite#setTarget + * @see VolatileCallSite#setTarget + */ public abstract void setTarget(MethodHandle newTarget); + void checkTargetChange(MethodHandle oldTarget, MethodHandle newTarget) { + MethodType oldType = oldTarget.type(); + MethodType newType = newTarget.type(); // null check! + if (!newType.equals(oldType)) + throw wrongTargetType(newTarget, oldType); + } + + private static WrongMethodTypeException wrongTargetType(MethodHandle target, MethodType type) { + return new WrongMethodTypeException(String.valueOf(target)+" should be of type "+type); + } + + /** + * Produces a method handle equivalent to an invokedynamic instruction + * which has been linked to this call site. + * <p> + * This method is equivalent to the following code: + * <blockquote><pre>{@code + * MethodHandle getTarget, invoker, result; + * getTarget = MethodHandles.publicLookup().bind(this, "getTarget", MethodType.methodType(MethodHandle.class)); + * invoker = MethodHandles.exactInvoker(this.type()); + * result = MethodHandles.foldArguments(invoker, getTarget) + * }</pre></blockquote> + * + * @return a method handle which always invokes this call site's current target + */ public abstract MethodHandle dynamicInvoker(); + /*non-public*/ MethodHandle makeDynamicInvoker() { + // Android-changed: Use bindTo() rather than bindArgumentL() (not implemented). + MethodHandle getTarget = GET_TARGET.bindTo(this); + MethodHandle invoker = MethodHandles.exactInvoker(this.type()); + return MethodHandles.foldArguments(invoker, getTarget); + } + + // Android-changed: no longer final. GET_TARGET assigned in initializeGetTarget(). + private static MethodHandle GET_TARGET = null; + + private void initializeGetTarget() { + // Android-changed: moved from static initializer for + // GET_TARGET to avoid issues with running early. Called from + // constructors. CallSite creation is not performance critical. + synchronized (CallSite.class) { + if (GET_TARGET == null) { + try { + GET_TARGET = IMPL_LOOKUP. + findVirtual(CallSite.class, "getTarget", + MethodType.methodType(MethodHandle.class)); + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } + } + } + } + + // Android-changed: not used. + // /** This guy is rolled into the default target if a MethodType is supplied to the constructor. */ + // /*package-private*/ + // static Empty uninitializedCallSite() { + // throw new IllegalStateException("uninitialized call site"); + // } + + // unsafe stuff: + private static final long TARGET_OFFSET; + static { + try { + TARGET_OFFSET = UNSAFE.objectFieldOffset(CallSite.class.getDeclaredField("target")); + } catch (Exception ex) { throw new Error(ex); } + } + + /*package-private*/ + void setTargetNormal(MethodHandle newTarget) { + // Android-changed: Set value directly. + // MethodHandleNatives.setCallSiteTargetNormal(this, newTarget); + target = newTarget; + } + /*package-private*/ + MethodHandle getTargetVolatile() { + return (MethodHandle) UNSAFE.getObjectVolatile(this, TARGET_OFFSET); + } + /*package-private*/ + void setTargetVolatile(MethodHandle newTarget) { + // Android-changed: Set value directly. + // MethodHandleNatives.setCallSiteTargetVolatile(this, newTarget); + UNSAFE.putObjectVolatile(this, TARGET_OFFSET, newTarget); + } + + // Android-changed: not used. + // this implements the upcall from the JVM, MethodHandleNatives.makeDynamicCallSite: + // static CallSite makeSite(MethodHandle bootstrapMethod, + // // Callee information: + // String name, MethodType type, + // // Extra arguments for BSM, if any: + // Object info, + // // Caller information: + // Class<?> callerClass) { + // MethodHandles.Lookup caller = IMPL_LOOKUP.in(callerClass); + // CallSite site; + // try { + // Object binding; + // info = maybeReBox(info); + // if (info == null) { + // binding = bootstrapMethod.invoke(caller, name, type); + // } else if (!info.getClass().isArray()) { + // binding = bootstrapMethod.invoke(caller, name, type, info); + // } else { + // Object[] argv = (Object[]) info; + // maybeReBoxElements(argv); + // switch (argv.length) { + // case 0: + // binding = bootstrapMethod.invoke(caller, name, type); + // break; + // case 1: + // binding = bootstrapMethod.invoke(caller, name, type, + // argv[0]); + // break; + // case 2: + // binding = bootstrapMethod.invoke(caller, name, type, + // argv[0], argv[1]); + // break; + // case 3: + // binding = bootstrapMethod.invoke(caller, name, type, + // argv[0], argv[1], argv[2]); + // break; + // case 4: + // binding = bootstrapMethod.invoke(caller, name, type, + // argv[0], argv[1], argv[2], argv[3]); + // break; + // case 5: + // binding = bootstrapMethod.invoke(caller, name, type, + // argv[0], argv[1], argv[2], argv[3], argv[4]); + // break; + // case 6: + // binding = bootstrapMethod.invoke(caller, name, type, + // argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); + // break; + // default: + // final int NON_SPREAD_ARG_COUNT = 3; // (caller, name, type) + // if (NON_SPREAD_ARG_COUNT + argv.length > MethodType.MAX_MH_ARITY) + // throw new BootstrapMethodError("too many bootstrap method arguments"); + // MethodType bsmType = bootstrapMethod.type(); + // MethodType invocationType = MethodType.genericMethodType(NON_SPREAD_ARG_COUNT + argv.length); + // MethodHandle typedBSM = bootstrapMethod.asType(invocationType); + // MethodHandle spreader = invocationType.invokers().spreadInvoker(NON_SPREAD_ARG_COUNT); + // binding = spreader.invokeExact(typedBSM, (Object)caller, (Object)name, (Object)type, argv); + // } + // } + // //System.out.println("BSM for "+name+type+" => "+binding); + // if (binding instanceof CallSite) { + // site = (CallSite) binding; + // } else { + // throw new ClassCastException("bootstrap method failed to produce a CallSite"); + // } + // if (!site.getTarget().type().equals(type)) + // throw wrongTargetType(site.getTarget(), type); + // } catch (Throwable ex) { + // BootstrapMethodError bex; + // if (ex instanceof BootstrapMethodError) + // bex = (BootstrapMethodError) ex; + // else + // bex = new BootstrapMethodError("call site initialization exception", ex); + // throw bex; + // } + // return site; + // } + + // private static Object maybeReBox(Object x) { + // if (x instanceof Integer) { + // int xi = (int) x; + // if (xi == (byte) xi) + // x = xi; // must rebox; see JLS 5.1.7 + // } + // return x; + // } + // private static void maybeReBoxElements(Object[] xa) { + // for (int i = 0; i < xa.length; i++) { + // xa[i] = maybeReBox(xa[i]); + // } + // } } diff --git a/java/lang/invoke/FieldVarHandle.java b/java/lang/invoke/FieldVarHandle.java deleted file mode 100644 index 0921040d..00000000 --- a/java/lang/invoke/FieldVarHandle.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2017 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 java.lang.invoke; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; - -/** - * A VarHandle that's associated with an ArtField. - * @hide - */ -final class FieldVarHandle extends VarHandle { - private final long artField; - - private FieldVarHandle(Field field, Class<?> declaringClass) { - super(field.getType(), Modifier.isFinal(field.getModifiers()), declaringClass); - artField = field.getArtField(); - } - - private FieldVarHandle(Field field) { - super(field.getType(), Modifier.isFinal(field.getModifiers())); - artField = field.getArtField(); - } - - static FieldVarHandle create(Field field) { - if (Modifier.isStatic(field.getModifiers())) { - return new FieldVarHandle(field); - } else { - return new FieldVarHandle(field, field.getDeclaringClass()); - } - } -} diff --git a/java/lang/invoke/MethodHandle.java b/java/lang/invoke/MethodHandle.java index 159f9dd7..af3db103 100644 --- a/java/lang/invoke/MethodHandle.java +++ b/java/lang/invoke/MethodHandle.java @@ -25,28 +25,1353 @@ package java.lang.invoke; + +import dalvik.system.EmulatedStackFrame; + +import static java.lang.invoke.MethodHandleStatics.*; + +/** + * A method handle is a typed, directly executable reference to an underlying method, + * constructor, field, or similar low-level operation, with optional + * transformations of arguments or return values. + * These transformations are quite general, and include such patterns as + * {@linkplain #asType conversion}, + * {@linkplain #bindTo insertion}, + * {@linkplain java.lang.invoke.MethodHandles#dropArguments deletion}, + * and {@linkplain java.lang.invoke.MethodHandles#filterArguments substitution}. + * + * <h1>Method handle contents</h1> + * Method handles are dynamically and strongly typed according to their parameter and return types. + * They are not distinguished by the name or the defining class of their underlying methods. + * A method handle must be invoked using a symbolic type descriptor which matches + * the method handle's own {@linkplain #type type descriptor}. + * <p> + * Every method handle reports its type descriptor via the {@link #type type} accessor. + * This type descriptor is a {@link java.lang.invoke.MethodType MethodType} object, + * whose structure is a series of classes, one of which is + * the return type of the method (or {@code void.class} if none). + * <p> + * A method handle's type controls the types of invocations it accepts, + * and the kinds of transformations that apply to it. + * <p> + * A method handle contains a pair of special invoker methods + * called {@link #invokeExact invokeExact} and {@link #invoke invoke}. + * Both invoker methods provide direct access to the method handle's + * underlying method, constructor, field, or other operation, + * as modified by transformations of arguments and return values. + * Both invokers accept calls which exactly match the method handle's own type. + * The plain, inexact invoker also accepts a range of other call types. + * <p> + * Method handles are immutable and have no visible state. + * Of course, they can be bound to underlying methods or data which exhibit state. + * With respect to the Java Memory Model, any method handle will behave + * as if all of its (internal) fields are final variables. This means that any method + * handle made visible to the application will always be fully formed. + * This is true even if the method handle is published through a shared + * variable in a data race. + * <p> + * Method handles cannot be subclassed by the user. + * Implementations may (or may not) create internal subclasses of {@code MethodHandle} + * which may be visible via the {@link java.lang.Object#getClass Object.getClass} + * operation. The programmer should not draw conclusions about a method handle + * from its specific class, as the method handle class hierarchy (if any) + * may change from time to time or across implementations from different vendors. + * + * <h1>Method handle compilation</h1> + * A Java method call expression naming {@code invokeExact} or {@code invoke} + * can invoke a method handle from Java source code. + * From the viewpoint of source code, these methods can take any arguments + * and their result can be cast to any return type. + * Formally this is accomplished by giving the invoker methods + * {@code Object} return types and variable arity {@code Object} arguments, + * but they have an additional quality called <em>signature polymorphism</em> + * which connects this freedom of invocation directly to the JVM execution stack. + * <p> + * As is usual with virtual methods, source-level calls to {@code invokeExact} + * and {@code invoke} compile to an {@code invokevirtual} instruction. + * More unusually, the compiler must record the actual argument types, + * and may not perform method invocation conversions on the arguments. + * Instead, it must push them on the stack according to their own unconverted types. + * The method handle object itself is pushed on the stack before the arguments. + * The compiler then calls the method handle with a symbolic type descriptor which + * describes the argument and return types. + * <p> + * To issue a complete symbolic type descriptor, the compiler must also determine + * the return type. This is based on a cast on the method invocation expression, + * if there is one, or else {@code Object} if the invocation is an expression + * or else {@code void} if the invocation is a statement. + * The cast may be to a primitive type (but not {@code void}). + * <p> + * As a corner case, an uncasted {@code null} argument is given + * a symbolic type descriptor of {@code java.lang.Void}. + * The ambiguity with the type {@code Void} is harmless, since there are no references of type + * {@code Void} except the null reference. + * + * <h1>Method handle invocation</h1> + * The first time a {@code invokevirtual} instruction is executed + * it is linked, by symbolically resolving the names in the instruction + * and verifying that the method call is statically legal. + * This is true of calls to {@code invokeExact} and {@code invoke}. + * In this case, the symbolic type descriptor emitted by the compiler is checked for + * correct syntax and names it contains are resolved. + * Thus, an {@code invokevirtual} instruction which invokes + * a method handle will always link, as long + * as the symbolic type descriptor is syntactically well-formed + * and the types exist. + * <p> + * When the {@code invokevirtual} is executed after linking, + * the receiving method handle's type is first checked by the JVM + * to ensure that it matches the symbolic type descriptor. + * If the type match fails, it means that the method which the + * caller is invoking is not present on the individual + * method handle being invoked. + * <p> + * In the case of {@code invokeExact}, the type descriptor of the invocation + * (after resolving symbolic type names) must exactly match the method type + * of the receiving method handle. + * In the case of plain, inexact {@code invoke}, the resolved type descriptor + * must be a valid argument to the receiver's {@link #asType asType} method. + * Thus, plain {@code invoke} is more permissive than {@code invokeExact}. + * <p> + * After type matching, a call to {@code invokeExact} directly + * and immediately invoke the method handle's underlying method + * (or other behavior, as the case may be). + * <p> + * A call to plain {@code invoke} works the same as a call to + * {@code invokeExact}, if the symbolic type descriptor specified by the caller + * exactly matches the method handle's own type. + * If there is a type mismatch, {@code invoke} attempts + * to adjust the type of the receiving method handle, + * as if by a call to {@link #asType asType}, + * to obtain an exactly invokable method handle {@code M2}. + * This allows a more powerful negotiation of method type + * between caller and callee. + * <p> + * (<em>Note:</em> The adjusted method handle {@code M2} is not directly observable, + * and implementations are therefore not required to materialize it.) + * + * <h1>Invocation checking</h1> + * In typical programs, method handle type matching will usually succeed. + * But if a match fails, the JVM will throw a {@link WrongMethodTypeException}, + * either directly (in the case of {@code invokeExact}) or indirectly as if + * by a failed call to {@code asType} (in the case of {@code invoke}). + * <p> + * Thus, a method type mismatch which might show up as a linkage error + * in a statically typed program can show up as + * a dynamic {@code WrongMethodTypeException} + * in a program which uses method handles. + * <p> + * Because method types contain "live" {@code Class} objects, + * method type matching takes into account both types names and class loaders. + * Thus, even if a method handle {@code M} is created in one + * class loader {@code L1} and used in another {@code L2}, + * method handle calls are type-safe, because the caller's symbolic type + * descriptor, as resolved in {@code L2}, + * is matched against the original callee method's symbolic type descriptor, + * as resolved in {@code L1}. + * The resolution in {@code L1} happens when {@code M} is created + * and its type is assigned, while the resolution in {@code L2} happens + * when the {@code invokevirtual} instruction is linked. + * <p> + * Apart from the checking of type descriptors, + * a method handle's capability to call its underlying method is unrestricted. + * If a method handle is formed on a non-public method by a class + * that has access to that method, the resulting handle can be used + * in any place by any caller who receives a reference to it. + * <p> + * Unlike with the Core Reflection API, where access is checked every time + * a reflective method is invoked, + * method handle access checking is performed + * <a href="MethodHandles.Lookup.html#access">when the method handle is created</a>. + * In the case of {@code ldc} (see below), access checking is performed as part of linking + * the constant pool entry underlying the constant method handle. + * <p> + * Thus, handles to non-public methods, or to methods in non-public classes, + * should generally be kept secret. + * They should not be passed to untrusted code unless their use from + * the untrusted code would be harmless. + * + * <h1>Method handle creation</h1> + * Java code can create a method handle that directly accesses + * any method, constructor, or field that is accessible to that code. + * This is done via a reflective, capability-based API called + * {@link java.lang.invoke.MethodHandles.Lookup MethodHandles.Lookup} + * For example, a static method handle can be obtained + * from {@link java.lang.invoke.MethodHandles.Lookup#findStatic Lookup.findStatic}. + * There are also conversion methods from Core Reflection API objects, + * such as {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}. + * <p> + * Like classes and strings, method handles that correspond to accessible + * fields, methods, and constructors can also be represented directly + * in a class file's constant pool as constants to be loaded by {@code ldc} bytecodes. + * A new type of constant pool entry, {@code CONSTANT_MethodHandle}, + * refers directly to an associated {@code CONSTANT_Methodref}, + * {@code CONSTANT_InterfaceMethodref}, or {@code CONSTANT_Fieldref} + * constant pool entry. + * (For full details on method handle constants, + * see sections 4.4.8 and 5.4.3.5 of the Java Virtual Machine Specification.) + * <p> + * Method handles produced by lookups or constant loads from methods or + * constructors with the variable arity modifier bit ({@code 0x0080}) + * have a corresponding variable arity, as if they were defined with + * the help of {@link #asVarargsCollector asVarargsCollector}. + * <p> + * A method reference may refer either to a static or non-static method. + * In the non-static case, the method handle type includes an explicit + * receiver argument, prepended before any other arguments. + * In the method handle's type, the initial receiver argument is typed + * according to the class under which the method was initially requested. + * (E.g., if a non-static method handle is obtained via {@code ldc}, + * the type of the receiver is the class named in the constant pool entry.) + * <p> + * Method handle constants are subject to the same link-time access checks + * their corresponding bytecode instructions, and the {@code ldc} instruction + * will throw corresponding linkage errors if the bytecode behaviors would + * throw such errors. + * <p> + * As a corollary of this, access to protected members is restricted + * to receivers only of the accessing class, or one of its subclasses, + * and the accessing class must in turn be a subclass (or package sibling) + * of the protected member's defining class. + * If a method reference refers to a protected non-static method or field + * of a class outside the current package, the receiver argument will + * be narrowed to the type of the accessing class. + * <p> + * When a method handle to a virtual method is invoked, the method is + * always looked up in the receiver (that is, the first argument). + * <p> + * A non-virtual method handle to a specific virtual method implementation + * can also be created. These do not perform virtual lookup based on + * receiver type. Such a method handle simulates the effect of + * an {@code invokespecial} instruction to the same method. + * + * <h1>Usage examples</h1> + * Here are some examples of usage: + * <blockquote><pre>{@code +Object x, y; String s; int i; +MethodType mt; MethodHandle mh; +MethodHandles.Lookup lookup = MethodHandles.lookup(); +// mt is (char,char)String +mt = MethodType.methodType(String.class, char.class, char.class); +mh = lookup.findVirtual(String.class, "replace", mt); +s = (String) mh.invokeExact("daddy",'d','n'); +// invokeExact(Ljava/lang/String;CC)Ljava/lang/String; +assertEquals(s, "nanny"); +// weakly typed invocation (using MHs.invoke) +s = (String) mh.invokeWithArguments("sappy", 'p', 'v'); +assertEquals(s, "savvy"); +// mt is (Object[])List +mt = MethodType.methodType(java.util.List.class, Object[].class); +mh = lookup.findStatic(java.util.Arrays.class, "asList", mt); +assert(mh.isVarargsCollector()); +x = mh.invoke("one", "two"); +// invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; +assertEquals(x, java.util.Arrays.asList("one","two")); +// mt is (Object,Object,Object)Object +mt = MethodType.genericMethodType(3); +mh = mh.asType(mt); +x = mh.invokeExact((Object)1, (Object)2, (Object)3); +// invokeExact(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +assertEquals(x, java.util.Arrays.asList(1,2,3)); +// mt is ()int +mt = MethodType.methodType(int.class); +mh = lookup.findVirtual(java.util.List.class, "size", mt); +i = (int) mh.invokeExact(java.util.Arrays.asList(1,2,3)); +// invokeExact(Ljava/util/List;)I +assert(i == 3); +mt = MethodType.methodType(void.class, String.class); +mh = lookup.findVirtual(java.io.PrintStream.class, "println", mt); +mh.invokeExact(System.out, "Hello, world."); +// invokeExact(Ljava/io/PrintStream;Ljava/lang/String;)V + * }</pre></blockquote> + * Each of the above calls to {@code invokeExact} or plain {@code invoke} + * generates a single invokevirtual instruction with + * the symbolic type descriptor indicated in the following comment. + * In these examples, the helper method {@code assertEquals} is assumed to + * be a method which calls {@link java.util.Objects#equals(Object,Object) Objects.equals} + * on its arguments, and asserts that the result is true. + * + * <h1>Exceptions</h1> + * The methods {@code invokeExact} and {@code invoke} are declared + * to throw {@link java.lang.Throwable Throwable}, + * which is to say that there is no static restriction on what a method handle + * can throw. Since the JVM does not distinguish between checked + * and unchecked exceptions (other than by their class, of course), + * there is no particular effect on bytecode shape from ascribing + * checked exceptions to method handle invocations. But in Java source + * code, methods which perform method handle calls must either explicitly + * throw {@code Throwable}, or else must catch all + * throwables locally, rethrowing only those which are legal in the context, + * and wrapping ones which are illegal. + * + * <h1><a name="sigpoly"></a>Signature polymorphism</h1> + * The unusual compilation and linkage behavior of + * {@code invokeExact} and plain {@code invoke} + * is referenced by the term <em>signature polymorphism</em>. + * As defined in the Java Language Specification, + * a signature polymorphic method is one which can operate with + * any of a wide range of call signatures and return types. + * <p> + * In source code, a call to a signature polymorphic method will + * compile, regardless of the requested symbolic type descriptor. + * As usual, the Java compiler emits an {@code invokevirtual} + * instruction with the given symbolic type descriptor against the named method. + * The unusual part is that the symbolic type descriptor is derived from + * the actual argument and return types, not from the method declaration. + * <p> + * When the JVM processes bytecode containing signature polymorphic calls, + * it will successfully link any such call, regardless of its symbolic type descriptor. + * (In order to retain type safety, the JVM will guard such calls with suitable + * dynamic type checks, as described elsewhere.) + * <p> + * Bytecode generators, including the compiler back end, are required to emit + * untransformed symbolic type descriptors for these methods. + * Tools which determine symbolic linkage are required to accept such + * untransformed descriptors, without reporting linkage errors. + * + * <h1>Interoperation between method handles and the Core Reflection API</h1> + * Using factory methods in the {@link java.lang.invoke.MethodHandles.Lookup Lookup} API, + * any class member represented by a Core Reflection API object + * can be converted to a behaviorally equivalent method handle. + * For example, a reflective {@link java.lang.reflect.Method Method} can + * be converted to a method handle using + * {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}. + * The resulting method handles generally provide more direct and efficient + * access to the underlying class members. + * <p> + * As a special case, + * when the Core Reflection API is used to view the signature polymorphic + * methods {@code invokeExact} or plain {@code invoke} in this class, + * they appear as ordinary non-polymorphic methods. + * Their reflective appearance, as viewed by + * {@link java.lang.Class#getDeclaredMethod Class.getDeclaredMethod}, + * is unaffected by their special status in this API. + * For example, {@link java.lang.reflect.Method#getModifiers Method.getModifiers} + * will report exactly those modifier bits required for any similarly + * declared method, including in this case {@code native} and {@code varargs} bits. + * <p> + * As with any reflected method, these methods (when reflected) may be + * invoked via {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}. + * However, such reflective calls do not result in method handle invocations. + * Such a call, if passed the required argument + * (a single one, of type {@code Object[]}), will ignore the argument and + * will throw an {@code UnsupportedOperationException}. + * <p> + * Since {@code invokevirtual} instructions can natively + * invoke method handles under any symbolic type descriptor, this reflective view conflicts + * with the normal presentation of these methods via bytecodes. + * Thus, these two native methods, when reflectively viewed by + * {@code Class.getDeclaredMethod}, may be regarded as placeholders only. + * <p> + * In order to obtain an invoker method for a particular type descriptor, + * use {@link java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvoker}, + * or {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker}. + * The {@link java.lang.invoke.MethodHandles.Lookup#findVirtual Lookup.findVirtual} + * API is also able to return a method handle + * to call {@code invokeExact} or plain {@code invoke}, + * for any specified type descriptor . + * + * <h1>Interoperation between method handles and Java generics</h1> + * A method handle can be obtained on a method, constructor, or field + * which is declared with Java generic types. + * As with the Core Reflection API, the type of the method handle + * will constructed from the erasure of the source-level type. + * When a method handle is invoked, the types of its arguments + * or the return value cast type may be generic types or type instances. + * If this occurs, the compiler will replace those + * types by their erasures when it constructs the symbolic type descriptor + * for the {@code invokevirtual} instruction. + * <p> + * Method handles do not represent + * their function-like types in terms of Java parameterized (generic) types, + * because there are three mismatches between function-like types and parameterized + * Java types. + * <ul> + * <li>Method types range over all possible arities, + * from no arguments to up to the <a href="MethodHandle.html#maxarity">maximum number</a> of allowed arguments. + * Generics are not variadic, and so cannot represent this.</li> + * <li>Method types can specify arguments of primitive types, + * which Java generic types cannot range over.</li> + * <li>Higher order functions over method handles (combinators) are + * often generic across a wide range of function types, including + * those of multiple arities. It is impossible to represent such + * genericity with a Java type parameter.</li> + * </ul> + * + * <h1><a name="maxarity"></a>Arity limits</h1> + * The JVM imposes on all methods and constructors of any kind an absolute + * limit of 255 stacked arguments. This limit can appear more restrictive + * in certain cases: + * <ul> + * <li>A {@code long} or {@code double} argument counts (for purposes of arity limits) as two argument slots. + * <li>A non-static method consumes an extra argument for the object on which the method is called. + * <li>A constructor consumes an extra argument for the object which is being constructed. + * <li>Since a method handle’s {@code invoke} method (or other signature-polymorphic method) is non-virtual, + * it consumes an extra argument for the method handle itself, in addition to any non-virtual receiver object. + * </ul> + * These limits imply that certain method handles cannot be created, solely because of the JVM limit on stacked arguments. + * For example, if a static JVM method accepts exactly 255 arguments, a method handle cannot be created for it. + * Attempts to create method handles with impossible method types lead to an {@link IllegalArgumentException}. + * In particular, a method handle’s type must not have an arity of the exact maximum 255. + * + * @see MethodType + * @see MethodHandles + * @author John Rose, JSR 292 EG + */ public abstract class MethodHandle { + // Android-changed: + // + // static { MethodHandleImpl.initStatics(); } + // + // LambdaForm and customizationCount are currently unused in our implementation + // and will be substituted with appropriate implementation / delegate classes. + // + // /*private*/ final LambdaForm form; + // form is not private so that invokers can easily fetch it + // /*non-public*/ byte customizationCount; + // customizationCount should be accessible from invokers + + + /** + * Internal marker interface which distinguishes (to the Java compiler) + * those methods which are <a href="MethodHandle.html#sigpoly">signature polymorphic</a>. + * + * @hide + */ + @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) + public @interface PolymorphicSignature { } + + /** + * The type of this method handle, this corresponds to the exact type of the method + * being invoked. + */ + private final MethodType type; + + /** + * The nominal type of this method handle, will be non-null if a method handle declares + * a different type from its "real" type, which is either the type of the method being invoked + * or the type of the emulated stackframe expected by an underyling adapter. + */ + private MethodType nominalType; + + /** + * The spread invoker associated with this type with zero trailing arguments. + * This is used to speed up invokeWithArguments. + */ + private MethodHandle cachedSpreadInvoker; + + /** + * The INVOKE* constants and SGET/SPUT and IGET/IPUT constants specify the behaviour of this + * method handle with respect to the ArtField* or the ArtMethod* that it operates on. These + * behaviours are equivalent to the dex bytecode behaviour on the respective method_id or + * field_id in the equivalent instruction. + * + * INVOKE_TRANSFORM is a special type of handle which doesn't encode any dex bytecode behaviour, + * instead it transforms the list of input arguments or performs other higher order operations + * before (optionally) delegating to another method handle. + * + * INVOKE_CALLSITE_TRANSFORM is a variation on INVOKE_TRANSFORM where the method type of + * a MethodHandle dynamically varies based on the callsite. This is used by + * the VarargsCollector implementation which places any number of trailing arguments + * into an array before invoking an arity method. The "any number of trailing arguments" means + * it would otherwise generate WrongMethodTypeExceptions as the callsite method type and + * VarargsCollector method type appear incompatible. + */ + + /** @hide */ public static final int INVOKE_VIRTUAL = 0; + /** @hide */ public static final int INVOKE_SUPER = 1; + /** @hide */ public static final int INVOKE_DIRECT = 2; + /** @hide */ public static final int INVOKE_STATIC = 3; + /** @hide */ public static final int INVOKE_INTERFACE = 4; + /** @hide */ public static final int INVOKE_TRANSFORM = 5; + /** @hide */ public static final int INVOKE_CALLSITE_TRANSFORM = 6; + /** @hide */ public static final int IGET = 7; + /** @hide */ public static final int IPUT = 8; + /** @hide */ public static final int SGET = 9; + /** @hide */ public static final int SPUT = 10; + + // The kind of this method handle (used by the runtime). This is one of the INVOKE_* + // constants or SGET/SPUT, IGET/IPUT. + /** @hide */ protected final int handleKind; + + // The ArtMethod* or ArtField* associated with this method handle (used by the runtime). + /** @hide */ protected final long artFieldOrMethod; + + /** @hide */ + protected MethodHandle(long artFieldOrMethod, int handleKind, MethodType type) { + this.artFieldOrMethod = artFieldOrMethod; + this.handleKind = handleKind; + this.type = type; + } + + /** + * Reports the type of this method handle. + * Every invocation of this method handle via {@code invokeExact} must exactly match this type. + * @return the method handle type + */ + public MethodType type() { + if (nominalType != null) { + return nominalType; + } + + return type; + } + + /** + * Invokes the method handle, allowing any caller type descriptor, but requiring an exact type match. + * The symbolic type descriptor at the call site of {@code invokeExact} must + * exactly match this method handle's {@link #type type}. + * No conversions are allowed on arguments or return values. + * <p> + * When this method is observed via the Core Reflection API, + * it will appear as a single native method, taking an object array and returning an object. + * If this native method is invoked directly via + * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI, + * or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}, + * it will throw an {@code UnsupportedOperationException}. + * @param args the signature-polymorphic parameter list, statically represented using varargs + * @return the signature-polymorphic result, statically represented using {@code Object} + * @throws WrongMethodTypeException if the target's type is not identical with the caller's symbolic type descriptor + * @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call + */ + public final native @PolymorphicSignature Object invokeExact(Object... args) throws Throwable; + + /** + * Invokes the method handle, allowing any caller type descriptor, + * and optionally performing conversions on arguments and return values. + * <p> + * If the call site's symbolic type descriptor exactly matches this method handle's {@link #type type}, + * the call proceeds as if by {@link #invokeExact invokeExact}. + * <p> + * Otherwise, the call proceeds as if this method handle were first + * adjusted by calling {@link #asType asType} to adjust this method handle + * to the required type, and then the call proceeds as if by + * {@link #invokeExact invokeExact} on the adjusted method handle. + * <p> + * There is no guarantee that the {@code asType} call is actually made. + * If the JVM can predict the results of making the call, it may perform + * adaptations directly on the caller's arguments, + * and call the target method handle according to its own exact type. + * <p> + * The resolved type descriptor at the call site of {@code invoke} must + * be a valid argument to the receivers {@code asType} method. + * In particular, the caller must specify the same argument arity + * as the callee's type, + * if the callee is not a {@linkplain #asVarargsCollector variable arity collector}. + * <p> + * When this method is observed via the Core Reflection API, + * it will appear as a single native method, taking an object array and returning an object. + * If this native method is invoked directly via + * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI, + * or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}, + * it will throw an {@code UnsupportedOperationException}. + * @param args the signature-polymorphic parameter list, statically represented using varargs + * @return the signature-polymorphic result, statically represented using {@code Object} + * @throws WrongMethodTypeException if the target's type cannot be adjusted to the caller's symbolic type descriptor + * @throws ClassCastException if the target's type can be adjusted to the caller, but a reference cast fails + * @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call + */ + public final native @PolymorphicSignature Object invoke(Object... args) throws Throwable; + + // Android-changed: Removed implementation details. + // + // /*non-public*/ final native @PolymorphicSignature Object invokeBasic(Object... args) + // /*non-public*/ static native @PolymorphicSignature Object linkToVirtual(Object... args) + // /*non-public*/ static native @PolymorphicSignature Object linkToStatic(Object... args) + // /*non-public*/ static native @PolymorphicSignature Object linkToSpecial(Object... args) + // /*non-public*/ static native @PolymorphicSignature Object linkToInterface(Object... args) + + /** + * Performs a variable arity invocation, passing the arguments in the given list + * to the method handle, as if via an inexact {@link #invoke invoke} from a call site + * which mentions only the type {@code Object}, and whose arity is the length + * of the argument list. + * <p> + * Specifically, execution proceeds as if by the following steps, + * although the methods are not guaranteed to be called if the JVM + * can predict their effects. + * <ul> + * <li>Determine the length of the argument array as {@code N}. + * For a null reference, {@code N=0}. </li> + * <li>Determine the general type {@code TN} of {@code N} arguments as + * as {@code TN=MethodType.genericMethodType(N)}.</li> + * <li>Force the original target method handle {@code MH0} to the + * required type, as {@code MH1 = MH0.asType(TN)}. </li> + * <li>Spread the array into {@code N} separate arguments {@code A0, ...}. </li> + * <li>Invoke the type-adjusted method handle on the unpacked arguments: + * MH1.invokeExact(A0, ...). </li> + * <li>Take the return value as an {@code Object} reference. </li> + * </ul> + * <p> + * Because of the action of the {@code asType} step, the following argument + * conversions are applied as necessary: + * <ul> + * <li>reference casting + * <li>unboxing + * <li>widening primitive conversions + * </ul> + * <p> + * The result returned by the call is boxed if it is a primitive, + * or forced to null if the return type is void. + * <p> + * This call is equivalent to the following code: + * <blockquote><pre>{@code + * MethodHandle invoker = MethodHandles.spreadInvoker(this.type(), 0); + * Object result = invoker.invokeExact(this, arguments); + * }</pre></blockquote> + * <p> + * Unlike the signature polymorphic methods {@code invokeExact} and {@code invoke}, + * {@code invokeWithArguments} can be accessed normally via the Core Reflection API and JNI. + * It can therefore be used as a bridge between native or reflective code and method handles. + * + * @param arguments the arguments to pass to the target + * @return the result returned by the target + * @throws ClassCastException if an argument cannot be converted by reference casting + * @throws WrongMethodTypeException if the target's type cannot be adjusted to take the given number of {@code Object} arguments + * @throws Throwable anything thrown by the target method invocation + * @see MethodHandles#spreadInvoker + */ + public Object invokeWithArguments(Object... arguments) throws Throwable { + MethodHandle invoker = null; + synchronized (this) { + if (cachedSpreadInvoker == null) { + cachedSpreadInvoker = MethodHandles.spreadInvoker(this.type(), 0); + } + + invoker = cachedSpreadInvoker; + } + + return invoker.invoke(this, arguments); + } + + /** + * Performs a variable arity invocation, passing the arguments in the given array + * to the method handle, as if via an inexact {@link #invoke invoke} from a call site + * which mentions only the type {@code Object}, and whose arity is the length + * of the argument array. + * <p> + * This method is also equivalent to the following code: + * <blockquote><pre>{@code + * invokeWithArguments(arguments.toArray() + * }</pre></blockquote> + * + * @param arguments the arguments to pass to the target + * @return the result returned by the target + * @throws NullPointerException if {@code arguments} is a null reference + * @throws ClassCastException if an argument cannot be converted by reference casting + * @throws WrongMethodTypeException if the target's type cannot be adjusted to take the given number of {@code Object} arguments + * @throws Throwable anything thrown by the target method invocation + */ + public Object invokeWithArguments(java.util.List<?> arguments) throws Throwable { + return invokeWithArguments(arguments.toArray()); + } + + /** + * Produces an adapter method handle which adapts the type of the + * current method handle to a new type. + * The resulting method handle is guaranteed to report a type + * which is equal to the desired new type. + * <p> + * If the original type and new type are equal, returns {@code this}. + * <p> + * The new method handle, when invoked, will perform the following + * steps: + * <ul> + * <li>Convert the incoming argument list to match the original + * method handle's argument list. + * <li>Invoke the original method handle on the converted argument list. + * <li>Convert any result returned by the original method handle + * to the return type of new method handle. + * </ul> + * <p> + * This method provides the crucial behavioral difference between + * {@link #invokeExact invokeExact} and plain, inexact {@link #invoke invoke}. + * The two methods + * perform the same steps when the caller's type descriptor exactly m atches + * the callee's, but when the types differ, plain {@link #invoke invoke} + * also calls {@code asType} (or some internal equivalent) in order + * to match up the caller's and callee's types. + * <p> + * If the current method is a variable arity method handle + * argument list conversion may involve the conversion and collection + * of several arguments into an array, as + * {@linkplain #asVarargsCollector described elsewhere}. + * In every other case, all conversions are applied <em>pairwise</em>, + * which means that each argument or return value is converted to + * exactly one argument or return value (or no return value). + * The applied conversions are defined by consulting the + * the corresponding component types of the old and new + * method handle types. + * <p> + * Let <em>T0</em> and <em>T1</em> be corresponding new and old parameter types, + * or old and new return types. Specifically, for some valid index {@code i}, let + * <em>T0</em>{@code =newType.parameterType(i)} and <em>T1</em>{@code =this.type().parameterType(i)}. + * Or else, going the other way for return values, let + * <em>T0</em>{@code =this.type().returnType()} and <em>T1</em>{@code =newType.returnType()}. + * If the types are the same, the new method handle makes no change + * to the corresponding argument or return value (if any). + * Otherwise, one of the following conversions is applied + * if possible: + * <ul> + * <li>If <em>T0</em> and <em>T1</em> are references, then a cast to <em>T1</em> is applied. + * (The types do not need to be related in any particular way. + * This is because a dynamic value of null can convert to any reference type.) + * <li>If <em>T0</em> and <em>T1</em> are primitives, then a Java method invocation + * conversion (JLS 5.3) is applied, if one exists. + * (Specifically, <em>T0</em> must convert to <em>T1</em> by a widening primitive conversion.) + * <li>If <em>T0</em> is a primitive and <em>T1</em> a reference, + * a Java casting conversion (JLS 5.5) is applied if one exists. + * (Specifically, the value is boxed from <em>T0</em> to its wrapper class, + * which is then widened as needed to <em>T1</em>.) + * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive, an unboxing + * conversion will be applied at runtime, possibly followed + * by a Java method invocation conversion (JLS 5.3) + * on the primitive value. (These are the primitive widening conversions.) + * <em>T0</em> must be a wrapper class or a supertype of one. + * (In the case where <em>T0</em> is Object, these are the conversions + * allowed by {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}.) + * The unboxing conversion must have a possibility of success, which means that + * if <em>T0</em> is not itself a wrapper class, there must exist at least one + * wrapper class <em>TW</em> which is a subtype of <em>T0</em> and whose unboxed + * primitive value can be widened to <em>T1</em>. + * <li>If the return type <em>T1</em> is marked as void, any returned value is discarded + * <li>If the return type <em>T0</em> is void and <em>T1</em> a reference, a null value is introduced. + * <li>If the return type <em>T0</em> is void and <em>T1</em> a primitive, + * a zero value is introduced. + * </ul> + * (<em>Note:</em> Both <em>T0</em> and <em>T1</em> may be regarded as static types, + * because neither corresponds specifically to the <em>dynamic type</em> of any + * actual argument or return value.) + * <p> + * The method handle conversion cannot be made if any one of the required + * pairwise conversions cannot be made. + * <p> + * At runtime, the conversions applied to reference arguments + * or return values may require additional runtime checks which can fail. + * An unboxing operation may fail because the original reference is null, + * causing a {@link java.lang.NullPointerException NullPointerException}. + * An unboxing operation or a reference cast may also fail on a reference + * to an object of the wrong type, + * causing a {@link java.lang.ClassCastException ClassCastException}. + * Although an unboxing operation may accept several kinds of wrappers, + * if none are available, a {@code ClassCastException} will be thrown. + * + * @param newType the expected type of the new method handle + * @return a method handle which delegates to {@code this} after performing + * any necessary argument conversions, and arranges for any + * necessary return value conversions + * @throws NullPointerException if {@code newType} is a null reference + * @throws WrongMethodTypeException if the conversion cannot be made + * @see MethodHandles#explicitCastArguments + */ + public MethodHandle asType(MethodType newType) { + // Fast path alternative to a heavyweight {@code asType} call. + // Return 'this' if the conversion will be a no-op. + if (newType == type) { + return this; + } + + if (!type.isConvertibleTo(newType)) { + throw new WrongMethodTypeException("cannot convert " + this + " to " + newType); + } + + MethodHandle mh = duplicate(); + mh.nominalType = newType; + return mh; + } + + /** + * Makes an <em>array-spreading</em> method handle, which accepts a trailing array argument + * and spreads its elements as positional arguments. + * The new method handle adapts, as its <i>target</i>, + * the current method handle. The type of the adapter will be + * the same as the type of the target, except that the final + * {@code arrayLength} parameters of the target's type are replaced + * by a single array parameter of type {@code arrayType}. + * <p> + * If the array element type differs from any of the corresponding + * argument types on the original target, + * the original target is adapted to take the array elements directly, + * as if by a call to {@link #asType asType}. + * <p> + * When called, the adapter replaces a trailing array argument + * by the array's elements, each as its own argument to the target. + * (The order of the arguments is preserved.) + * They are converted pairwise by casting and/or unboxing + * to the types of the trailing parameters of the target. + * Finally the target is called. + * What the target eventually returns is returned unchanged by the adapter. + * <p> + * Before calling the target, the adapter verifies that the array + * contains exactly enough elements to provide a correct argument count + * to the target method handle. + * (The array may also be null when zero elements are required.) + * <p> + * If, when the adapter is called, the supplied array argument does + * not have the correct number of elements, the adapter will throw + * an {@link IllegalArgumentException} instead of invoking the target. + * <p> + * Here are some simple examples of array-spreading method handles: + * <blockquote><pre>{@code +MethodHandle equals = publicLookup() + .findVirtual(String.class, "equals", methodType(boolean.class, Object.class)); +assert( (boolean) equals.invokeExact("me", (Object)"me")); +assert(!(boolean) equals.invokeExact("me", (Object)"thee")); +// spread both arguments from a 2-array: +MethodHandle eq2 = equals.asSpreader(Object[].class, 2); +assert( (boolean) eq2.invokeExact(new Object[]{ "me", "me" })); +assert(!(boolean) eq2.invokeExact(new Object[]{ "me", "thee" })); +// try to spread from anything but a 2-array: +for (int n = 0; n <= 10; n++) { + Object[] badArityArgs = (n == 2 ? null : new Object[n]); + try { assert((boolean) eq2.invokeExact(badArityArgs) && false); } + catch (IllegalArgumentException ex) { } // OK +} +// spread both arguments from a String array: +MethodHandle eq2s = equals.asSpreader(String[].class, 2); +assert( (boolean) eq2s.invokeExact(new String[]{ "me", "me" })); +assert(!(boolean) eq2s.invokeExact(new String[]{ "me", "thee" })); +// spread second arguments from a 1-array: +MethodHandle eq1 = equals.asSpreader(Object[].class, 1); +assert( (boolean) eq1.invokeExact("me", new Object[]{ "me" })); +assert(!(boolean) eq1.invokeExact("me", new Object[]{ "thee" })); +// spread no arguments from a 0-array or null: +MethodHandle eq0 = equals.asSpreader(Object[].class, 0); +assert( (boolean) eq0.invokeExact("me", (Object)"me", new Object[0])); +assert(!(boolean) eq0.invokeExact("me", (Object)"thee", (Object[])null)); +// asSpreader and asCollector are approximate inverses: +for (int n = 0; n <= 2; n++) { + for (Class<?> a : new Class<?>[]{Object[].class, String[].class, CharSequence[].class}) { + MethodHandle equals2 = equals.asSpreader(a, n).asCollector(a, n); + assert( (boolean) equals2.invokeWithArguments("me", "me")); + assert(!(boolean) equals2.invokeWithArguments("me", "thee")); + } +} +MethodHandle caToString = publicLookup() + .findStatic(Arrays.class, "toString", methodType(String.class, char[].class)); +assertEquals("[A, B, C]", (String) caToString.invokeExact("ABC".toCharArray())); +MethodHandle caString3 = caToString.asCollector(char[].class, 3); +assertEquals("[A, B, C]", (String) caString3.invokeExact('A', 'B', 'C')); +MethodHandle caToString2 = caString3.asSpreader(char[].class, 2); +assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray())); + * }</pre></blockquote> + * @param arrayType usually {@code Object[]}, the type of the array argument from which to extract the spread arguments + * @param arrayLength the number of arguments to spread from an incoming array argument + * @return a new method handle which spreads its final array argument, + * before calling the original method handle + * @throws NullPointerException if {@code arrayType} is a null reference + * @throws IllegalArgumentException if {@code arrayType} is not an array type, + * or if target does not have at least + * {@code arrayLength} parameter types, + * or if {@code arrayLength} is negative, + * or if the resulting method handle's type would have + * <a href="MethodHandle.html#maxarity">too many parameters</a> + * @throws WrongMethodTypeException if the implied {@code asType} call fails + * @see #asCollector + */ + public MethodHandle asSpreader(Class<?> arrayType, int arrayLength) { + MethodType postSpreadType = asSpreaderChecks(arrayType, arrayLength); + + final int targetParamCount = postSpreadType.parameterCount(); + MethodType dropArrayArgs = postSpreadType.dropParameterTypes( + (targetParamCount - arrayLength), targetParamCount); + MethodType adapterType = dropArrayArgs.appendParameterTypes(arrayType); + + return new Transformers.Spreader(this, adapterType, arrayLength); + } + + /** + * See if {@code asSpreader} can be validly called with the given arguments. + * Return the type of the method handle call after spreading but before conversions. + */ + private MethodType asSpreaderChecks(Class<?> arrayType, int arrayLength) { + spreadArrayChecks(arrayType, arrayLength); + int nargs = type().parameterCount(); + if (nargs < arrayLength || arrayLength < 0) + throw newIllegalArgumentException("bad spread array length"); + Class<?> arrayElement = arrayType.getComponentType(); + MethodType mtype = type(); + boolean match = true, fail = false; + for (int i = nargs - arrayLength; i < nargs; i++) { + Class<?> ptype = mtype.parameterType(i); + if (ptype != arrayElement) { + match = false; + if (!MethodType.canConvert(arrayElement, ptype)) { + fail = true; + break; + } + } + } + if (match) return mtype; + MethodType needType = mtype.asSpreaderType(arrayType, arrayLength); + if (!fail) return needType; + // elicit an error: + this.asType(needType); + throw newInternalError("should not return", null); + } + + private void spreadArrayChecks(Class<?> arrayType, int arrayLength) { + Class<?> arrayElement = arrayType.getComponentType(); + if (arrayElement == null) + throw newIllegalArgumentException("not an array type", arrayType); + if ((arrayLength & 0x7F) != arrayLength) { + if ((arrayLength & 0xFF) != arrayLength) + throw newIllegalArgumentException("array length is not legal", arrayLength); + assert(arrayLength >= 128); + if (arrayElement == long.class || + arrayElement == double.class) + throw newIllegalArgumentException("array length is not legal for long[] or double[]", arrayLength); + } + } + + /** + * Makes an <em>array-collecting</em> method handle, which accepts a given number of trailing + * positional arguments and collects them into an array argument. + * The new method handle adapts, as its <i>target</i>, + * the current method handle. The type of the adapter will be + * the same as the type of the target, except that a single trailing + * parameter (usually of type {@code arrayType}) is replaced by + * {@code arrayLength} parameters whose type is element type of {@code arrayType}. + * <p> + * If the array type differs from the final argument type on the original target, + * the original target is adapted to take the array type directly, + * as if by a call to {@link #asType asType}. + * <p> + * When called, the adapter replaces its trailing {@code arrayLength} + * arguments by a single new array of type {@code arrayType}, whose elements + * comprise (in order) the replaced arguments. + * Finally the target is called. + * What the target eventually returns is returned unchanged by the adapter. + * <p> + * (The array may also be a shared constant when {@code arrayLength} is zero.) + * <p> + * (<em>Note:</em> The {@code arrayType} is often identical to the last + * parameter type of the original target. + * It is an explicit argument for symmetry with {@code asSpreader}, and also + * to allow the target to use a simple {@code Object} as its last parameter type.) + * <p> + * In order to create a collecting adapter which is not restricted to a particular + * number of collected arguments, use {@link #asVarargsCollector asVarargsCollector} instead. + * <p> + * Here are some examples of array-collecting method handles: + * <blockquote><pre>{@code +MethodHandle deepToString = publicLookup() + .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class)); +assertEquals("[won]", (String) deepToString.invokeExact(new Object[]{"won"})); +MethodHandle ts1 = deepToString.asCollector(Object[].class, 1); +assertEquals(methodType(String.class, Object.class), ts1.type()); +//assertEquals("[won]", (String) ts1.invokeExact( new Object[]{"won"})); //FAIL +assertEquals("[[won]]", (String) ts1.invokeExact((Object) new Object[]{"won"})); +// arrayType can be a subtype of Object[] +MethodHandle ts2 = deepToString.asCollector(String[].class, 2); +assertEquals(methodType(String.class, String.class, String.class), ts2.type()); +assertEquals("[two, too]", (String) ts2.invokeExact("two", "too")); +MethodHandle ts0 = deepToString.asCollector(Object[].class, 0); +assertEquals("[]", (String) ts0.invokeExact()); +// collectors can be nested, Lisp-style +MethodHandle ts22 = deepToString.asCollector(Object[].class, 3).asCollector(String[].class, 2); +assertEquals("[A, B, [C, D]]", ((String) ts22.invokeExact((Object)'A', (Object)"B", "C", "D"))); +// arrayType can be any primitive array type +MethodHandle bytesToString = publicLookup() + .findStatic(Arrays.class, "toString", methodType(String.class, byte[].class)) + .asCollector(byte[].class, 3); +assertEquals("[1, 2, 3]", (String) bytesToString.invokeExact((byte)1, (byte)2, (byte)3)); +MethodHandle longsToString = publicLookup() + .findStatic(Arrays.class, "toString", methodType(String.class, long[].class)) + .asCollector(long[].class, 1); +assertEquals("[123]", (String) longsToString.invokeExact((long)123)); + * }</pre></blockquote> + * @param arrayType often {@code Object[]}, the type of the array argument which will collect the arguments + * @param arrayLength the number of arguments to collect into a new array argument + * @return a new method handle which collects some trailing argument + * into an array, before calling the original method handle + * @throws NullPointerException if {@code arrayType} is a null reference + * @throws IllegalArgumentException if {@code arrayType} is not an array type + * or {@code arrayType} is not assignable to this method handle's trailing parameter type, + * or {@code arrayLength} is not a legal array size, + * or the resulting method handle's type would have + * <a href="MethodHandle.html#maxarity">too many parameters</a> + * @throws WrongMethodTypeException if the implied {@code asType} call fails + * @see #asSpreader + * @see #asVarargsCollector + */ + public MethodHandle asCollector(Class<?> arrayType, int arrayLength) { + asCollectorChecks(arrayType, arrayLength); + + return new Transformers.Collector(this, arrayType, arrayLength); + } + + /** + * See if {@code asCollector} can be validly called with the given arguments. + * Return false if the last parameter is not an exact match to arrayType. + */ + /*non-public*/ boolean asCollectorChecks(Class<?> arrayType, int arrayLength) { + spreadArrayChecks(arrayType, arrayLength); + int nargs = type().parameterCount(); + if (nargs != 0) { + Class<?> lastParam = type().parameterType(nargs-1); + if (lastParam == arrayType) return true; + if (lastParam.isAssignableFrom(arrayType)) return false; + } + throw newIllegalArgumentException("array type not assignable to trailing argument", this, arrayType); + } + + /** + * Makes a <em>variable arity</em> adapter which is able to accept + * any number of trailing positional arguments and collect them + * into an array argument. + * <p> + * The type and behavior of the adapter will be the same as + * the type and behavior of the target, except that certain + * {@code invoke} and {@code asType} requests can lead to + * trailing positional arguments being collected into target's + * trailing parameter. + * Also, the last parameter type of the adapter will be + * {@code arrayType}, even if the target has a different + * last parameter type. + * <p> + * This transformation may return {@code this} if the method handle is + * already of variable arity and its trailing parameter type + * is identical to {@code arrayType}. + * <p> + * When called with {@link #invokeExact invokeExact}, the adapter invokes + * the target with no argument changes. + * (<em>Note:</em> This behavior is different from a + * {@linkplain #asCollector fixed arity collector}, + * since it accepts a whole array of indeterminate length, + * rather than a fixed number of arguments.) + * <p> + * When called with plain, inexact {@link #invoke invoke}, if the caller + * type is the same as the adapter, the adapter invokes the target as with + * {@code invokeExact}. + * (This is the normal behavior for {@code invoke} when types match.) + * <p> + * Otherwise, if the caller and adapter arity are the same, and the + * trailing parameter type of the caller is a reference type identical to + * or assignable to the trailing parameter type of the adapter, + * the arguments and return values are converted pairwise, + * as if by {@link #asType asType} on a fixed arity + * method handle. + * <p> + * Otherwise, the arities differ, or the adapter's trailing parameter + * type is not assignable from the corresponding caller type. + * In this case, the adapter replaces all trailing arguments from + * the original trailing argument position onward, by + * a new array of type {@code arrayType}, whose elements + * comprise (in order) the replaced arguments. + * <p> + * The caller type must provides as least enough arguments, + * and of the correct type, to satisfy the target's requirement for + * positional arguments before the trailing array argument. + * Thus, the caller must supply, at a minimum, {@code N-1} arguments, + * where {@code N} is the arity of the target. + * Also, there must exist conversions from the incoming arguments + * to the target's arguments. + * As with other uses of plain {@code invoke}, if these basic + * requirements are not fulfilled, a {@code WrongMethodTypeException} + * may be thrown. + * <p> + * In all cases, what the target eventually returns is returned unchanged by the adapter. + * <p> + * In the final case, it is exactly as if the target method handle were + * temporarily adapted with a {@linkplain #asCollector fixed arity collector} + * to the arity required by the caller type. + * (As with {@code asCollector}, if the array length is zero, + * a shared constant may be used instead of a new array. + * If the implied call to {@code asCollector} would throw + * an {@code IllegalArgumentException} or {@code WrongMethodTypeException}, + * the call to the variable arity adapter must throw + * {@code WrongMethodTypeException}.) + * <p> + * The behavior of {@link #asType asType} is also specialized for + * variable arity adapters, to maintain the invariant that + * plain, inexact {@code invoke} is always equivalent to an {@code asType} + * call to adjust the target type, followed by {@code invokeExact}. + * Therefore, a variable arity adapter responds + * to an {@code asType} request by building a fixed arity collector, + * if and only if the adapter and requested type differ either + * in arity or trailing argument type. + * The resulting fixed arity collector has its type further adjusted + * (if necessary) to the requested type by pairwise conversion, + * as if by another application of {@code asType}. + * <p> + * When a method handle is obtained by executing an {@code ldc} instruction + * of a {@code CONSTANT_MethodHandle} constant, and the target method is marked + * as a variable arity method (with the modifier bit {@code 0x0080}), + * the method handle will accept multiple arities, as if the method handle + * constant were created by means of a call to {@code asVarargsCollector}. + * <p> + * In order to create a collecting adapter which collects a predetermined + * number of arguments, and whose type reflects this predetermined number, + * use {@link #asCollector asCollector} instead. + * <p> + * No method handle transformations produce new method handles with + * variable arity, unless they are documented as doing so. + * Therefore, besides {@code asVarargsCollector}, + * all methods in {@code MethodHandle} and {@code MethodHandles} + * will return a method handle with fixed arity, + * except in the cases where they are specified to return their original + * operand (e.g., {@code asType} of the method handle's own type). + * <p> + * Calling {@code asVarargsCollector} on a method handle which is already + * of variable arity will produce a method handle with the same type and behavior. + * It may (or may not) return the original variable arity method handle. + * <p> + * Here is an example, of a list-making variable arity method handle: + * <blockquote><pre>{@code +MethodHandle deepToString = publicLookup() + .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class)); +MethodHandle ts1 = deepToString.asVarargsCollector(Object[].class); +assertEquals("[won]", (String) ts1.invokeExact( new Object[]{"won"})); +assertEquals("[won]", (String) ts1.invoke( new Object[]{"won"})); +assertEquals("[won]", (String) ts1.invoke( "won" )); +assertEquals("[[won]]", (String) ts1.invoke((Object) new Object[]{"won"})); +// findStatic of Arrays.asList(...) produces a variable arity method handle: +MethodHandle asList = publicLookup() + .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class)); +assertEquals(methodType(List.class, Object[].class), asList.type()); +assert(asList.isVarargsCollector()); +assertEquals("[]", asList.invoke().toString()); +assertEquals("[1]", asList.invoke(1).toString()); +assertEquals("[two, too]", asList.invoke("two", "too").toString()); +String[] argv = { "three", "thee", "tee" }; +assertEquals("[three, thee, tee]", asList.invoke(argv).toString()); +assertEquals("[three, thee, tee]", asList.invoke((Object[])argv).toString()); +List ls = (List) asList.invoke((Object)argv); +assertEquals(1, ls.size()); +assertEquals("[three, thee, tee]", Arrays.toString((Object[])ls.get(0))); + * }</pre></blockquote> + * <p style="font-size:smaller;"> + * <em>Discussion:</em> + * These rules are designed as a dynamically-typed variation + * of the Java rules for variable arity methods. + * In both cases, callers to a variable arity method or method handle + * can either pass zero or more positional arguments, or else pass + * pre-collected arrays of any length. Users should be aware of the + * special role of the final argument, and of the effect of a + * type match on that final argument, which determines whether + * or not a single trailing argument is interpreted as a whole + * array or a single element of an array to be collected. + * Note that the dynamic type of the trailing argument has no + * effect on this decision, only a comparison between the symbolic + * type descriptor of the call site and the type descriptor of the method handle.) + * + * @param arrayType often {@code Object[]}, the type of the array argument which will collect the arguments + * @return a new method handle which can collect any number of trailing arguments + * into an array, before calling the original method handle + * @throws NullPointerException if {@code arrayType} is a null reference + * @throws IllegalArgumentException if {@code arrayType} is not an array type + * or {@code arrayType} is not assignable to this method handle's trailing parameter type + * @see #asCollector + * @see #isVarargsCollector + * @see #asFixedArity + */ + public MethodHandle asVarargsCollector(Class<?> arrayType) { + arrayType.getClass(); // explicit NPE + boolean lastMatch = asCollectorChecks(arrayType, 0); + if (isVarargsCollector() && lastMatch) + return this; - public MethodType type() { return null; } + return new Transformers.VarargsCollector(this); + } - public final Object invokeExact(Object... args) throws Throwable { return null; } + /** + * Determines if this method handle + * supports {@linkplain #asVarargsCollector variable arity} calls. + * Such method handles arise from the following sources: + * <ul> + * <li>a call to {@linkplain #asVarargsCollector asVarargsCollector} + * <li>a call to a {@linkplain java.lang.invoke.MethodHandles.Lookup lookup method} + * which resolves to a variable arity Java method or constructor + * <li>an {@code ldc} instruction of a {@code CONSTANT_MethodHandle} + * which resolves to a variable arity Java method or constructor + * </ul> + * @return true if this method handle accepts more than one arity of plain, inexact {@code invoke} calls + * @see #asVarargsCollector + * @see #asFixedArity + */ + public boolean isVarargsCollector() { + return false; + } - public final Object invoke(Object... args) throws Throwable { return null; } + /** + * Makes a <em>fixed arity</em> method handle which is otherwise + * equivalent to the current method handle. + * <p> + * If the current method handle is not of + * {@linkplain #asVarargsCollector variable arity}, + * the current method handle is returned. + * This is true even if the current method handle + * could not be a valid input to {@code asVarargsCollector}. + * <p> + * Otherwise, the resulting fixed-arity method handle has the same + * type and behavior of the current method handle, + * except that {@link #isVarargsCollector isVarargsCollector} + * will be false. + * The fixed-arity method handle may (or may not) be the + * a previous argument to {@code asVarargsCollector}. + * <p> + * Here is an example, of a list-making variable arity method handle: + * <blockquote><pre>{@code +MethodHandle asListVar = publicLookup() + .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class)) + .asVarargsCollector(Object[].class); +MethodHandle asListFix = asListVar.asFixedArity(); +assertEquals("[1]", asListVar.invoke(1).toString()); +Exception caught = null; +try { asListFix.invoke((Object)1); } +catch (Exception ex) { caught = ex; } +assert(caught instanceof ClassCastException); +assertEquals("[two, too]", asListVar.invoke("two", "too").toString()); +try { asListFix.invoke("two", "too"); } +catch (Exception ex) { caught = ex; } +assert(caught instanceof WrongMethodTypeException); +Object[] argv = { "three", "thee", "tee" }; +assertEquals("[three, thee, tee]", asListVar.invoke(argv).toString()); +assertEquals("[three, thee, tee]", asListFix.invoke(argv).toString()); +assertEquals(1, ((List) asListVar.invoke((Object)argv)).size()); +assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString()); + * }</pre></blockquote> + * + * @return a new method handle which accepts only a fixed number of arguments + * @see #asVarargsCollector + * @see #isVarargsCollector + */ + public MethodHandle asFixedArity() { + // Android-changed: implementation specific. + MethodHandle mh = this; + if (mh.isVarargsCollector()) { + mh = ((Transformers.VarargsCollector) mh).asFixedArity(); + } + assert(!mh.isVarargsCollector()); + return mh; + } - public Object invokeWithArguments(Object... arguments) throws Throwable { return null; } + /** + * Binds a value {@code x} to the first argument of a method handle, without invoking it. + * The new method handle adapts, as its <i>target</i>, + * the current method handle by binding it to the given argument. + * The type of the bound handle will be + * the same as the type of the target, except that a single leading + * reference parameter will be omitted. + * <p> + * When called, the bound handle inserts the given value {@code x} + * as a new leading argument to the target. The other arguments are + * also passed unchanged. + * What the target eventually returns is returned unchanged by the bound handle. + * <p> + * The reference {@code x} must be convertible to the first parameter + * type of the target. + * <p> + * (<em>Note:</em> Because method handles are immutable, the target method handle + * retains its original type and behavior.) + * @param x the value to bind to the first argument of the target + * @return a new method handle which prepends the given value to the incoming + * argument list, before calling the original method handle + * @throws IllegalArgumentException if the target does not have a + * leading parameter type that is a reference type + * @throws ClassCastException if {@code x} cannot be converted + * to the leading parameter type of the target + * @see MethodHandles#insertArguments + */ + public MethodHandle bindTo(Object x) { + x = type.leadingReferenceParameter().cast(x); // throw CCE if needed - public Object invokeWithArguments(java.util.List<?> arguments) throws Throwable { return null; } + return new Transformers.BindTo(this, x); + } - public MethodHandle asType(MethodType newType) { return null; } + /** + * Returns a string representation of the method handle, + * starting with the string {@code "MethodHandle"} and + * ending with the string representation of the method handle's type. + * In other words, this method returns a string equal to the value of: + * <blockquote><pre>{@code + * "MethodHandle" + type().toString() + * }</pre></blockquote> + * <p> + * (<em>Note:</em> Future releases of this API may add further information + * to the string representation. + * Therefore, the present syntax should not be parsed by applications.) + * + * @return a string representation of the method handle + */ + @Override + public String toString() { + // Android-changed: Removed debugging support. + return "MethodHandle"+type; + } - public MethodHandle asCollector(Class<?> arrayType, int arrayLength) { return null; } + /** @hide */ + public int getHandleKind() { + return handleKind; + } - public MethodHandle asVarargsCollector(Class<?> arrayType) { return null; } + /** @hide */ + protected void transform(EmulatedStackFrame arguments) throws Throwable { + throw new AssertionError("MethodHandle.transform should never be called."); + } - public boolean isVarargsCollector() { return false; } + /** + * Creates a copy of this method handle, copying all relevant data. + * + * @hide + */ + protected MethodHandle duplicate() { + try { + return (MethodHandle) this.clone(); + } catch (CloneNotSupportedException cnse) { + throw new AssertionError("Subclass of Transformer is not cloneable"); + } + } - public MethodHandle asFixedArity() { return null; } - public MethodHandle bindTo(Object x) { return null; } + /** + * This is the entry point for all transform calls, and dispatches to the protected + * transform method. This layer of indirection exists purely for convenience, because + * we can invoke-direct on a fixed ArtMethod for all transform variants. + * + * NOTE: If this extra layer of indirection proves to be a problem, we can get rid + * of this layer of indirection at the cost of some additional ugliness. + */ + private void transformInternal(EmulatedStackFrame arguments) throws Throwable { + transform(arguments); + } + // Android-changed: Removed implementation details : + // + // String standardString(); + // String debugString(); + // + //// Implementation methods. + //// Sub-classes can override these default implementations. + //// All these methods assume arguments are already validated. + // + // Other transforms to do: convert, explicitCast, permute, drop, filter, fold, GWT, catch + // + // BoundMethodHandle bindArgumentL(int pos, Object value); + // /*non-public*/ MethodHandle setVarargs(MemberName member); + // /*non-public*/ MethodHandle viewAsType(MethodType newType, boolean strict); + // /*non-public*/ boolean viewAsTypeChecks(MethodType newType, boolean strict); + // + // Decoding + // + // /*non-public*/ LambdaForm internalForm(); + // /*non-public*/ MemberName internalMemberName(); + // /*non-public*/ Class<?> internalCallerClass(); + // /*non-public*/ MethodHandleImpl.Intrinsic intrinsicName(); + // /*non-public*/ MethodHandle withInternalMemberName(MemberName member, boolean isInvokeSpecial); + // /*non-public*/ boolean isInvokeSpecial(); + // /*non-public*/ Object internalValues(); + // /*non-public*/ Object internalProperties(); + // + //// Method handle implementation methods. + //// Sub-classes can override these default implementations. + //// All these methods assume arguments are already validated. + // + // /*non-public*/ abstract MethodHandle copyWith(MethodType mt, LambdaForm lf); + // abstract BoundMethodHandle rebind(); + // /*non-public*/ void updateForm(LambdaForm newForm); + // /*non-public*/ void customize(); + // private static final long FORM_OFFSET; } diff --git a/java/lang/invoke/MethodHandles.java b/java/lang/invoke/MethodHandles.java index f27ad988..88ce6e08 100644 --- a/java/lang/invoke/MethodHandles.java +++ b/java/lang/invoke/MethodHandles.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,129 +25,3410 @@ package java.lang.invoke; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Member; -import java.lang.reflect.Method; +import java.lang.reflect.*; +import java.nio.ByteOrder; import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.NoSuchElementException; +import dalvik.system.VMStack; +import sun.invoke.util.VerifyAccess; +import sun.invoke.util.Wrapper; +import static java.lang.invoke.MethodHandleStatics.*; + +/** + * This class consists exclusively of static methods that operate on or return + * method handles. They fall into several categories: + * <ul> + * <li>Lookup methods which help create method handles for methods and fields. + * <li>Combinator methods, which combine or transform pre-existing method handles into new ones. + * <li>Other factory methods to create method handles that emulate other common JVM operations or control flow patterns. + * </ul> + * <p> + * @author John Rose, JSR 292 EG + * @since 1.7 + */ public class MethodHandles { - public static Lookup lookup() { return null; } + private MethodHandles() { } // do not instantiate + + // BEGIN Android-added: unsupported() helper function. + // TODO(b/65872996): Remove when complete. + private static void unsupported(String msg) throws UnsupportedOperationException { + throw new UnsupportedOperationException(msg); + } + // END Android-added: unsupported() helper function. + + // Android-changed: We do not use MemberName / MethodHandleImpl. + // + // private static final MemberName.Factory IMPL_NAMES = MemberName.getFactory(); + // static { MethodHandleImpl.initStatics(); } + // See IMPL_LOOKUP below. - public static Lookup publicLookup() { return null; } + //// Method handle creation from ordinary methods. + /** + * Returns a {@link Lookup lookup object} with + * full capabilities to emulate all supported bytecode behaviors of the caller. + * These capabilities include <a href="MethodHandles.Lookup.html#privacc">private access</a> to the caller. + * Factory methods on the lookup object can create + * <a href="MethodHandleInfo.html#directmh">direct method handles</a> + * for any member that the caller has access to via bytecodes, + * including protected and private fields and methods. + * This lookup object is a <em>capability</em> which may be delegated to trusted agents. + * Do not store it in place where untrusted code can access it. + * <p> + * This method is caller sensitive, which means that it may return different + * values to different callers. + * <p> + * For any given caller class {@code C}, the lookup object returned by this call + * has equivalent capabilities to any lookup object + * supplied by the JVM to the bootstrap method of an + * <a href="package-summary.html#indyinsn">invokedynamic instruction</a> + * executing in the same caller class {@code C}. + * @return a lookup object for the caller of this method, with private access + */ + // Android-changed: Remove caller sensitive. + // @CallerSensitive + public static Lookup lookup() { + // Android-changed: Do not use Reflection.getCallerClass(). + return new Lookup(VMStack.getStackClass1()); + } + + /** + * Returns a {@link Lookup lookup object} which is trusted minimally. + * It can only be used to create method handles to + * publicly accessible fields and methods. + * <p> + * As a matter of pure convention, the {@linkplain Lookup#lookupClass lookup class} + * of this lookup object will be {@link java.lang.Object}. + * + * <p style="font-size:smaller;"> + * <em>Discussion:</em> + * The lookup class can be changed to any other class {@code C} using an expression of the form + * {@link Lookup#in publicLookup().in(C.class)}. + * Since all classes have equal access to public names, + * such a change would confer no new access rights. + * A public lookup object is always subject to + * <a href="MethodHandles.Lookup.html#secmgr">security manager checks</a>. + * Also, it cannot access + * <a href="MethodHandles.Lookup.html#callsens">caller sensitive methods</a>. + * @return a lookup object which is trusted minimally + */ + public static Lookup publicLookup() { + return Lookup.PUBLIC_LOOKUP; + } + + /** + * Performs an unchecked "crack" of a + * <a href="MethodHandleInfo.html#directmh">direct method handle</a>. + * The result is as if the user had obtained a lookup object capable enough + * to crack the target method handle, called + * {@link java.lang.invoke.MethodHandles.Lookup#revealDirect Lookup.revealDirect} + * on the target to obtain its symbolic reference, and then called + * {@link java.lang.invoke.MethodHandleInfo#reflectAs MethodHandleInfo.reflectAs} + * to resolve the symbolic reference to a member. + * <p> + * If there is a security manager, its {@code checkPermission} method + * is called with a {@code ReflectPermission("suppressAccessChecks")} permission. + * @param <T> the desired type of the result, either {@link Member} or a subtype + * @param target a direct method handle to crack into symbolic reference components + * @param expected a class object representing the desired result type {@code T} + * @return a reference to the method, constructor, or field object + * @exception SecurityException if the caller is not privileged to call {@code setAccessible} + * @exception NullPointerException if either argument is {@code null} + * @exception IllegalArgumentException if the target is not a direct method handle + * @exception ClassCastException if the member is not of the expected type + * @since 1.8 + */ public static <T extends Member> T - reflectAs(Class<T> expected, MethodHandle target) { return null; } + reflectAs(Class<T> expected, MethodHandle target) { + MethodHandleImpl directTarget = getMethodHandleImpl(target); + // Given that this is specified to be an "unchecked" crack, we can directly allocate + // a member from the underlying ArtField / Method and bypass all associated access checks. + return expected.cast(directTarget.getMemberInternal()); + } + /** + * A <em>lookup object</em> is a factory for creating method handles, + * when the creation requires access checking. + * Method handles do not perform + * access checks when they are called, but rather when they are created. + * Therefore, method handle access + * restrictions must be enforced when a method handle is created. + * The caller class against which those restrictions are enforced + * is known as the {@linkplain #lookupClass lookup class}. + * <p> + * A lookup class which needs to create method handles will call + * {@link #lookup MethodHandles.lookup} to create a factory for itself. + * When the {@code Lookup} factory object is created, the identity of the lookup class is + * determined, and securely stored in the {@code Lookup} object. + * The lookup class (or its delegates) may then use factory methods + * on the {@code Lookup} object to create method handles for access-checked members. + * This includes all methods, constructors, and fields which are allowed to the lookup class, + * even private ones. + * + * <h1><a name="lookups"></a>Lookup Factory Methods</h1> + * The factory methods on a {@code Lookup} object correspond to all major + * use cases for methods, constructors, and fields. + * Each method handle created by a factory method is the functional + * equivalent of a particular <em>bytecode behavior</em>. + * (Bytecode behaviors are described in section 5.4.3.5 of the Java Virtual Machine Specification.) + * Here is a summary of the correspondence between these factory methods and + * the behavior the resulting method handles: + * <table border=1 cellpadding=5 summary="lookup method behaviors"> + * <tr> + * <th><a name="equiv"></a>lookup expression</th> + * <th>member</th> + * <th>bytecode behavior</th> + * </tr> + * <tr> + * <td>{@link java.lang.invoke.MethodHandles.Lookup#findGetter lookup.findGetter(C.class,"f",FT.class)}</td> + * <td>{@code FT f;}</td><td>{@code (T) this.f;}</td> + * </tr> + * <tr> + * <td>{@link java.lang.invoke.MethodHandles.Lookup#findStaticGetter lookup.findStaticGetter(C.class,"f",FT.class)}</td> + * <td>{@code static}<br>{@code FT f;}</td><td>{@code (T) C.f;}</td> + * </tr> + * <tr> + * <td>{@link java.lang.invoke.MethodHandles.Lookup#findSetter lookup.findSetter(C.class,"f",FT.class)}</td> + * <td>{@code FT f;}</td><td>{@code this.f = x;}</td> + * </tr> + * <tr> + * <td>{@link java.lang.invoke.MethodHandles.Lookup#findStaticSetter lookup.findStaticSetter(C.class,"f",FT.class)}</td> + * <td>{@code static}<br>{@code FT f;}</td><td>{@code C.f = arg;}</td> + * </tr> + * <tr> + * <td>{@link java.lang.invoke.MethodHandles.Lookup#findVirtual lookup.findVirtual(C.class,"m",MT)}</td> + * <td>{@code T m(A*);}</td><td>{@code (T) this.m(arg*);}</td> + * </tr> + * <tr> + * <td>{@link java.lang.invoke.MethodHandles.Lookup#findStatic lookup.findStatic(C.class,"m",MT)}</td> + * <td>{@code static}<br>{@code T m(A*);}</td><td>{@code (T) C.m(arg*);}</td> + * </tr> + * <tr> + * <td>{@link java.lang.invoke.MethodHandles.Lookup#findSpecial lookup.findSpecial(C.class,"m",MT,this.class)}</td> + * <td>{@code T m(A*);}</td><td>{@code (T) super.m(arg*);}</td> + * </tr> + * <tr> + * <td>{@link java.lang.invoke.MethodHandles.Lookup#findConstructor lookup.findConstructor(C.class,MT)}</td> + * <td>{@code C(A*);}</td><td>{@code new C(arg*);}</td> + * </tr> + * <tr> + * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectGetter lookup.unreflectGetter(aField)}</td> + * <td>({@code static})?<br>{@code FT f;}</td><td>{@code (FT) aField.get(thisOrNull);}</td> + * </tr> + * <tr> + * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectSetter lookup.unreflectSetter(aField)}</td> + * <td>({@code static})?<br>{@code FT f;}</td><td>{@code aField.set(thisOrNull, arg);}</td> + * </tr> + * <tr> + * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflect lookup.unreflect(aMethod)}</td> + * <td>({@code static})?<br>{@code T m(A*);}</td><td>{@code (T) aMethod.invoke(thisOrNull, arg*);}</td> + * </tr> + * <tr> + * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectConstructor lookup.unreflectConstructor(aConstructor)}</td> + * <td>{@code C(A*);}</td><td>{@code (C) aConstructor.newInstance(arg*);}</td> + * </tr> + * <tr> + * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflect lookup.unreflect(aMethod)}</td> + * <td>({@code static})?<br>{@code T m(A*);}</td><td>{@code (T) aMethod.invoke(thisOrNull, arg*);}</td> + * </tr> + * </table> + * + * Here, the type {@code C} is the class or interface being searched for a member, + * documented as a parameter named {@code refc} in the lookup methods. + * The method type {@code MT} is composed from the return type {@code T} + * and the sequence of argument types {@code A*}. + * The constructor also has a sequence of argument types {@code A*} and + * is deemed to return the newly-created object of type {@code C}. + * Both {@code MT} and the field type {@code FT} are documented as a parameter named {@code type}. + * The formal parameter {@code this} stands for the self-reference of type {@code C}; + * if it is present, it is always the leading argument to the method handle invocation. + * (In the case of some {@code protected} members, {@code this} may be + * restricted in type to the lookup class; see below.) + * The name {@code arg} stands for all the other method handle arguments. + * In the code examples for the Core Reflection API, the name {@code thisOrNull} + * stands for a null reference if the accessed method or field is static, + * and {@code this} otherwise. + * The names {@code aMethod}, {@code aField}, and {@code aConstructor} stand + * for reflective objects corresponding to the given members. + * <p> + * In cases where the given member is of variable arity (i.e., a method or constructor) + * the returned method handle will also be of {@linkplain MethodHandle#asVarargsCollector variable arity}. + * In all other cases, the returned method handle will be of fixed arity. + * <p style="font-size:smaller;"> + * <em>Discussion:</em> + * The equivalence between looked-up method handles and underlying + * class members and bytecode behaviors + * can break down in a few ways: + * <ul style="font-size:smaller;"> + * <li>If {@code C} is not symbolically accessible from the lookup class's loader, + * the lookup can still succeed, even when there is no equivalent + * Java expression or bytecoded constant. + * <li>Likewise, if {@code T} or {@code MT} + * is not symbolically accessible from the lookup class's loader, + * the lookup can still succeed. + * For example, lookups for {@code MethodHandle.invokeExact} and + * {@code MethodHandle.invoke} will always succeed, regardless of requested type. + * <li>If there is a security manager installed, it can forbid the lookup + * on various grounds (<a href="MethodHandles.Lookup.html#secmgr">see below</a>). + * By contrast, the {@code ldc} instruction on a {@code CONSTANT_MethodHandle} + * constant is not subject to security manager checks. + * <li>If the looked-up method has a + * <a href="MethodHandle.html#maxarity">very large arity</a>, + * the method handle creation may fail, due to the method handle + * type having too many parameters. + * </ul> + * + * <h1><a name="access"></a>Access checking</h1> + * Access checks are applied in the factory methods of {@code Lookup}, + * when a method handle is created. + * This is a key difference from the Core Reflection API, since + * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke} + * performs access checking against every caller, on every call. + * <p> + * All access checks start from a {@code Lookup} object, which + * compares its recorded lookup class against all requests to + * create method handles. + * A single {@code Lookup} object can be used to create any number + * of access-checked method handles, all checked against a single + * lookup class. + * <p> + * A {@code Lookup} object can be shared with other trusted code, + * such as a metaobject protocol. + * A shared {@code Lookup} object delegates the capability + * to create method handles on private members of the lookup class. + * Even if privileged code uses the {@code Lookup} object, + * the access checking is confined to the privileges of the + * original lookup class. + * <p> + * A lookup can fail, because + * the containing class is not accessible to the lookup class, or + * because the desired class member is missing, or because the + * desired class member is not accessible to the lookup class, or + * because the lookup object is not trusted enough to access the member. + * In any of these cases, a {@code ReflectiveOperationException} will be + * thrown from the attempted lookup. The exact class will be one of + * the following: + * <ul> + * <li>NoSuchMethodException — if a method is requested but does not exist + * <li>NoSuchFieldException — if a field is requested but does not exist + * <li>IllegalAccessException — if the member exists but an access check fails + * </ul> + * <p> + * In general, the conditions under which a method handle may be + * looked up for a method {@code M} are no more restrictive than the conditions + * under which the lookup class could have compiled, verified, and resolved a call to {@code M}. + * Where the JVM would raise exceptions like {@code NoSuchMethodError}, + * a method handle lookup will generally raise a corresponding + * checked exception, such as {@code NoSuchMethodException}. + * And the effect of invoking the method handle resulting from the lookup + * is <a href="MethodHandles.Lookup.html#equiv">exactly equivalent</a> + * to executing the compiled, verified, and resolved call to {@code M}. + * The same point is true of fields and constructors. + * <p style="font-size:smaller;"> + * <em>Discussion:</em> + * Access checks only apply to named and reflected methods, + * constructors, and fields. + * Other method handle creation methods, such as + * {@link MethodHandle#asType MethodHandle.asType}, + * do not require any access checks, and are used + * independently of any {@code Lookup} object. + * <p> + * If the desired member is {@code protected}, the usual JVM rules apply, + * including the requirement that the lookup class must be either be in the + * same package as the desired member, or must inherit that member. + * (See the Java Virtual Machine Specification, sections 4.9.2, 5.4.3.5, and 6.4.) + * In addition, if the desired member is a non-static field or method + * in a different package, the resulting method handle may only be applied + * to objects of the lookup class or one of its subclasses. + * This requirement is enforced by narrowing the type of the leading + * {@code this} parameter from {@code C} + * (which will necessarily be a superclass of the lookup class) + * to the lookup class itself. + * <p> + * The JVM imposes a similar requirement on {@code invokespecial} instruction, + * that the receiver argument must match both the resolved method <em>and</em> + * the current class. Again, this requirement is enforced by narrowing the + * type of the leading parameter to the resulting method handle. + * (See the Java Virtual Machine Specification, section 4.10.1.9.) + * <p> + * The JVM represents constructors and static initializer blocks as internal methods + * with special names ({@code "<init>"} and {@code "<clinit>"}). + * The internal syntax of invocation instructions allows them to refer to such internal + * methods as if they were normal methods, but the JVM bytecode verifier rejects them. + * A lookup of such an internal method will produce a {@code NoSuchMethodException}. + * <p> + * In some cases, access between nested classes is obtained by the Java compiler by creating + * an wrapper method to access a private method of another class + * in the same top-level declaration. + * For example, a nested class {@code C.D} + * can access private members within other related classes such as + * {@code C}, {@code C.D.E}, or {@code C.B}, + * but the Java compiler may need to generate wrapper methods in + * those related classes. In such cases, a {@code Lookup} object on + * {@code C.E} would be unable to those private members. + * A workaround for this limitation is the {@link Lookup#in Lookup.in} method, + * which can transform a lookup on {@code C.E} into one on any of those other + * classes, without special elevation of privilege. + * <p> + * The accesses permitted to a given lookup object may be limited, + * according to its set of {@link #lookupModes lookupModes}, + * to a subset of members normally accessible to the lookup class. + * For example, the {@link #publicLookup publicLookup} + * method produces a lookup object which is only allowed to access + * public members in public classes. + * The caller sensitive method {@link #lookup lookup} + * produces a lookup object with full capabilities relative to + * its caller class, to emulate all supported bytecode behaviors. + * Also, the {@link Lookup#in Lookup.in} method may produce a lookup object + * with fewer access modes than the original lookup object. + * + * <p style="font-size:smaller;"> + * <a name="privacc"></a> + * <em>Discussion of private access:</em> + * We say that a lookup has <em>private access</em> + * if its {@linkplain #lookupModes lookup modes} + * include the possibility of accessing {@code private} members. + * As documented in the relevant methods elsewhere, + * only lookups with private access possess the following capabilities: + * <ul style="font-size:smaller;"> + * <li>access private fields, methods, and constructors of the lookup class + * <li>create method handles which invoke <a href="MethodHandles.Lookup.html#callsens">caller sensitive</a> methods, + * such as {@code Class.forName} + * <li>create method handles which {@link Lookup#findSpecial emulate invokespecial} instructions + * <li>avoid <a href="MethodHandles.Lookup.html#secmgr">package access checks</a> + * for classes accessible to the lookup class + * <li>create {@link Lookup#in delegated lookup objects} which have private access to other classes + * within the same package member + * </ul> + * <p style="font-size:smaller;"> + * Each of these permissions is a consequence of the fact that a lookup object + * with private access can be securely traced back to an originating class, + * whose <a href="MethodHandles.Lookup.html#equiv">bytecode behaviors</a> and Java language access permissions + * can be reliably determined and emulated by method handles. + * + * <h1><a name="secmgr"></a>Security manager interactions</h1> + * Although bytecode instructions can only refer to classes in + * a related class loader, this API can search for methods in any + * class, as long as a reference to its {@code Class} object is + * available. Such cross-loader references are also possible with the + * Core Reflection API, and are impossible to bytecode instructions + * such as {@code invokestatic} or {@code getfield}. + * There is a {@linkplain java.lang.SecurityManager security manager API} + * to allow applications to check such cross-loader references. + * These checks apply to both the {@code MethodHandles.Lookup} API + * and the Core Reflection API + * (as found on {@link java.lang.Class Class}). + * <p> + * If a security manager is present, member lookups are subject to + * additional checks. + * From one to three calls are made to the security manager. + * Any of these calls can refuse access by throwing a + * {@link java.lang.SecurityException SecurityException}. + * Define {@code smgr} as the security manager, + * {@code lookc} as the lookup class of the current lookup object, + * {@code refc} as the containing class in which the member + * is being sought, and {@code defc} as the class in which the + * member is actually defined. + * The value {@code lookc} is defined as <em>not present</em> + * if the current lookup object does not have + * <a href="MethodHandles.Lookup.html#privacc">private access</a>. + * The calls are made according to the following rules: + * <ul> + * <li><b>Step 1:</b> + * If {@code lookc} is not present, or if its class loader is not + * the same as or an ancestor of the class loader of {@code refc}, + * then {@link SecurityManager#checkPackageAccess + * smgr.checkPackageAccess(refcPkg)} is called, + * where {@code refcPkg} is the package of {@code refc}. + * <li><b>Step 2:</b> + * If the retrieved member is not public and + * {@code lookc} is not present, then + * {@link SecurityManager#checkPermission smgr.checkPermission} + * with {@code RuntimePermission("accessDeclaredMembers")} is called. + * <li><b>Step 3:</b> + * If the retrieved member is not public, + * and if {@code lookc} is not present, + * and if {@code defc} and {@code refc} are different, + * then {@link SecurityManager#checkPackageAccess + * smgr.checkPackageAccess(defcPkg)} is called, + * where {@code defcPkg} is the package of {@code defc}. + * </ul> + * Security checks are performed after other access checks have passed. + * Therefore, the above rules presuppose a member that is public, + * or else that is being accessed from a lookup class that has + * rights to access the member. + * + * <h1><a name="callsens"></a>Caller sensitive methods</h1> + * A small number of Java methods have a special property called caller sensitivity. + * A <em>caller-sensitive</em> method can behave differently depending on the + * identity of its immediate caller. + * <p> + * If a method handle for a caller-sensitive method is requested, + * the general rules for <a href="MethodHandles.Lookup.html#equiv">bytecode behaviors</a> apply, + * but they take account of the lookup class in a special way. + * The resulting method handle behaves as if it were called + * from an instruction contained in the lookup class, + * so that the caller-sensitive method detects the lookup class. + * (By contrast, the invoker of the method handle is disregarded.) + * Thus, in the case of caller-sensitive methods, + * different lookup classes may give rise to + * differently behaving method handles. + * <p> + * In cases where the lookup object is + * {@link #publicLookup publicLookup()}, + * or some other lookup object without + * <a href="MethodHandles.Lookup.html#privacc">private access</a>, + * the lookup class is disregarded. + * In such cases, no caller-sensitive method handle can be created, + * access is forbidden, and the lookup fails with an + * {@code IllegalAccessException}. + * <p style="font-size:smaller;"> + * <em>Discussion:</em> + * For example, the caller-sensitive method + * {@link java.lang.Class#forName(String) Class.forName(x)} + * can return varying classes or throw varying exceptions, + * depending on the class loader of the class that calls it. + * A public lookup of {@code Class.forName} will fail, because + * there is no reasonable way to determine its bytecode behavior. + * <p style="font-size:smaller;"> + * If an application caches method handles for broad sharing, + * it should use {@code publicLookup()} to create them. + * If there is a lookup of {@code Class.forName}, it will fail, + * and the application must take appropriate action in that case. + * It may be that a later lookup, perhaps during the invocation of a + * bootstrap method, can incorporate the specific identity + * of the caller, making the method accessible. + * <p style="font-size:smaller;"> + * The function {@code MethodHandles.lookup} is caller sensitive + * so that there can be a secure foundation for lookups. + * Nearly all other methods in the JSR 292 API rely on lookup + * objects to check access requests. + */ + // Android-changed: Change link targets from MethodHandles#[public]Lookup to + // #[public]Lookup to work around complaints from javadoc. public static final class Lookup { - public static final int PUBLIC = 0; + /** The class on behalf of whom the lookup is being performed. */ + /* @NonNull */ private final Class<?> lookupClass; + + /** The allowed sorts of members which may be looked up (PUBLIC, etc.). */ + private final int allowedModes; + + /** A single-bit mask representing {@code public} access, + * which may contribute to the result of {@link #lookupModes lookupModes}. + * The value, {@code 0x01}, happens to be the same as the value of the + * {@code public} {@linkplain java.lang.reflect.Modifier#PUBLIC modifier bit}. + */ + public static final int PUBLIC = Modifier.PUBLIC; + + /** A single-bit mask representing {@code private} access, + * which may contribute to the result of {@link #lookupModes lookupModes}. + * The value, {@code 0x02}, happens to be the same as the value of the + * {@code private} {@linkplain java.lang.reflect.Modifier#PRIVATE modifier bit}. + */ + public static final int PRIVATE = Modifier.PRIVATE; + + /** A single-bit mask representing {@code protected} access, + * which may contribute to the result of {@link #lookupModes lookupModes}. + * The value, {@code 0x04}, happens to be the same as the value of the + * {@code protected} {@linkplain java.lang.reflect.Modifier#PROTECTED modifier bit}. + */ + public static final int PROTECTED = Modifier.PROTECTED; + + /** A single-bit mask representing {@code package} access (default access), + * which may contribute to the result of {@link #lookupModes lookupModes}. + * The value is {@code 0x08}, which does not correspond meaningfully to + * any particular {@linkplain java.lang.reflect.Modifier modifier bit}. + */ + public static final int PACKAGE = Modifier.STATIC; + + private static final int ALL_MODES = (PUBLIC | PRIVATE | PROTECTED | PACKAGE); + + // Android-note: Android has no notion of a trusted lookup. If required, such lookups + // are performed by the runtime. As a result, we always use lookupClass, which will always + // be non-null in our implementation. + // + // private static final int TRUSTED = -1; + + private static int fixmods(int mods) { + mods &= (ALL_MODES - PACKAGE); + return (mods != 0) ? mods : PACKAGE; + } + + /** Tells which class is performing the lookup. It is this class against + * which checks are performed for visibility and access permissions. + * <p> + * The class implies a maximum level of access permission, + * but the permissions may be additionally limited by the bitmask + * {@link #lookupModes lookupModes}, which controls whether non-public members + * can be accessed. + * @return the lookup class, on behalf of which this lookup object finds members + */ + public Class<?> lookupClass() { + return lookupClass; + } + + /** Tells which access-protection classes of members this lookup object can produce. + * The result is a bit-mask of the bits + * {@linkplain #PUBLIC PUBLIC (0x01)}, + * {@linkplain #PRIVATE PRIVATE (0x02)}, + * {@linkplain #PROTECTED PROTECTED (0x04)}, + * and {@linkplain #PACKAGE PACKAGE (0x08)}. + * <p> + * A freshly-created lookup object + * on the {@linkplain java.lang.invoke.MethodHandles#lookup() caller's class} + * has all possible bits set, since the caller class can access all its own members. + * A lookup object on a new lookup class + * {@linkplain java.lang.invoke.MethodHandles.Lookup#in created from a previous lookup object} + * may have some mode bits set to zero. + * The purpose of this is to restrict access via the new lookup object, + * so that it can access only names which can be reached by the original + * lookup object, and also by the new lookup class. + * @return the lookup modes, which limit the kinds of access performed by this lookup object + */ + public int lookupModes() { + return allowedModes & ALL_MODES; + } + + /** Embody the current class (the lookupClass) as a lookup class + * for method handle creation. + * Must be called by from a method in this package, + * which in turn is called by a method not in this package. + */ + Lookup(Class<?> lookupClass) { + this(lookupClass, ALL_MODES); + // make sure we haven't accidentally picked up a privileged class: + checkUnprivilegedlookupClass(lookupClass, ALL_MODES); + } + + private Lookup(Class<?> lookupClass, int allowedModes) { + this.lookupClass = lookupClass; + this.allowedModes = allowedModes; + } - public static final int PRIVATE = 0; + /** + * Creates a lookup on the specified new lookup class. + * The resulting object will report the specified + * class as its own {@link #lookupClass lookupClass}. + * <p> + * However, the resulting {@code Lookup} object is guaranteed + * to have no more access capabilities than the original. + * In particular, access capabilities can be lost as follows:<ul> + * <li>If the new lookup class differs from the old one, + * protected members will not be accessible by virtue of inheritance. + * (Protected members may continue to be accessible because of package sharing.) + * <li>If the new lookup class is in a different package + * than the old one, protected and default (package) members will not be accessible. + * <li>If the new lookup class is not within the same package member + * as the old one, private members will not be accessible. + * <li>If the new lookup class is not accessible to the old lookup class, + * then no members, not even public members, will be accessible. + * (In all other cases, public members will continue to be accessible.) + * </ul> + * + * @param requestedLookupClass the desired lookup class for the new lookup object + * @return a lookup object which reports the desired lookup class + * @throws NullPointerException if the argument is null + */ + public Lookup in(Class<?> requestedLookupClass) { + requestedLookupClass.getClass(); // null check + // Android-changed: There's no notion of a trusted lookup. + // if (allowedModes == TRUSTED) // IMPL_LOOKUP can make any lookup at all + // return new Lookup(requestedLookupClass, ALL_MODES); - public static final int PROTECTED = 0; + if (requestedLookupClass == this.lookupClass) + return this; // keep same capabilities + int newModes = (allowedModes & (ALL_MODES & ~PROTECTED)); + if ((newModes & PACKAGE) != 0 + && !VerifyAccess.isSamePackage(this.lookupClass, requestedLookupClass)) { + newModes &= ~(PACKAGE|PRIVATE); + } + // Allow nestmate lookups to be created without special privilege: + if ((newModes & PRIVATE) != 0 + && !VerifyAccess.isSamePackageMember(this.lookupClass, requestedLookupClass)) { + newModes &= ~PRIVATE; + } + if ((newModes & PUBLIC) != 0 + && !VerifyAccess.isClassAccessible(requestedLookupClass, this.lookupClass, allowedModes)) { + // The requested class it not accessible from the lookup class. + // No permissions. + newModes = 0; + } + checkUnprivilegedlookupClass(requestedLookupClass, newModes); + return new Lookup(requestedLookupClass, newModes); + } - public static final int PACKAGE = 0; + // Make sure outer class is initialized first. + // + // Android-changed: Removed unnecessary reference to IMPL_NAMES. + // static { IMPL_NAMES.getClass(); } - public Class<?> lookupClass() { return null; } + /** Version of lookup which is trusted minimally. + * It can only be used to create method handles to + * publicly accessible members. + */ + static final Lookup PUBLIC_LOOKUP = new Lookup(Object.class, PUBLIC); - public int lookupModes() { return 0; } + /** Package-private version of lookup which is trusted. */ + static final Lookup IMPL_LOOKUP = new Lookup(Object.class, ALL_MODES); - public Lookup in(Class<?> requestedLookupClass) { return null; } + private static void checkUnprivilegedlookupClass(Class<?> lookupClass, int allowedModes) { + String name = lookupClass.getName(); + if (name.startsWith("java.lang.invoke.")) + throw newIllegalArgumentException("illegal lookupClass: "+lookupClass); + // For caller-sensitive MethodHandles.lookup() + // disallow lookup more restricted packages + // + // Android-changed: The bootstrap classloader isn't null. + if (allowedModes == ALL_MODES && + lookupClass.getClassLoader() == Object.class.getClassLoader()) { + if (name.startsWith("java.") || + (name.startsWith("sun.") + && !name.startsWith("sun.invoke.") + && !name.equals("sun.reflect.ReflectionFactory"))) { + throw newIllegalArgumentException("illegal lookupClass: " + lookupClass); + } + } + } + + /** + * Displays the name of the class from which lookups are to be made. + * (The name is the one reported by {@link java.lang.Class#getName() Class.getName}.) + * If there are restrictions on the access permitted to this lookup, + * this is indicated by adding a suffix to the class name, consisting + * of a slash and a keyword. The keyword represents the strongest + * allowed access, and is chosen as follows: + * <ul> + * <li>If no access is allowed, the suffix is "/noaccess". + * <li>If only public access is allowed, the suffix is "/public". + * <li>If only public and package access are allowed, the suffix is "/package". + * <li>If only public, package, and private access are allowed, the suffix is "/private". + * </ul> + * If none of the above cases apply, it is the case that full + * access (public, package, private, and protected) is allowed. + * In this case, no suffix is added. + * This is true only of an object obtained originally from + * {@link java.lang.invoke.MethodHandles#lookup MethodHandles.lookup}. + * Objects created by {@link java.lang.invoke.MethodHandles.Lookup#in Lookup.in} + * always have restricted access, and will display a suffix. + * <p> + * (It may seem strange that protected access should be + * stronger than private access. Viewed independently from + * package access, protected access is the first to be lost, + * because it requires a direct subclass relationship between + * caller and callee.) + * @see #in + */ + @Override + public String toString() { + String cname = lookupClass.getName(); + switch (allowedModes) { + case 0: // no privileges + return cname + "/noaccess"; + case PUBLIC: + return cname + "/public"; + case PUBLIC|PACKAGE: + return cname + "/package"; + case ALL_MODES & ~PROTECTED: + return cname + "/private"; + case ALL_MODES: + return cname; + // Android-changed: No support for TRUSTED callers. + // case TRUSTED: + // return "/trusted"; // internal only; not exported + default: // Should not happen, but it's a bitfield... + cname = cname + "/" + Integer.toHexString(allowedModes); + assert(false) : cname; + return cname; + } + } + + /** + * Produces a method handle for a static method. + * The type of the method handle will be that of the method. + * (Since static methods do not take receivers, there is no + * additional receiver argument inserted into the method handle type, + * as there would be with {@link #findVirtual findVirtual} or {@link #findSpecial findSpecial}.) + * The method and all its argument types must be accessible to the lookup object. + * <p> + * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the method's variable arity modifier bit ({@code 0x0080}) is set. + * <p> + * If the returned method handle is invoked, the method's class will + * be initialized, if it has not already been initialized. + * <p><b>Example:</b> + * <blockquote><pre>{@code +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +... +MethodHandle MH_asList = publicLookup().findStatic(Arrays.class, + "asList", methodType(List.class, Object[].class)); +assertEquals("[x, y]", MH_asList.invoke("x", "y").toString()); + * }</pre></blockquote> + * @param refc the class from which the method is accessed + * @param name the name of the method + * @param type the type of the method + * @return the desired method handle + * @throws NoSuchMethodException if the method does not exist + * @throws IllegalAccessException if access checking fails, + * or if the method is not {@code static}, + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @exception SecurityException if a security manager is present and it + * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a> + * @throws NullPointerException if any argument is null + */ public - MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; } + MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { + Method method = refc.getDeclaredMethod(name, type.ptypes()); + final int modifiers = method.getModifiers(); + if (!Modifier.isStatic(modifiers)) { + throw new IllegalAccessException("Method" + method + " is not static"); + } + checkReturnType(method, type); + checkAccess(refc, method.getDeclaringClass(), modifiers, method.getName()); + return createMethodHandle(method, MethodHandle.INVOKE_STATIC, type); + } + + private MethodHandle findVirtualForMH(String name, MethodType type) { + // these names require special lookups because of the implicit MethodType argument + if ("invoke".equals(name)) + return invoker(type); + if ("invokeExact".equals(name)) + return exactInvoker(type); + return null; + } + + private static MethodHandle createMethodHandle(Method method, int handleKind, + MethodType methodType) { + MethodHandle mh = new MethodHandleImpl(method.getArtMethod(), handleKind, methodType); + if (method.isVarArgs()) { + return new Transformers.VarargsCollector(mh); + } else { + return mh; + } + } + + /** + * Produces a method handle for a virtual method. + * The type of the method handle will be that of the method, + * with the receiver type (usually {@code refc}) prepended. + * The method and all its argument types must be accessible to the lookup object. + * <p> + * When called, the handle will treat the first argument as a receiver + * and dispatch on the receiver's type to determine which method + * implementation to enter. + * (The dispatching action is identical with that performed by an + * {@code invokevirtual} or {@code invokeinterface} instruction.) + * <p> + * The first argument will be of type {@code refc} if the lookup + * class has full privileges to access the member. Otherwise + * the member must be {@code protected} and the first argument + * will be restricted in type to the lookup class. + * <p> + * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the method's variable arity modifier bit ({@code 0x0080}) is set. + * <p> + * Because of the general <a href="MethodHandles.Lookup.html#equiv">equivalence</a> between {@code invokevirtual} + * instructions and method handles produced by {@code findVirtual}, + * if the class is {@code MethodHandle} and the name string is + * {@code invokeExact} or {@code invoke}, the resulting + * method handle is equivalent to one produced by + * {@link java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvoker} or + * {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker} + * with the same {@code type} argument. + * + * <b>Example:</b> + * <blockquote><pre>{@code +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +... +MethodHandle MH_concat = publicLookup().findVirtual(String.class, + "concat", methodType(String.class, String.class)); +MethodHandle MH_hashCode = publicLookup().findVirtual(Object.class, + "hashCode", methodType(int.class)); +MethodHandle MH_hashCode_String = publicLookup().findVirtual(String.class, + "hashCode", methodType(int.class)); +assertEquals("xy", (String) MH_concat.invokeExact("x", "y")); +assertEquals("xy".hashCode(), (int) MH_hashCode.invokeExact((Object)"xy")); +assertEquals("xy".hashCode(), (int) MH_hashCode_String.invokeExact("xy")); +// interface method: +MethodHandle MH_subSequence = publicLookup().findVirtual(CharSequence.class, + "subSequence", methodType(CharSequence.class, int.class, int.class)); +assertEquals("def", MH_subSequence.invoke("abcdefghi", 3, 6).toString()); +// constructor "internal method" must be accessed differently: +MethodType MT_newString = methodType(void.class); //()V for new String() +try { assertEquals("impossible", lookup() + .findVirtual(String.class, "<init>", MT_newString)); + } catch (NoSuchMethodException ex) { } // OK +MethodHandle MH_newString = publicLookup() + .findConstructor(String.class, MT_newString); +assertEquals("", (String) MH_newString.invokeExact()); + * }</pre></blockquote> + * + * @param refc the class or interface from which the method is accessed + * @param name the name of the method + * @param type the type of the method, with the receiver argument omitted + * @return the desired method handle + * @throws NoSuchMethodException if the method does not exist + * @throws IllegalAccessException if access checking fails, + * or if the method is {@code static} + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @exception SecurityException if a security manager is present and it + * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a> + * @throws NullPointerException if any argument is null + */ + public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { + // Special case : when we're looking up a virtual method on the MethodHandles class + // itself, we can return one of our specialized invokers. + if (refc == MethodHandle.class) { + MethodHandle mh = findVirtualForMH(name, type); + if (mh != null) { + return mh; + } + } + // BEGIN Android-changed: Added VarHandle case here. + // Implementation to follow. TODO(b/65872996) + if (refc == VarHandle.class) { + unsupported("MethodHandles.findVirtual with refc == VarHandle.class"); + return null; + } + // END Android-changed: Added VarHandle handling here. + + Method method = refc.getInstanceMethod(name, type.ptypes()); + if (method == null) { + // This is pretty ugly and a consequence of the MethodHandles API. We have to throw + // an IAE and not an NSME if the method exists but is static (even though the RI's + // IAE has a message that says "no such method"). We confine the ugliness and + // slowness to the failure case, and allow getInstanceMethod to remain fairly + // general. + try { + Method m = refc.getDeclaredMethod(name, type.ptypes()); + if (Modifier.isStatic(m.getModifiers())) { + throw new IllegalAccessException("Method" + m + " is static"); + } + } catch (NoSuchMethodException ignored) { + } + + throw new NoSuchMethodException(name + " " + Arrays.toString(type.ptypes())); + } + checkReturnType(method, type); + + // We have a valid method, perform access checks. + checkAccess(refc, method.getDeclaringClass(), method.getModifiers(), method.getName()); + + // Insert the leading reference parameter. + MethodType handleType = type.insertParameterTypes(0, refc); + return createMethodHandle(method, MethodHandle.INVOKE_VIRTUAL, handleType); + } + + /** + * Produces a method handle which creates an object and initializes it, using + * the constructor of the specified type. + * The parameter types of the method handle will be those of the constructor, + * while the return type will be a reference to the constructor's class. + * The constructor and all its argument types must be accessible to the lookup object. + * <p> + * The requested type must have a return type of {@code void}. + * (This is consistent with the JVM's treatment of constructor type descriptors.) + * <p> + * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the constructor's variable arity modifier bit ({@code 0x0080}) is set. + * <p> + * If the returned method handle is invoked, the constructor's class will + * be initialized, if it has not already been initialized. + * <p><b>Example:</b> + * <blockquote><pre>{@code +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +... +MethodHandle MH_newArrayList = publicLookup().findConstructor( + ArrayList.class, methodType(void.class, Collection.class)); +Collection orig = Arrays.asList("x", "y"); +Collection copy = (ArrayList) MH_newArrayList.invokeExact(orig); +assert(orig != copy); +assertEquals(orig, copy); +// a variable-arity constructor: +MethodHandle MH_newProcessBuilder = publicLookup().findConstructor( + ProcessBuilder.class, methodType(void.class, String[].class)); +ProcessBuilder pb = (ProcessBuilder) + MH_newProcessBuilder.invoke("x", "y", "z"); +assertEquals("[x, y, z]", pb.command().toString()); + * }</pre></blockquote> + * @param refc the class or interface from which the method is accessed + * @param type the type of the method, with the receiver argument omitted, and a void return type + * @return the desired method handle + * @throws NoSuchMethodException if the constructor does not exist + * @throws IllegalAccessException if access checking fails + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @exception SecurityException if a security manager is present and it + * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a> + * @throws NullPointerException if any argument is null + */ + public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException { + if (refc.isArray()) { + throw new NoSuchMethodException("no constructor for array class: " + refc.getName()); + } + // The queried |type| is (PT1,PT2,..)V + Constructor constructor = refc.getDeclaredConstructor(type.ptypes()); + if (constructor == null) { + throw new NoSuchMethodException( + "No constructor for " + constructor.getDeclaringClass() + " matching " + type); + } + checkAccess(refc, constructor.getDeclaringClass(), constructor.getModifiers(), + constructor.getName()); + + return createMethodHandleForConstructor(constructor); + } - public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; } + private MethodHandle createMethodHandleForConstructor(Constructor constructor) { + Class<?> refc = constructor.getDeclaringClass(); + MethodType constructorType = + MethodType.methodType(refc, constructor.getParameterTypes()); + MethodHandle mh; + if (refc == String.class) { + // String constructors have optimized StringFactory methods + // that matches returned type. These factory methods combine the + // memory allocation and initialization calls for String objects. + mh = new MethodHandleImpl(constructor.getArtMethod(), MethodHandle.INVOKE_DIRECT, + constructorType); + } else { + // Constructors for all other classes use a Construct transformer to perform + // their memory allocation and call to <init>. + MethodType initType = initMethodType(constructorType); + MethodHandle initHandle = new MethodHandleImpl( + constructor.getArtMethod(), MethodHandle.INVOKE_DIRECT, initType); + mh = new Transformers.Construct(initHandle, constructorType); + } - public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; } + if (constructor.isVarArgs()) { + mh = new Transformers.VarargsCollector(mh); + } + return mh; + } + private static MethodType initMethodType(MethodType constructorType) { + // Returns a MethodType appropriate for class <init> + // methods. Constructor MethodTypes have the form + // (PT1,PT2,...)C and class <init> MethodTypes have the + // form (C,PT1,PT2,...)V. + assert constructorType.rtype() != void.class; + + // Insert constructorType C as the first parameter type in + // the MethodType for <init>. + Class<?> [] initPtypes = new Class<?> [constructorType.ptypes().length + 1]; + initPtypes[0] = constructorType.rtype(); + System.arraycopy(constructorType.ptypes(), 0, initPtypes, 1, + constructorType.ptypes().length); + + // Set the return type for the <init> MethodType to be void. + return MethodType.methodType(void.class, initPtypes); + } + + /** + * Produces an early-bound method handle for a virtual method. + * It will bypass checks for overriding methods on the receiver, + * <a href="MethodHandles.Lookup.html#equiv">as if called</a> from an {@code invokespecial} + * instruction from within the explicitly specified {@code specialCaller}. + * The type of the method handle will be that of the method, + * with a suitably restricted receiver type prepended. + * (The receiver type will be {@code specialCaller} or a subtype.) + * The method and all its argument types must be accessible + * to the lookup object. + * <p> + * Before method resolution, + * if the explicitly specified caller class is not identical with the + * lookup class, or if this lookup object does not have + * <a href="MethodHandles.Lookup.html#privacc">private access</a> + * privileges, the access fails. + * <p> + * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the method's variable arity modifier bit ({@code 0x0080}) is set. + * <p style="font-size:smaller;"> + * <em>(Note: JVM internal methods named {@code "<init>"} are not visible to this API, + * even though the {@code invokespecial} instruction can refer to them + * in special circumstances. Use {@link #findConstructor findConstructor} + * to access instance initialization methods in a safe manner.)</em> + * <p><b>Example:</b> + * <blockquote><pre>{@code +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +... +static class Listie extends ArrayList { + public String toString() { return "[wee Listie]"; } + static Lookup lookup() { return MethodHandles.lookup(); } +} +... +// no access to constructor via invokeSpecial: +MethodHandle MH_newListie = Listie.lookup() + .findConstructor(Listie.class, methodType(void.class)); +Listie l = (Listie) MH_newListie.invokeExact(); +try { assertEquals("impossible", Listie.lookup().findSpecial( + Listie.class, "<init>", methodType(void.class), Listie.class)); + } catch (NoSuchMethodException ex) { } // OK +// access to super and self methods via invokeSpecial: +MethodHandle MH_super = Listie.lookup().findSpecial( + ArrayList.class, "toString" , methodType(String.class), Listie.class); +MethodHandle MH_this = Listie.lookup().findSpecial( + Listie.class, "toString" , methodType(String.class), Listie.class); +MethodHandle MH_duper = Listie.lookup().findSpecial( + Object.class, "toString" , methodType(String.class), Listie.class); +assertEquals("[]", (String) MH_super.invokeExact(l)); +assertEquals(""+l, (String) MH_this.invokeExact(l)); +assertEquals("[]", (String) MH_duper.invokeExact(l)); // ArrayList method +try { assertEquals("inaccessible", Listie.lookup().findSpecial( + String.class, "toString", methodType(String.class), Listie.class)); + } catch (IllegalAccessException ex) { } // OK +Listie subl = new Listie() { public String toString() { return "[subclass]"; } }; +assertEquals(""+l, (String) MH_this.invokeExact(subl)); // Listie method + * }</pre></blockquote> + * + * @param refc the class or interface from which the method is accessed + * @param name the name of the method (which must not be "<init>") + * @param type the type of the method, with the receiver argument omitted + * @param specialCaller the proposed calling class to perform the {@code invokespecial} + * @return the desired method handle + * @throws NoSuchMethodException if the method does not exist + * @throws IllegalAccessException if access checking fails + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @exception SecurityException if a security manager is present and it + * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a> + * @throws NullPointerException if any argument is null + */ public MethodHandle findSpecial(Class<?> refc, String name, MethodType type, - Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException { return null; } + Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException { + if (specialCaller == null) { + throw new NullPointerException("specialCaller == null"); + } + + if (type == null) { + throw new NullPointerException("type == null"); + } + + if (name == null) { + throw new NullPointerException("name == null"); + } + + if (refc == null) { + throw new NullPointerException("ref == null"); + } + + // Make sure that the special caller is identical to the lookup class or that we have + // private access. + checkSpecialCaller(specialCaller); + + // Even though constructors are invoked using a "special" invoke, handles to them can't + // be created using findSpecial. Callers must use findConstructor instead. Similarly, + // there is no path for calling static class initializers. + if (name.startsWith("<")) { + throw new NoSuchMethodException(name + " is not a valid method name."); + } + + Method method = refc.getDeclaredMethod(name, type.ptypes()); + checkReturnType(method, type); + return findSpecial(method, type, refc, specialCaller); + } + + private MethodHandle findSpecial(Method method, MethodType type, + Class<?> refc, Class<?> specialCaller) + throws IllegalAccessException { + if (Modifier.isStatic(method.getModifiers())) { + throw new IllegalAccessException("expected a non-static method:" + method); + } + + if (Modifier.isPrivate(method.getModifiers())) { + // Since this is a private method, we'll need to also make sure that the + // lookup class is the same as the refering class. We've already checked that + // the specialCaller is the same as the special lookup class, both of these must + // be the same as the declaring class(*) in order to access the private method. + // + // (*) Well, this isn't true for nested classes but OpenJDK doesn't support those + // either. + if (refc != lookupClass()) { + throw new IllegalAccessException("no private access for invokespecial : " + + refc + ", from" + this); + } + + // This is a private method, so there's nothing special to do. + MethodType handleType = type.insertParameterTypes(0, refc); + return createMethodHandle(method, MethodHandle.INVOKE_DIRECT, handleType); + } + + // This is a public, protected or package-private method, which means we're expecting + // invoke-super semantics. We'll have to restrict the receiver type appropriately on the + // handle once we check that there really is a "super" relationship between them. + if (!method.getDeclaringClass().isAssignableFrom(specialCaller)) { + throw new IllegalAccessException(refc + "is not assignable from " + specialCaller); + } + + // Note that we restrict the receiver to "specialCaller" instances. + MethodType handleType = type.insertParameterTypes(0, specialCaller); + return createMethodHandle(method, MethodHandle.INVOKE_SUPER, handleType); + } + + /** + * Produces a method handle giving read access to a non-static field. + * The type of the method handle will have a return type of the field's + * value type. + * The method handle's single argument will be the instance containing + * the field. + * Access checking is performed immediately on behalf of the lookup class. + * @param refc the class or interface from which the method is accessed + * @param name the field's name + * @param type the field's type + * @return a method handle which can load values from the field + * @throws NoSuchFieldException if the field does not exist + * @throws IllegalAccessException if access checking fails, or if the field is {@code static} + * @exception SecurityException if a security manager is present and it + * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a> + * @throws NullPointerException if any argument is null + */ + public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { + return findAccessor(refc, name, type, MethodHandle.IGET); + } + + private MethodHandle findAccessor(Class<?> refc, String name, Class<?> type, int kind) + throws NoSuchFieldException, IllegalAccessException { + final Field field = refc.getDeclaredField(name); + final Class<?> fieldType = field.getType(); + if (fieldType != type) { + throw new NoSuchFieldException( + "Field has wrong type: " + fieldType + " != " + type); + } + + return findAccessor(field, refc, type, kind, true /* performAccessChecks */); + } + + private MethodHandle findAccessor(Field field, Class<?> refc, Class<?> fieldType, int kind, + boolean performAccessChecks) + throws IllegalAccessException { + if (!performAccessChecks) { + checkAccess(refc, field.getDeclaringClass(), field.getModifiers(), field.getName()); + } + + final boolean isStaticKind = kind == MethodHandle.SGET || kind == MethodHandle.SPUT; + final int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) != isStaticKind) { + String reason = "Field " + field + " is " + + (isStaticKind ? "not " : "") + "static"; + throw new IllegalAccessException(reason); + } + + final boolean isSetterKind = kind == MethodHandle.IPUT || kind == MethodHandle.SPUT; + if (Modifier.isFinal(modifiers) && isSetterKind) { + throw new IllegalAccessException("Field " + field + " is final"); + } + + final MethodType methodType; + switch (kind) { + case MethodHandle.SGET: + methodType = MethodType.methodType(fieldType); + break; + case MethodHandle.SPUT: + methodType = MethodType.methodType(void.class, fieldType); + break; + case MethodHandle.IGET: + methodType = MethodType.methodType(fieldType, refc); + break; + case MethodHandle.IPUT: + methodType = MethodType.methodType(void.class, refc, fieldType); + break; + default: + throw new IllegalArgumentException("Invalid kind " + kind); + } + return new MethodHandleImpl(field.getArtField(), kind, methodType); + } + + /** + * Produces a method handle giving write access to a non-static field. + * The type of the method handle will have a void return type. + * The method handle will take two arguments, the instance containing + * the field, and the value to be stored. + * The second argument will be of the field's value type. + * Access checking is performed immediately on behalf of the lookup class. + * @param refc the class or interface from which the method is accessed + * @param name the field's name + * @param type the field's type + * @return a method handle which can store values into the field + * @throws NoSuchFieldException if the field does not exist + * @throws IllegalAccessException if access checking fails, or if the field is {@code static} + * @exception SecurityException if a security manager is present and it + * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a> + * @throws NullPointerException if any argument is null + */ + public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { + return findAccessor(refc, name, type, MethodHandle.IPUT); + } + + // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes. + /** + * Produces a VarHandle giving access to a non-static field {@code name} + * of type {@code type} declared in a class of type {@code recv}. + * The VarHandle's variable type is {@code type} and it has one + * coordinate type, {@code recv}. + * <p> + * Access checking is performed immediately on behalf of the lookup + * class. + * <p> + * Certain access modes of the returned VarHandle are unsupported under + * the following conditions: + * <ul> + * <li>if the field is declared {@code final}, then the write, atomic + * update, numeric atomic update, and bitwise atomic update access + * modes are unsupported. + * <li>if the field type is anything other than {@code byte}, + * {@code short}, {@code char}, {@code int}, {@code long}, + * {@code float}, or {@code double} then numeric atomic update + * access modes are unsupported. + * <li>if the field type is anything other than {@code boolean}, + * {@code byte}, {@code short}, {@code char}, {@code int} or + * {@code long} then bitwise atomic update access modes are + * unsupported. + * </ul> + * <p> + * If the field is declared {@code volatile} then the returned VarHandle + * will override access to the field (effectively ignore the + * {@code volatile} declaration) in accordance to its specified + * access modes. + * <p> + * If the field type is {@code float} or {@code double} then numeric + * and atomic update access modes compare values using their bitwise + * representation (see {@link Float#floatToRawIntBits} and + * {@link Double#doubleToRawLongBits}, respectively). + * @apiNote + * Bitwise comparison of {@code float} values or {@code double} values, + * as performed by the numeric and atomic update access modes, differ + * from the primitive {@code ==} operator and the {@link Float#equals} + * and {@link Double#equals} methods, specifically with respect to + * comparing NaN values or comparing {@code -0.0} with {@code +0.0}. + * Care should be taken when performing a compare and set or a compare + * and exchange operation with such values since the operation may + * unexpectedly fail. + * There are many possible NaN values that are considered to be + * {@code NaN} in Java, although no IEEE 754 floating-point operation + * provided by Java can distinguish between them. Operation failure can + * occur if the expected or witness value is a NaN value and it is + * transformed (perhaps in a platform specific manner) into another NaN + * value, and thus has a different bitwise representation (see + * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more + * details). + * The values {@code -0.0} and {@code +0.0} have different bitwise + * representations but are considered equal when using the primitive + * {@code ==} operator. Operation failure can occur if, for example, a + * numeric algorithm computes an expected value to be say {@code -0.0} + * and previously computed the witness value to be say {@code +0.0}. + * @param recv the receiver class, of type {@code R}, that declares the + * non-static field + * @param name the field's name + * @param type the field's type, of type {@code T} + * @return a VarHandle giving access to non-static fields. + * @throws NoSuchFieldException if the field does not exist + * @throws IllegalAccessException if access checking fails, or if the field is {@code static} + * @exception SecurityException if a security manager is present and it + * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a> + * @throws NullPointerException if any argument is null + * @since 9 + * @hide + */ + public VarHandle findVarHandle(Class<?> recv, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { + unsupported("MethodHandles.Lookup.findVarHandle()"); // TODO(b/65872996) + return null; + } + // END Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes. + + /** + * Produces a method handle giving read access to a static field. + * The type of the method handle will have a return type of the field's + * value type. + * The method handle will take no arguments. + * Access checking is performed immediately on behalf of the lookup class. + * <p> + * If the returned method handle is invoked, the field's class will + * be initialized, if it has not already been initialized. + * @param refc the class or interface from which the method is accessed + * @param name the field's name + * @param type the field's type + * @return a method handle which can load values from the field + * @throws NoSuchFieldException if the field does not exist + * @throws IllegalAccessException if access checking fails, or if the field is not {@code static} + * @exception SecurityException if a security manager is present and it + * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a> + * @throws NullPointerException if any argument is null + */ + public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { + return findAccessor(refc, name, type, MethodHandle.SGET); + } + + /** + * Produces a method handle giving write access to a static field. + * The type of the method handle will have a void return type. + * The method handle will take a single + * argument, of the field's value type, the value to be stored. + * Access checking is performed immediately on behalf of the lookup class. + * <p> + * If the returned method handle is invoked, the field's class will + * be initialized, if it has not already been initialized. + * @param refc the class or interface from which the method is accessed + * @param name the field's name + * @param type the field's type + * @return a method handle which can store values into the field + * @throws NoSuchFieldException if the field does not exist + * @throws IllegalAccessException if access checking fails, or if the field is not {@code static} + * @exception SecurityException if a security manager is present and it + * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a> + * @throws NullPointerException if any argument is null + */ + public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { + return findAccessor(refc, name, type, MethodHandle.SPUT); + } + + // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes. + /** + * Produces a VarHandle giving access to a static field {@code name} of + * type {@code type} declared in a class of type {@code decl}. + * The VarHandle's variable type is {@code type} and it has no + * coordinate types. + * <p> + * Access checking is performed immediately on behalf of the lookup + * class. + * <p> + * If the returned VarHandle is operated on, the declaring class will be + * initialized, if it has not already been initialized. + * <p> + * Certain access modes of the returned VarHandle are unsupported under + * the following conditions: + * <ul> + * <li>if the field is declared {@code final}, then the write, atomic + * update, numeric atomic update, and bitwise atomic update access + * modes are unsupported. + * <li>if the field type is anything other than {@code byte}, + * {@code short}, {@code char}, {@code int}, {@code long}, + * {@code float}, or {@code double}, then numeric atomic update + * access modes are unsupported. + * <li>if the field type is anything other than {@code boolean}, + * {@code byte}, {@code short}, {@code char}, {@code int} or + * {@code long} then bitwise atomic update access modes are + * unsupported. + * </ul> + * <p> + * If the field is declared {@code volatile} then the returned VarHandle + * will override access to the field (effectively ignore the + * {@code volatile} declaration) in accordance to its specified + * access modes. + * <p> + * If the field type is {@code float} or {@code double} then numeric + * and atomic update access modes compare values using their bitwise + * representation (see {@link Float#floatToRawIntBits} and + * {@link Double#doubleToRawLongBits}, respectively). + * @apiNote + * Bitwise comparison of {@code float} values or {@code double} values, + * as performed by the numeric and atomic update access modes, differ + * from the primitive {@code ==} operator and the {@link Float#equals} + * and {@link Double#equals} methods, specifically with respect to + * comparing NaN values or comparing {@code -0.0} with {@code +0.0}. + * Care should be taken when performing a compare and set or a compare + * and exchange operation with such values since the operation may + * unexpectedly fail. + * There are many possible NaN values that are considered to be + * {@code NaN} in Java, although no IEEE 754 floating-point operation + * provided by Java can distinguish between them. Operation failure can + * occur if the expected or witness value is a NaN value and it is + * transformed (perhaps in a platform specific manner) into another NaN + * value, and thus has a different bitwise representation (see + * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more + * details). + * The values {@code -0.0} and {@code +0.0} have different bitwise + * representations but are considered equal when using the primitive + * {@code ==} operator. Operation failure can occur if, for example, a + * numeric algorithm computes an expected value to be say {@code -0.0} + * and previously computed the witness value to be say {@code +0.0}. + * @param decl the class that declares the static field + * @param name the field's name + * @param type the field's type, of type {@code T} + * @return a VarHandle giving access to a static field + * @throws NoSuchFieldException if the field does not exist + * @throws IllegalAccessException if access checking fails, or if the field is not {@code static} + * @exception SecurityException if a security manager is present and it + * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a> + * @throws NullPointerException if any argument is null + * @since 9 + * @hide + */ + public VarHandle findStaticVarHandle(Class<?> decl, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { + unsupported("MethodHandles.Lookup.findStaticVarHandle()"); // TODO(b/65872996) + return null; + } + // END Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes. + + /** + * Produces an early-bound method handle for a non-static method. + * The receiver must have a supertype {@code defc} in which a method + * of the given name and type is accessible to the lookup class. + * The method and all its argument types must be accessible to the lookup object. + * The type of the method handle will be that of the method, + * without any insertion of an additional receiver parameter. + * The given receiver will be bound into the method handle, + * so that every call to the method handle will invoke the + * requested method on the given receiver. + * <p> + * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the method's variable arity modifier bit ({@code 0x0080}) is set + * <em>and</em> the trailing array argument is not the only argument. + * (If the trailing array argument is the only argument, + * the given receiver value will be bound to it.) + * <p> + * This is equivalent to the following code: + * <blockquote><pre>{@code +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +... +MethodHandle mh0 = lookup().findVirtual(defc, name, type); +MethodHandle mh1 = mh0.bindTo(receiver); +MethodType mt1 = mh1.type(); +if (mh0.isVarargsCollector()) + mh1 = mh1.asVarargsCollector(mt1.parameterType(mt1.parameterCount()-1)); +return mh1; + * }</pre></blockquote> + * where {@code defc} is either {@code receiver.getClass()} or a super + * type of that class, in which the requested method is accessible + * to the lookup class. + * (Note that {@code bindTo} does not preserve variable arity.) + * @param receiver the object from which the method is accessed + * @param name the name of the method + * @param type the type of the method, with the receiver argument omitted + * @return the desired method handle + * @throws NoSuchMethodException if the method does not exist + * @throws IllegalAccessException if access checking fails + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @exception SecurityException if a security manager is present and it + * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a> + * @throws NullPointerException if any argument is null + * @see MethodHandle#bindTo + * @see #findVirtual + */ + public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { + MethodHandle handle = findVirtual(receiver.getClass(), name, type); + MethodHandle adapter = handle.bindTo(receiver); + MethodType adapterType = adapter.type(); + if (handle.isVarargsCollector()) { + adapter = adapter.asVarargsCollector( + adapterType.parameterType(adapterType.parameterCount() - 1)); + } + + return adapter; + } + + /** + * Makes a <a href="MethodHandleInfo.html#directmh">direct method handle</a> + * to <i>m</i>, if the lookup class has permission. + * If <i>m</i> is non-static, the receiver argument is treated as an initial argument. + * If <i>m</i> is virtual, overriding is respected on every call. + * Unlike the Core Reflection API, exceptions are <em>not</em> wrapped. + * The type of the method handle will be that of the method, + * with the receiver type prepended (but only if it is non-static). + * If the method's {@code accessible} flag is not set, + * access checking is performed immediately on behalf of the lookup class. + * If <i>m</i> is not public, do not share the resulting handle with untrusted parties. + * <p> + * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the method's variable arity modifier bit ({@code 0x0080}) is set. + * <p> + * If <i>m</i> is static, and + * if the returned method handle is invoked, the method's class will + * be initialized, if it has not already been initialized. + * @param m the reflected method + * @return a method handle which can invoke the reflected method + * @throws IllegalAccessException if access checking fails + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @throws NullPointerException if the argument is null + */ + public MethodHandle unreflect(Method m) throws IllegalAccessException { + if (m == null) { + throw new NullPointerException("m == null"); + } + + MethodType methodType = MethodType.methodType(m.getReturnType(), + m.getParameterTypes()); + + // We should only perform access checks if setAccessible hasn't been called yet. + if (!m.isAccessible()) { + checkAccess(m.getDeclaringClass(), m.getDeclaringClass(), m.getModifiers(), + m.getName()); + } + + if (Modifier.isStatic(m.getModifiers())) { + return createMethodHandle(m, MethodHandle.INVOKE_STATIC, methodType); + } else { + methodType = methodType.insertParameterTypes(0, m.getDeclaringClass()); + return createMethodHandle(m, MethodHandle.INVOKE_VIRTUAL, methodType); + } + } + + /** + * Produces a method handle for a reflected method. + * It will bypass checks for overriding methods on the receiver, + * <a href="MethodHandles.Lookup.html#equiv">as if called</a> from an {@code invokespecial} + * instruction from within the explicitly specified {@code specialCaller}. + * The type of the method handle will be that of the method, + * with a suitably restricted receiver type prepended. + * (The receiver type will be {@code specialCaller} or a subtype.) + * If the method's {@code accessible} flag is not set, + * access checking is performed immediately on behalf of the lookup class, + * as if {@code invokespecial} instruction were being linked. + * <p> + * Before method resolution, + * if the explicitly specified caller class is not identical with the + * lookup class, or if this lookup object does not have + * <a href="MethodHandles.Lookup.html#privacc">private access</a> + * privileges, the access fails. + * <p> + * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the method's variable arity modifier bit ({@code 0x0080}) is set. + * @param m the reflected method + * @param specialCaller the class nominally calling the method + * @return a method handle which can invoke the reflected method + * @throws IllegalAccessException if access checking fails + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @throws NullPointerException if any argument is null + */ + public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) throws IllegalAccessException { + if (m == null) { + throw new NullPointerException("m == null"); + } + + if (specialCaller == null) { + throw new NullPointerException("specialCaller == null"); + } + + if (!m.isAccessible()) { + checkSpecialCaller(specialCaller); + } + + final MethodType methodType = MethodType.methodType(m.getReturnType(), + m.getParameterTypes()); + return findSpecial(m, methodType, m.getDeclaringClass() /* refc */, specialCaller); + } + + /** + * Produces a method handle for a reflected constructor. + * The type of the method handle will be that of the constructor, + * with the return type changed to the declaring class. + * The method handle will perform a {@code newInstance} operation, + * creating a new instance of the constructor's class on the + * arguments passed to the method handle. + * <p> + * If the constructor's {@code accessible} flag is not set, + * access checking is performed immediately on behalf of the lookup class. + * <p> + * The returned method handle will have + * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if + * the constructor's variable arity modifier bit ({@code 0x0080}) is set. + * <p> + * If the returned method handle is invoked, the constructor's class will + * be initialized, if it has not already been initialized. + * @param c the reflected constructor + * @return a method handle which can invoke the reflected constructor + * @throws IllegalAccessException if access checking fails + * or if the method's variable arity modifier bit + * is set and {@code asVarargsCollector} fails + * @throws NullPointerException if the argument is null + */ + public MethodHandle unreflectConstructor(Constructor<?> c) throws IllegalAccessException { + if (c == null) { + throw new NullPointerException("c == null"); + } + + if (!c.isAccessible()) { + checkAccess(c.getDeclaringClass(), c.getDeclaringClass(), c.getModifiers(), + c.getName()); + } + + return createMethodHandleForConstructor(c); + } + + /** + * Produces a method handle giving read access to a reflected field. + * The type of the method handle will have a return type of the field's + * value type. + * If the field is static, the method handle will take no arguments. + * Otherwise, its single argument will be the instance containing + * the field. + * If the field's {@code accessible} flag is not set, + * access checking is performed immediately on behalf of the lookup class. + * <p> + * If the field is static, and + * if the returned method handle is invoked, the field's class will + * be initialized, if it has not already been initialized. + * @param f the reflected field + * @return a method handle which can load values from the reflected field + * @throws IllegalAccessException if access checking fails + * @throws NullPointerException if the argument is null + */ + public MethodHandle unreflectGetter(Field f) throws IllegalAccessException { + return findAccessor(f, f.getDeclaringClass(), f.getType(), + Modifier.isStatic(f.getModifiers()) ? MethodHandle.SGET : MethodHandle.IGET, + f.isAccessible() /* performAccessChecks */); + } + + /** + * Produces a method handle giving write access to a reflected field. + * The type of the method handle will have a void return type. + * If the field is static, the method handle will take a single + * argument, of the field's value type, the value to be stored. + * Otherwise, the two arguments will be the instance containing + * the field, and the value to be stored. + * If the field's {@code accessible} flag is not set, + * access checking is performed immediately on behalf of the lookup class. + * <p> + * If the field is static, and + * if the returned method handle is invoked, the field's class will + * be initialized, if it has not already been initialized. + * @param f the reflected field + * @return a method handle which can store values into the reflected field + * @throws IllegalAccessException if access checking fails + * @throws NullPointerException if the argument is null + */ + public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { + return findAccessor(f, f.getDeclaringClass(), f.getType(), + Modifier.isStatic(f.getModifiers()) ? MethodHandle.SPUT : MethodHandle.IPUT, + f.isAccessible() /* performAccessChecks */); + } + + // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes. + /** + * Produces a VarHandle giving access to a reflected field {@code f} + * of type {@code T} declared in a class of type {@code R}. + * The VarHandle's variable type is {@code T}. + * If the field is non-static the VarHandle has one coordinate type, + * {@code R}. Otherwise, the field is static, and the VarHandle has no + * coordinate types. + * <p> + * Access checking is performed immediately on behalf of the lookup + * class, regardless of the value of the field's {@code accessible} + * flag. + * <p> + * If the field is static, and if the returned VarHandle is operated + * on, the field's declaring class will be initialized, if it has not + * already been initialized. + * <p> + * Certain access modes of the returned VarHandle are unsupported under + * the following conditions: + * <ul> + * <li>if the field is declared {@code final}, then the write, atomic + * update, numeric atomic update, and bitwise atomic update access + * modes are unsupported. + * <li>if the field type is anything other than {@code byte}, + * {@code short}, {@code char}, {@code int}, {@code long}, + * {@code float}, or {@code double} then numeric atomic update + * access modes are unsupported. + * <li>if the field type is anything other than {@code boolean}, + * {@code byte}, {@code short}, {@code char}, {@code int} or + * {@code long} then bitwise atomic update access modes are + * unsupported. + * </ul> + * <p> + * If the field is declared {@code volatile} then the returned VarHandle + * will override access to the field (effectively ignore the + * {@code volatile} declaration) in accordance to its specified + * access modes. + * <p> + * If the field type is {@code float} or {@code double} then numeric + * and atomic update access modes compare values using their bitwise + * representation (see {@link Float#floatToRawIntBits} and + * {@link Double#doubleToRawLongBits}, respectively). + * @apiNote + * Bitwise comparison of {@code float} values or {@code double} values, + * as performed by the numeric and atomic update access modes, differ + * from the primitive {@code ==} operator and the {@link Float#equals} + * and {@link Double#equals} methods, specifically with respect to + * comparing NaN values or comparing {@code -0.0} with {@code +0.0}. + * Care should be taken when performing a compare and set or a compare + * and exchange operation with such values since the operation may + * unexpectedly fail. + * There are many possible NaN values that are considered to be + * {@code NaN} in Java, although no IEEE 754 floating-point operation + * provided by Java can distinguish between them. Operation failure can + * occur if the expected or witness value is a NaN value and it is + * transformed (perhaps in a platform specific manner) into another NaN + * value, and thus has a different bitwise representation (see + * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more + * details). + * The values {@code -0.0} and {@code +0.0} have different bitwise + * representations but are considered equal when using the primitive + * {@code ==} operator. Operation failure can occur if, for example, a + * numeric algorithm computes an expected value to be say {@code -0.0} + * and previously computed the witness value to be say {@code +0.0}. + * @param f the reflected field, with a field of type {@code T}, and + * a declaring class of type {@code R} + * @return a VarHandle giving access to non-static fields or a static + * field + * @throws IllegalAccessException if access checking fails + * @throws NullPointerException if the argument is null + * @since 9 + * @hide + */ + public VarHandle unreflectVarHandle(Field f) throws IllegalAccessException { + unsupported("MethodHandles.Lookup.unreflectVarHandle()"); // TODO(b/65872996) + return null; + } + // END Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes. + + /** + * Cracks a <a href="MethodHandleInfo.html#directmh">direct method handle</a> + * created by this lookup object or a similar one. + * Security and access checks are performed to ensure that this lookup object + * is capable of reproducing the target method handle. + * This means that the cracking may fail if target is a direct method handle + * but was created by an unrelated lookup object. + * This can happen if the method handle is <a href="MethodHandles.Lookup.html#callsens">caller sensitive</a> + * and was created by a lookup object for a different class. + * @param target a direct method handle to crack into symbolic reference components + * @return a symbolic reference which can be used to reconstruct this method handle from this lookup object + * @exception SecurityException if a security manager is present and it + * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a> + * @throws IllegalArgumentException if the target is not a direct method handle or if access checking fails + * @exception NullPointerException if the target is {@code null} + * @see MethodHandleInfo + * @since 1.8 + */ + public MethodHandleInfo revealDirect(MethodHandle target) { + MethodHandleImpl directTarget = getMethodHandleImpl(target); + MethodHandleInfo info = directTarget.reveal(); + + try { + checkAccess(lookupClass(), info.getDeclaringClass(), info.getModifiers(), + info.getName()); + } catch (IllegalAccessException exception) { + throw new IllegalArgumentException("Unable to access memeber.", exception); + } + + return info; + } + + private boolean hasPrivateAccess() { + return (allowedModes & PRIVATE) != 0; + } + + /** Check public/protected/private bits on the symbolic reference class and its member. */ + void checkAccess(Class<?> refc, Class<?> defc, int mods, String methName) + throws IllegalAccessException { + int allowedModes = this.allowedModes; + + if (Modifier.isProtected(mods) && + defc == Object.class && + "clone".equals(methName) && + refc.isArray()) { + // The JVM does this hack also. + // (See ClassVerifier::verify_invoke_instructions + // and LinkResolver::check_method_accessability.) + // Because the JVM does not allow separate methods on array types, + // there is no separate method for int[].clone. + // All arrays simply inherit Object.clone. + // But for access checking logic, we make Object.clone + // (normally protected) appear to be public. + // Later on, when the DirectMethodHandle is created, + // its leading argument will be restricted to the + // requested array type. + // N.B. The return type is not adjusted, because + // that is *not* the bytecode behavior. + mods ^= Modifier.PROTECTED | Modifier.PUBLIC; + } + + if (Modifier.isProtected(mods) && Modifier.isConstructor(mods)) { + // cannot "new" a protected ctor in a different package + mods ^= Modifier.PROTECTED; + } + + if (Modifier.isPublic(mods) && Modifier.isPublic(refc.getModifiers()) && allowedModes != 0) + return; // common case + int requestedModes = fixmods(mods); // adjust 0 => PACKAGE + if ((requestedModes & allowedModes) != 0) { + if (VerifyAccess.isMemberAccessible(refc, defc, mods, lookupClass(), allowedModes)) + return; + } else { + // Protected members can also be checked as if they were package-private. + if ((requestedModes & PROTECTED) != 0 && (allowedModes & PACKAGE) != 0 + && VerifyAccess.isSamePackage(defc, lookupClass())) + return; + } + + throwMakeAccessException(accessFailedMessage(refc, defc, mods), this); + } + + String accessFailedMessage(Class<?> refc, Class<?> defc, int mods) { + // check the class first: + boolean classOK = (Modifier.isPublic(defc.getModifiers()) && + (defc == refc || + Modifier.isPublic(refc.getModifiers()))); + if (!classOK && (allowedModes & PACKAGE) != 0) { + classOK = (VerifyAccess.isClassAccessible(defc, lookupClass(), ALL_MODES) && + (defc == refc || + VerifyAccess.isClassAccessible(refc, lookupClass(), ALL_MODES))); + } + if (!classOK) + return "class is not public"; + if (Modifier.isPublic(mods)) + return "access to public member failed"; // (how?) + if (Modifier.isPrivate(mods)) + return "member is private"; + if (Modifier.isProtected(mods)) + return "member is protected"; + return "member is private to package"; + } + + // Android-changed: checkSpecialCaller assumes that ALLOW_NESTMATE_ACCESS = false, + // as in upstream OpenJDK. + // + // private static final boolean ALLOW_NESTMATE_ACCESS = false; - public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; } + private void checkSpecialCaller(Class<?> specialCaller) throws IllegalAccessException { + // Android-changed: No support for TRUSTED lookups. Also construct the + // IllegalAccessException by hand because the upstream code implicitly assumes + // that the lookupClass == specialCaller. + // + // if (allowedModes == TRUSTED) return; + if (!hasPrivateAccess() || (specialCaller != lookupClass())) { + throw new IllegalAccessException("no private access for invokespecial : " + + specialCaller + ", from" + this); + } + } - public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; } + private void throwMakeAccessException(String message, Object from) throws + IllegalAccessException{ + message = message + ": "+ toString(); + if (from != null) message += ", from " + from; + throw new IllegalAccessException(message); + } - public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; } + private void checkReturnType(Method method, MethodType methodType) + throws NoSuchMethodException { + if (method.getReturnType() != methodType.rtype()) { + throw new NoSuchMethodException(method.getName() + methodType); + } + } + } + + /** + * "Cracks" {@code target} to reveal the underlying {@code MethodHandleImpl}. + */ + private static MethodHandleImpl getMethodHandleImpl(MethodHandle target) { + // Special case : We implement handles to constructors as transformers, + // so we must extract the underlying handle from the transformer. + if (target instanceof Transformers.Construct) { + target = ((Transformers.Construct) target).getConstructorHandle(); + } - public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; } + // Special case: Var-args methods are also implemented as Transformers, + // so we should get the underlying handle in that case as well. + if (target instanceof Transformers.VarargsCollector) { + target = target.asFixedArity(); + } - public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; } + if (target instanceof MethodHandleImpl) { + return (MethodHandleImpl) target; + } - public MethodHandle unreflect(Method m) throws IllegalAccessException { return null; } + throw new IllegalArgumentException(target + " is not a direct handle"); + } - public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) throws IllegalAccessException { return null; } + /** + * Produces a method handle giving read access to elements of an array. + * The type of the method handle will have a return type of the array's + * element type. Its first argument will be the array type, + * and the second will be {@code int}. + * @param arrayClass an array type + * @return a method handle which can load values from the given array type + * @throws NullPointerException if the argument is null + * @throws IllegalArgumentException if arrayClass is not an array type + */ + public static + MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException { + final Class<?> componentType = arrayClass.getComponentType(); + if (componentType == null) { + throw new IllegalArgumentException("Not an array type: " + arrayClass); + } - public MethodHandle unreflectConstructor(Constructor<?> c) throws IllegalAccessException { return null; } + if (componentType.isPrimitive()) { + try { + return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class, + "arrayElementGetter", + MethodType.methodType(componentType, arrayClass, int.class)); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new AssertionError(exception); + } + } - public MethodHandle unreflectGetter(Field f) throws IllegalAccessException { return null; } + return new Transformers.ReferenceArrayElementGetter(arrayClass); + } - public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { return null; } + /** @hide */ public static byte arrayElementGetter(byte[] array, int i) { return array[i]; } + /** @hide */ public static boolean arrayElementGetter(boolean[] array, int i) { return array[i]; } + /** @hide */ public static char arrayElementGetter(char[] array, int i) { return array[i]; } + /** @hide */ public static short arrayElementGetter(short[] array, int i) { return array[i]; } + /** @hide */ public static int arrayElementGetter(int[] array, int i) { return array[i]; } + /** @hide */ public static long arrayElementGetter(long[] array, int i) { return array[i]; } + /** @hide */ public static float arrayElementGetter(float[] array, int i) { return array[i]; } + /** @hide */ public static double arrayElementGetter(double[] array, int i) { return array[i]; } - public MethodHandleInfo revealDirect(MethodHandle target) { return null; } + /** + * Produces a method handle giving write access to elements of an array. + * The type of the method handle will have a void return type. + * Its last argument will be the array's element type. + * The first and second arguments will be the array type and int. + * @param arrayClass the class of an array + * @return a method handle which can store values into the array type + * @throws NullPointerException if the argument is null + * @throws IllegalArgumentException if arrayClass is not an array type + */ + public static + MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException { + final Class<?> componentType = arrayClass.getComponentType(); + if (componentType == null) { + throw new IllegalArgumentException("Not an array type: " + arrayClass); + } + if (componentType.isPrimitive()) { + try { + return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class, + "arrayElementSetter", + MethodType.methodType(void.class, arrayClass, int.class, componentType)); + } catch (NoSuchMethodException | IllegalAccessException exception) { + throw new AssertionError(exception); + } + } + + return new Transformers.ReferenceArrayElementSetter(arrayClass); + } + + /** @hide */ + public static void arrayElementSetter(byte[] array, int i, byte val) { array[i] = val; } + /** @hide */ + public static void arrayElementSetter(boolean[] array, int i, boolean val) { array[i] = val; } + /** @hide */ + public static void arrayElementSetter(char[] array, int i, char val) { array[i] = val; } + /** @hide */ + public static void arrayElementSetter(short[] array, int i, short val) { array[i] = val; } + /** @hide */ + public static void arrayElementSetter(int[] array, int i, int val) { array[i] = val; } + /** @hide */ + public static void arrayElementSetter(long[] array, int i, long val) { array[i] = val; } + /** @hide */ + public static void arrayElementSetter(float[] array, int i, float val) { array[i] = val; } + /** @hide */ + public static void arrayElementSetter(double[] array, int i, double val) { array[i] = val; } + + // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory methods for bring up purposes. + /** + * Produces a VarHandle giving access to elements of an array of type + * {@code arrayClass}. The VarHandle's variable type is the component type + * of {@code arrayClass} and the list of coordinate types is + * {@code (arrayClass, int)}, where the {@code int} coordinate type + * corresponds to an argument that is an index into an array. + * <p> + * Certain access modes of the returned VarHandle are unsupported under + * the following conditions: + * <ul> + * <li>if the component type is anything other than {@code byte}, + * {@code short}, {@code char}, {@code int}, {@code long}, + * {@code float}, or {@code double} then numeric atomic update access + * modes are unsupported. + * <li>if the field type is anything other than {@code boolean}, + * {@code byte}, {@code short}, {@code char}, {@code int} or + * {@code long} then bitwise atomic update access modes are + * unsupported. + * </ul> + * <p> + * If the component type is {@code float} or {@code double} then numeric + * and atomic update access modes compare values using their bitwise + * representation (see {@link Float#floatToRawIntBits} and + * {@link Double#doubleToRawLongBits}, respectively). + * @apiNote + * Bitwise comparison of {@code float} values or {@code double} values, + * as performed by the numeric and atomic update access modes, differ + * from the primitive {@code ==} operator and the {@link Float#equals} + * and {@link Double#equals} methods, specifically with respect to + * comparing NaN values or comparing {@code -0.0} with {@code +0.0}. + * Care should be taken when performing a compare and set or a compare + * and exchange operation with such values since the operation may + * unexpectedly fail. + * There are many possible NaN values that are considered to be + * {@code NaN} in Java, although no IEEE 754 floating-point operation + * provided by Java can distinguish between them. Operation failure can + * occur if the expected or witness value is a NaN value and it is + * transformed (perhaps in a platform specific manner) into another NaN + * value, and thus has a different bitwise representation (see + * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more + * details). + * The values {@code -0.0} and {@code +0.0} have different bitwise + * representations but are considered equal when using the primitive + * {@code ==} operator. Operation failure can occur if, for example, a + * numeric algorithm computes an expected value to be say {@code -0.0} + * and previously computed the witness value to be say {@code +0.0}. + * @param arrayClass the class of an array, of type {@code T[]} + * @return a VarHandle giving access to elements of an array + * @throws NullPointerException if the arrayClass is null + * @throws IllegalArgumentException if arrayClass is not an array type + * @since 9 + * @hide + */ + public static + VarHandle arrayElementVarHandle(Class<?> arrayClass) throws IllegalArgumentException { + unsupported("MethodHandles.arrayElementVarHandle()"); // TODO(b/65872996) + return null; } + /** + * Produces a VarHandle giving access to elements of a {@code byte[]} array + * viewed as if it were a different primitive array type, such as + * {@code int[]} or {@code long[]}. + * The VarHandle's variable type is the component type of + * {@code viewArrayClass} and the list of coordinate types is + * {@code (byte[], int)}, where the {@code int} coordinate type + * corresponds to an argument that is an index into a {@code byte[]} array. + * The returned VarHandle accesses bytes at an index in a {@code byte[]} + * array, composing bytes to or from a value of the component type of + * {@code viewArrayClass} according to the given endianness. + * <p> + * The supported component types (variables types) are {@code short}, + * {@code char}, {@code int}, {@code long}, {@code float} and + * {@code double}. + * <p> + * Access of bytes at a given index will result in an + * {@code IndexOutOfBoundsException} if the index is less than {@code 0} + * or greater than the {@code byte[]} array length minus the size (in bytes) + * of {@code T}. + * <p> + * Access of bytes at an index may be aligned or misaligned for {@code T}, + * with respect to the underlying memory address, {@code A} say, associated + * with the array and index. + * If access is misaligned then access for anything other than the + * {@code get} and {@code set} access modes will result in an + * {@code IllegalStateException}. In such cases atomic access is only + * guaranteed with respect to the largest power of two that divides the GCD + * of {@code A} and the size (in bytes) of {@code T}. + * If access is aligned then following access modes are supported and are + * guaranteed to support atomic access: + * <ul> + * <li>read write access modes for all {@code T}, with the exception of + * access modes {@code get} and {@code set} for {@code long} and + * {@code double} on 32-bit platforms. + * <li>atomic update access modes for {@code int}, {@code long}, + * {@code float} or {@code double}. + * (Future major platform releases of the JDK may support additional + * types for certain currently unsupported access modes.) + * <li>numeric atomic update access modes for {@code int} and {@code long}. + * (Future major platform releases of the JDK may support additional + * numeric types for certain currently unsupported access modes.) + * <li>bitwise atomic update access modes for {@code int} and {@code long}. + * (Future major platform releases of the JDK may support additional + * numeric types for certain currently unsupported access modes.) + * </ul> + * <p> + * Misaligned access, and therefore atomicity guarantees, may be determined + * for {@code byte[]} arrays without operating on a specific array. Given + * an {@code index}, {@code T} and it's corresponding boxed type, + * {@code T_BOX}, misalignment may be determined as follows: + * <pre>{@code + * int sizeOfT = T_BOX.BYTES; // size in bytes of T + * int misalignedAtZeroIndex = ByteBuffer.wrap(new byte[0]). + * alignmentOffset(0, sizeOfT); + * int misalignedAtIndex = (misalignedAtZeroIndex + index) % sizeOfT; + * boolean isMisaligned = misalignedAtIndex != 0; + * }</pre> + * <p> + * If the variable type is {@code float} or {@code double} then atomic + * update access modes compare values using their bitwise representation + * (see {@link Float#floatToRawIntBits} and + * {@link Double#doubleToRawLongBits}, respectively). + * @param viewArrayClass the view array class, with a component type of + * type {@code T} + * @param byteOrder the endianness of the view array elements, as + * stored in the underlying {@code byte} array + * @return a VarHandle giving access to elements of a {@code byte[]} array + * viewed as if elements corresponding to the components type of the view + * array class + * @throws NullPointerException if viewArrayClass or byteOrder is null + * @throws IllegalArgumentException if viewArrayClass is not an array type + * @throws UnsupportedOperationException if the component type of + * viewArrayClass is not supported as a variable type + * @since 9 + * @hide + */ public static - MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException { return null; } + VarHandle byteArrayViewVarHandle(Class<?> viewArrayClass, + ByteOrder byteOrder) throws IllegalArgumentException { + unsupported("MethodHandles.byteArrayViewVarHandle()"); // TODO(b/65872996) + return null; + } + /** + * Produces a VarHandle giving access to elements of a {@code ByteBuffer} + * viewed as if it were an array of elements of a different primitive + * component type to that of {@code byte}, such as {@code int[]} or + * {@code long[]}. + * The VarHandle's variable type is the component type of + * {@code viewArrayClass} and the list of coordinate types is + * {@code (ByteBuffer, int)}, where the {@code int} coordinate type + * corresponds to an argument that is an index into a {@code byte[]} array. + * The returned VarHandle accesses bytes at an index in a + * {@code ByteBuffer}, composing bytes to or from a value of the component + * type of {@code viewArrayClass} according to the given endianness. + * <p> + * The supported component types (variables types) are {@code short}, + * {@code char}, {@code int}, {@code long}, {@code float} and + * {@code double}. + * <p> + * Access will result in a {@code ReadOnlyBufferException} for anything + * other than the read access modes if the {@code ByteBuffer} is read-only. + * <p> + * Access of bytes at a given index will result in an + * {@code IndexOutOfBoundsException} if the index is less than {@code 0} + * or greater than the {@code ByteBuffer} limit minus the size (in bytes) of + * {@code T}. + * <p> + * Access of bytes at an index may be aligned or misaligned for {@code T}, + * with respect to the underlying memory address, {@code A} say, associated + * with the {@code ByteBuffer} and index. + * If access is misaligned then access for anything other than the + * {@code get} and {@code set} access modes will result in an + * {@code IllegalStateException}. In such cases atomic access is only + * guaranteed with respect to the largest power of two that divides the GCD + * of {@code A} and the size (in bytes) of {@code T}. + * If access is aligned then following access modes are supported and are + * guaranteed to support atomic access: + * <ul> + * <li>read write access modes for all {@code T}, with the exception of + * access modes {@code get} and {@code set} for {@code long} and + * {@code double} on 32-bit platforms. + * <li>atomic update access modes for {@code int}, {@code long}, + * {@code float} or {@code double}. + * (Future major platform releases of the JDK may support additional + * types for certain currently unsupported access modes.) + * <li>numeric atomic update access modes for {@code int} and {@code long}. + * (Future major platform releases of the JDK may support additional + * numeric types for certain currently unsupported access modes.) + * <li>bitwise atomic update access modes for {@code int} and {@code long}. + * (Future major platform releases of the JDK may support additional + * numeric types for certain currently unsupported access modes.) + * </ul> + * <p> + * Misaligned access, and therefore atomicity guarantees, may be determined + * for a {@code ByteBuffer}, {@code bb} (direct or otherwise), an + * {@code index}, {@code T} and it's corresponding boxed type, + * {@code T_BOX}, as follows: + * <pre>{@code + * int sizeOfT = T_BOX.BYTES; // size in bytes of T + * ByteBuffer bb = ... + * int misalignedAtIndex = bb.alignmentOffset(index, sizeOfT); + * boolean isMisaligned = misalignedAtIndex != 0; + * }</pre> + * <p> + * If the variable type is {@code float} or {@code double} then atomic + * update access modes compare values using their bitwise representation + * (see {@link Float#floatToRawIntBits} and + * {@link Double#doubleToRawLongBits}, respectively). + * @param viewArrayClass the view array class, with a component type of + * type {@code T} + * @param byteOrder the endianness of the view array elements, as + * stored in the underlying {@code ByteBuffer} (Note this overrides the + * endianness of a {@code ByteBuffer}) + * @return a VarHandle giving access to elements of a {@code ByteBuffer} + * viewed as if elements corresponding to the components type of the view + * array class + * @throws NullPointerException if viewArrayClass or byteOrder is null + * @throws IllegalArgumentException if viewArrayClass is not an array type + * @throws UnsupportedOperationException if the component type of + * viewArrayClass is not supported as a variable type + * @since 9 + * @hide + */ public static - MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException { return null; } + VarHandle byteBufferViewVarHandle(Class<?> viewArrayClass, + ByteOrder byteOrder) throws IllegalArgumentException { + + unsupported("MethodHandles.byteBufferViewVarHandle()"); // TODO(b/65872996) + return null; + } + // END Android-changed: OpenJDK 9+181 VarHandle API factory methods for bring up purposes. + + /// method handle invocation (reflective style) + + /** + * Produces a method handle which will invoke any method handle of the + * given {@code type}, with a given number of trailing arguments replaced by + * a single trailing {@code Object[]} array. + * The resulting invoker will be a method handle with the following + * arguments: + * <ul> + * <li>a single {@code MethodHandle} target + * <li>zero or more leading values (counted by {@code leadingArgCount}) + * <li>an {@code Object[]} array containing trailing arguments + * </ul> + * <p> + * The invoker will invoke its target like a call to {@link MethodHandle#invoke invoke} with + * the indicated {@code type}. + * That is, if the target is exactly of the given {@code type}, it will behave + * like {@code invokeExact}; otherwise it behave as if {@link MethodHandle#asType asType} + * is used to convert the target to the required {@code type}. + * <p> + * The type of the returned invoker will not be the given {@code type}, but rather + * will have all parameters except the first {@code leadingArgCount} + * replaced by a single array of type {@code Object[]}, which will be + * the final parameter. + * <p> + * Before invoking its target, the invoker will spread the final array, apply + * reference casts as necessary, and unbox and widen primitive arguments. + * If, when the invoker is called, the supplied array argument does + * not have the correct number of elements, the invoker will throw + * an {@link IllegalArgumentException} instead of invoking the target. + * <p> + * This method is equivalent to the following code (though it may be more efficient): + * <blockquote><pre>{@code +MethodHandle invoker = MethodHandles.invoker(type); +int spreadArgCount = type.parameterCount() - leadingArgCount; +invoker = invoker.asSpreader(Object[].class, spreadArgCount); +return invoker; + * }</pre></blockquote> + * This method throws no reflective or security exceptions. + * @param type the desired target type + * @param leadingArgCount number of fixed arguments, to be passed unchanged to the target + * @return a method handle suitable for invoking any method handle of the given type + * @throws NullPointerException if {@code type} is null + * @throws IllegalArgumentException if {@code leadingArgCount} is not in + * the range from 0 to {@code type.parameterCount()} inclusive, + * or if the resulting method handle's type would have + * <a href="MethodHandle.html#maxarity">too many parameters</a> + */ + static public + MethodHandle spreadInvoker(MethodType type, int leadingArgCount) { + if (leadingArgCount < 0 || leadingArgCount > type.parameterCount()) + throw newIllegalArgumentException("bad argument count", leadingArgCount); + + MethodHandle invoker = MethodHandles.invoker(type); + int spreadArgCount = type.parameterCount() - leadingArgCount; + invoker = invoker.asSpreader(Object[].class, spreadArgCount); + return invoker; + } + + /** + * Produces a special <em>invoker method handle</em> which can be used to + * invoke any method handle of the given type, as if by {@link MethodHandle#invokeExact invokeExact}. + * The resulting invoker will have a type which is + * exactly equal to the desired type, except that it will accept + * an additional leading argument of type {@code MethodHandle}. + * <p> + * This method is equivalent to the following code (though it may be more efficient): + * {@code publicLookup().findVirtual(MethodHandle.class, "invokeExact", type)} + * + * <p style="font-size:smaller;"> + * <em>Discussion:</em> + * Invoker method handles can be useful when working with variable method handles + * of unknown types. + * For example, to emulate an {@code invokeExact} call to a variable method + * handle {@code M}, extract its type {@code T}, + * look up the invoker method {@code X} for {@code T}, + * and call the invoker method, as {@code X.invoke(T, A...)}. + * (It would not work to call {@code X.invokeExact}, since the type {@code T} + * is unknown.) + * If spreading, collecting, or other argument transformations are required, + * they can be applied once to the invoker {@code X} and reused on many {@code M} + * method handle values, as long as they are compatible with the type of {@code X}. + * <p style="font-size:smaller;"> + * <em>(Note: The invoker method is not available via the Core Reflection API. + * An attempt to call {@linkplain java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke} + * on the declared {@code invokeExact} or {@code invoke} method will raise an + * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}.)</em> + * <p> + * This method throws no reflective or security exceptions. + * @param type the desired target type + * @return a method handle suitable for invoking any method handle of the given type + * @throws IllegalArgumentException if the resulting method handle's type would have + * <a href="MethodHandle.html#maxarity">too many parameters</a> + */ + static public + MethodHandle exactInvoker(MethodType type) { + return new Transformers.Invoker(type, true /* isExactInvoker */); + } + /** + * Produces a special <em>invoker method handle</em> which can be used to + * invoke any method handle compatible with the given type, as if by {@link MethodHandle#invoke invoke}. + * The resulting invoker will have a type which is + * exactly equal to the desired type, except that it will accept + * an additional leading argument of type {@code MethodHandle}. + * <p> + * Before invoking its target, if the target differs from the expected type, + * the invoker will apply reference casts as + * necessary and box, unbox, or widen primitive values, as if by {@link MethodHandle#asType asType}. + * Similarly, the return value will be converted as necessary. + * If the target is a {@linkplain MethodHandle#asVarargsCollector variable arity method handle}, + * the required arity conversion will be made, again as if by {@link MethodHandle#asType asType}. + * <p> + * This method is equivalent to the following code (though it may be more efficient): + * {@code publicLookup().findVirtual(MethodHandle.class, "invoke", type)} + * <p style="font-size:smaller;"> + * <em>Discussion:</em> + * A {@linkplain MethodType#genericMethodType general method type} is one which + * mentions only {@code Object} arguments and return values. + * An invoker for such a type is capable of calling any method handle + * of the same arity as the general type. + * <p style="font-size:smaller;"> + * <em>(Note: The invoker method is not available via the Core Reflection API. + * An attempt to call {@linkplain java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke} + * on the declared {@code invokeExact} or {@code invoke} method will raise an + * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}.)</em> + * <p> + * This method throws no reflective or security exceptions. + * @param type the desired target type + * @return a method handle suitable for invoking any method handle convertible to the given type + * @throws IllegalArgumentException if the resulting method handle's type would have + * <a href="MethodHandle.html#maxarity">too many parameters</a> + */ static public - MethodHandle spreadInvoker(MethodType type, int leadingArgCount) { return null; } + MethodHandle invoker(MethodType type) { + return new Transformers.Invoker(type, false /* isExactInvoker */); + } + /** + * Produces a special <em>invoker method handle</em> which can be used to + * invoke a signature-polymorphic access mode method on any VarHandle whose + * associated access mode type is compatible with the given type. + * The resulting invoker will have a type which is exactly equal to the + * desired given type, except that it will accept an additional leading + * argument of type {@code VarHandle}. + * + * @param accessMode the VarHandle access mode + * @param type the desired target type + * @return a method handle suitable for invoking an access mode method of + * any VarHandle whose access mode type is of the given type. + * @since 9 + * @hide + */ static public - MethodHandle exactInvoker(MethodType type) { return null; } + MethodHandle varHandleExactInvoker(VarHandle.AccessMode accessMode, MethodType type) { + unsupported("MethodHandles.varHandleExactInvoker()"); // TODO(b/65872996) + return null; + } + /** + * Produces a special <em>invoker method handle</em> which can be used to + * invoke a signature-polymorphic access mode method on any VarHandle whose + * associated access mode type is compatible with the given type. + * The resulting invoker will have a type which is exactly equal to the + * desired given type, except that it will accept an additional leading + * argument of type {@code VarHandle}. + * <p> + * Before invoking its target, if the access mode type differs from the + * desired given type, the invoker will apply reference casts as necessary + * and box, unbox, or widen primitive values, as if by + * {@link MethodHandle#asType asType}. Similarly, the return value will be + * converted as necessary. + * <p> + * This method is equivalent to the following code (though it may be more + * efficient): {@code publicLookup().findVirtual(VarHandle.class, accessMode.name(), type)} + * + * @param accessMode the VarHandle access mode + * @param type the desired target type + * @return a method handle suitable for invoking an access mode method of + * any VarHandle whose access mode type is convertible to the given + * type. + * @since 9 + * @hide + */ static public - MethodHandle invoker(MethodType type) { return null; } + MethodHandle varHandleInvoker(VarHandle.AccessMode accessMode, MethodType type) { + unsupported("MethodHandles.varHandleInvoker()"); // TODO(b/65872996) + return null; + } + // Android-changed: Basic invokers are not supported. + // + // static /*non-public*/ + // MethodHandle basicInvoker(MethodType type) { + // return type.invokers().basicInvoker(); + // } + + /// method handle modification (creation from other method handles) + + /** + * Produces a method handle which adapts the type of the + * given method handle to a new type by pairwise argument and return type conversion. + * The original type and new type must have the same number of arguments. + * The resulting method handle is guaranteed to report a type + * which is equal to the desired new type. + * <p> + * If the original type and new type are equal, returns target. + * <p> + * The same conversions are allowed as for {@link MethodHandle#asType MethodHandle.asType}, + * and some additional conversions are also applied if those conversions fail. + * Given types <em>T0</em>, <em>T1</em>, one of the following conversions is applied + * if possible, before or instead of any conversions done by {@code asType}: + * <ul> + * <li>If <em>T0</em> and <em>T1</em> are references, and <em>T1</em> is an interface type, + * then the value of type <em>T0</em> is passed as a <em>T1</em> without a cast. + * (This treatment of interfaces follows the usage of the bytecode verifier.) + * <li>If <em>T0</em> is boolean and <em>T1</em> is another primitive, + * the boolean is converted to a byte value, 1 for true, 0 for false. + * (This treatment follows the usage of the bytecode verifier.) + * <li>If <em>T1</em> is boolean and <em>T0</em> is another primitive, + * <em>T0</em> is converted to byte via Java casting conversion (JLS 5.5), + * and the low order bit of the result is tested, as if by {@code (x & 1) != 0}. + * <li>If <em>T0</em> and <em>T1</em> are primitives other than boolean, + * then a Java casting conversion (JLS 5.5) is applied. + * (Specifically, <em>T0</em> will convert to <em>T1</em> by + * widening and/or narrowing.) + * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive, an unboxing + * conversion will be applied at runtime, possibly followed + * by a Java casting conversion (JLS 5.5) on the primitive value, + * possibly followed by a conversion from byte to boolean by testing + * the low-order bit. + * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive, + * and if the reference is null at runtime, a zero value is introduced. + * </ul> + * @param target the method handle to invoke after arguments are retyped + * @param newType the expected type of the new method handle + * @return a method handle which delegates to the target after performing + * any necessary argument conversions, and arranges for any + * necessary return value conversions + * @throws NullPointerException if either argument is null + * @throws WrongMethodTypeException if the conversion cannot be made + * @see MethodHandle#asType + */ public static - MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) { return null; } + MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) { + explicitCastArgumentsChecks(target, newType); + // use the asTypeCache when possible: + MethodType oldType = target.type(); + if (oldType == newType) return target; + if (oldType.explicitCastEquivalentToAsType(newType)) { + return target.asFixedArity().asType(newType); + } + return new Transformers.ExplicitCastArguments(target, newType); + } + + private static void explicitCastArgumentsChecks(MethodHandle target, MethodType newType) { + if (target.type().parameterCount() != newType.parameterCount()) { + throw new WrongMethodTypeException("cannot explicitly cast " + target + " to " + newType); + } + } + + /** + * Produces a method handle which adapts the calling sequence of the + * given method handle to a new type, by reordering the arguments. + * The resulting method handle is guaranteed to report a type + * which is equal to the desired new type. + * <p> + * The given array controls the reordering. + * Call {@code #I} the number of incoming parameters (the value + * {@code newType.parameterCount()}, and call {@code #O} the number + * of outgoing parameters (the value {@code target.type().parameterCount()}). + * Then the length of the reordering array must be {@code #O}, + * and each element must be a non-negative number less than {@code #I}. + * For every {@code N} less than {@code #O}, the {@code N}-th + * outgoing argument will be taken from the {@code I}-th incoming + * argument, where {@code I} is {@code reorder[N]}. + * <p> + * No argument or return value conversions are applied. + * The type of each incoming argument, as determined by {@code newType}, + * must be identical to the type of the corresponding outgoing parameter + * or parameters in the target method handle. + * The return type of {@code newType} must be identical to the return + * type of the original target. + * <p> + * The reordering array need not specify an actual permutation. + * An incoming argument will be duplicated if its index appears + * more than once in the array, and an incoming argument will be dropped + * if its index does not appear in the array. + * As in the case of {@link #dropArguments(MethodHandle,int,List) dropArguments}, + * incoming arguments which are not mentioned in the reordering array + * are may be any type, as determined only by {@code newType}. + * <blockquote><pre>{@code +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +... +MethodType intfn1 = methodType(int.class, int.class); +MethodType intfn2 = methodType(int.class, int.class, int.class); +MethodHandle sub = ... (int x, int y) -> (x-y) ...; +assert(sub.type().equals(intfn2)); +MethodHandle sub1 = permuteArguments(sub, intfn2, 0, 1); +MethodHandle rsub = permuteArguments(sub, intfn2, 1, 0); +assert((int)rsub.invokeExact(1, 100) == 99); +MethodHandle add = ... (int x, int y) -> (x+y) ...; +assert(add.type().equals(intfn2)); +MethodHandle twice = permuteArguments(add, intfn1, 0, 0); +assert(twice.type().equals(intfn1)); +assert((int)twice.invokeExact(21) == 42); + * }</pre></blockquote> + * @param target the method handle to invoke after arguments are reordered + * @param newType the expected type of the new method handle + * @param reorder an index array which controls the reordering + * @return a method handle which delegates to the target after it + * drops unused arguments and moves and/or duplicates the other arguments + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if the index array length is not equal to + * the arity of the target, or if any index array element + * not a valid index for a parameter of {@code newType}, + * or if two corresponding parameter types in + * {@code target.type()} and {@code newType} are not identical, + */ public static - MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) { return null; } + MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) { + reorder = reorder.clone(); // get a private copy + MethodType oldType = target.type(); + permuteArgumentChecks(reorder, newType, oldType); + + return new Transformers.PermuteArguments(newType, target, reorder); + } + // Android-changed: findFirstDupOrDrop is unused and removed. + // private static int findFirstDupOrDrop(int[] reorder, int newArity); + + private static boolean permuteArgumentChecks(int[] reorder, MethodType newType, MethodType oldType) { + if (newType.returnType() != oldType.returnType()) + throw newIllegalArgumentException("return types do not match", + oldType, newType); + if (reorder.length == oldType.parameterCount()) { + int limit = newType.parameterCount(); + boolean bad = false; + for (int j = 0; j < reorder.length; j++) { + int i = reorder[j]; + if (i < 0 || i >= limit) { + bad = true; break; + } + Class<?> src = newType.parameterType(i); + Class<?> dst = oldType.parameterType(j); + if (src != dst) + throw newIllegalArgumentException("parameter types do not match after reorder", + oldType, newType); + } + if (!bad) return true; + } + throw newIllegalArgumentException("bad reorder array: "+Arrays.toString(reorder)); + } + + /** + * Produces a method handle of the requested return type which returns the given + * constant value every time it is invoked. + * <p> + * Before the method handle is returned, the passed-in value is converted to the requested type. + * If the requested type is primitive, widening primitive conversions are attempted, + * else reference conversions are attempted. + * <p>The returned method handle is equivalent to {@code identity(type).bindTo(value)}. + * @param type the return type of the desired method handle + * @param value the value to return + * @return a method handle of the given return type and no arguments, which always returns the given value + * @throws NullPointerException if the {@code type} argument is null + * @throws ClassCastException if the value cannot be converted to the required return type + * @throws IllegalArgumentException if the given type is {@code void.class} + */ public static - MethodHandle constant(Class<?> type, Object value) { return null; } + MethodHandle constant(Class<?> type, Object value) { + if (type.isPrimitive()) { + if (type == void.class) + throw newIllegalArgumentException("void type"); + Wrapper w = Wrapper.forPrimitiveType(type); + value = w.convert(value, type); + } + return new Transformers.Constant(type, value); + } + + /** + * Produces a method handle which returns its sole argument when invoked. + * @param type the type of the sole parameter and return value of the desired method handle + * @return a unary method handle which accepts and returns the given type + * @throws NullPointerException if the argument is null + * @throws IllegalArgumentException if the given type is {@code void.class} + */ public static - MethodHandle identity(Class<?> type) { return null; } + MethodHandle identity(Class<?> type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + if (type.isPrimitive()) { + try { + return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class, "identity", + MethodType.methodType(type, type)); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new AssertionError(e); + } + } + return new Transformers.ReferenceIdentity(type); + } + + /** @hide */ public static byte identity(byte val) { return val; } + /** @hide */ public static boolean identity(boolean val) { return val; } + /** @hide */ public static char identity(char val) { return val; } + /** @hide */ public static short identity(short val) { return val; } + /** @hide */ public static int identity(int val) { return val; } + /** @hide */ public static long identity(long val) { return val; } + /** @hide */ public static float identity(float val) { return val; } + /** @hide */ public static double identity(double val) { return val; } + + /** + * Provides a target method handle with one or more <em>bound arguments</em> + * in advance of the method handle's invocation. + * The formal parameters to the target corresponding to the bound + * arguments are called <em>bound parameters</em>. + * Returns a new method handle which saves away the bound arguments. + * When it is invoked, it receives arguments for any non-bound parameters, + * binds the saved arguments to their corresponding parameters, + * and calls the original target. + * <p> + * The type of the new method handle will drop the types for the bound + * parameters from the original target type, since the new method handle + * will no longer require those arguments to be supplied by its callers. + * <p> + * Each given argument object must match the corresponding bound parameter type. + * If a bound parameter type is a primitive, the argument object + * must be a wrapper, and will be unboxed to produce the primitive value. + * <p> + * The {@code pos} argument selects which parameters are to be bound. + * It may range between zero and <i>N-L</i> (inclusively), + * where <i>N</i> is the arity of the target method handle + * and <i>L</i> is the length of the values array. + * @param target the method handle to invoke after the argument is inserted + * @param pos where to insert the argument (zero for the first) + * @param values the series of arguments to insert + * @return a method handle which inserts an additional argument, + * before calling the original method handle + * @throws NullPointerException if the target or the {@code values} array is null + * @see MethodHandle#bindTo + */ public static - MethodHandle insertArguments(MethodHandle target, int pos, Object... values) { return null; } + MethodHandle insertArguments(MethodHandle target, int pos, Object... values) { + int insCount = values.length; + Class<?>[] ptypes = insertArgumentsChecks(target, insCount, pos); + if (insCount == 0) { + return target; + } + // Throw ClassCastExceptions early if we can't cast any of the provided values + // to the required type. + for (int i = 0; i < insCount; i++) { + final Class<?> ptype = ptypes[pos + i]; + if (!ptype.isPrimitive()) { + ptypes[pos + i].cast(values[i]); + } else { + // Will throw a ClassCastException if something terrible happens. + values[i] = Wrapper.forPrimitiveType(ptype).convert(values[i], ptype); + } + } + + return new Transformers.InsertArguments(target, pos, values); + } + + // Android-changed: insertArgumentPrimitive is unused. + // + // private static BoundMethodHandle insertArgumentPrimitive(BoundMethodHandle result, int pos, + // Class<?> ptype, Object value) { + // Wrapper w = Wrapper.forPrimitiveType(ptype); + // // perform unboxing and/or primitive conversion + // value = w.convert(value, ptype); + // switch (w) { + // case INT: return result.bindArgumentI(pos, (int)value); + // case LONG: return result.bindArgumentJ(pos, (long)value); + // case FLOAT: return result.bindArgumentF(pos, (float)value); + // case DOUBLE: return result.bindArgumentD(pos, (double)value); + // default: return result.bindArgumentI(pos, ValueConversions.widenSubword(value)); + // } + // } + + private static Class<?>[] insertArgumentsChecks(MethodHandle target, int insCount, int pos) throws RuntimeException { + MethodType oldType = target.type(); + int outargs = oldType.parameterCount(); + int inargs = outargs - insCount; + if (inargs < 0) + throw newIllegalArgumentException("too many values to insert"); + if (pos < 0 || pos > inargs) + throw newIllegalArgumentException("no argument type to append"); + return oldType.ptypes(); + } + + /** + * Produces a method handle which will discard some dummy arguments + * before calling some other specified <i>target</i> method handle. + * The type of the new method handle will be the same as the target's type, + * except it will also include the dummy argument types, + * at some given position. + * <p> + * The {@code pos} argument may range between zero and <i>N</i>, + * where <i>N</i> is the arity of the target. + * If {@code pos} is zero, the dummy arguments will precede + * the target's real arguments; if {@code pos} is <i>N</i> + * they will come after. + * <p> + * <b>Example:</b> + * <blockquote><pre>{@code +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +... +MethodHandle cat = lookup().findVirtual(String.class, + "concat", methodType(String.class, String.class)); +assertEquals("xy", (String) cat.invokeExact("x", "y")); +MethodType bigType = cat.type().insertParameterTypes(0, int.class, String.class); +MethodHandle d0 = dropArguments(cat, 0, bigType.parameterList().subList(0,2)); +assertEquals(bigType, d0.type()); +assertEquals("yz", (String) d0.invokeExact(123, "x", "y", "z")); + * }</pre></blockquote> + * <p> + * This method is also equivalent to the following code: + * <blockquote><pre> + * {@link #dropArguments(MethodHandle,int,Class...) dropArguments}{@code (target, pos, valueTypes.toArray(new Class[0]))} + * </pre></blockquote> + * @param target the method handle to invoke after the arguments are dropped + * @param valueTypes the type(s) of the argument(s) to drop + * @param pos position of first argument to drop (zero for the leftmost) + * @return a method handle which drops arguments of the given types, + * before calling the original method handle + * @throws NullPointerException if the target is null, + * or if the {@code valueTypes} list or any of its elements is null + * @throws IllegalArgumentException if any element of {@code valueTypes} is {@code void.class}, + * or if {@code pos} is negative or greater than the arity of the target, + * or if the new method handle's type would have too many parameters + */ public static - MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) { return null; } + MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) { + valueTypes = copyTypes(valueTypes); + MethodType oldType = target.type(); // get NPE + int dropped = dropArgumentChecks(oldType, pos, valueTypes); + + MethodType newType = oldType.insertParameterTypes(pos, valueTypes); + if (dropped == 0) { + return target; + } + + return new Transformers.DropArguments(newType, target, pos, valueTypes.size()); + } + + private static List<Class<?>> copyTypes(List<Class<?>> types) { + Object[] a = types.toArray(); + return Arrays.asList(Arrays.copyOf(a, a.length, Class[].class)); + } + + private static int dropArgumentChecks(MethodType oldType, int pos, List<Class<?>> valueTypes) { + int dropped = valueTypes.size(); + MethodType.checkSlotCount(dropped); + int outargs = oldType.parameterCount(); + int inargs = outargs + dropped; + if (pos < 0 || pos > outargs) + throw newIllegalArgumentException("no argument type to remove" + + Arrays.asList(oldType, pos, valueTypes, inargs, outargs) + ); + return dropped; + } + /** + * Produces a method handle which will discard some dummy arguments + * before calling some other specified <i>target</i> method handle. + * The type of the new method handle will be the same as the target's type, + * except it will also include the dummy argument types, + * at some given position. + * <p> + * The {@code pos} argument may range between zero and <i>N</i>, + * where <i>N</i> is the arity of the target. + * If {@code pos} is zero, the dummy arguments will precede + * the target's real arguments; if {@code pos} is <i>N</i> + * they will come after. + * <p> + * <b>Example:</b> + * <blockquote><pre>{@code +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +... +MethodHandle cat = lookup().findVirtual(String.class, + "concat", methodType(String.class, String.class)); +assertEquals("xy", (String) cat.invokeExact("x", "y")); +MethodHandle d0 = dropArguments(cat, 0, String.class); +assertEquals("yz", (String) d0.invokeExact("x", "y", "z")); +MethodHandle d1 = dropArguments(cat, 1, String.class); +assertEquals("xz", (String) d1.invokeExact("x", "y", "z")); +MethodHandle d2 = dropArguments(cat, 2, String.class); +assertEquals("xy", (String) d2.invokeExact("x", "y", "z")); +MethodHandle d12 = dropArguments(cat, 1, int.class, boolean.class); +assertEquals("xz", (String) d12.invokeExact("x", 12, true, "z")); + * }</pre></blockquote> + * <p> + * This method is also equivalent to the following code: + * <blockquote><pre> + * {@link #dropArguments(MethodHandle,int,List) dropArguments}{@code (target, pos, Arrays.asList(valueTypes))} + * </pre></blockquote> + * @param target the method handle to invoke after the arguments are dropped + * @param valueTypes the type(s) of the argument(s) to drop + * @param pos position of first argument to drop (zero for the leftmost) + * @return a method handle which drops arguments of the given types, + * before calling the original method handle + * @throws NullPointerException if the target is null, + * or if the {@code valueTypes} array or any of its elements is null + * @throws IllegalArgumentException if any element of {@code valueTypes} is {@code void.class}, + * or if {@code pos} is negative or greater than the arity of the target, + * or if the new method handle's type would have + * <a href="MethodHandle.html#maxarity">too many parameters</a> + */ public static - MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) { return null; } + MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) { + return dropArguments(target, pos, Arrays.asList(valueTypes)); + } + /** + * Adapts a target method handle by pre-processing + * one or more of its arguments, each with its own unary filter function, + * and then calling the target with each pre-processed argument + * replaced by the result of its corresponding filter function. + * <p> + * The pre-processing is performed by one or more method handles, + * specified in the elements of the {@code filters} array. + * The first element of the filter array corresponds to the {@code pos} + * argument of the target, and so on in sequence. + * <p> + * Null arguments in the array are treated as identity functions, + * and the corresponding arguments left unchanged. + * (If there are no non-null elements in the array, the original target is returned.) + * Each filter is applied to the corresponding argument of the adapter. + * <p> + * If a filter {@code F} applies to the {@code N}th argument of + * the target, then {@code F} must be a method handle which + * takes exactly one argument. The type of {@code F}'s sole argument + * replaces the corresponding argument type of the target + * in the resulting adapted method handle. + * The return type of {@code F} must be identical to the corresponding + * parameter type of the target. + * <p> + * It is an error if there are elements of {@code filters} + * (null or not) + * which do not correspond to argument positions in the target. + * <p><b>Example:</b> + * <blockquote><pre>{@code +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +... +MethodHandle cat = lookup().findVirtual(String.class, + "concat", methodType(String.class, String.class)); +MethodHandle upcase = lookup().findVirtual(String.class, + "toUpperCase", methodType(String.class)); +assertEquals("xy", (String) cat.invokeExact("x", "y")); +MethodHandle f0 = filterArguments(cat, 0, upcase); +assertEquals("Xy", (String) f0.invokeExact("x", "y")); // Xy +MethodHandle f1 = filterArguments(cat, 1, upcase); +assertEquals("xY", (String) f1.invokeExact("x", "y")); // xY +MethodHandle f2 = filterArguments(cat, 0, upcase, upcase); +assertEquals("XY", (String) f2.invokeExact("x", "y")); // XY + * }</pre></blockquote> + * <p> Here is pseudocode for the resulting adapter: + * <blockquote><pre>{@code + * V target(P... p, A[i]... a[i], B... b); + * A[i] filter[i](V[i]); + * T adapter(P... p, V[i]... v[i], B... b) { + * return target(p..., f[i](v[i])..., b...); + * } + * }</pre></blockquote> + * + * @param target the method handle to invoke after arguments are filtered + * @param pos the position of the first argument to filter + * @param filters method handles to call initially on filtered arguments + * @return method handle which incorporates the specified argument filtering logic + * @throws NullPointerException if the target is null + * or if the {@code filters} array is null + * @throws IllegalArgumentException if a non-null element of {@code filters} + * does not match a corresponding argument type of target as described above, + * or if the {@code pos+filters.length} is greater than {@code target.type().parameterCount()}, + * or if the resulting method handle's type would have + * <a href="MethodHandle.html#maxarity">too many parameters</a> + */ public static - MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters) { return null; } + MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters) { + filterArgumentsCheckArity(target, pos, filters); + + for (int i = 0; i < filters.length; ++i) { + filterArgumentChecks(target, i + pos, filters[i]); + } + + return new Transformers.FilterArguments(target, pos, filters); + } + + private static void filterArgumentsCheckArity(MethodHandle target, int pos, MethodHandle[] filters) { + MethodType targetType = target.type(); + int maxPos = targetType.parameterCount(); + if (pos + filters.length > maxPos) + throw newIllegalArgumentException("too many filters"); + } + + private static void filterArgumentChecks(MethodHandle target, int pos, MethodHandle filter) throws RuntimeException { + MethodType targetType = target.type(); + MethodType filterType = filter.type(); + if (filterType.parameterCount() != 1 + || filterType.returnType() != targetType.parameterType(pos)) + throw newIllegalArgumentException("target and filter types do not match", targetType, filterType); + } + + /** + * Adapts a target method handle by pre-processing + * a sub-sequence of its arguments with a filter (another method handle). + * The pre-processed arguments are replaced by the result (if any) of the + * filter function. + * The target is then called on the modified (usually shortened) argument list. + * <p> + * If the filter returns a value, the target must accept that value as + * its argument in position {@code pos}, preceded and/or followed by + * any arguments not passed to the filter. + * If the filter returns void, the target must accept all arguments + * not passed to the filter. + * No arguments are reordered, and a result returned from the filter + * replaces (in order) the whole subsequence of arguments originally + * passed to the adapter. + * <p> + * The argument types (if any) of the filter + * replace zero or one argument types of the target, at position {@code pos}, + * in the resulting adapted method handle. + * The return type of the filter (if any) must be identical to the + * argument type of the target at position {@code pos}, and that target argument + * is supplied by the return value of the filter. + * <p> + * In all cases, {@code pos} must be greater than or equal to zero, and + * {@code pos} must also be less than or equal to the target's arity. + * <p><b>Example:</b> + * <blockquote><pre>{@code +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +... +MethodHandle deepToString = publicLookup() + .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class)); + +MethodHandle ts1 = deepToString.asCollector(String[].class, 1); +assertEquals("[strange]", (String) ts1.invokeExact("strange")); + +MethodHandle ts2 = deepToString.asCollector(String[].class, 2); +assertEquals("[up, down]", (String) ts2.invokeExact("up", "down")); +MethodHandle ts3 = deepToString.asCollector(String[].class, 3); +MethodHandle ts3_ts2 = collectArguments(ts3, 1, ts2); +assertEquals("[top, [up, down], strange]", + (String) ts3_ts2.invokeExact("top", "up", "down", "strange")); + +MethodHandle ts3_ts2_ts1 = collectArguments(ts3_ts2, 3, ts1); +assertEquals("[top, [up, down], [strange]]", + (String) ts3_ts2_ts1.invokeExact("top", "up", "down", "strange")); + +MethodHandle ts3_ts2_ts3 = collectArguments(ts3_ts2, 1, ts3); +assertEquals("[top, [[up, down, strange], charm], bottom]", + (String) ts3_ts2_ts3.invokeExact("top", "up", "down", "strange", "charm", "bottom")); + * }</pre></blockquote> + * <p> Here is pseudocode for the resulting adapter: + * <blockquote><pre>{@code + * T target(A...,V,C...); + * V filter(B...); + * T adapter(A... a,B... b,C... c) { + * V v = filter(b...); + * return target(a...,v,c...); + * } + * // and if the filter has no arguments: + * T target2(A...,V,C...); + * V filter2(); + * T adapter2(A... a,C... c) { + * V v = filter2(); + * return target2(a...,v,c...); + * } + * // and if the filter has a void return: + * T target3(A...,C...); + * void filter3(B...); + * void adapter3(A... a,B... b,C... c) { + * filter3(b...); + * return target3(a...,c...); + * } + * }</pre></blockquote> + * <p> + * A collection adapter {@code collectArguments(mh, 0, coll)} is equivalent to + * one which first "folds" the affected arguments, and then drops them, in separate + * steps as follows: + * <blockquote><pre>{@code + * mh = MethodHandles.dropArguments(mh, 1, coll.type().parameterList()); //step 2 + * mh = MethodHandles.foldArguments(mh, coll); //step 1 + * }</pre></blockquote> + * If the target method handle consumes no arguments besides than the result + * (if any) of the filter {@code coll}, then {@code collectArguments(mh, 0, coll)} + * is equivalent to {@code filterReturnValue(coll, mh)}. + * If the filter method handle {@code coll} consumes one argument and produces + * a non-void result, then {@code collectArguments(mh, N, coll)} + * is equivalent to {@code filterArguments(mh, N, coll)}. + * Other equivalences are possible but would require argument permutation. + * + * @param target the method handle to invoke after filtering the subsequence of arguments + * @param pos the position of the first adapter argument to pass to the filter, + * and/or the target argument which receives the result of the filter + * @param filter method handle to call on the subsequence of arguments + * @return method handle which incorporates the specified argument subsequence filtering logic + * @throws NullPointerException if either argument is null + * @throws IllegalArgumentException if the return type of {@code filter} + * is non-void and is not the same as the {@code pos} argument of the target, + * or if {@code pos} is not between 0 and the target's arity, inclusive, + * or if the resulting method handle's type would have + * <a href="MethodHandle.html#maxarity">too many parameters</a> + * @see MethodHandles#foldArguments + * @see MethodHandles#filterArguments + * @see MethodHandles#filterReturnValue + */ public static - MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter) { return null; } + MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter) { + MethodType newType = collectArgumentsChecks(target, pos, filter); + return new Transformers.CollectArguments(target, filter, pos, newType); + } + private static MethodType collectArgumentsChecks(MethodHandle target, int pos, MethodHandle filter) throws RuntimeException { + MethodType targetType = target.type(); + MethodType filterType = filter.type(); + Class<?> rtype = filterType.returnType(); + List<Class<?>> filterArgs = filterType.parameterList(); + if (rtype == void.class) { + return targetType.insertParameterTypes(pos, filterArgs); + } + if (rtype != targetType.parameterType(pos)) { + throw newIllegalArgumentException("target and filter types do not match", targetType, filterType); + } + return targetType.dropParameterTypes(pos, pos+1).insertParameterTypes(pos, filterArgs); + } + + /** + * Adapts a target method handle by post-processing + * its return value (if any) with a filter (another method handle). + * The result of the filter is returned from the adapter. + * <p> + * If the target returns a value, the filter must accept that value as + * its only argument. + * If the target returns void, the filter must accept no arguments. + * <p> + * The return type of the filter + * replaces the return type of the target + * in the resulting adapted method handle. + * The argument type of the filter (if any) must be identical to the + * return type of the target. + * <p><b>Example:</b> + * <blockquote><pre>{@code +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +... +MethodHandle cat = lookup().findVirtual(String.class, + "concat", methodType(String.class, String.class)); +MethodHandle length = lookup().findVirtual(String.class, + "length", methodType(int.class)); +System.out.println((String) cat.invokeExact("x", "y")); // xy +MethodHandle f0 = filterReturnValue(cat, length); +System.out.println((int) f0.invokeExact("x", "y")); // 2 + * }</pre></blockquote> + * <p> Here is pseudocode for the resulting adapter: + * <blockquote><pre>{@code + * V target(A...); + * T filter(V); + * T adapter(A... a) { + * V v = target(a...); + * return filter(v); + * } + * // and if the target has a void return: + * void target2(A...); + * T filter2(); + * T adapter2(A... a) { + * target2(a...); + * return filter2(); + * } + * // and if the filter has a void return: + * V target3(A...); + * void filter3(V); + * void adapter3(A... a) { + * V v = target3(a...); + * filter3(v); + * } + * }</pre></blockquote> + * @param target the method handle to invoke before filtering the return value + * @param filter method handle to call on the return value + * @return method handle which incorporates the specified return value filtering logic + * @throws NullPointerException if either argument is null + * @throws IllegalArgumentException if the argument list of {@code filter} + * does not match the return type of target as described above + */ public static - MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) { return null; } + MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) { + MethodType targetType = target.type(); + MethodType filterType = filter.type(); + filterReturnValueChecks(targetType, filterType); + return new Transformers.FilterReturnValue(target, filter); + } + + private static void filterReturnValueChecks(MethodType targetType, MethodType filterType) throws RuntimeException { + Class<?> rtype = targetType.returnType(); + int filterValues = filterType.parameterCount(); + if (filterValues == 0 + ? (rtype != void.class) + : (rtype != filterType.parameterType(0) || filterValues != 1)) + throw newIllegalArgumentException("target and filter types do not match", targetType, filterType); + } + + /** + * Adapts a target method handle by pre-processing + * some of its arguments, and then calling the target with + * the result of the pre-processing, inserted into the original + * sequence of arguments. + * <p> + * The pre-processing is performed by {@code combiner}, a second method handle. + * Of the arguments passed to the adapter, the first {@code N} arguments + * are copied to the combiner, which is then called. + * (Here, {@code N} is defined as the parameter count of the combiner.) + * After this, control passes to the target, with any result + * from the combiner inserted before the original {@code N} incoming + * arguments. + * <p> + * If the combiner returns a value, the first parameter type of the target + * must be identical with the return type of the combiner, and the next + * {@code N} parameter types of the target must exactly match the parameters + * of the combiner. + * <p> + * If the combiner has a void return, no result will be inserted, + * and the first {@code N} parameter types of the target + * must exactly match the parameters of the combiner. + * <p> + * The resulting adapter is the same type as the target, except that the + * first parameter type is dropped, + * if it corresponds to the result of the combiner. + * <p> + * (Note that {@link #dropArguments(MethodHandle,int,List) dropArguments} can be used to remove any arguments + * that either the combiner or the target does not wish to receive. + * If some of the incoming arguments are destined only for the combiner, + * consider using {@link MethodHandle#asCollector asCollector} instead, since those + * arguments will not need to be live on the stack on entry to the + * target.) + * <p><b>Example:</b> + * <blockquote><pre>{@code +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +... +MethodHandle trace = publicLookup().findVirtual(java.io.PrintStream.class, + "println", methodType(void.class, String.class)) + .bindTo(System.out); +MethodHandle cat = lookup().findVirtual(String.class, + "concat", methodType(String.class, String.class)); +assertEquals("boojum", (String) cat.invokeExact("boo", "jum")); +MethodHandle catTrace = foldArguments(cat, trace); +// also prints "boo": +assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum")); + * }</pre></blockquote> + * <p> Here is pseudocode for the resulting adapter: + * <blockquote><pre>{@code + * // there are N arguments in A... + * T target(V, A[N]..., B...); + * V combiner(A...); + * T adapter(A... a, B... b) { + * V v = combiner(a...); + * return target(v, a..., b...); + * } + * // and if the combiner has a void return: + * T target2(A[N]..., B...); + * void combiner2(A...); + * T adapter2(A... a, B... b) { + * combiner2(a...); + * return target2(a..., b...); + * } + * }</pre></blockquote> + * @param target the method handle to invoke after arguments are combined + * @param combiner method handle to call initially on the incoming arguments + * @return method handle which incorporates the specified argument folding logic + * @throws NullPointerException if either argument is null + * @throws IllegalArgumentException if {@code combiner}'s return type + * is non-void and not the same as the first argument type of + * the target, or if the initial {@code N} argument types + * of the target + * (skipping one matching the {@code combiner}'s return type) + * are not identical with the argument types of {@code combiner} + */ public static - MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) { return null; } + MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) { + int foldPos = 0; + MethodType targetType = target.type(); + MethodType combinerType = combiner.type(); + Class<?> rtype = foldArgumentChecks(foldPos, targetType, combinerType); + + return new Transformers.FoldArguments(target, combiner); + } + private static Class<?> foldArgumentChecks(int foldPos, MethodType targetType, MethodType combinerType) { + int foldArgs = combinerType.parameterCount(); + Class<?> rtype = combinerType.returnType(); + int foldVals = rtype == void.class ? 0 : 1; + int afterInsertPos = foldPos + foldVals; + boolean ok = (targetType.parameterCount() >= afterInsertPos + foldArgs); + if (ok && !(combinerType.parameterList() + .equals(targetType.parameterList().subList(afterInsertPos, + afterInsertPos + foldArgs)))) + ok = false; + if (ok && foldVals != 0 && combinerType.returnType() != targetType.parameterType(0)) + ok = false; + if (!ok) + throw misMatchedTypes("target and combiner types", targetType, combinerType); + return rtype; + } + + /** + * Makes a method handle which adapts a target method handle, + * by guarding it with a test, a boolean-valued method handle. + * If the guard fails, a fallback handle is called instead. + * All three method handles must have the same corresponding + * argument and return types, except that the return type + * of the test must be boolean, and the test is allowed + * to have fewer arguments than the other two method handles. + * <p> Here is pseudocode for the resulting adapter: + * <blockquote><pre>{@code + * boolean test(A...); + * T target(A...,B...); + * T fallback(A...,B...); + * T adapter(A... a,B... b) { + * if (test(a...)) + * return target(a..., b...); + * else + * return fallback(a..., b...); + * } + * }</pre></blockquote> + * Note that the test arguments ({@code a...} in the pseudocode) cannot + * be modified by execution of the test, and so are passed unchanged + * from the caller to the target or fallback as appropriate. + * @param test method handle used for test, must return boolean + * @param target method handle to call if test passes + * @param fallback method handle to call if test fails + * @return method handle which incorporates the specified if/then/else logic + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if {@code test} does not return boolean, + * or if all three method types do not match (with the return + * type of {@code test} changed to match that of the target). + */ public static MethodHandle guardWithTest(MethodHandle test, MethodHandle target, - MethodHandle fallback) { return null; } + MethodHandle fallback) { + MethodType gtype = test.type(); + MethodType ttype = target.type(); + MethodType ftype = fallback.type(); + if (!ttype.equals(ftype)) + throw misMatchedTypes("target and fallback types", ttype, ftype); + if (gtype.returnType() != boolean.class) + throw newIllegalArgumentException("guard type is not a predicate "+gtype); + List<Class<?>> targs = ttype.parameterList(); + List<Class<?>> gargs = gtype.parameterList(); + if (!targs.equals(gargs)) { + int gpc = gargs.size(), tpc = targs.size(); + if (gpc >= tpc || !targs.subList(0, gpc).equals(gargs)) + throw misMatchedTypes("target and test types", ttype, gtype); + test = dropArguments(test, gpc, targs.subList(gpc, tpc)); + gtype = test.type(); + } + + return new Transformers.GuardWithTest(test, target, fallback); + } + + static RuntimeException misMatchedTypes(String what, MethodType t1, MethodType t2) { + return newIllegalArgumentException(what + " must match: " + t1 + " != " + t2); + } + /** + * Makes a method handle which adapts a target method handle, + * by running it inside an exception handler. + * If the target returns normally, the adapter returns that value. + * If an exception matching the specified type is thrown, the fallback + * handle is called instead on the exception, plus the original arguments. + * <p> + * The target and handler must have the same corresponding + * argument and return types, except that handler may omit trailing arguments + * (similarly to the predicate in {@link #guardWithTest guardWithTest}). + * Also, the handler must have an extra leading parameter of {@code exType} or a supertype. + * <p> Here is pseudocode for the resulting adapter: + * <blockquote><pre>{@code + * T target(A..., B...); + * T handler(ExType, A...); + * T adapter(A... a, B... b) { + * try { + * return target(a..., b...); + * } catch (ExType ex) { + * return handler(ex, a...); + * } + * } + * }</pre></blockquote> + * Note that the saved arguments ({@code a...} in the pseudocode) cannot + * be modified by execution of the target, and so are passed unchanged + * from the caller to the handler, if the handler is invoked. + * <p> + * The target and handler must return the same type, even if the handler + * always throws. (This might happen, for instance, because the handler + * is simulating a {@code finally} clause). + * To create such a throwing handler, compose the handler creation logic + * with {@link #throwException throwException}, + * in order to create a method handle of the correct return type. + * @param target method handle to call + * @param exType the type of exception which the handler will catch + * @param handler method handle to call if a matching exception is thrown + * @return method handle which incorporates the specified try/catch logic + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if {@code handler} does not accept + * the given exception type, or if the method handle types do + * not match in their return types and their + * corresponding parameters + */ public static MethodHandle catchException(MethodHandle target, Class<? extends Throwable> exType, - MethodHandle handler) { return null; } + MethodHandle handler) { + MethodType ttype = target.type(); + MethodType htype = handler.type(); + if (htype.parameterCount() < 1 || + !htype.parameterType(0).isAssignableFrom(exType)) + throw newIllegalArgumentException("handler does not accept exception type "+exType); + if (htype.returnType() != ttype.returnType()) + throw misMatchedTypes("target and handler return types", ttype, htype); + List<Class<?>> targs = ttype.parameterList(); + List<Class<?>> hargs = htype.parameterList(); + hargs = hargs.subList(1, hargs.size()); // omit leading parameter from handler + if (!targs.equals(hargs)) { + int hpc = hargs.size(), tpc = targs.size(); + if (hpc >= tpc || !targs.subList(0, hpc).equals(hargs)) + throw misMatchedTypes("target and handler types", ttype, htype); + } + + return new Transformers.CatchException(target, handler, exType); + } + /** + * Produces a method handle which will throw exceptions of the given {@code exType}. + * The method handle will accept a single argument of {@code exType}, + * and immediately throw it as an exception. + * The method type will nominally specify a return of {@code returnType}. + * The return type may be anything convenient: It doesn't matter to the + * method handle's behavior, since it will never return normally. + * @param returnType the return type of the desired method handle + * @param exType the parameter type of the desired method handle + * @return method handle which can throw the given exceptions + * @throws NullPointerException if either argument is null + */ public static - MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType) { return null; } + MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType) { + if (!Throwable.class.isAssignableFrom(exType)) + throw new ClassCastException(exType.getName()); + + return new Transformers.AlwaysThrow(returnType, exType); + } } diff --git a/java/lang/invoke/MethodType.java b/java/lang/invoke/MethodType.java index 4cb5c226..bfa7ccd5 100644 --- a/java/lang/invoke/MethodType.java +++ b/java/lang/invoke/MethodType.java @@ -25,78 +25,1227 @@ package java.lang.invoke; +import sun.invoke.util.Wrapper; +import java.lang.ref.WeakReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentHashMap; +import sun.invoke.util.BytecodeDescriptor; +import static java.lang.invoke.MethodHandleStatics.*; +/** + * A method type represents the arguments and return type accepted and + * returned by a method handle, or the arguments and return type passed + * and expected by a method handle caller. Method types must be properly + * matched between a method handle and all its callers, + * and the JVM's operations enforce this matching at, specifically + * during calls to {@link MethodHandle#invokeExact MethodHandle.invokeExact} + * and {@link MethodHandle#invoke MethodHandle.invoke}, and during execution + * of {@code invokedynamic} instructions. + * <p> + * The structure is a return type accompanied by any number of parameter types. + * The types (primitive, {@code void}, and reference) are represented by {@link Class} objects. + * (For ease of exposition, we treat {@code void} as if it were a type. + * In fact, it denotes the absence of a return type.) + * <p> + * All instances of {@code MethodType} are immutable. + * Two instances are completely interchangeable if they compare equal. + * Equality depends on pairwise correspondence of the return and parameter types and on nothing else. + * <p> + * This type can be created only by factory methods. + * All factory methods may cache values, though caching is not guaranteed. + * Some factory methods are static, while others are virtual methods which + * modify precursor method types, e.g., by changing a selected parameter. + * <p> + * Factory methods which operate on groups of parameter types + * are systematically presented in two versions, so that both Java arrays and + * Java lists can be used to work with groups of parameter types. + * The query methods {@code parameterArray} and {@code parameterList} + * also provide a choice between arrays and lists. + * <p> + * {@code MethodType} objects are sometimes derived from bytecode instructions + * such as {@code invokedynamic}, specifically from the type descriptor strings associated + * with the instructions in a class file's constant pool. + * <p> + * Like classes and strings, method types can also be represented directly + * in a class file's constant pool as constants. + * A method type may be loaded by an {@code ldc} instruction which refers + * to a suitable {@code CONSTANT_MethodType} constant pool entry. + * The entry refers to a {@code CONSTANT_Utf8} spelling for the descriptor string. + * (For full details on method type constants, + * see sections 4.4.8 and 5.4.3.5 of the Java Virtual Machine Specification.) + * <p> + * When the JVM materializes a {@code MethodType} from a descriptor string, + * all classes named in the descriptor must be accessible, and will be loaded. + * (But the classes need not be initialized, as is the case with a {@code CONSTANT_Class}.) + * This loading may occur at any time before the {@code MethodType} object is first derived. + * @author John Rose, JSR 292 EG + */ public final class MethodType implements java.io.Serializable { + private static final long serialVersionUID = 292L; // {rtype, {ptype...}} + + // The rtype and ptypes fields define the structural identity of the method type: + private final Class<?> rtype; + private final Class<?>[] ptypes; + + // The remaining fields are caches of various sorts: + private @Stable MethodTypeForm form; // erased form, plus cached data about primitives + private @Stable MethodType wrapAlt; // alternative wrapped/unwrapped version + // Android-changed: Remove adapter cache. We're not dynamically generating any + // adapters at this point. + // private @Stable Invokers invokers; // cache of handy higher-order adapters + private @Stable String methodDescriptor; // cache for toMethodDescriptorString + + /** + * Check the given parameters for validity and store them into the final fields. + */ + private MethodType(Class<?> rtype, Class<?>[] ptypes, boolean trusted) { + checkRtype(rtype); + checkPtypes(ptypes); + this.rtype = rtype; + // defensively copy the array passed in by the user + this.ptypes = trusted ? ptypes : Arrays.copyOf(ptypes, ptypes.length); + } + + /** + * Construct a temporary unchecked instance of MethodType for use only as a key to the intern table. + * Does not check the given parameters for validity, and must be discarded after it is used as a searching key. + * The parameters are reversed for this constructor, so that is is not accidentally used. + */ + private MethodType(Class<?>[] ptypes, Class<?> rtype) { + this.rtype = rtype; + this.ptypes = ptypes; + } + + /*trusted*/ MethodTypeForm form() { return form; } + /*trusted*/ /** @hide */ public Class<?> rtype() { return rtype; } + /*trusted*/ /** @hide */ public Class<?>[] ptypes() { return ptypes; } + // Android-changed: Removed method setForm. It's unused in the JDK and there's no + // good reason to allow the form to be set externally. + // + // void setForm(MethodTypeForm f) { form = f; } + + /** This number, mandated by the JVM spec as 255, + * is the maximum number of <em>slots</em> + * that any Java method can receive in its argument list. + * It limits both JVM signatures and method type objects. + * The longest possible invocation will look like + * {@code staticMethod(arg1, arg2, ..., arg255)} or + * {@code x.virtualMethod(arg1, arg2, ..., arg254)}. + */ + /*non-public*/ static final int MAX_JVM_ARITY = 255; // this is mandated by the JVM spec. + + /** This number is the maximum arity of a method handle, 254. + * It is derived from the absolute JVM-imposed arity by subtracting one, + * which is the slot occupied by the method handle itself at the + * beginning of the argument list used to invoke the method handle. + * The longest possible invocation will look like + * {@code mh.invoke(arg1, arg2, ..., arg254)}. + */ + // Issue: Should we allow MH.invokeWithArguments to go to the full 255? + /*non-public*/ static final int MAX_MH_ARITY = MAX_JVM_ARITY-1; // deduct one for mh receiver + + /** This number is the maximum arity of a method handle invoker, 253. + * It is derived from the absolute JVM-imposed arity by subtracting two, + * which are the slots occupied by invoke method handle, and the + * target method handle, which are both at the beginning of the argument + * list used to invoke the target method handle. + * The longest possible invocation will look like + * {@code invokermh.invoke(targetmh, arg1, arg2, ..., arg253)}. + */ + /*non-public*/ static final int MAX_MH_INVOKER_ARITY = MAX_MH_ARITY-1; // deduct one more for invoker + + private static void checkRtype(Class<?> rtype) { + Objects.requireNonNull(rtype); + } + private static void checkPtype(Class<?> ptype) { + Objects.requireNonNull(ptype); + if (ptype == void.class) + throw newIllegalArgumentException("parameter type cannot be void"); + } + /** Return number of extra slots (count of long/double args). */ + private static int checkPtypes(Class<?>[] ptypes) { + int slots = 0; + for (Class<?> ptype : ptypes) { + checkPtype(ptype); + if (ptype == double.class || ptype == long.class) { + slots++; + } + } + checkSlotCount(ptypes.length + slots); + return slots; + } + static void checkSlotCount(int count) { + assert((MAX_JVM_ARITY & (MAX_JVM_ARITY+1)) == 0); + // MAX_JVM_ARITY must be power of 2 minus 1 for following code trick to work: + if ((count & MAX_JVM_ARITY) != count) + throw newIllegalArgumentException("bad parameter count "+count); + } + private static IndexOutOfBoundsException newIndexOutOfBoundsException(Object num) { + if (num instanceof Integer) num = "bad index: "+num; + return new IndexOutOfBoundsException(num.toString()); + } + + static final ConcurrentWeakInternSet<MethodType> internTable = new ConcurrentWeakInternSet<>(); + + static final Class<?>[] NO_PTYPES = {}; + + /** + * Finds or creates an instance of the given method type. + * @param rtype the return type + * @param ptypes the parameter types + * @return a method type with the given components + * @throws NullPointerException if {@code rtype} or {@code ptypes} or any element of {@code ptypes} is null + * @throws IllegalArgumentException if any element of {@code ptypes} is {@code void.class} + */ public static MethodType methodType(Class<?> rtype, Class<?>[] ptypes) { - return null; + return makeImpl(rtype, ptypes, false); } + /** + * Finds or creates a method type with the given components. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param rtype the return type + * @param ptypes the parameter types + * @return a method type with the given components + * @throws NullPointerException if {@code rtype} or {@code ptypes} or any element of {@code ptypes} is null + * @throws IllegalArgumentException if any element of {@code ptypes} is {@code void.class} + */ public static MethodType methodType(Class<?> rtype, List<Class<?>> ptypes) { - return null; + boolean notrust = false; // random List impl. could return evil ptypes array + return makeImpl(rtype, listToArray(ptypes), notrust); } + private static Class<?>[] listToArray(List<Class<?>> ptypes) { + // sanity check the size before the toArray call, since size might be huge + checkSlotCount(ptypes.size()); + return ptypes.toArray(NO_PTYPES); + } + + /** + * Finds or creates a method type with the given components. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * The leading parameter type is prepended to the remaining array. + * @param rtype the return type + * @param ptype0 the first parameter type + * @param ptypes the remaining parameter types + * @return a method type with the given components + * @throws NullPointerException if {@code rtype} or {@code ptype0} or {@code ptypes} or any element of {@code ptypes} is null + * @throws IllegalArgumentException if {@code ptype0} or {@code ptypes} or any element of {@code ptypes} is {@code void.class} + */ public static - MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes) { return null; } + MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes) { + Class<?>[] ptypes1 = new Class<?>[1+ptypes.length]; + ptypes1[0] = ptype0; + System.arraycopy(ptypes, 0, ptypes1, 1, ptypes.length); + return makeImpl(rtype, ptypes1, true); + } + /** + * Finds or creates a method type with the given components. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * The resulting method has no parameter types. + * @param rtype the return type + * @return a method type with the given return value + * @throws NullPointerException if {@code rtype} is null + */ public static - MethodType methodType(Class<?> rtype) { return null; } + MethodType methodType(Class<?> rtype) { + return makeImpl(rtype, NO_PTYPES, true); + } + /** + * Finds or creates a method type with the given components. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * The resulting method has the single given parameter type. + * @param rtype the return type + * @param ptype0 the parameter type + * @return a method type with the given return value and parameter type + * @throws NullPointerException if {@code rtype} or {@code ptype0} is null + * @throws IllegalArgumentException if {@code ptype0} is {@code void.class} + */ public static - MethodType methodType(Class<?> rtype, Class<?> ptype0) { return null; } + MethodType methodType(Class<?> rtype, Class<?> ptype0) { + return makeImpl(rtype, new Class<?>[]{ ptype0 }, true); + } + /** + * Finds or creates a method type with the given components. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * The resulting method has the same parameter types as {@code ptypes}, + * and the specified return type. + * @param rtype the return type + * @param ptypes the method type which supplies the parameter types + * @return a method type with the given components + * @throws NullPointerException if {@code rtype} or {@code ptypes} is null + */ public static - MethodType methodType(Class<?> rtype, MethodType ptypes) { return null; } + MethodType methodType(Class<?> rtype, MethodType ptypes) { + return makeImpl(rtype, ptypes.ptypes, true); + } + + /** + * Sole factory method to find or create an interned method type. + * @param rtype desired return type + * @param ptypes desired parameter types + * @param trusted whether the ptypes can be used without cloning + * @return the unique method type of the desired structure + */ + /*trusted*/ static + MethodType makeImpl(Class<?> rtype, Class<?>[] ptypes, boolean trusted) { + MethodType mt = internTable.get(new MethodType(ptypes, rtype)); + if (mt != null) + return mt; + if (ptypes.length == 0) { + ptypes = NO_PTYPES; trusted = true; + } + mt = new MethodType(rtype, ptypes, trusted); + // promote the object to the Real Thing, and reprobe + mt.form = MethodTypeForm.findForm(mt); + return internTable.add(mt); + } + private static final MethodType[] objectOnlyTypes = new MethodType[20]; + /** + * Finds or creates a method type whose components are {@code Object} with an optional trailing {@code Object[]} array. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * All parameters and the return type will be {@code Object}, + * except the final array parameter if any, which will be {@code Object[]}. + * @param objectArgCount number of parameters (excluding the final array parameter if any) + * @param finalArray whether there will be a trailing array parameter, of type {@code Object[]} + * @return a generally applicable method type, for all calls of the given fixed argument count and a collected array of further arguments + * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255 (or 254, if {@code finalArray} is true) + * @see #genericMethodType(int) + */ public static - MethodType genericMethodType(int objectArgCount, boolean finalArray) { return null; } + MethodType genericMethodType(int objectArgCount, boolean finalArray) { + MethodType mt; + checkSlotCount(objectArgCount); + int ivarargs = (!finalArray ? 0 : 1); + int ootIndex = objectArgCount*2 + ivarargs; + if (ootIndex < objectOnlyTypes.length) { + mt = objectOnlyTypes[ootIndex]; + if (mt != null) return mt; + } + Class<?>[] ptypes = new Class<?>[objectArgCount + ivarargs]; + Arrays.fill(ptypes, Object.class); + if (ivarargs != 0) ptypes[objectArgCount] = Object[].class; + mt = makeImpl(Object.class, ptypes, true); + if (ootIndex < objectOnlyTypes.length) { + objectOnlyTypes[ootIndex] = mt; // cache it here also! + } + return mt; + } + /** + * Finds or creates a method type whose components are all {@code Object}. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * All parameters and the return type will be Object. + * @param objectArgCount number of parameters + * @return a generally applicable method type, for all calls of the given argument count + * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255 + * @see #genericMethodType(int, boolean) + */ public static - MethodType genericMethodType(int objectArgCount) { return null; } + MethodType genericMethodType(int objectArgCount) { + return genericMethodType(objectArgCount, false); + } + + /** + * Finds or creates a method type with a single different parameter type. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param num the index (zero-based) of the parameter type to change + * @param nptype a new parameter type to replace the old one with + * @return the same type, except with the selected parameter changed + * @throws IndexOutOfBoundsException if {@code num} is not a valid index into {@code parameterArray()} + * @throws IllegalArgumentException if {@code nptype} is {@code void.class} + * @throws NullPointerException if {@code nptype} is null + */ + public MethodType changeParameterType(int num, Class<?> nptype) { + if (parameterType(num) == nptype) return this; + checkPtype(nptype); + Class<?>[] nptypes = ptypes.clone(); + nptypes[num] = nptype; + return makeImpl(rtype, nptypes, true); + } + + /** + * Finds or creates a method type with additional parameter types. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param num the position (zero-based) of the inserted parameter type(s) + * @param ptypesToInsert zero or more new parameter types to insert into the parameter list + * @return the same type, except with the selected parameter(s) inserted + * @throws IndexOutOfBoundsException if {@code num} is negative or greater than {@code parameterCount()} + * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class} + * or if the resulting method type would have more than 255 parameter slots + * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null + */ + public MethodType insertParameterTypes(int num, Class<?>... ptypesToInsert) { + int len = ptypes.length; + if (num < 0 || num > len) + throw newIndexOutOfBoundsException(num); + int ins = checkPtypes(ptypesToInsert); + checkSlotCount(parameterSlotCount() + ptypesToInsert.length + ins); + int ilen = ptypesToInsert.length; + if (ilen == 0) return this; + Class<?>[] nptypes = Arrays.copyOfRange(ptypes, 0, len+ilen); + System.arraycopy(nptypes, num, nptypes, num+ilen, len-num); + System.arraycopy(ptypesToInsert, 0, nptypes, num, ilen); + return makeImpl(rtype, nptypes, true); + } + + /** + * Finds or creates a method type with additional parameter types. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param ptypesToInsert zero or more new parameter types to insert after the end of the parameter list + * @return the same type, except with the selected parameter(s) appended + * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class} + * or if the resulting method type would have more than 255 parameter slots + * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null + */ + public MethodType appendParameterTypes(Class<?>... ptypesToInsert) { + return insertParameterTypes(parameterCount(), ptypesToInsert); + } + + /** + * Finds or creates a method type with additional parameter types. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param num the position (zero-based) of the inserted parameter type(s) + * @param ptypesToInsert zero or more new parameter types to insert into the parameter list + * @return the same type, except with the selected parameter(s) inserted + * @throws IndexOutOfBoundsException if {@code num} is negative or greater than {@code parameterCount()} + * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class} + * or if the resulting method type would have more than 255 parameter slots + * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null + */ + public MethodType insertParameterTypes(int num, List<Class<?>> ptypesToInsert) { + return insertParameterTypes(num, listToArray(ptypesToInsert)); + } + + /** + * Finds or creates a method type with additional parameter types. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param ptypesToInsert zero or more new parameter types to insert after the end of the parameter list + * @return the same type, except with the selected parameter(s) appended + * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class} + * or if the resulting method type would have more than 255 parameter slots + * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null + */ + public MethodType appendParameterTypes(List<Class<?>> ptypesToInsert) { + return insertParameterTypes(parameterCount(), ptypesToInsert); + } + + /** + * Finds or creates a method type with modified parameter types. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param start the position (zero-based) of the first replaced parameter type(s) + * @param end the position (zero-based) after the last replaced parameter type(s) + * @param ptypesToInsert zero or more new parameter types to insert into the parameter list + * @return the same type, except with the selected parameter(s) replaced + * @throws IndexOutOfBoundsException if {@code start} is negative or greater than {@code parameterCount()} + * or if {@code end} is negative or greater than {@code parameterCount()} + * or if {@code start} is greater than {@code end} + * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class} + * or if the resulting method type would have more than 255 parameter slots + * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null + */ + /*non-public*/ MethodType replaceParameterTypes(int start, int end, Class<?>... ptypesToInsert) { + if (start == end) + return insertParameterTypes(start, ptypesToInsert); + int len = ptypes.length; + if (!(0 <= start && start <= end && end <= len)) + throw newIndexOutOfBoundsException("start="+start+" end="+end); + int ilen = ptypesToInsert.length; + if (ilen == 0) + return dropParameterTypes(start, end); + return dropParameterTypes(start, end).insertParameterTypes(start, ptypesToInsert); + } + + /** Replace the last arrayLength parameter types with the component type of arrayType. + * @param arrayType any array type + * @param arrayLength the number of parameter types to change + * @return the resulting type + */ + /*non-public*/ MethodType asSpreaderType(Class<?> arrayType, int arrayLength) { + assert(parameterCount() >= arrayLength); + int spreadPos = ptypes.length - arrayLength; + if (arrayLength == 0) return this; // nothing to change + if (arrayType == Object[].class) { + if (isGeneric()) return this; // nothing to change + if (spreadPos == 0) { + // no leading arguments to preserve; go generic + MethodType res = genericMethodType(arrayLength); + if (rtype != Object.class) { + res = res.changeReturnType(rtype); + } + return res; + } + } + Class<?> elemType = arrayType.getComponentType(); + assert(elemType != null); + for (int i = spreadPos; i < ptypes.length; i++) { + if (ptypes[i] != elemType) { + Class<?>[] fixedPtypes = ptypes.clone(); + Arrays.fill(fixedPtypes, i, ptypes.length, elemType); + return methodType(rtype, fixedPtypes); + } + } + return this; // arguments check out; no change + } + + /** Return the leading parameter type, which must exist and be a reference. + * @return the leading parameter type, after error checks + */ + /*non-public*/ Class<?> leadingReferenceParameter() { + Class<?> ptype; + if (ptypes.length == 0 || + (ptype = ptypes[0]).isPrimitive()) + throw newIllegalArgumentException("no leading reference parameter"); + return ptype; + } + + /** Delete the last parameter type and replace it with arrayLength copies of the component type of arrayType. + * @param arrayType any array type + * @param arrayLength the number of parameter types to insert + * @return the resulting type + */ + /*non-public*/ MethodType asCollectorType(Class<?> arrayType, int arrayLength) { + assert(parameterCount() >= 1); + assert(lastParameterType().isAssignableFrom(arrayType)); + MethodType res; + if (arrayType == Object[].class) { + res = genericMethodType(arrayLength); + if (rtype != Object.class) { + res = res.changeReturnType(rtype); + } + } else { + Class<?> elemType = arrayType.getComponentType(); + assert(elemType != null); + res = methodType(rtype, Collections.nCopies(arrayLength, elemType)); + } + if (ptypes.length == 1) { + return res; + } else { + return res.insertParameterTypes(0, parameterList().subList(0, ptypes.length-1)); + } + } + + /** + * Finds or creates a method type with some parameter types omitted. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param start the index (zero-based) of the first parameter type to remove + * @param end the index (greater than {@code start}) of the first parameter type after not to remove + * @return the same type, except with the selected parameter(s) removed + * @throws IndexOutOfBoundsException if {@code start} is negative or greater than {@code parameterCount()} + * or if {@code end} is negative or greater than {@code parameterCount()} + * or if {@code start} is greater than {@code end} + */ + public MethodType dropParameterTypes(int start, int end) { + int len = ptypes.length; + if (!(0 <= start && start <= end && end <= len)) + throw newIndexOutOfBoundsException("start="+start+" end="+end); + if (start == end) return this; + Class<?>[] nptypes; + if (start == 0) { + if (end == len) { + // drop all parameters + nptypes = NO_PTYPES; + } else { + // drop initial parameter(s) + nptypes = Arrays.copyOfRange(ptypes, end, len); + } + } else { + if (end == len) { + // drop trailing parameter(s) + nptypes = Arrays.copyOfRange(ptypes, 0, start); + } else { + int tail = len - end; + nptypes = Arrays.copyOfRange(ptypes, 0, start + tail); + System.arraycopy(ptypes, end, nptypes, start, tail); + } + } + return makeImpl(rtype, nptypes, true); + } + + /** + * Finds or creates a method type with a different return type. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * @param nrtype a return parameter type to replace the old one with + * @return the same type, except with the return type change + * @throws NullPointerException if {@code nrtype} is null + */ + public MethodType changeReturnType(Class<?> nrtype) { + if (returnType() == nrtype) return this; + return makeImpl(nrtype, ptypes, true); + } + + /** + * Reports if this type contains a primitive argument or return value. + * The return type {@code void} counts as a primitive. + * @return true if any of the types are primitives + */ + public boolean hasPrimitives() { + return form.hasPrimitives(); + } + + /** + * Reports if this type contains a wrapper argument or return value. + * Wrappers are types which box primitive values, such as {@link Integer}. + * The reference type {@code java.lang.Void} counts as a wrapper, + * if it occurs as a return type. + * @return true if any of the types are wrappers + */ + public boolean hasWrappers() { + return unwrap() != this; + } + + /** + * Erases all reference types to {@code Object}. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * All primitive types (including {@code void}) will remain unchanged. + * @return a version of the original type with all reference types replaced + */ + public MethodType erase() { + return form.erasedType(); + } + + /** + * Erases all reference types to {@code Object}, and all subword types to {@code int}. + * This is the reduced type polymorphism used by private methods + * such as {@link MethodHandle#invokeBasic invokeBasic}. + * @return a version of the original type with all reference and subword types replaced + */ + /*non-public*/ MethodType basicType() { + return form.basicType(); + } + + /** + * @return a version of the original type with MethodHandle prepended as the first argument + */ + /*non-public*/ MethodType invokerType() { + return insertParameterTypes(0, MethodHandle.class); + } - public MethodType changeParameterType(int num, Class<?> nptype) { return null; } + /** + * Converts all types, both reference and primitive, to {@code Object}. + * Convenience method for {@link #genericMethodType(int) genericMethodType}. + * The expression {@code type.wrap().erase()} produces the same value + * as {@code type.generic()}. + * @return a version of the original type with all types replaced + */ + public MethodType generic() { + return genericMethodType(parameterCount()); + } - public MethodType insertParameterTypes(int num, Class<?>... ptypesToInsert) { return null; } + /*non-public*/ boolean isGeneric() { + return this == erase() && !hasPrimitives(); + } - public MethodType appendParameterTypes(Class<?>... ptypesToInsert) { return null; } + /** + * Converts all primitive types to their corresponding wrapper types. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * All reference types (including wrapper types) will remain unchanged. + * A {@code void} return type is changed to the type {@code java.lang.Void}. + * The expression {@code type.wrap().erase()} produces the same value + * as {@code type.generic()}. + * @return a version of the original type with all primitive types replaced + */ + public MethodType wrap() { + return hasPrimitives() ? wrapWithPrims(this) : this; + } - public MethodType insertParameterTypes(int num, List<Class<?>> ptypesToInsert) { return null; } + /** + * Converts all wrapper types to their corresponding primitive types. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * All primitive types (including {@code void}) will remain unchanged. + * A return type of {@code java.lang.Void} is changed to {@code void}. + * @return a version of the original type with all wrapper types replaced + */ + public MethodType unwrap() { + MethodType noprims = !hasPrimitives() ? this : wrapWithPrims(this); + return unwrapWithNoPrims(noprims); + } - public MethodType appendParameterTypes(List<Class<?>> ptypesToInsert) { return null; } + private static MethodType wrapWithPrims(MethodType pt) { + assert(pt.hasPrimitives()); + MethodType wt = pt.wrapAlt; + if (wt == null) { + // fill in lazily + wt = MethodTypeForm.canonicalize(pt, MethodTypeForm.WRAP, MethodTypeForm.WRAP); + assert(wt != null); + pt.wrapAlt = wt; + } + return wt; + } - public MethodType dropParameterTypes(int start, int end) { return null; } + private static MethodType unwrapWithNoPrims(MethodType wt) { + assert(!wt.hasPrimitives()); + MethodType uwt = wt.wrapAlt; + if (uwt == null) { + // fill in lazily + uwt = MethodTypeForm.canonicalize(wt, MethodTypeForm.UNWRAP, MethodTypeForm.UNWRAP); + if (uwt == null) + uwt = wt; // type has no wrappers or prims at all + wt.wrapAlt = uwt; + } + return uwt; + } - public MethodType changeReturnType(Class<?> nrtype) { return null; } + /** + * Returns the parameter type at the specified index, within this method type. + * @param num the index (zero-based) of the desired parameter type + * @return the selected parameter type + * @throws IndexOutOfBoundsException if {@code num} is not a valid index into {@code parameterArray()} + */ + public Class<?> parameterType(int num) { + return ptypes[num]; + } + /** + * Returns the number of parameter types in this method type. + * @return the number of parameter types + */ + public int parameterCount() { + return ptypes.length; + } + /** + * Returns the return type of this method type. + * @return the return type + */ + public Class<?> returnType() { + return rtype; + } - public boolean hasPrimitives() { return false; } + /** + * Presents the parameter types as a list (a convenience method). + * The list will be immutable. + * @return the parameter types (as an immutable list) + */ + public List<Class<?>> parameterList() { + return Collections.unmodifiableList(Arrays.asList(ptypes.clone())); + } - public boolean hasWrappers() { return false; } + /*non-public*/ Class<?> lastParameterType() { + int len = ptypes.length; + return len == 0 ? void.class : ptypes[len-1]; + } - public MethodType erase() { return null; } + /** + * Presents the parameter types as an array (a convenience method). + * Changes to the array will not result in changes to the type. + * @return the parameter types (as a fresh copy if necessary) + */ + public Class<?>[] parameterArray() { + return ptypes.clone(); + } - public MethodType generic() { return null; } + /** + * Compares the specified object with this type for equality. + * That is, it returns <tt>true</tt> if and only if the specified object + * is also a method type with exactly the same parameters and return type. + * @param x object to compare + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object x) { + return this == x || x instanceof MethodType && equals((MethodType)x); + } - public MethodType wrap() { return null; } + private boolean equals(MethodType that) { + return this.rtype == that.rtype + && Arrays.equals(this.ptypes, that.ptypes); + } - public MethodType unwrap() { return null; } + /** + * Returns the hash code value for this method type. + * It is defined to be the same as the hashcode of a List + * whose elements are the return type followed by the + * parameter types. + * @return the hash code value for this method type + * @see Object#hashCode() + * @see #equals(Object) + * @see List#hashCode() + */ + @Override + public int hashCode() { + int hashCode = 31 + rtype.hashCode(); + for (Class<?> ptype : ptypes) + hashCode = 31*hashCode + ptype.hashCode(); + return hashCode; + } - public Class<?> parameterType(int num) { return null; } + /** + * Returns a string representation of the method type, + * of the form {@code "(PT0,PT1...)RT"}. + * The string representation of a method type is a + * parenthesis enclosed, comma separated list of type names, + * followed immediately by the return type. + * <p> + * Each type is represented by its + * {@link java.lang.Class#getSimpleName simple name}. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (int i = 0; i < ptypes.length; i++) { + if (i > 0) sb.append(","); + sb.append(ptypes[i].getSimpleName()); + } + sb.append(")"); + sb.append(rtype.getSimpleName()); + return sb.toString(); + } - public int parameterCount() { return 0; } + /** True if the old return type can always be viewed (w/o casting) under new return type, + * and the new parameters can be viewed (w/o casting) under the old parameter types. + */ + // Android-changed: Removed implementation details. + // boolean isViewableAs(MethodType newType, boolean keepInterfaces); + // boolean parametersAreViewableAs(MethodType newType, boolean keepInterfaces); + /*non-public*/ + boolean isConvertibleTo(MethodType newType) { + MethodTypeForm oldForm = this.form(); + MethodTypeForm newForm = newType.form(); + if (oldForm == newForm) + // same parameter count, same primitive/object mix + return true; + if (!canConvert(returnType(), newType.returnType())) + return false; + Class<?>[] srcTypes = newType.ptypes; + Class<?>[] dstTypes = ptypes; + if (srcTypes == dstTypes) + return true; + int argc; + if ((argc = srcTypes.length) != dstTypes.length) + return false; + if (argc <= 1) { + if (argc == 1 && !canConvert(srcTypes[0], dstTypes[0])) + return false; + return true; + } + if ((oldForm.primitiveParameterCount() == 0 && oldForm.erasedType == this) || + (newForm.primitiveParameterCount() == 0 && newForm.erasedType == newType)) { + // Somewhat complicated test to avoid a loop of 2 or more trips. + // If either type has only Object parameters, we know we can convert. + assert(canConvertParameters(srcTypes, dstTypes)); + return true; + } + return canConvertParameters(srcTypes, dstTypes); + } + + /** Returns true if MHs.explicitCastArguments produces the same result as MH.asType. + * If the type conversion is impossible for either, the result should be false. + */ + /*non-public*/ + boolean explicitCastEquivalentToAsType(MethodType newType) { + if (this == newType) return true; + if (!explicitCastEquivalentToAsType(rtype, newType.rtype)) { + return false; + } + Class<?>[] srcTypes = newType.ptypes; + Class<?>[] dstTypes = ptypes; + if (dstTypes == srcTypes) { + return true; + } + assert(dstTypes.length == srcTypes.length); + for (int i = 0; i < dstTypes.length; i++) { + if (!explicitCastEquivalentToAsType(srcTypes[i], dstTypes[i])) { + return false; + } + } + return true; + } + + /** Reports true if the src can be converted to the dst, by both asType and MHs.eCE, + * and with the same effect. + * MHs.eCA has the following "upgrades" to MH.asType: + * 1. interfaces are unchecked (that is, treated as if aliased to Object) + * Therefore, {@code Object->CharSequence} is possible in both cases but has different semantics + * 2a. the full matrix of primitive-to-primitive conversions is supported + * Narrowing like {@code long->byte} and basic-typing like {@code boolean->int} + * are not supported by asType, but anything supported by asType is equivalent + * with MHs.eCE. + * 2b. conversion of void->primitive means explicit cast has to insert zero/false/null. + * 3a. unboxing conversions can be followed by the full matrix of primitive conversions + * 3b. unboxing of null is permitted (creates a zero primitive value) + * Other than interfaces, reference-to-reference conversions are the same. + * Boxing primitives to references is the same for both operators. + */ + private static boolean explicitCastEquivalentToAsType(Class<?> src, Class<?> dst) { + if (src == dst || dst == Object.class || dst == void.class) { + return true; + } else if (src.isPrimitive() && src != void.class) { + // Could be a prim/prim conversion, where casting is a strict superset. + // Or a boxing conversion, which is always to an exact wrapper class. + return canConvert(src, dst); + } else if (dst.isPrimitive()) { + // Unboxing behavior is different between MHs.eCA & MH.asType (see 3b). + return false; + } else { + // R->R always works, but we have to avoid a check-cast to an interface. + return !dst.isInterface() || dst.isAssignableFrom(src); + } + } - public Class<?> returnType() { return null; } + private boolean canConvertParameters(Class<?>[] srcTypes, Class<?>[] dstTypes) { + for (int i = 0; i < srcTypes.length; i++) { + if (!canConvert(srcTypes[i], dstTypes[i])) { + return false; + } + } + return true; + } - public List<Class<?>> parameterList() { return null; } + /*non-public*/ + static boolean canConvert(Class<?> src, Class<?> dst) { + // short-circuit a few cases: + if (src == dst || src == Object.class || dst == Object.class) return true; + // the remainder of this logic is documented in MethodHandle.asType + if (src.isPrimitive()) { + // can force void to an explicit null, a la reflect.Method.invoke + // can also force void to a primitive zero, by analogy + if (src == void.class) return true; //or !dst.isPrimitive()? + Wrapper sw = Wrapper.forPrimitiveType(src); + if (dst.isPrimitive()) { + // P->P must widen + return Wrapper.forPrimitiveType(dst).isConvertibleFrom(sw); + } else { + // P->R must box and widen + return dst.isAssignableFrom(sw.wrapperType()); + } + } else if (dst.isPrimitive()) { + // any value can be dropped + if (dst == void.class) return true; + Wrapper dw = Wrapper.forPrimitiveType(dst); + // R->P must be able to unbox (from a dynamically chosen type) and widen + // For example: + // Byte/Number/Comparable/Object -> dw:Byte -> byte. + // Character/Comparable/Object -> dw:Character -> char + // Boolean/Comparable/Object -> dw:Boolean -> boolean + // This means that dw must be cast-compatible with src. + if (src.isAssignableFrom(dw.wrapperType())) { + return true; + } + // The above does not work if the source reference is strongly typed + // to a wrapper whose primitive must be widened. For example: + // Byte -> unbox:byte -> short/int/long/float/double + // Character -> unbox:char -> int/long/float/double + if (Wrapper.isWrapperType(src) && + dw.isConvertibleFrom(Wrapper.forWrapperType(src))) { + // can unbox from src and then widen to dst + return true; + } + // We have already covered cases which arise due to runtime unboxing + // of a reference type which covers several wrapper types: + // Object -> cast:Integer -> unbox:int -> long/float/double + // Serializable -> cast:Byte -> unbox:byte -> byte/short/int/long/float/double + // An marginal case is Number -> dw:Character -> char, which would be OK if there were a + // subclass of Number which wraps a value that can convert to char. + // Since there is none, we don't need an extra check here to cover char or boolean. + return false; + } else { + // R->R always works, since null is always valid dynamically + return true; + } + } - public Class<?>[] parameterArray() { return null; } + /** Reports the number of JVM stack slots required to invoke a method + * of this type. Note that (for historical reasons) the JVM requires + * a second stack slot to pass long and double arguments. + * So this method returns {@link #parameterCount() parameterCount} plus the + * number of long and double parameters (if any). + * <p> + * This method is included for the benefit of applications that must + * generate bytecodes that process method handles and invokedynamic. + * @return the number of JVM stack slots for this type's parameters + */ + /*non-public*/ int parameterSlotCount() { + return form.parameterSlotCount(); + } + /// Queries which have to do with the bytecode architecture + + // Android-changed: These methods aren't needed on Android and are unused within the JDK. + // + // int parameterSlotDepth(int num); + // int returnSlotCount(); + // + // Android-changed: Removed cache of higher order adapters. + // + // Invokers invokers(); + + /** + * Finds or creates an instance of a method type, given the spelling of its bytecode descriptor. + * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. + * Any class or interface name embedded in the descriptor string + * will be resolved by calling {@link ClassLoader#loadClass(java.lang.String)} + * on the given loader (or if it is null, on the system class loader). + * <p> + * Note that it is possible to encounter method types which cannot be + * constructed by this method, because their component types are + * not all reachable from a common class loader. + * <p> + * This method is included for the benefit of applications that must + * generate bytecodes that process method handles and {@code invokedynamic}. + * @param descriptor a bytecode-level type descriptor string "(T...)T" + * @param loader the class loader in which to look up the types + * @return a method type matching the bytecode-level type descriptor + * @throws NullPointerException if the string is null + * @throws IllegalArgumentException if the string is not well-formed + * @throws TypeNotPresentException if a named type cannot be found + */ public static MethodType fromMethodDescriptorString(String descriptor, ClassLoader loader) - throws IllegalArgumentException, TypeNotPresentException { return null; } + throws IllegalArgumentException, TypeNotPresentException + { + if (!descriptor.startsWith("(") || // also generates NPE if needed + descriptor.indexOf(')') < 0 || + descriptor.indexOf('.') >= 0) + throw newIllegalArgumentException("not a method descriptor: "+descriptor); + List<Class<?>> types = BytecodeDescriptor.parseMethod(descriptor, loader); + Class<?> rtype = types.remove(types.size() - 1); + checkSlotCount(types.size()); + Class<?>[] ptypes = listToArray(types); + return makeImpl(rtype, ptypes, true); + } + + /** + * Produces a bytecode descriptor representation of the method type. + * <p> + * Note that this is not a strict inverse of {@link #fromMethodDescriptorString fromMethodDescriptorString}. + * Two distinct classes which share a common name but have different class loaders + * will appear identical when viewed within descriptor strings. + * <p> + * This method is included for the benefit of applications that must + * generate bytecodes that process method handles and {@code invokedynamic}. + * {@link #fromMethodDescriptorString(java.lang.String, java.lang.ClassLoader) fromMethodDescriptorString}, + * because the latter requires a suitable class loader argument. + * @return the bytecode type descriptor representation + */ + public String toMethodDescriptorString() { + String desc = methodDescriptor; + if (desc == null) { + desc = BytecodeDescriptor.unparse(this); + methodDescriptor = desc; + } + return desc; + } + + /*non-public*/ static String toFieldDescriptorString(Class<?> cls) { + return BytecodeDescriptor.unparse(cls); + } - public String toMethodDescriptorString() { return null; } + /// Serialization. + + /** + * There are no serializable fields for {@code MethodType}. + */ + private static final java.io.ObjectStreamField[] serialPersistentFields = { }; + + /** + * Save the {@code MethodType} instance to a stream. + * + * @serialData + * For portability, the serialized format does not refer to named fields. + * Instead, the return type and parameter type arrays are written directly + * from the {@code writeObject} method, using two calls to {@code s.writeObject} + * as follows: + * <blockquote><pre>{@code +s.writeObject(this.returnType()); +s.writeObject(this.parameterArray()); + * }</pre></blockquote> + * <p> + * The deserialized field values are checked as if they were + * provided to the factory method {@link #methodType(Class,Class[]) methodType}. + * For example, null values, or {@code void} parameter types, + * will lead to exceptions during deserialization. + * @param s the stream to write the object to + * @throws java.io.IOException if there is a problem writing the object + */ + private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + s.defaultWriteObject(); // requires serialPersistentFields to be an empty array + s.writeObject(returnType()); + s.writeObject(parameterArray()); + } + + /** + * Reconstitute the {@code MethodType} instance from a stream (that is, + * deserialize it). + * This instance is a scratch object with bogus final fields. + * It provides the parameters to the factory method called by + * {@link #readResolve readResolve}. + * After that call it is discarded. + * @param s the stream to read the object from + * @throws java.io.IOException if there is a problem reading the object + * @throws ClassNotFoundException if one of the component classes cannot be resolved + * @see #MethodType() + * @see #readResolve + * @see #writeObject + */ + private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); // requires serialPersistentFields to be an empty array + + Class<?> returnType = (Class<?>) s.readObject(); + Class<?>[] parameterArray = (Class<?>[]) s.readObject(); + + // Probably this object will never escape, but let's check + // the field values now, just to be sure. + checkRtype(returnType); + checkPtypes(parameterArray); + + parameterArray = parameterArray.clone(); // make sure it is unshared + MethodType_init(returnType, parameterArray); + } + + /** + * For serialization only. + * Sets the final fields to null, pending {@code Unsafe.putObject}. + */ + private MethodType() { + this.rtype = null; + this.ptypes = null; + } + private void MethodType_init(Class<?> rtype, Class<?>[] ptypes) { + // In order to communicate these values to readResolve, we must + // store them into the implementation-specific final fields. + checkRtype(rtype); + checkPtypes(ptypes); + UNSAFE.putObject(this, rtypeOffset, rtype); + UNSAFE.putObject(this, ptypesOffset, ptypes); + } + + // Support for resetting final fields while deserializing + private static final long rtypeOffset, ptypesOffset; + static { + try { + rtypeOffset = UNSAFE.objectFieldOffset + (MethodType.class.getDeclaredField("rtype")); + ptypesOffset = UNSAFE.objectFieldOffset + (MethodType.class.getDeclaredField("ptypes")); + } catch (Exception ex) { + throw new Error(ex); + } + } + + /** + * Resolves and initializes a {@code MethodType} object + * after serialization. + * @return the fully initialized {@code MethodType} object + */ + private Object readResolve() { + // Do not use a trusted path for deserialization: + //return makeImpl(rtype, ptypes, true); + // Verify all operands, and make sure ptypes is unshared: + return methodType(rtype, ptypes); + } + + /** + * Simple implementation of weak concurrent intern set. + * + * @param <T> interned type + */ + private static class ConcurrentWeakInternSet<T> { + + private final ConcurrentMap<WeakEntry<T>, WeakEntry<T>> map; + private final ReferenceQueue<T> stale; + + public ConcurrentWeakInternSet() { + this.map = new ConcurrentHashMap<>(); + this.stale = new ReferenceQueue<>(); + } + + /** + * Get the existing interned element. + * This method returns null if no element is interned. + * + * @param elem element to look up + * @return the interned element + */ + public T get(T elem) { + if (elem == null) throw new NullPointerException(); + expungeStaleElements(); + + WeakEntry<T> value = map.get(new WeakEntry<>(elem)); + if (value != null) { + T res = value.get(); + if (res != null) { + return res; + } + } + return null; + } + + /** + * Interns the element. + * Always returns non-null element, matching the one in the intern set. + * Under the race against another add(), it can return <i>different</i> + * element, if another thread beats us to interning it. + * + * @param elem element to add + * @return element that was actually added + */ + public T add(T elem) { + if (elem == null) throw new NullPointerException(); + + // Playing double race here, and so spinloop is required. + // First race is with two concurrent updaters. + // Second race is with GC purging weak ref under our feet. + // Hopefully, we almost always end up with a single pass. + T interned; + WeakEntry<T> e = new WeakEntry<>(elem, stale); + do { + expungeStaleElements(); + WeakEntry<T> exist = map.putIfAbsent(e, e); + interned = (exist == null) ? elem : exist.get(); + } while (interned == null); + return interned; + } + + private void expungeStaleElements() { + Reference<? extends T> reference; + while ((reference = stale.poll()) != null) { + map.remove(reference); + } + } + + private static class WeakEntry<T> extends WeakReference<T> { + + public final int hashcode; + + public WeakEntry(T key, ReferenceQueue<T> queue) { + super(key, queue); + hashcode = key.hashCode(); + } + + public WeakEntry(T key) { + super(key); + hashcode = key.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof WeakEntry) { + Object that = ((WeakEntry) obj).get(); + Object mine = get(); + return (that == null || mine == null) ? (this == obj) : mine.equals(that); + } + return false; + } + + @Override + public int hashCode() { + return hashcode; + } + + } + } } diff --git a/java/lang/invoke/VarHandle.java b/java/lang/invoke/VarHandle.java index 562efb6e..bb93fcf5 100644 --- a/java/lang/invoke/VarHandle.java +++ b/java/lang/invoke/VarHandle.java @@ -25,10 +25,6 @@ package java.lang.invoke; -import dalvik.system.VMRuntime; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -446,16 +442,9 @@ public abstract class VarHandle { */ // END Android-removed: No VarForm in Android implementation. - // BEGIN Android-added: fields for common metadata. - /** The target type for accesses. */ - private final Class<?> varType; - - /** The coordinate types of a VarHandle instance. */ - private final List<Class<?>> coordinateTypes; - - /** BitMask of supported access mode indexed by AccessMode.ordinal(). */ - private final int accessModesBitMask; - // END Android-added: fields for common metadata. + RuntimeException unsupported() { + return new UnsupportedOperationException(); + } // Plain accessors @@ -485,8 +474,8 @@ public abstract class VarHandle { * symbolic type descriptor, but a reference cast fails. */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object get(Object... args); @@ -512,8 +501,8 @@ public abstract class VarHandle { * symbolic type descriptor, but a reference cast fails. */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate void set(Object... args); @@ -545,8 +534,8 @@ public abstract class VarHandle { * symbolic type descriptor, but a reference cast fails. */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getVolatile(Object... args); @@ -576,8 +565,8 @@ public abstract class VarHandle { * symbolic type descriptor, but a reference cast fails. */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate void setVolatile(Object... args); @@ -607,8 +596,8 @@ public abstract class VarHandle { * symbolic type descriptor, but a reference cast fails. */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getOpaque(Object... args); @@ -635,8 +624,8 @@ public abstract class VarHandle { * symbolic type descriptor, but a reference cast fails. */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate void setOpaque(Object... args); @@ -673,8 +662,8 @@ public abstract class VarHandle { * symbolic type descriptor, but a reference cast fails. */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAcquire(Object... args); @@ -705,8 +694,8 @@ public abstract class VarHandle { * symbolic type descriptor, but a reference cast fails. */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate void setRelease(Object... args); @@ -742,8 +731,8 @@ public abstract class VarHandle { * @see #getVolatile(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate boolean compareAndSet(Object... args); @@ -778,8 +767,8 @@ public abstract class VarHandle { * @see #getVolatile(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object compareAndExchange(Object... args); @@ -814,8 +803,8 @@ public abstract class VarHandle { * @see #getAcquire(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object compareAndExchangeAcquire(Object... args); @@ -850,8 +839,8 @@ public abstract class VarHandle { * @see #get(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object compareAndExchangeRelease(Object... args); @@ -890,8 +879,8 @@ public abstract class VarHandle { * @see #get(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate boolean weakCompareAndSetPlain(Object... args); @@ -928,8 +917,8 @@ public abstract class VarHandle { * @see #getVolatile(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate boolean weakCompareAndSet(Object... args); @@ -967,8 +956,8 @@ public abstract class VarHandle { * @see #getAcquire(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate boolean weakCompareAndSetAcquire(Object... args); @@ -1006,8 +995,8 @@ public abstract class VarHandle { * @see #get(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate boolean weakCompareAndSetRelease(Object... args); @@ -1040,8 +1029,8 @@ public abstract class VarHandle { * @see #getVolatile(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndSet(Object... args); @@ -1074,8 +1063,8 @@ public abstract class VarHandle { * @see #getVolatile(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndSetAcquire(Object... args); @@ -1108,8 +1097,8 @@ public abstract class VarHandle { * @see #getVolatile(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndSetRelease(Object... args); @@ -1145,8 +1134,8 @@ public abstract class VarHandle { * @see #getVolatile(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndAdd(Object... args); @@ -1179,8 +1168,8 @@ public abstract class VarHandle { * @see #getVolatile(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndAddAcquire(Object... args); @@ -1213,8 +1202,8 @@ public abstract class VarHandle { * @see #getVolatile(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndAddRelease(Object... args); @@ -1255,8 +1244,8 @@ public abstract class VarHandle { * @see #getVolatile(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndBitwiseOr(Object... args); @@ -1293,8 +1282,8 @@ public abstract class VarHandle { * @see #getAcquire(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndBitwiseOrAcquire(Object... args); @@ -1331,8 +1320,8 @@ public abstract class VarHandle { * @see #get(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndBitwiseOrRelease(Object... args); @@ -1369,8 +1358,8 @@ public abstract class VarHandle { * @see #getVolatile(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndBitwiseAnd(Object... args); @@ -1407,8 +1396,8 @@ public abstract class VarHandle { * @see #getAcquire(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndBitwiseAndAcquire(Object... args); @@ -1445,8 +1434,8 @@ public abstract class VarHandle { * @see #get(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndBitwiseAndRelease(Object... args); @@ -1483,8 +1472,8 @@ public abstract class VarHandle { * @see #getVolatile(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndBitwiseXor(Object... args); @@ -1521,8 +1510,8 @@ public abstract class VarHandle { * @see #getAcquire(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndBitwiseXorAcquire(Object... args); @@ -1559,8 +1548,8 @@ public abstract class VarHandle { * @see #get(Object...) */ public final native - @MethodHandle.PolymorphicSignature - // Android-removed: unsupported annotation. + // Android-removed: unsupported annotations. + // @MethodHandle.PolymorphicSignature // @HotSpotIntrinsicCandidate Object getAndBitwiseXorRelease(Object... args); @@ -1570,11 +1559,7 @@ public abstract class VarHandle { SET(void.class), COMPARE_AND_SWAP(boolean.class), COMPARE_AND_EXCHANGE(Object.class), - GET_AND_UPDATE(Object.class), - // Android-added: Finer grained access types. - // These are used to help categorize the access modes that a VarHandle supports. - GET_AND_UPDATE_BITWISE(Object.class), - GET_AND_UPDATE_NUMERIC(Object.class); + GET_AND_UPDATE(Object.class); final Class<?> returnType; final boolean isMonomorphicInReturnType; @@ -1611,8 +1596,6 @@ public abstract class VarHandle { ps[i] = value; return MethodType.methodType(value, ps); case GET_AND_UPDATE: - case GET_AND_UPDATE_BITWISE: - case GET_AND_UPDATE_NUMERIC: ps = allocateParameters(1, receiver, intermediate); i = fillParameters(ps, receiver, intermediate); ps[i] = value; @@ -1763,73 +1746,73 @@ public abstract class VarHandle { * method * {@link VarHandle#getAndAdd VarHandle.getAndAdd} */ - GET_AND_ADD("getAndAdd", AccessType.GET_AND_UPDATE_NUMERIC), + GET_AND_ADD("getAndAdd", AccessType.GET_AND_UPDATE), /** * The access mode whose access is specified by the corresponding * method * {@link VarHandle#getAndAddAcquire VarHandle.getAndAddAcquire} */ - GET_AND_ADD_ACQUIRE("getAndAddAcquire", AccessType.GET_AND_UPDATE_NUMERIC), + GET_AND_ADD_ACQUIRE("getAndAddAcquire", AccessType.GET_AND_UPDATE), /** * The access mode whose access is specified by the corresponding * method * {@link VarHandle#getAndAddRelease VarHandle.getAndAddRelease} */ - GET_AND_ADD_RELEASE("getAndAddRelease", AccessType.GET_AND_UPDATE_NUMERIC), + GET_AND_ADD_RELEASE("getAndAddRelease", AccessType.GET_AND_UPDATE), /** * The access mode whose access is specified by the corresponding * method * {@link VarHandle#getAndBitwiseOr VarHandle.getAndBitwiseOr} */ - GET_AND_BITWISE_OR("getAndBitwiseOr", AccessType.GET_AND_UPDATE_BITWISE), + GET_AND_BITWISE_OR("getAndBitwiseOr", AccessType.GET_AND_UPDATE), /** * The access mode whose access is specified by the corresponding * method * {@link VarHandle#getAndBitwiseOrRelease VarHandle.getAndBitwiseOrRelease} */ - GET_AND_BITWISE_OR_RELEASE("getAndBitwiseOrRelease", AccessType.GET_AND_UPDATE_BITWISE), + GET_AND_BITWISE_OR_RELEASE("getAndBitwiseOrRelease", AccessType.GET_AND_UPDATE), /** * The access mode whose access is specified by the corresponding * method * {@link VarHandle#getAndBitwiseOrAcquire VarHandle.getAndBitwiseOrAcquire} */ - GET_AND_BITWISE_OR_ACQUIRE("getAndBitwiseOrAcquire", AccessType.GET_AND_UPDATE_BITWISE), + GET_AND_BITWISE_OR_ACQUIRE("getAndBitwiseOrAcquire", AccessType.GET_AND_UPDATE), /** * The access mode whose access is specified by the corresponding * method * {@link VarHandle#getAndBitwiseAnd VarHandle.getAndBitwiseAnd} */ - GET_AND_BITWISE_AND("getAndBitwiseAnd", AccessType.GET_AND_UPDATE_BITWISE), + GET_AND_BITWISE_AND("getAndBitwiseAnd", AccessType.GET_AND_UPDATE), /** * The access mode whose access is specified by the corresponding * method * {@link VarHandle#getAndBitwiseAndRelease VarHandle.getAndBitwiseAndRelease} */ - GET_AND_BITWISE_AND_RELEASE("getAndBitwiseAndRelease", AccessType.GET_AND_UPDATE_BITWISE), + GET_AND_BITWISE_AND_RELEASE("getAndBitwiseAndRelease", AccessType.GET_AND_UPDATE), /** * The access mode whose access is specified by the corresponding * method * {@link VarHandle#getAndBitwiseAndAcquire VarHandle.getAndBitwiseAndAcquire} */ - GET_AND_BITWISE_AND_ACQUIRE("getAndBitwiseAndAcquire", AccessType.GET_AND_UPDATE_BITWISE), + GET_AND_BITWISE_AND_ACQUIRE("getAndBitwiseAndAcquire", AccessType.GET_AND_UPDATE), /** * The access mode whose access is specified by the corresponding * method * {@link VarHandle#getAndBitwiseXor VarHandle.getAndBitwiseXor} */ - GET_AND_BITWISE_XOR("getAndBitwiseXor", AccessType.GET_AND_UPDATE_BITWISE), + GET_AND_BITWISE_XOR("getAndBitwiseXor", AccessType.GET_AND_UPDATE), /** * The access mode whose access is specified by the corresponding * method * {@link VarHandle#getAndBitwiseXorRelease VarHandle.getAndBitwiseXorRelease} */ - GET_AND_BITWISE_XOR_RELEASE("getAndBitwiseXorRelease", AccessType.GET_AND_UPDATE_BITWISE), + GET_AND_BITWISE_XOR_RELEASE("getAndBitwiseXorRelease", AccessType.GET_AND_UPDATE), /** * The access mode whose access is specified by the corresponding * method * {@link VarHandle#getAndBitwiseXorAcquire VarHandle.getAndBitwiseXorAcquire} */ - GET_AND_BITWISE_XOR_ACQUIRE("getAndBitwiseXorAcquire", AccessType.GET_AND_UPDATE_BITWISE), + GET_AND_BITWISE_XOR_ACQUIRE("getAndBitwiseXorAcquire", AccessType.GET_AND_UPDATE), ; static final Map<String, AccessMode> methodNameToAccessMode; @@ -1915,11 +1898,8 @@ public abstract class VarHandle { * @return the variable type of variables referenced by this VarHandle */ public final Class<?> varType() { - // Android-removed: existing implementation. - // MethodType typeSet = accessModeType(AccessMode.SET); - // return typeSet.parameterType(typeSet.parameterCount() - 1) - // Android-added: return instance field. - return varType; + MethodType typeSet = accessModeType(AccessMode.SET); + return typeSet.parameterType(typeSet.parameterCount() - 1); } /** @@ -1929,11 +1909,8 @@ public abstract class VarHandle { * list is unmodifiable */ public final List<Class<?>> coordinateTypes() { - // Android-removed: existing implementation. - // MethodType typeGet = accessModeType(AccessMode.GET); - // return typeGet.parameterList(); - // Android-added: return instance field. - return coordinateTypes; + MethodType typeGet = accessModeType(AccessMode.GET); + return typeGet.parameterList(); } /** @@ -1963,18 +1940,9 @@ public abstract class VarHandle { */ // END Android-removed: Relies on internal class that is not part of the // Android implementation. - // Android-added: alternative implementation. - switch (coordinateTypes.size()) { - case 0: - return accessMode.at.accessModeType(null, varType); - case 1: - return accessMode.at.accessModeType(coordinateTypes.get(0), varType); - case 2: - return accessMode.at.accessModeType(coordinateTypes.get(0), varType, - coordinateTypes.get(1)); - default: - throw new InternalError("bad coordinateTypes: " + coordinateTypes); - } + // Android-added: Throw an exception until implemented. + unsupported(); // TODO(b/65872996) + return null; } // Android-removed: Not part of the Android implementation. @@ -1996,9 +1964,9 @@ public abstract class VarHandle { public final boolean isAccessModeSupported(AccessMode accessMode) { // Android-removed: Refers to unused field vform. // return AccessMode.getMemberName(accessMode.ordinal(), vform) != null; - // Android-added: use accessModesBitsMask field. - final int testBit = 1 << accessMode.ordinal(); - return (accessModesBitMask & testBit) == testBit; + // Android-added: Throw an exception until implemented. + unsupported(); // TODO(b/65872996) + return false; } /** @@ -2034,10 +2002,9 @@ public abstract class VarHandle { bindTo(this); } */ - // END Android-removed: no vform field in Android implementation. - - // Android-added: basic implementation following description in javadoc for this method. - return MethodHandles.varHandleInvoker(accessMode, accessModeType(accessMode)).bindTo(this); + // Android-added: Throw an exception until implemented. + unsupported(); // TODO(b/65872996) + return null; } // BEGIN Android-removed: Not used in Android implementation. @@ -2088,8 +2055,8 @@ public abstract class VarHandle { */ // END Android-removed: Not used in Android implementation. - // BEGIN Android-removed: No VarForm in Android implementation. /*non-public*/ + // BEGIN Android-removed: No VarForm in Android implementation. /* final void updateVarForm(VarForm newVForm) { if (vform == newVForm) return; @@ -2191,197 +2158,4 @@ public abstract class VarHandle { // NB The compiler recognizes all the fences here as intrinsics. UNSAFE.storeFence(); } - - // BEGIN Android-added: package private constructors. - /** - * Constructor for VarHandle with no coordinates. - * - * @param varType the variable type of variables to be referenced - * @param isFinal whether the target variables are final (non-modifiable) - * @hide - */ - VarHandle(Class<?> varType, boolean isFinal) { - this.varType = varType; - this.coordinateTypes = Collections.EMPTY_LIST; - this.accessModesBitMask = alignedAccessModesBitMask(varType, isFinal); - } - - /** - * Constructor for VarHandle with one coordinate. - * - * @param varType the variable type of variables to be referenced - * @param isFinal whether the target variables are final (non-modifiable) - * @param coordinate the coordinate - * @hide - */ - VarHandle(Class<?> varType, boolean isFinal, Class<?> coordinate) { - this.varType = varType; - this.coordinateTypes = Collections.singletonList(coordinate); - this.accessModesBitMask = alignedAccessModesBitMask(varType, isFinal); - } - - /** - * Constructor for VarHandle with two coordinates. - * - * @param varType the variable type of variables to be referenced - * @param backingArrayType the type of the array accesses will be performed on - * @param isFinal whether the target variables are final (non-modifiable) - * @param coordinate0 the first coordinate - * @param coordinate1 the second coordinate - * @hide - */ - VarHandle(Class<?> varType, Class<?> backingArrayType, boolean isFinal, - Class<?> coordinate0, Class<?> coordinate1) { - this.varType = varType; - this.coordinateTypes = Collections.unmodifiableList( - Arrays.asList(coordinate0, coordinate1)); - Class<?> backingArrayComponentType = backingArrayType.getComponentType(); - if (backingArrayComponentType != varType && backingArrayComponentType != byte.class) { - throw new InternalError("Unsupported backingArrayType: " + backingArrayType); - } - - if (backingArrayType.getComponentType() == varType) { - this.accessModesBitMask = alignedAccessModesBitMask(varType, isFinal); - } else { - this.accessModesBitMask = unalignedAccessModesBitMask(varType); - } - } - // END Android-changed: package private constructors. - - // BEGIN Android-added: helper state for VarHandle properties. - - /** BitMask of access modes that do not change the memory referenced by a VarHandle. - * An example being a read of a variable with volatile ordering effects. */ - private final static int READ_ACCESS_MODES_BIT_MASK; - - /** BitMask of access modes that write to the memory referenced by - * a VarHandle. This does not include any compare and update - * access modes, nor any bitwise or numeric access modes. An - * example being a write to variable with release ordering - * effects. - */ - private final static int WRITE_ACCESS_MODES_BIT_MASK; - - /** BitMask of access modes that are applicable to types - * supporting for atomic updates. This includes access modes that - * both read and write a variable such as compare-and-set. - */ - private final static int ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK; - - /** BitMask of access modes that are applicable to types - * supporting numeric atomic update operations. */ - private final static int NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK; - - /** BitMask of access modes that are applicable to types - * supporting bitwise atomic update operations. */ - private final static int BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK; - - /** BitMask of all access modes. */ - private final static int ALL_MODES_BIT_MASK; - - /** Indicator of machine word size. */ - private final static boolean RUNNING_ON_64BIT = VMRuntime.getRuntime().is64Bit(); - - static { - // Check we're not about to overflow the storage of the - // bitmasks here and in the accessModesBitMask field. - if (AccessMode.values().length > Integer.SIZE) { - throw new InternalError("accessModes overflow"); - } - - // Access modes bit mask declarations and initialization order - // follows the presentation order in JEP193. - READ_ACCESS_MODES_BIT_MASK = accessTypesToBitMask(EnumSet.of(AccessType.GET)); - - WRITE_ACCESS_MODES_BIT_MASK = accessTypesToBitMask(EnumSet.of(AccessType.SET)); - - ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK = - accessTypesToBitMask(EnumSet.of(AccessType.COMPARE_AND_EXCHANGE, - AccessType.COMPARE_AND_SWAP, - AccessType.GET_AND_UPDATE)); - - NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK = - accessTypesToBitMask(EnumSet.of(AccessType.GET_AND_UPDATE_NUMERIC)); - - BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK = - accessTypesToBitMask(EnumSet.of(AccessType.GET_AND_UPDATE_BITWISE)); - - ALL_MODES_BIT_MASK = (READ_ACCESS_MODES_BIT_MASK | - WRITE_ACCESS_MODES_BIT_MASK | - ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK | - NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK | - BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK); - } - - static int accessTypesToBitMask(final EnumSet<AccessType> accessTypes) { - int m = 0; - for (AccessMode accessMode : AccessMode.values()) { - if (accessTypes.contains(accessMode.at)) { - m |= 1 << accessMode.ordinal(); - } - } - return m; - } - - static int alignedAccessModesBitMask(Class<?> varType, boolean isFinal) { - // For aligned accesses, the supported access modes are described in: - // @see java.lang.invoke.MethodHandles.Lookup#findVarHandle - int bitMask = ALL_MODES_BIT_MASK; - - // If the field is declared final, keep only the read access modes. - if (isFinal) { - bitMask &= READ_ACCESS_MODES_BIT_MASK; - } - - // If the field is anything other than byte, short, char, int, - // long, float, double then remove the numeric atomic update - // access modes. - if (varType != byte.class && varType != short.class && varType != char.class && - varType != int.class && varType != long.class - && varType != float.class && varType != double.class) { - bitMask &= ~NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK; - } - - // If the field is not integral, remove the bitwise atomic update access modes. - if (varType != boolean.class && varType != byte.class && varType != short.class && - varType != char.class && varType != int.class && varType != long.class) { - bitMask &= ~BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK; - } - return bitMask; - } - - static int unalignedAccessModesBitMask(Class<?> varType) { - // The VarHandle refers to a view of byte array or a - // view of a byte buffer. The corresponding accesses - // maybe unaligned so the access modes are more - // restrictive than field or array element accesses. - // - // The supported access modes are described in: - // @see java.lang.invoke.MethodHandles#byteArrayViewVarHandle - int bitMask = 0; - - // Read/write access modes supported for all types except for - // long and double on 32-bit platforms. - if (RUNNING_ON_64BIT || (varType != long.class && varType != double.class)) { - bitMask |= READ_ACCESS_MODES_BIT_MASK | WRITE_ACCESS_MODES_BIT_MASK; - } - - // int, long, float, double support atomic update modes per documentation. - if (varType == int.class || varType == long.class || - varType == float.class || varType == double.class) { - bitMask |= ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK; - } - - // int and long support numeric updates per documentation. - if (varType == int.class || varType == long.class) { - bitMask |= NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK; - } - - // int and long support bitwise updates per documentation. - if (varType == int.class || varType == long.class) { - bitMask |= BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK; - } - return bitMask; - } - // END Android-added: helper class for VarHandle properties. } diff --git a/java/net/Socket.java b/java/net/Socket.java index e36d15b2..03e2b717 100644 --- a/java/net/Socket.java +++ b/java/net/Socket.java @@ -122,7 +122,7 @@ class Socket implements java.io.Closeable { Proxy p = proxy == Proxy.NO_PROXY ? Proxy.NO_PROXY : sun.net.ApplicationProxy.create(proxy); Proxy.Type type = p.type(); - // Android-changed: Removed HTTP proxy support. + // Android-changed: Removed HTTP proxy suppport. // if (type == Proxy.Type.SOCKS || type == Proxy.Type.HTTP) { if (type == Proxy.Type.SOCKS) { SecurityManager security = System.getSecurityManager(); @@ -214,7 +214,6 @@ class Socket implements java.io.Closeable { public Socket(String host, int port) throws UnknownHostException, IOException { - // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735 this(InetAddress.getAllByName(host), port, (SocketAddress) null, true); } @@ -246,7 +245,6 @@ class Socket implements java.io.Closeable { * @see SecurityManager#checkConnect */ public Socket(InetAddress address, int port) throws IOException { - // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735 this(nonNullAddress(address), port, (SocketAddress) null, true); } @@ -288,7 +286,6 @@ class Socket implements java.io.Closeable { */ public Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException { - // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735 this(InetAddress.getAllByName(host), port, new InetSocketAddress(localAddr, localPort), true); } @@ -330,7 +327,6 @@ class Socket implements java.io.Closeable { */ public Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException { - // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735 this(nonNullAddress(address), port, new InetSocketAddress(localAddr, localPort), true); } @@ -378,7 +374,6 @@ class Socket implements java.io.Closeable { */ @Deprecated public Socket(String host, int port, boolean stream) throws IOException { - // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735 this(InetAddress.getAllByName(host), port, (SocketAddress) null, stream); } @@ -420,11 +415,9 @@ class Socket implements java.io.Closeable { */ @Deprecated public Socket(InetAddress host, int port, boolean stream) throws IOException { - // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735 this(nonNullAddress(host), port, new InetSocketAddress(0), stream); } - // BEGIN Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735 private static InetAddress[] nonNullAddress(InetAddress address) { // backward compatibility if (address == null) @@ -433,6 +426,8 @@ class Socket implements java.io.Closeable { return new InetAddress[] { address }; } + // Android-changed: Socket ctor should try all addresses + // b/30007735 private Socket(InetAddress[] addresses, int port, SocketAddress localAddr, boolean stream) throws IOException { if (addresses == null || addresses.length == 0) { @@ -451,8 +446,9 @@ class Socket implements java.io.Closeable { break; } catch (IOException | IllegalArgumentException | SecurityException e) { try { - // Android-changed: Let ctor call impl.close() instead of overridable close(). - // Subclasses may not expect a call to close() coming from this constructor. + // Android-changed: + // Do not call #close, classes that extend this class may do not expect a call + // to #close coming from the superclass constructor. impl.close(); closed = true; } catch (IOException ce) { @@ -472,7 +468,6 @@ class Socket implements java.io.Closeable { closed = false; } } - // END Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735 /** * Creates the socket implementation. @@ -1065,7 +1060,7 @@ class Socket implements java.io.Closeable { * * The setting only affects socket close. * - * @return the setting for {@link SocketOptions#SO_LINGER SO_LINGER}. + * @return the setting for SO_LINGER. * @exception SocketException if there is an error * in the underlying protocol, such as a TCP error. * @since JDK1.1 @@ -1773,7 +1768,7 @@ class Socket implements java.io.Closeable { /* Not implemented yet */ } - // Android-added: getFileDescriptor$() method for testing and internal use. + // Android-added: for testing and internal use. /** * @hide internal use only */ diff --git a/java/net/SocketException.java b/java/net/SocketException.java index 64ae7710..286bc427 100644 --- a/java/net/SocketException.java +++ b/java/net/SocketException.java @@ -54,7 +54,6 @@ class SocketException extends IOException { public SocketException() { } - // BEGIN Android-added: SocketException ctor with cause for internal use. /** @hide */ public SocketException(Throwable cause) { super(cause); @@ -64,5 +63,4 @@ class SocketException extends IOException { public SocketException(String msg, Throwable cause) { super(msg, cause); } - // END Android-added: SocketException ctor with cause for internal use. } diff --git a/java/net/SocketImpl.java b/java/net/SocketImpl.java index ade2630f..c0db070e 100644 --- a/java/net/SocketImpl.java +++ b/java/net/SocketImpl.java @@ -227,7 +227,6 @@ public abstract class SocketImpl implements SocketOptions { return fd; } - // Android-added: getFD$() for testing. /** * @hide used by java.nio tests */ diff --git a/java/net/SocketInputStream.java b/java/net/SocketInputStream.java index 8d0e0c57..f5c4c8f8 100644 --- a/java/net/SocketInputStream.java +++ b/java/net/SocketInputStream.java @@ -44,11 +44,6 @@ import sun.net.ConnectionResetException; */ class SocketInputStream extends FileInputStream { - // Android-removed: Android doesn't need to call native init. - // static { - // init(); - //} - private boolean eof; private AbstractPlainSocketImpl impl = null; private byte temp[]; @@ -171,7 +166,6 @@ class SocketInputStream extends FileInputStream // acquire file descriptor and do the read FileDescriptor fd = impl.acquireFD(); try { - // Android-added: Check BlockGuard policy in read(). BlockGuard.getThreadPolicy().onNetwork(); n = socketRead(fd, b, off, length, timeout); if (n > 0) { @@ -295,11 +289,4 @@ class SocketInputStream extends FileInputStream * Overrides finalize, the fd is closed by the Socket. */ protected void finalize() {} - - // Android-removed: Android doesn't need native init. - /* - * Perform class load-time initializations. - * - private native static void init(); - */ } diff --git a/java/net/SocketOutputStream.java b/java/net/SocketOutputStream.java index 9e8e7926..c0173f0d 100644 --- a/java/net/SocketOutputStream.java +++ b/java/net/SocketOutputStream.java @@ -43,11 +43,6 @@ import dalvik.system.BlockGuard; */ class SocketOutputStream extends FileOutputStream { - // Android-removed: Android doesn't need to call native init. - // static { - // init(); - //} - private AbstractPlainSocketImpl impl = null; private byte temp[] = new byte[1]; private Socket socket = null; @@ -100,8 +95,6 @@ class SocketOutputStream extends FileOutputStream * @exception IOException If an I/O error has occurred. */ private void socketWrite(byte b[], int off, int len) throws IOException { - - if (len <= 0 || off < 0 || len > b.length - off) { if (len == 0) { return; @@ -112,7 +105,6 @@ class SocketOutputStream extends FileOutputStream FileDescriptor fd = impl.acquireFD(); try { - // Android-added: Check BlockGuard policy in socketWrite. BlockGuard.getThreadPolicy().onNetwork(); socketWrite0(fd, b, off, len); } catch (SocketException se) { @@ -182,11 +174,4 @@ class SocketOutputStream extends FileOutputStream * Overrides finalize, the fd is closed by the Socket. */ protected void finalize() {} - - // Android-removed: Android doesn't need native init. - /* - * Perform class load-time initializations. - * - private native static void init(); - */ } diff --git a/java/net/SocksSocketImpl.java b/java/net/SocksSocketImpl.java index 0d9d8f59..a81e219b 100644 --- a/java/net/SocksSocketImpl.java +++ b/java/net/SocksSocketImpl.java @@ -347,91 +347,12 @@ class SocksSocketImpl extends PlainSocketImpl implements SocksConsts { epoint.getPort()); } if (server == null) { - // Android-removed: Logic to establish proxy connection based on default ProxySelector. - // Removed code that tried to establish proxy connection if ProxySelector#getDefault() - // is not null. This was never the case in previous Android releases, was causing - // issues and therefore was removed. + // Android-removed: Logic to establish proxy connection based on default ProxySelector /* - // This is the general case - // server is not null only when the socket was created with a - // specified proxy in which case it does bypass the ProxySelector - ProxySelector sel = java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction<ProxySelector>() { - public ProxySelector run() { - return ProxySelector.getDefault(); - } - }); - if (sel == null) { - /* - * No default proxySelector --> direct connection - * - super.connect(epoint, remainingMillis(deadlineMillis)); - return; - } - URI uri; - // Use getHostString() to avoid reverse lookups - String host = epoint.getHostString(); - // IPv6 litteral? - if (epoint.getAddress() instanceof Inet6Address && - (!host.startsWith("[")) && (host.indexOf(":") >= 0)) { - host = "[" + host + "]"; - } - try { - uri = new URI("socket://" + ParseUtil.encodePath(host) + ":"+ epoint.getPort()); - } catch (URISyntaxException e) { - // This shouldn't happen - assert false : e; - uri = null; - } - Proxy p = null; - IOException savedExc = null; - java.util.Iterator<Proxy> iProxy = null; - iProxy = sel.select(uri).iterator(); - if (iProxy == null || !(iProxy.hasNext())) { - super.connect(epoint, remainingMillis(deadlineMillis)); - return; - } - while (iProxy.hasNext()) { - p = iProxy.next(); - if (p == null || p.type() != Proxy.Type.SOCKS) { - super.connect(epoint, remainingMillis(deadlineMillis)); - return; - } - - if (!(p.address() instanceof InetSocketAddress)) - throw new SocketException("Unknown address type for proxy: " + p); - // Use getHostString() to avoid reverse lookups - server = ((InetSocketAddress) p.address()).getHostString(); - serverPort = ((InetSocketAddress) p.address()).getPort(); - if (p instanceof SocksProxy) { - if (((SocksProxy)p).protocolVersion() == 4) { - useV4 = true; - } - } - - // Connects to the SOCKS server - try { - privilegedConnect(server, serverPort, remainingMillis(deadlineMillis)); - // Worked, let's get outta here - break; - } catch (IOException e) { - // Ooops, let's notify the ProxySelector - sel.connectFailed(uri,p.address(),e); - server = null; - serverPort = -1; - savedExc = e; - // Will continue the while loop and try the next proxy - } - } - - /* - * If server is still null at this point, none of the proxy - * worked - * - if (server == null) { - throw new SocketException("Can't connect to SOCKS proxy:" - + savedExc.getMessage()); - } + * Removed code that tried to establish proxy connection if + * ProxySelector#getDefault() is not null. + * This was never the case in previous android releases, was causing + * issues and therefore was removed. */ super.connect(epoint, remainingMillis(deadlineMillis)); return; @@ -587,458 +508,6 @@ class SocksSocketImpl extends PlainSocketImpl implements SocksConsts { external_address = epoint; } - // Android-removed: Dead code. bindV4, socksBind, acceptFrom methods. - /* - private void bindV4(InputStream in, OutputStream out, - InetAddress baddr, - int lport) throws IOException { - if (!(baddr instanceof Inet4Address)) { - throw new SocketException("SOCKS V4 requires IPv4 only addresses"); - } - super.bind(baddr, lport); - byte[] addr1 = baddr.getAddress(); - /* Test for AnyLocal * - InetAddress naddr = baddr; - if (naddr.isAnyLocalAddress()) { - naddr = AccessController.doPrivileged( - new PrivilegedAction<InetAddress>() { - public InetAddress run() { - return cmdsock.getLocalAddress(); - - } - }); - addr1 = naddr.getAddress(); - } - out.write(PROTO_VERS4); - out.write(BIND); - out.write((super.getLocalPort() >> 8) & 0xff); - out.write((super.getLocalPort() >> 0) & 0xff); - out.write(addr1); - String userName = getUserName(); - try { - out.write(userName.getBytes("ISO-8859-1")); - } catch (java.io.UnsupportedEncodingException uee) { - assert false; - } - out.write(0); - out.flush(); - byte[] data = new byte[8]; - int n = readSocksReply(in, data); - if (n != 8) - throw new SocketException("Reply from SOCKS server has bad length: " + n); - if (data[0] != 0 && data[0] != 4) - throw new SocketException("Reply from SOCKS server has bad version"); - SocketException ex = null; - switch (data[1]) { - case 90: - // Success! - external_address = new InetSocketAddress(baddr, lport); - break; - case 91: - ex = new SocketException("SOCKS request rejected"); - break; - case 92: - ex = new SocketException("SOCKS server couldn't reach destination"); - break; - case 93: - ex = new SocketException("SOCKS authentication failed"); - break; - default: - ex = new SocketException("Reply from SOCKS server contains bad status"); - break; - } - if (ex != null) { - in.close(); - out.close(); - throw ex; - } - - } - - /** - * Sends the Bind request to the SOCKS proxy. In the SOCKS protocol, bind - * means "accept incoming connection from", so the SocketAddress is the - * the one of the host we do accept connection from. - * - * @param saddr the Socket address of the remote host. - * @exception IOException if an I/O error occurs when binding this socket. - * - protected synchronized void socksBind(InetSocketAddress saddr) throws IOException { - if (socket != null) { - // this is a client socket, not a server socket, don't - // call the SOCKS proxy for a bind! - return; - } - - // Connects to the SOCKS server - - if (server == null) { - // This is the general case - // server is not null only when the socket was created with a - // specified proxy in which case it does bypass the ProxySelector - ProxySelector sel = java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction<ProxySelector>() { - public ProxySelector run() { - return ProxySelector.getDefault(); - } - }); - if (sel == null) { - /* - * No default proxySelector --> direct connection - * - return; - } - URI uri; - // Use getHostString() to avoid reverse lookups - String host = saddr.getHostString(); - // IPv6 litteral? - if (saddr.getAddress() instanceof Inet6Address && - (!host.startsWith("[")) && (host.indexOf(":") >= 0)) { - host = "[" + host + "]"; - } - try { - uri = new URI("serversocket://" + ParseUtil.encodePath(host) + ":"+ saddr.getPort()); - } catch (URISyntaxException e) { - // This shouldn't happen - assert false : e; - uri = null; - } - Proxy p = null; - Exception savedExc = null; - java.util.Iterator<Proxy> iProxy = null; - iProxy = sel.select(uri).iterator(); - if (iProxy == null || !(iProxy.hasNext())) { - return; - } - while (iProxy.hasNext()) { - p = iProxy.next(); - if (p == null || p.type() != Proxy.Type.SOCKS) { - return; - } - - if (!(p.address() instanceof InetSocketAddress)) - throw new SocketException("Unknown address type for proxy: " + p); - // Use getHostString() to avoid reverse lookups - server = ((InetSocketAddress) p.address()).getHostString(); - serverPort = ((InetSocketAddress) p.address()).getPort(); - if (p instanceof SocksProxy) { - if (((SocksProxy)p).protocolVersion() == 4) { - useV4 = true; - } - } - - // Connects to the SOCKS server - try { - AccessController.doPrivileged( - new PrivilegedExceptionAction<Void>() { - public Void run() throws Exception { - cmdsock = new Socket(new PlainSocketImpl()); - cmdsock.connect(new InetSocketAddress(server, serverPort)); - cmdIn = cmdsock.getInputStream(); - cmdOut = cmdsock.getOutputStream(); - return null; - } - }); - } catch (Exception e) { - // Ooops, let's notify the ProxySelector - sel.connectFailed(uri,p.address(),new SocketException(e.getMessage())); - server = null; - serverPort = -1; - cmdsock = null; - savedExc = e; - // Will continue the while loop and try the next proxy - } - } - - /* - * If server is still null at this point, none of the proxy - * worked - * - if (server == null || cmdsock == null) { - throw new SocketException("Can't connect to SOCKS proxy:" - + savedExc.getMessage()); - } - } else { - try { - AccessController.doPrivileged( - new PrivilegedExceptionAction<Void>() { - public Void run() throws Exception { - cmdsock = new Socket(new PlainSocketImpl()); - cmdsock.connect(new InetSocketAddress(server, serverPort)); - cmdIn = cmdsock.getInputStream(); - cmdOut = cmdsock.getOutputStream(); - return null; - } - }); - } catch (Exception e) { - throw new SocketException(e.getMessage()); - } - } - BufferedOutputStream out = new BufferedOutputStream(cmdOut, 512); - InputStream in = cmdIn; - if (useV4) { - bindV4(in, out, saddr.getAddress(), saddr.getPort()); - return; - } - out.write(PROTO_VERS); - out.write(2); - out.write(NO_AUTH); - out.write(USER_PASSW); - out.flush(); - byte[] data = new byte[2]; - int i = readSocksReply(in, data); - if (i != 2 || ((int)data[0]) != PROTO_VERS) { - // Maybe it's not a V5 sever after all - // Let's try V4 before we give up - bindV4(in, out, saddr.getAddress(), saddr.getPort()); - return; - } - if (((int)data[1]) == NO_METHODS) - throw new SocketException("SOCKS : No acceptable methods"); - if (!authenticate(data[1], in, out)) { - throw new SocketException("SOCKS : authentication failed"); - } - // We're OK. Let's issue the BIND command. - out.write(PROTO_VERS); - out.write(BIND); - out.write(0); - int lport = saddr.getPort(); - if (saddr.isUnresolved()) { - out.write(DOMAIN_NAME); - out.write(saddr.getHostName().length()); - try { - out.write(saddr.getHostName().getBytes("ISO-8859-1")); - } catch (java.io.UnsupportedEncodingException uee) { - assert false; - } - out.write((lport >> 8) & 0xff); - out.write((lport >> 0) & 0xff); - } else if (saddr.getAddress() instanceof Inet4Address) { - byte[] addr1 = saddr.getAddress().getAddress(); - out.write(IPV4); - out.write(addr1); - out.write((lport >> 8) & 0xff); - out.write((lport >> 0) & 0xff); - out.flush(); - } else if (saddr.getAddress() instanceof Inet6Address) { - byte[] addr1 = saddr.getAddress().getAddress(); - out.write(IPV6); - out.write(addr1); - out.write((lport >> 8) & 0xff); - out.write((lport >> 0) & 0xff); - out.flush(); - } else { - cmdsock.close(); - throw new SocketException("unsupported address type : " + saddr); - } - data = new byte[4]; - i = readSocksReply(in, data); - SocketException ex = null; - int len, nport; - byte[] addr; - switch (data[1]) { - case REQUEST_OK: - // success! - switch(data[3]) { - case IPV4: - addr = new byte[4]; - i = readSocksReply(in, addr); - if (i != 4) - throw new SocketException("Reply from SOCKS server badly formatted"); - data = new byte[2]; - i = readSocksReply(in, data); - if (i != 2) - throw new SocketException("Reply from SOCKS server badly formatted"); - nport = ((int)data[0] & 0xff) << 8; - nport += ((int)data[1] & 0xff); - external_address = - new InetSocketAddress(new Inet4Address("", addr) , nport); - break; - case DOMAIN_NAME: - len = data[1]; - byte[] host = new byte[len]; - i = readSocksReply(in, host); - if (i != len) - throw new SocketException("Reply from SOCKS server badly formatted"); - data = new byte[2]; - i = readSocksReply(in, data); - if (i != 2) - throw new SocketException("Reply from SOCKS server badly formatted"); - nport = ((int)data[0] & 0xff) << 8; - nport += ((int)data[1] & 0xff); - external_address = new InetSocketAddress(new String(host), nport); - break; - case IPV6: - len = data[1]; - addr = new byte[len]; - i = readSocksReply(in, addr); - if (i != len) - throw new SocketException("Reply from SOCKS server badly formatted"); - data = new byte[2]; - i = readSocksReply(in, data); - if (i != 2) - throw new SocketException("Reply from SOCKS server badly formatted"); - nport = ((int)data[0] & 0xff) << 8; - nport += ((int)data[1] & 0xff); - external_address = - new InetSocketAddress(new Inet6Address("", addr), nport); - break; - } - break; - case GENERAL_FAILURE: - ex = new SocketException("SOCKS server general failure"); - break; - case NOT_ALLOWED: - ex = new SocketException("SOCKS: Bind not allowed by ruleset"); - break; - case NET_UNREACHABLE: - ex = new SocketException("SOCKS: Network unreachable"); - break; - case HOST_UNREACHABLE: - ex = new SocketException("SOCKS: Host unreachable"); - break; - case CONN_REFUSED: - ex = new SocketException("SOCKS: Connection refused"); - break; - case TTL_EXPIRED: - ex = new SocketException("SOCKS: TTL expired"); - break; - case CMD_NOT_SUPPORTED: - ex = new SocketException("SOCKS: Command not supported"); - break; - case ADDR_TYPE_NOT_SUP: - ex = new SocketException("SOCKS: address type not supported"); - break; - } - if (ex != null) { - in.close(); - out.close(); - cmdsock.close(); - cmdsock = null; - throw ex; - } - cmdIn = in; - cmdOut = out; - } - - /** - * Accepts a connection from a specific host. - * - * @param s the accepted connection. - * @param saddr the socket address of the host we do accept - * connection from - * @exception IOException if an I/O error occurs when accepting the - * connection. - * - protected void acceptFrom(SocketImpl s, InetSocketAddress saddr) throws IOException { - if (cmdsock == null) { - // Not a Socks ServerSocket. - return; - } - InputStream in = cmdIn; - // Sends the "SOCKS BIND" request. - socksBind(saddr); - in.read(); - int i = in.read(); - in.read(); - SocketException ex = null; - int nport; - byte[] addr; - InetSocketAddress real_end = null; - switch (i) { - case REQUEST_OK: - // success! - i = in.read(); - switch(i) { - case IPV4: - addr = new byte[4]; - readSocksReply(in, addr); - nport = in.read() << 8; - nport += in.read(); - real_end = - new InetSocketAddress(new Inet4Address("", addr) , nport); - break; - case DOMAIN_NAME: - int len = in.read(); - addr = new byte[len]; - readSocksReply(in, addr); - nport = in.read() << 8; - nport += in.read(); - real_end = new InetSocketAddress(new String(addr), nport); - break; - case IPV6: - addr = new byte[16]; - readSocksReply(in, addr); - nport = in.read() << 8; - nport += in.read(); - real_end = - new InetSocketAddress(new Inet6Address("", addr), nport); - break; - } - break; - case GENERAL_FAILURE: - ex = new SocketException("SOCKS server general failure"); - break; - case NOT_ALLOWED: - ex = new SocketException("SOCKS: Accept not allowed by ruleset"); - break; - case NET_UNREACHABLE: - ex = new SocketException("SOCKS: Network unreachable"); - break; - case HOST_UNREACHABLE: - ex = new SocketException("SOCKS: Host unreachable"); - break; - case CONN_REFUSED: - ex = new SocketException("SOCKS: Connection refused"); - break; - case TTL_EXPIRED: - ex = new SocketException("SOCKS: TTL expired"); - break; - case CMD_NOT_SUPPORTED: - ex = new SocketException("SOCKS: Command not supported"); - break; - case ADDR_TYPE_NOT_SUP: - ex = new SocketException("SOCKS: address type not supported"); - break; - } - if (ex != null) { - cmdIn.close(); - cmdOut.close(); - cmdsock.close(); - cmdsock = null; - throw ex; - } - - /** - * This is where we have to do some fancy stuff. - * The datastream from the socket "accepted" by the proxy will - * come through the cmdSocket. So we have to swap the socketImpls - * - if (s instanceof SocksSocketImpl) { - ((SocksSocketImpl)s).external_address = real_end; - } - if (s instanceof PlainSocketImpl) { - PlainSocketImpl psi = (PlainSocketImpl) s; - psi.setInputStream((SocketInputStream) in); - psi.setFileDescriptor(cmdsock.getImpl().getFileDescriptor()); - psi.setAddress(cmdsock.getImpl().getInetAddress()); - psi.setPort(cmdsock.getImpl().getPort()); - psi.setLocalPort(cmdsock.getImpl().getLocalPort()); - } else { - s.fd = cmdsock.getImpl().fd; - s.address = cmdsock.getImpl().address; - s.port = cmdsock.getImpl().port; - s.localport = cmdsock.getImpl().localport; - } - - // Need to do that so that the socket won't be closed - // when the ServerSocket is closed by the user. - // It kinds of detaches the Socket because it is now - // used elsewhere. - cmdsock = null; - } - */ - /** * Returns the value of this socket's {@code address} field. * diff --git a/java/net/URLClassLoader.java b/java/net/URLClassLoader.java index 6e8acb1c..edb9b882 100644 --- a/java/net/URLClassLoader.java +++ b/java/net/URLClassLoader.java @@ -103,8 +103,8 @@ public class URLClassLoader extends SecureClassLoader implements Closeable { if (security != null) { security.checkCreateClassLoader(); } + ucp = new URLClassPath(urls); this.acc = AccessController.getContext(); - ucp = new URLClassPath(urls, acc); } URLClassLoader(URL[] urls, ClassLoader parent, @@ -115,8 +115,8 @@ public class URLClassLoader extends SecureClassLoader implements Closeable { if (security != null) { security.checkCreateClassLoader(); } + ucp = new URLClassPath(urls); this.acc = acc; - ucp = new URLClassPath(urls, acc); } /** @@ -147,8 +147,8 @@ public class URLClassLoader extends SecureClassLoader implements Closeable { if (security != null) { security.checkCreateClassLoader(); } + ucp = new URLClassPath(urls); this.acc = AccessController.getContext(); - ucp = new URLClassPath(urls, acc); } URLClassLoader(URL[] urls, AccessControlContext acc) { @@ -158,8 +158,8 @@ public class URLClassLoader extends SecureClassLoader implements Closeable { if (security != null) { security.checkCreateClassLoader(); } + ucp = new URLClassPath(urls); this.acc = acc; - ucp = new URLClassPath(urls, acc); } /** @@ -180,7 +180,6 @@ public class URLClassLoader extends SecureClassLoader implements Closeable { * @exception SecurityException if a security manager exists and its * {@code checkCreateClassLoader} method doesn't allow * creation of a class loader. - * @exception NullPointerException if {@code urls} is {@code null}. * @see SecurityManager#checkCreateClassLoader */ public URLClassLoader(URL[] urls, ClassLoader parent, @@ -272,13 +271,13 @@ public class URLClassLoader extends SecureClassLoader implements Closeable { * and errors are not caught. Calling close on an already closed * loader has no effect. * <p> - * @exception IOException if closing any file opened by this class loader + * @throws IOException if closing any file opened by this class loader * resulted in an IOException. Any such exceptions are caught internally. * If only one is caught, then it is re-thrown. If more than one exception * is caught, then the second and following exceptions are added * as suppressed exceptions of the first one caught, which is then re-thrown. * - * @exception SecurityException if a security manager is set, and it denies + * @throws SecurityException if a security manager is set, and it denies * {@link RuntimePermission}{@code ("closeClassLoader")} * * @since 1.7 @@ -456,16 +455,12 @@ public class URLClassLoader extends SecureClassLoader implements Closeable { // Use (direct) ByteBuffer: CodeSigner[] signers = res.getCodeSigners(); CodeSource cs = new CodeSource(url, signers); - // Android-removed: Android doesn't use sun.misc.PerfCounter. - // sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); return defineClass(name, bb, cs); } else { byte[] b = res.getBytes(); // must read certificates AFTER reading bytes. CodeSigner[] signers = res.getCodeSigners(); CodeSource cs = new CodeSource(url, signers); - // Android-removed: Android doesn't use sun.misc.PerfCounter. - // sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); return defineClass(name, b, 0, b.length, cs); } } @@ -771,7 +766,6 @@ public class URLClassLoader extends SecureClassLoader implements Closeable { } static { - // Android-removed: SharedSecrets.setJavaNetAccess call. Android doesn't use it. /*sun.misc.SharedSecrets.setJavaNetAccess ( new sun.misc.JavaNetAccess() { public URLClassPath getURLClassPath (URLClassLoader u) { diff --git a/java/security/AlgorithmParameters.java b/java/security/AlgorithmParameters.java index 864866ef..36bb3ee0 100644 --- a/java/security/AlgorithmParameters.java +++ b/java/security/AlgorithmParameters.java @@ -29,8 +29,6 @@ import java.io.*; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidParameterSpecException; -import sun.security.jca.Providers; - /** * This class is used as an opaque representation of cryptographic parameters. * @@ -287,8 +285,6 @@ public class AlgorithmParameters { { if (provider == null || provider.length() == 0) throw new IllegalArgumentException("missing provider"); - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "AlgorithmParameters", algorithm); Object[] objs = Security.getImpl(algorithm, "AlgorithmParameters", provider); return new AlgorithmParameters((AlgorithmParametersSpi)objs[0], @@ -334,8 +330,6 @@ public class AlgorithmParameters { { if (provider == null) throw new IllegalArgumentException("missing provider"); - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "AlgorithmParameters", algorithm); Object[] objs = Security.getImpl(algorithm, "AlgorithmParameters", provider); return new AlgorithmParameters((AlgorithmParametersSpi)objs[0], diff --git a/java/security/KeyFactory.java b/java/security/KeyFactory.java index d01ce161..a6b912c5 100644 --- a/java/security/KeyFactory.java +++ b/java/security/KeyFactory.java @@ -231,8 +231,6 @@ public class KeyFactory { */ public static KeyFactory getInstance(String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException { - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "KeyFactory", algorithm); Instance instance = GetInstance.getInstance("KeyFactory", KeyFactorySpi.class, algorithm, provider); return new KeyFactory((KeyFactorySpi)instance.impl, @@ -270,8 +268,6 @@ public class KeyFactory { */ public static KeyFactory getInstance(String algorithm, Provider provider) throws NoSuchAlgorithmException { - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "KeyFactory", algorithm); Instance instance = GetInstance.getInstance("KeyFactory", KeyFactorySpi.class, algorithm, provider); return new KeyFactory((KeyFactorySpi)instance.impl, diff --git a/java/security/KeyPairGenerator.java b/java/security/KeyPairGenerator.java index 51a0ec93..68ab5e94 100644 --- a/java/security/KeyPairGenerator.java +++ b/java/security/KeyPairGenerator.java @@ -299,8 +299,6 @@ public abstract class KeyPairGenerator extends KeyPairGeneratorSpi { public static KeyPairGenerator getInstance(String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException { - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "KeyPairGenerator", algorithm); Instance instance = GetInstance.getInstance("KeyPairGenerator", KeyPairGeneratorSpi.class, algorithm, provider); return getInstance(instance, algorithm); @@ -337,8 +335,6 @@ public abstract class KeyPairGenerator extends KeyPairGeneratorSpi { */ public static KeyPairGenerator getInstance(String algorithm, Provider provider) throws NoSuchAlgorithmException { - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "KeyPairGenerator", algorithm); Instance instance = GetInstance.getInstance("KeyPairGenerator", KeyPairGeneratorSpi.class, algorithm, provider); return getInstance(instance, algorithm); diff --git a/java/security/MessageDigest.java b/java/security/MessageDigest.java index ab2614a5..8e5dab14 100644 --- a/java/security/MessageDigest.java +++ b/java/security/MessageDigest.java @@ -35,8 +35,6 @@ import java.io.ByteArrayInputStream; import java.nio.ByteBuffer; -import sun.security.jca.Providers; - /** * This MessageDigest class provides applications the functionality of a * message digest algorithm, such as SHA-1 or SHA-256. @@ -257,8 +255,6 @@ public abstract class MessageDigest extends MessageDigestSpi { { if (provider == null || provider.length() == 0) throw new IllegalArgumentException("missing provider"); - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "MessageDigest", algorithm); Object[] objs = Security.getImpl(algorithm, "MessageDigest", provider); if (objs[0] instanceof MessageDigest) { MessageDigest md = (MessageDigest)objs[0]; @@ -307,8 +303,6 @@ public abstract class MessageDigest extends MessageDigestSpi { { if (provider == null) throw new IllegalArgumentException("missing provider"); - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "MessageDigest", algorithm); Object[] objs = Security.getImpl(algorithm, "MessageDigest", provider); if (objs[0] instanceof MessageDigest) { MessageDigest md = (MessageDigest)objs[0]; diff --git a/java/security/Signature.java b/java/security/Signature.java index 9deaf564..5a0e6a87 100644 --- a/java/security/Signature.java +++ b/java/security/Signature.java @@ -498,8 +498,6 @@ public abstract class Signature extends SignatureSpi { } return getInstanceRSA(p); } - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "Signature", algorithm); Instance instance = GetInstance.getInstance ("Signature", SignatureSpi.class, algorithm, provider); return getInstance(instance, algorithm); @@ -543,8 +541,6 @@ public abstract class Signature extends SignatureSpi { } return getInstanceRSA(provider); } - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "Signature", algorithm); Instance instance = GetInstance.getInstance ("Signature", SignatureSpi.class, algorithm, provider); return getInstance(instance, algorithm); diff --git a/java/security/cert/CertificateFactory.java b/java/security/cert/CertificateFactory.java index 5ccbb333..e74ff0a2 100644 --- a/java/security/cert/CertificateFactory.java +++ b/java/security/cert/CertificateFactory.java @@ -250,8 +250,6 @@ public class CertificateFactory { String provider) throws CertificateException, NoSuchProviderException { try { - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "CertificateFactory", type); Instance instance = GetInstance.getInstance("CertificateFactory", CertificateFactorySpi.class, type, provider); return new CertificateFactory((CertificateFactorySpi)instance.impl, @@ -293,8 +291,6 @@ public class CertificateFactory { public static final CertificateFactory getInstance(String type, Provider provider) throws CertificateException { try { - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "CertificateFactory", type); Instance instance = GetInstance.getInstance("CertificateFactory", CertificateFactorySpi.class, type, provider); return new CertificateFactory((CertificateFactorySpi)instance.impl, diff --git a/javax/crypto/KeyAgreement.java b/javax/crypto/KeyAgreement.java index ce42de8a..ffa18b1c 100644 --- a/javax/crypto/KeyAgreement.java +++ b/javax/crypto/KeyAgreement.java @@ -252,8 +252,6 @@ public class KeyAgreement { public static final KeyAgreement getInstance(String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException { - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "KeyAgreement", algorithm); Instance instance = JceSecurity.getInstance ("KeyAgreement", KeyAgreementSpi.class, algorithm, provider); return new KeyAgreement((KeyAgreementSpi)instance.impl, @@ -294,8 +292,6 @@ public class KeyAgreement { */ public static final KeyAgreement getInstance(String algorithm, Provider provider) throws NoSuchAlgorithmException { - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "KeyAgreement", algorithm); Instance instance = JceSecurity.getInstance ("KeyAgreement", KeyAgreementSpi.class, algorithm, provider); return new KeyAgreement((KeyAgreementSpi)instance.impl, diff --git a/javax/crypto/KeyGenerator.java b/javax/crypto/KeyGenerator.java index b0977f0a..5dfde971 100644 --- a/javax/crypto/KeyGenerator.java +++ b/javax/crypto/KeyGenerator.java @@ -326,8 +326,6 @@ public class KeyGenerator { public static final KeyGenerator getInstance(String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException { - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "KeyGenerator", algorithm); Instance instance = JceSecurity.getInstance("KeyGenerator", KeyGeneratorSpi.class, algorithm, provider); return new KeyGenerator((KeyGeneratorSpi)instance.impl, @@ -366,8 +364,6 @@ public class KeyGenerator { */ public static final KeyGenerator getInstance(String algorithm, Provider provider) throws NoSuchAlgorithmException { - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "KeyGenerator", algorithm); Instance instance = JceSecurity.getInstance("KeyGenerator", KeyGeneratorSpi.class, algorithm, provider); return new KeyGenerator((KeyGeneratorSpi)instance.impl, diff --git a/javax/crypto/Mac.java b/javax/crypto/Mac.java index dab6971c..c3d99fb3 100644 --- a/javax/crypto/Mac.java +++ b/javax/crypto/Mac.java @@ -309,8 +309,6 @@ public class Mac implements Cloneable { */ public static final Mac getInstance(String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException { - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "Mac", algorithm); Instance instance = JceSecurity.getInstance ("Mac", MacSpi.class, algorithm, provider); return new Mac((MacSpi)instance.impl, instance.provider, algorithm); @@ -346,8 +344,6 @@ public class Mac implements Cloneable { */ public static final Mac getInstance(String algorithm, Provider provider) throws NoSuchAlgorithmException { - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "Mac", algorithm); Instance instance = JceSecurity.getInstance ("Mac", MacSpi.class, algorithm, provider); return new Mac((MacSpi)instance.impl, instance.provider, algorithm); diff --git a/javax/crypto/SecretKeyFactory.java b/javax/crypto/SecretKeyFactory.java index be1cef47..c1358c20 100644 --- a/javax/crypto/SecretKeyFactory.java +++ b/javax/crypto/SecretKeyFactory.java @@ -385,8 +385,6 @@ public class SecretKeyFactory { public static final SecretKeyFactory getInstance(String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException { - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "SecretKeyFactory", algorithm); Instance instance = JceSecurity.getInstance("SecretKeyFactory", SecretKeyFactorySpi.class, algorithm, provider); return new SecretKeyFactory((SecretKeyFactorySpi)instance.impl, @@ -427,8 +425,6 @@ public class SecretKeyFactory { */ public static final SecretKeyFactory getInstance(String algorithm, Provider provider) throws NoSuchAlgorithmException { - // Android-added: Check for Bouncy Castle deprecation - Providers.checkBouncyCastleDeprecation(provider, "SecretKeyFactory", algorithm); Instance instance = JceSecurity.getInstance("SecretKeyFactory", SecretKeyFactorySpi.class, algorithm, provider); return new SecretKeyFactory((SecretKeyFactorySpi)instance.impl, |