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 /android | |
parent | d439404c9988df6001e4ff8bce31537e2692660e (diff) | |
download | android-28-07f9f65561c2b81bcd189b895b31bb2ad0438d74.tar.gz |
Revert "Import Android SDK Platform P [4402356]"
This reverts commit d439404c9988df6001e4ff8bce31537e2692660e.
Change-Id: I825790bdf38523800388bc1bb531cecfcd7e60bd
Diffstat (limited to 'android')
218 files changed, 9379 insertions, 9650 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()); |