diff options
81 files changed, 1948 insertions, 816 deletions
diff --git a/api/current.txt b/api/current.txt index f75ef0b5580e..e91244074522 100644 --- a/api/current.txt +++ b/api/current.txt @@ -321,7 +321,7 @@ package android { field public static final int buttonBarNeutralButtonStyle = 16843914; // 0x101048a field public static final int buttonBarPositiveButtonStyle = 16843913; // 0x1010489 field public static final int buttonBarStyle = 16843566; // 0x101032e - field public static final int buttonGravity = 16844030; // 0x10104fe + field public static final int buttonGravity = 16844031; // 0x10104ff field public static final int buttonStyle = 16842824; // 0x1010048 field public static final int buttonStyleInset = 16842826; // 0x101004a field public static final int buttonStyleSmall = 16842825; // 0x1010049 @@ -371,7 +371,7 @@ package android { field public static final int codes = 16843330; // 0x1010242 field public static final int collapseColumns = 16843083; // 0x101014b field public static final int collapseContentDescription = 16843984; // 0x10104d0 - field public static final int collapseIcon = 16844031; // 0x10104ff + field public static final int collapseIcon = 16844032; // 0x1010500 field public static final int color = 16843173; // 0x10101a5 field public static final int colorAccent = 16843829; // 0x1010435 field public static final int colorActivatedHighlight = 16843664; // 0x1010390 @@ -412,7 +412,7 @@ package android { field public static final int contentInsetRight = 16843862; // 0x1010456 field public static final int contentInsetStart = 16843859; // 0x1010453 field public static final int contextClickable = 16844007; // 0x10104e7 - field public static final int contextPopupMenuStyle = 16844033; // 0x1010501 + field public static final int contextPopupMenuStyle = 16844034; // 0x1010502 field public static final int controlX1 = 16843772; // 0x10103fc field public static final int controlX2 = 16843774; // 0x10103fe field public static final int controlY1 = 16843773; // 0x10103fd @@ -780,7 +780,7 @@ package android { field public static final int layout_y = 16843136; // 0x1010180 field public static final int left = 16843181; // 0x10101ad field public static final int letterSpacing = 16843958; // 0x10104b6 - field public static final int level = 16844032; // 0x1010500 + field public static final int level = 16844033; // 0x1010501 field public static final int lineSpacingExtra = 16843287; // 0x1010217 field public static final int lineSpacingMultiplier = 16843288; // 0x1010218 field public static final int lines = 16843092; // 0x1010154 @@ -813,7 +813,7 @@ package android { field public static final int marqueeRepeatLimit = 16843293; // 0x101021d field public static final int matchOrder = 16843855; // 0x101044f field public static final int max = 16843062; // 0x1010136 - field public static final int maxButtonHeight = 16844029; // 0x10104fd + field public static final int maxButtonHeight = 16844030; // 0x10104fe field public static final int maxDate = 16843584; // 0x1010340 field public static final int maxEms = 16843095; // 0x1010157 field public static final int maxHeight = 16843040; // 0x1010120 @@ -1174,6 +1174,7 @@ package android { field public static final int summaryOn = 16843247; // 0x10101ef field public static final int supportsAssist = 16844016; // 0x10104f0 field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1 + field public static final int supportsPictureInPicture = 16844024; // 0x10104f8 field public static final int supportsRtl = 16843695; // 0x10103af field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb field public static final int supportsUploading = 16843419; // 0x101029b @@ -1222,7 +1223,7 @@ package android { field public static final int textAppearanceListItemSmall = 16843679; // 0x101039f field public static final int textAppearanceMedium = 16842817; // 0x1010041 field public static final int textAppearanceMediumInverse = 16842820; // 0x1010044 - field public static final int textAppearancePopupMenuHeader = 16844034; // 0x1010502 + field public static final int textAppearancePopupMenuHeader = 16844035; // 0x1010503 field public static final int textAppearanceSearchResultSubtitle = 16843424; // 0x10102a0 field public static final int textAppearanceSearchResultTitle = 16843425; // 0x10102a1 field public static final int textAppearanceSmall = 16842818; // 0x1010042 @@ -1291,11 +1292,11 @@ package android { field public static final int tintMode = 16843771; // 0x10103fb field public static final int title = 16843233; // 0x10101e1 field public static final int titleCondensed = 16843234; // 0x10101e2 - field public static final int titleMargin = 16844024; // 0x10104f8 - field public static final int titleMarginBottom = 16844028; // 0x10104fc - field public static final int titleMarginEnd = 16844026; // 0x10104fa - field public static final int titleMarginStart = 16844025; // 0x10104f9 - field public static final int titleMarginTop = 16844027; // 0x10104fb + field public static final int titleMargin = 16844025; // 0x10104f9 + field public static final int titleMarginBottom = 16844029; // 0x10104fd + field public static final int titleMarginEnd = 16844027; // 0x10104fb + field public static final int titleMarginStart = 16844026; // 0x10104fa + field public static final int titleMarginTop = 16844028; // 0x10104fc field public static final int titleTextAppearance = 16843822; // 0x101042e field public static final int titleTextColor = 16844003; // 0x10104e3 field public static final int titleTextStyle = 16843512; // 0x10102f8 @@ -1395,7 +1396,7 @@ package android { field public static final int windowAllowReturnTransitionOverlap = 16843835; // 0x101043b field public static final int windowAnimationStyle = 16842926; // 0x10100ae field public static final int windowBackground = 16842836; // 0x1010054 - field public static final int windowBackgroundFallback = 16844035; // 0x1010503 + field public static final int windowBackgroundFallback = 16844036; // 0x1010504 field public static final int windowClipToOutline = 16843947; // 0x10104ab field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b field public static final int windowContentOverlay = 16842841; // 0x1010059 diff --git a/api/system-current.txt b/api/system-current.txt index c4c6f1c9edff..b996af313a07 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -413,7 +413,7 @@ package android { field public static final int buttonBarNeutralButtonStyle = 16843914; // 0x101048a field public static final int buttonBarPositiveButtonStyle = 16843913; // 0x1010489 field public static final int buttonBarStyle = 16843566; // 0x101032e - field public static final int buttonGravity = 16844030; // 0x10104fe + field public static final int buttonGravity = 16844031; // 0x10104ff field public static final int buttonStyle = 16842824; // 0x1010048 field public static final int buttonStyleInset = 16842826; // 0x101004a field public static final int buttonStyleSmall = 16842825; // 0x1010049 @@ -463,7 +463,7 @@ package android { field public static final int codes = 16843330; // 0x1010242 field public static final int collapseColumns = 16843083; // 0x101014b field public static final int collapseContentDescription = 16843984; // 0x10104d0 - field public static final int collapseIcon = 16844031; // 0x10104ff + field public static final int collapseIcon = 16844032; // 0x1010500 field public static final int color = 16843173; // 0x10101a5 field public static final int colorAccent = 16843829; // 0x1010435 field public static final int colorActivatedHighlight = 16843664; // 0x1010390 @@ -504,7 +504,7 @@ package android { field public static final int contentInsetRight = 16843862; // 0x1010456 field public static final int contentInsetStart = 16843859; // 0x1010453 field public static final int contextClickable = 16844007; // 0x10104e7 - field public static final int contextPopupMenuStyle = 16844033; // 0x1010501 + field public static final int contextPopupMenuStyle = 16844034; // 0x1010502 field public static final int controlX1 = 16843772; // 0x10103fc field public static final int controlX2 = 16843774; // 0x10103fe field public static final int controlY1 = 16843773; // 0x10103fd @@ -872,7 +872,7 @@ package android { field public static final int layout_y = 16843136; // 0x1010180 field public static final int left = 16843181; // 0x10101ad field public static final int letterSpacing = 16843958; // 0x10104b6 - field public static final int level = 16844032; // 0x1010500 + field public static final int level = 16844033; // 0x1010501 field public static final int lineSpacingExtra = 16843287; // 0x1010217 field public static final int lineSpacingMultiplier = 16843288; // 0x1010218 field public static final int lines = 16843092; // 0x1010154 @@ -905,7 +905,7 @@ package android { field public static final int marqueeRepeatLimit = 16843293; // 0x101021d field public static final int matchOrder = 16843855; // 0x101044f field public static final int max = 16843062; // 0x1010136 - field public static final int maxButtonHeight = 16844029; // 0x10104fd + field public static final int maxButtonHeight = 16844030; // 0x10104fe field public static final int maxDate = 16843584; // 0x1010340 field public static final int maxEms = 16843095; // 0x1010157 field public static final int maxHeight = 16843040; // 0x1010120 @@ -1270,6 +1270,7 @@ package android { field public static final int summaryOn = 16843247; // 0x10101ef field public static final int supportsAssist = 16844016; // 0x10104f0 field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1 + field public static final int supportsPictureInPicture = 16844024; // 0x10104f8 field public static final int supportsRtl = 16843695; // 0x10103af field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb field public static final int supportsUploading = 16843419; // 0x101029b @@ -1318,7 +1319,7 @@ package android { field public static final int textAppearanceListItemSmall = 16843679; // 0x101039f field public static final int textAppearanceMedium = 16842817; // 0x1010041 field public static final int textAppearanceMediumInverse = 16842820; // 0x1010044 - field public static final int textAppearancePopupMenuHeader = 16844034; // 0x1010502 + field public static final int textAppearancePopupMenuHeader = 16844035; // 0x1010503 field public static final int textAppearanceSearchResultSubtitle = 16843424; // 0x10102a0 field public static final int textAppearanceSearchResultTitle = 16843425; // 0x10102a1 field public static final int textAppearanceSmall = 16842818; // 0x1010042 @@ -1387,11 +1388,11 @@ package android { field public static final int tintMode = 16843771; // 0x10103fb field public static final int title = 16843233; // 0x10101e1 field public static final int titleCondensed = 16843234; // 0x10101e2 - field public static final int titleMargin = 16844024; // 0x10104f8 - field public static final int titleMarginBottom = 16844028; // 0x10104fc - field public static final int titleMarginEnd = 16844026; // 0x10104fa - field public static final int titleMarginStart = 16844025; // 0x10104f9 - field public static final int titleMarginTop = 16844027; // 0x10104fb + field public static final int titleMargin = 16844025; // 0x10104f9 + field public static final int titleMarginBottom = 16844029; // 0x10104fd + field public static final int titleMarginEnd = 16844027; // 0x10104fb + field public static final int titleMarginStart = 16844026; // 0x10104fa + field public static final int titleMarginTop = 16844028; // 0x10104fc field public static final int titleTextAppearance = 16843822; // 0x101042e field public static final int titleTextColor = 16844003; // 0x10104e3 field public static final int titleTextStyle = 16843512; // 0x10102f8 @@ -1491,7 +1492,7 @@ package android { field public static final int windowAllowReturnTransitionOverlap = 16843835; // 0x101043b field public static final int windowAnimationStyle = 16842926; // 0x10100ae field public static final int windowBackground = 16842836; // 0x1010054 - field public static final int windowBackgroundFallback = 16844035; // 0x1010503 + field public static final int windowBackgroundFallback = 16844036; // 0x1010504 field public static final int windowClipToOutline = 16843947; // 0x10104ab field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b field public static final int windowContentOverlay = 16842841; // 0x1010059 diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 1e2e33dd8ed1..12780a8742b9 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -154,7 +154,7 @@ public class Am extends BaseCommand { " am stack movetask <TASK_ID> <STACK_ID> [true|false]\n" + " am stack resize <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" + " am stack size-docked-stack-test: <STEP_SIZE> <l|t|r|b> [DELAY_MS]\n" + - " am stack split <STACK_ID> <v|h> [INTENT]\n" + + " am stack move-top-activity-to-pinned-stack: <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" + " am stack positiontask <TASK_ID> <STACK_ID> <POSITION>\n" + " am stack list\n" + " am stack info <STACK_ID>\n" + @@ -297,11 +297,9 @@ public class Am extends BaseCommand { " <STEP_SIZE> increments from the side <l>eft, <t>op, <r>ight, or <b>ottom\n" + " applying the optional [DELAY_MS] between each step.\n" + "\n" + - "am stack split: split <STACK_ID> into 2 stacks <v>ertically or <h>orizontally\n" + - " starting the new stack with [INTENT] if specified. If [INTENT] isn't\n" + - " specified and the current stack has more than one task, then the top task\n" + - " of the current task will be moved to the new stack. Command will also force\n" + - " all current tasks in both stacks to be resizeable.\n" + + "am stack move-top-activity-to-pinned-stack: moves the top activity from\n" + + " <STACK_ID> to the pinned stack using <LEFT,TOP,RIGHT,BOTTOM> for the\n" + + " bounds of the pinned stack.\n" + "\n" + "am stack positiontask: place <TASK_ID> in <STACK_ID> at <POSITION>" + "\n" + @@ -1954,25 +1952,34 @@ public class Am extends BaseCommand { private void runStack() throws Exception { String op = nextArgRequired(); - if (op.equals("start")) { - runStackStart(); - } else if (op.equals("movetask")) { - runStackMoveTask(); - } else if (op.equals("resize")) { - runStackResize(); - } else if (op.equals("positiontask")) { - runStackPositionTask(); - } else if (op.equals("list")) { - runStackList(); - } else if (op.equals("info")) { - runStackInfo(); - } else if (op.equals("split")) { - runStackSplit(); - } else if (op.equals("size-docked-stack-test")) { - runStackSizeDockedStackTest(); - } else { - showError("Error: unknown command '" + op + "'"); - return; + switch (op) { + case "start": + runStackStart(); + break; + case "movetask": + runStackMoveTask(); + break; + case "resize": + runStackResize(); + break; + case "positiontask": + runStackPositionTask(); + break; + case "list": + runStackList(); + break; + case "info": + runStackInfo(); + break; + case "move-top-activity-to-pinned-stack": + runMoveTopActivityToPinnedStack(); + break; + case "size-docked-stack-test": + runStackSizeDockedStackTest(); + break; + default: + showError("Error: unknown command '" + op + "'"); + break; } } @@ -2072,61 +2079,21 @@ public class Am extends BaseCommand { } } - private void runStackSplit() throws Exception { - final int stackId = Integer.valueOf(nextArgRequired()); - final String splitDirection = nextArgRequired(); - Intent intent = null; - try { - intent = makeIntent(UserHandle.USER_CURRENT); - } catch (IllegalArgumentException e) { - // no intent supplied. + private void runMoveTopActivityToPinnedStack() throws Exception { + int stackId = Integer.valueOf(nextArgRequired()); + final Rect bounds = getBounds(); + if (bounds == null) { + System.err.println("Error: invalid input bounds"); + return; } try { - final StackInfo currentStackInfo = mAm.getStackInfo(stackId); - // Calculate bounds for new and current stack. - final Rect currentStackBounds = new Rect(currentStackInfo.bounds); - final Rect newStackBounds = new Rect(currentStackInfo.bounds); - if ("v".equals(splitDirection)) { - currentStackBounds.right = newStackBounds.left = currentStackInfo.bounds.centerX(); - } else if ("h".equals(splitDirection)) { - currentStackBounds.bottom = newStackBounds.top = currentStackInfo.bounds.centerY(); - } else { - showError("Error: unknown split direction '" + splitDirection + "'"); - return; + if (!mAm.moveTopActivityToPinnedStack(stackId, bounds)) { + showError("Didn't move top activity to pinned stack."); } - - // Create new stack - IActivityContainer container = mAm.createStackOnDisplay(currentStackInfo.displayId); - if (container == null) { - showError("Error: Unable to create new stack..."); - } - - final int newStackId = container.getStackId(); - - if (intent != null) { - container.startActivity(intent); - } else if (currentStackInfo.taskIds != null && currentStackInfo.taskIds.length > 1) { - // Move top task over to new stack - mAm.moveTaskToStack(currentStackInfo.taskIds[currentStackInfo.taskIds.length - 1], - newStackId, true); - } - - final StackInfo newStackInfo = mAm.getStackInfo(newStackId); - - // Make all tasks in the stacks resizeable. - for (int taskId : currentStackInfo.taskIds) { - mAm.setTaskResizeable(taskId, true); - } - - for (int taskId : newStackInfo.taskIds) { - mAm.setTaskResizeable(taskId, true); - } - - // Resize stacks - mAm.resizeStack(currentStackInfo.stackId, currentStackBounds, false); - mAm.resizeStack(newStackInfo.stackId, newStackBounds, false); } catch (RemoteException e) { + showError("Unable to move top activity: " + e); + return; } } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 3bfeff0969af..6ae32d082928 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -434,10 +434,17 @@ public class ActivityManager { public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1; /** + * ID of stack that always on top (always visible) when it exist. + * Mainly used for this in Picture-in-Picture mode. + * @hide + */ + public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1; + + /** * Last static stack stack ID. * @hide */ - public static final int LAST_STATIC_STACK_ID = DOCKED_STACK_ID; + public static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID; /** * Start of ID range used by stacks that are created dynamically. diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index b97f94735b9f..70f1bcce0e42 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -755,6 +755,16 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case MOVE_TOP_ACTIVITY_TO_PINNED_STACK: { + data.enforceInterface(IActivityManager.descriptor); + final int stackId = data.readInt(); + final Rect r = Rect.CREATOR.createFromParcel(data); + final boolean res = moveTopActivityToPinnedStack(stackId, r); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + case RESIZE_STACK_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); final int stackId = data.readInt(); @@ -3555,6 +3565,22 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } @Override + public boolean moveTopActivityToPinnedStack(int stackId, Rect r) + throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(stackId); + r.writeToParcel(data, 0); + mRemote.transact(MOVE_TOP_ACTIVITY_TO_PINNED_STACK, data, reply, 0); + reply.readException(); + final boolean res = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return res; + } + @Override public void resizeStack(int stackId, Rect r, boolean allowResizeInDockedMode) throws RemoteException { diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index c26a44cae57c..ebdd302c6d5d 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -143,6 +143,7 @@ public interface IActivityManager extends IInterface { public void moveTaskToStack(int taskId, int stackId, boolean toTop) throws RemoteException; public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop) throws RemoteException; + public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) throws RemoteException; public void resizeStack(int stackId, Rect bounds, boolean allowResizeInDockedMode) throws RemoteException; public void positionTaskInStack(int taskId, int stackId, int position) throws RemoteException; public List<StackInfo> getAllStackInfos() throws RemoteException; @@ -898,4 +899,5 @@ public interface IActivityManager extends IInterface { int MOVE_TASK_TO_DOCKED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 346; int SUPPRESS_RESIZE_CONFIG_CHANGES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 347; int REMOVE_STACK = IBinder.FIRST_CALL_TRANSACTION + 348; + int MOVE_TOP_ACTIVITY_TO_PINNED_STACK = IBinder.FIRST_CALL_TRANSACTION + 349; } diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 38537720d119..7ca39cb7c443 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -671,6 +671,13 @@ public class ActivityInfo extends ComponentInfo */ public boolean resizeable; + /** + * Value indicating if the activity is supports picture-in-picture form of multi-window mode. + * See {@link android.R.attr#supportsPictureInPicture}. + * @hide + */ + public boolean supportsPip; + /** @hide */ public static final int LOCK_TASK_LAUNCH_MODE_DEFAULT = 0; /** @hide */ @@ -723,6 +730,7 @@ public class ActivityInfo extends ComponentInfo parentActivityName = orig.parentActivityName; maxRecents = orig.maxRecents; resizeable = orig.resizeable; + supportsPip = orig.supportsPip; lockTaskLaunchMode = orig.lockTaskLaunchMode; layout = orig.layout; } @@ -769,8 +777,8 @@ public class ActivityInfo extends ComponentInfo if (uiOptions != 0) { pw.println(prefix + " uiOptions=0x" + Integer.toHexString(uiOptions)); } - pw.println(prefix + "resizeable=" + resizeable + " lockTaskLaunchMode=" - + lockTaskLaunchModeToString(lockTaskLaunchMode)); + pw.println(prefix + "resizeable=" + resizeable + " supportsPip=" + supportsPip); + pw.println(prefix + "lockTaskLaunchMode=" + lockTaskLaunchModeToString(lockTaskLaunchMode)); if (layout != null) { pw.println(prefix + "initialLayout=" + layout.width + "|" + layout.widthFraction + ", " + layout.height + "|" @@ -806,6 +814,7 @@ public class ActivityInfo extends ComponentInfo dest.writeInt(persistableMode); dest.writeInt(maxRecents); dest.writeInt(resizeable ? 1 : 0); + dest.writeInt(supportsPip ? 1 : 0); dest.writeInt(lockTaskLaunchMode); if (layout != null) { dest.writeInt(1); @@ -847,6 +856,7 @@ public class ActivityInfo extends ComponentInfo persistableMode = source.readInt(); maxRecents = source.readInt(); resizeable = (source.readInt() == 1); + supportsPip = (source.readInt() == 1); lockTaskLaunchMode = source.readInt(); if (source.readInt() == 1) { layout = new Layout(source); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 1e85dfb14d7b..5d73b06acecc 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -3220,6 +3220,9 @@ public class PackageParser { R.styleable.AndroidManifestActivity_resizeableActivity, owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N); + a.info.supportsPip = a.info.resizeable ? sa.getBoolean( + R.styleable.AndroidManifestActivity_supportsPictureInPicture, false) : false; + a.info.screenOrientation = sa.getInt( R.styleable.AndroidManifestActivity_screenOrientation, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 11effd040ab6..a1f9743d4d4e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6277,6 +6277,15 @@ public final class Settings { */ public static final String FORCE_ALLOW_ON_EXTERNAL = "force_allow_on_external"; + /** + * Whether any activity can be resized. When this is true, any + * activity, regardless of manifest values, can be resized for multi-window. + * (0 = false, 1 = true) + * @hide + */ + public static final String DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES + = "force_resizable_activities"; + /** * Whether user has enabled development settings. */ diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index de509b2d7b9a..2459cfa84636 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -238,6 +238,7 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme initialScrollY = Touch.getInitialScrollY(widget, buffer); } + boolean wasTouchSelecting = isTouchSelecting(isMouse, buffer); boolean handled = Touch.onTouchEvent(widget, buffer, event); if (widget.didTouchFocusSelect() && !isMouse) { @@ -267,9 +268,9 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme // Cursor can be active at any location in the text while mouse pointer can start // selection from a totally different location. Use LAST_TAP_DOWN span to ensure // text selection will start from mouse pointer location. + final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN); if (isMouse && Touch.isSelectionStarted(buffer)) { - int offset = buffer.getSpanStart(LAST_TAP_DOWN); - Selection.setSelection(buffer, offset); + Selection.setSelection(buffer, startOffset); } if (isTouchSelecting(isMouse, buffer) && handled) { @@ -284,9 +285,9 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme // Update selection as we're moving the selection area. // Get the current touch position - int offset = widget.getOffsetForPosition(event.getX(), event.getY()); - - Selection.extendSelection(buffer, offset); + final int offset = widget.getOffsetForPosition(event.getX(), event.getY()); + Selection.setSelection(buffer, Math.min(startOffset, offset), + Math.max(startOffset, offset)); return true; } } else if (action == MotionEvent.ACTION_UP) { @@ -300,10 +301,12 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme return true; } - int offset = widget.getOffsetForPosition(event.getX(), event.getY()); - if (isTouchSelecting(isMouse, buffer)) { + if (wasTouchSelecting) { + final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN); + final int endOffset = widget.getOffsetForPosition(event.getX(), event.getY()); + Selection.setSelection(buffer, Math.min(startOffset, endOffset), + Math.max(startOffset, endOffset)); buffer.removeSpan(LAST_TAP_DOWN); - Selection.extendSelection(buffer, offset); } MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index c1eb80d28690..37d675707a84 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -343,15 +343,15 @@ public class TimeUtils { int seconds = (int) Math.floor(duration / 1000); int days = 0, hours = 0, minutes = 0; - if (seconds > SECONDS_PER_DAY) { + if (seconds >= SECONDS_PER_DAY) { days = seconds / SECONDS_PER_DAY; seconds -= days * SECONDS_PER_DAY; } - if (seconds > SECONDS_PER_HOUR) { + if (seconds >= SECONDS_PER_HOUR) { hours = seconds / SECONDS_PER_HOUR; seconds -= hours * SECONDS_PER_HOUR; } - if (seconds > SECONDS_PER_MINUTE) { + if (seconds >= SECONDS_PER_MINUTE) { minutes = seconds / SECONDS_PER_MINUTE; seconds -= minutes * SECONDS_PER_MINUTE; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 7d39a0cc20d9..e17bdd7d9a3e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4191,7 +4191,10 @@ public final class ViewRootImpl implements ViewParent, if (mPointerIconShape != pointerShape) { mPointerIconShape = pointerShape; - event.getDevice().setPointerShape(pointerShape); + final InputDevice inputDevice = event.getDevice(); + if (inputDevice != null) { + inputDevice.setPointerShape(pointerShape); + } } } else if (event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) { mPointerIconShape = PointerIcon.STYLE_NOT_SPECIFIED; diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 46442110aa10..afef7633bc69 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -20,6 +20,7 @@ import static android.app.ActivityManager.FIRST_DYNAMIC_STACK_ID; import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.INVALID_STACK_ID; +import static android.app.ActivityManager.PINNED_STACK_ID; import static android.view.View.MeasureSpec.AT_MOST; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.getMode; @@ -5408,7 +5409,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { * @Return Returns true if the window should show a non client decor. **/ private static boolean hasNonClientDecor(int workspaceId) { - return workspaceId == FREEFORM_WORKSPACE_STACK_ID; + return workspaceId == FREEFORM_WORKSPACE_STACK_ID || workspaceId == PINNED_STACK_ID; } /** @@ -5417,7 +5418,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { * @Return Returns true if the window should show a shadow. **/ private static boolean nonClientDecorHasShadow(int workspaceId) { - return workspaceId == FREEFORM_WORKSPACE_STACK_ID; + return workspaceId == FREEFORM_WORKSPACE_STACK_ID || workspaceId == PINNED_STACK_ID; } @Override diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 322ac4f9dfa7..07ac471ae6f4 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1059,7 +1059,7 @@ at the same time. <p>The default value is <code>false</code> for applications with - <code>targetSdkVersion</code> lesser than {@link android.os.Build.VERSION_CODES#M} and + <code>targetSdkVersion</code> lesser than {@link android.os.Build.VERSION_CODES#N} and <code>true</code> otherwise. <p>NOTE: A task's root activity value is applied to all additional activities launched in @@ -1067,11 +1067,24 @@ all other activities in the task as resizeable and will not if the root activity isn't resizeable. - <p>NOTE: The value of {@link android.R.attr#screenOrientation} will be ignored for - resizeable activities as the system doesn't support fixed orientation on a resizeable - activity. --> + <p>NOTE: The value of {@link android.R.attr#screenOrientation} is ignored for + resizeable activities when in multi-window mode. --> <attr name="resizeableActivity" format="boolean" /> + <!-- Indicates that the activity supports the picture-in-picture (PiP) form of multi-window. + While it makes sense to be able to resize most activities types in multi-window mode when + {@link android.R.attr#resizeableActivity} is set. It only makes sense to put specific types + of activities in PiP mode of multi-window. For example, activities that play video. When + set the activity will be allowed to enter PiP mode when the system deems it appropriate on + devices that support PiP. + + <p>The default value is <code>false</code> for applications with + <code>targetSdkVersion</code> lesser than {@link android.os.Build.VERSION_CODES#N} and + <code>true</code> otherwise. + + <p>NOTE: Attribute is only used if {@link android.R.attr#resizeableActivity} is true. --> + <attr name="supportsPictureInPicture" format="boolean" /> + <!-- This value indicates how tasks rooted at this activity will behave in lockTask mode. While in lockTask mode the system will not launch non-permitted tasks until lockTask mode is disabled. @@ -1826,6 +1839,7 @@ <attr name="relinquishTaskIdentity" /> <attr name="resumeWhilePausing" /> <attr name="resizeableActivity" /> + <attr name="supportsPictureInPicture" /> <attr name="lockTaskMode" /> <attr name="showForAllUsers" /> </declare-styleable> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index c05b585be298..037f1c43d745 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2666,6 +2666,7 @@ <public type="attr" name="initialHeight" /> <public type="attr" name="minimalSize" /> <public type="attr" name="resizeableActivity" /> + <public type="attr" name="supportsPictureInPicture" /> <public type="attr" name="titleMargin" /> <public type="attr" name="titleMarginStart" /> <public type="attr" name="titleMarginEnd" /> diff --git a/core/tests/coretests/src/android/util/TimeUtilsTest.java b/core/tests/coretests/src/android/util/TimeUtilsTest.java index 74c8e04b8dc1..237062748f9f 100644 --- a/core/tests/coretests/src/android/util/TimeUtilsTest.java +++ b/core/tests/coretests/src/android/util/TimeUtilsTest.java @@ -436,15 +436,17 @@ public class TimeUtilsTest extends TestCase { assertFormatDuration("+100ms", 100); assertFormatDuration("+101ms", 101); assertFormatDuration("+330ms", 330); + assertFormatDuration("+1s0ms", 1000); assertFormatDuration("+1s330ms", 1330); assertFormatDuration("+10s24ms", 10024); + assertFormatDuration("+1m0s30ms", 60030); + assertFormatDuration("+1h0m0s30ms", 3600030); + assertFormatDuration("+1d0h0m0s30ms", 86400030); } public void testFormatHugeDuration() { - //assertFormatDuration("+15542d1h11m11s555ms", 1342833071555L); - // TODO: improve formatDuration() API - assertFormatDuration("+999d23h59m59s999ms", 1342833071555L); - assertFormatDuration("-999d23h59m59s999ms", -1342833071555L); + assertFormatDuration("+15542d1h11m11s555ms", 1342833071555L); + assertFormatDuration("-15542d1h11m11s555ms", -1342833071555L); } private void assertFormatDuration(String expected, long duration) { diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java new file mode 100644 index 000000000000..c5e2ae67affc --- /dev/null +++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.widget; + +import static android.widget.espresso.TextViewActions.mouseDragOnText; +import static android.widget.espresso.TextViewAssertions.hasSelection; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.typeTextIntoFocusedView; +import static android.support.test.espresso.matcher.ViewMatchers.withId; + +import com.android.frameworks.coretests.R; + +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.SmallTest; + +/** + * Tests mouse interaction of the TextView widget from an Activity + */ +public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2<TextViewActivity>{ + + public TextViewActivityMouseTest() { + super(TextViewActivity.class); + } + + @SmallTest + public void testSelectTextByDrag() throws Exception { + getActivity(); + + final String helloWorld = "Hello world!"; + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld)); + onView(withId(R.id.textview)).perform( + mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!"))); + + onView(withId(R.id.textview)).check(hasSelection("llo wor")); + } + + @SmallTest + public void testSelectTextByDrag_reverse() throws Exception { + getActivity(); + + final String helloWorld = "Hello world!"; + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld)); + onView(withId(R.id.textview)).perform( + mouseDragOnText( helloWorld.indexOf("ld!"), helloWorld.indexOf("llo"))); + + onView(withId(R.id.textview)).check(hasSelection("llo wor")); + } +} diff --git a/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java b/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java index a0cd848a53f5..9ff8e8219f16 100644 --- a/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java +++ b/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java @@ -20,25 +20,22 @@ import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFro import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; import static com.android.internal.util.Preconditions.checkNotNull; import static org.hamcrest.Matchers.allOf; - import android.annotation.Nullable; import android.os.SystemClock; import android.support.test.espresso.UiController; import android.support.test.espresso.PerformException; import android.support.test.espresso.ViewAction; import android.support.test.espresso.action.CoordinatesProvider; -import android.support.test.espresso.action.GeneralClickAction; import android.support.test.espresso.action.MotionEvents; import android.support.test.espresso.action.PrecisionDescriber; -import android.support.test.espresso.action.Press; import android.support.test.espresso.action.Swiper; -import android.support.test.espresso.action.Tap; import android.support.test.espresso.util.HumanReadables; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.widget.TextView; + import org.hamcrest.Matcher; @@ -51,11 +48,48 @@ import org.hamcrest.Matcher; * <ul> */ public final class DragOnTextViewActions implements ViewAction { + public interface Dragger extends Swiper { + UiController wrapUiController(UiController uiController); + } /** * Executes different "drag on text" types to given positions. */ - public enum Drag implements Swiper { + public enum Drag implements Dragger { + + /** + * Starts a drag with a mouse down. + */ + MOUSE_DOWN { + private DownMotionPerformer downMotion = new DownMotionPerformer() { + @Override + public MotionEvent perform( + UiController uiController, float[] coordinates, float[] precision) { + MotionEvent downEvent = MotionEvents.sendDown( + uiController, coordinates, precision) + .down; + return downEvent; + } + }; + + @Override + public Status sendSwipe( + UiController uiController, + float[] startCoordinates, float[] endCoordinates, float[] precision) { + return sendLinearDrag( + uiController, downMotion, startCoordinates, endCoordinates, precision); + } + + @Override + public String toString() { + return "mouse down and drag to select"; + } + + @Override + public UiController wrapUiController(UiController uiController) { + return new MouseUiController(uiController); + } + }, /** * Starts a drag with a long-press. @@ -197,6 +231,11 @@ public final class DragOnTextViewActions implements ViewAction { return res; } + + @Override + public UiController wrapUiController(UiController uiController) { + return uiController; + } } /** @@ -215,13 +254,13 @@ public final class DragOnTextViewActions implements ViewAction { MotionEvent perform(UiController uiController, float[] coordinates, float[] precision); } - private final Swiper mDragger; + private final Dragger mDragger; private final CoordinatesProvider mStartCoordinatesProvider; private final CoordinatesProvider mEndCoordinatesProvider; private final PrecisionDescriber mPrecisionDescriber; public DragOnTextViewActions( - Swiper dragger, + Dragger dragger, CoordinatesProvider startCoordinatesProvider, CoordinatesProvider endCoordinatesProvider, PrecisionDescriber precisionDescriber) { @@ -242,6 +281,8 @@ public final class DragOnTextViewActions implements ViewAction { checkNotNull(uiController); checkNotNull(view); + uiController = mDragger.wrapUiController(uiController); + float[] startCoordinates = mStartCoordinatesProvider.calculateCoordinates(view); float[] endCoordinates = mEndCoordinatesProvider.calculateCoordinates(view); float[] precision = mPrecisionDescriber.describePrecision(); diff --git a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java new file mode 100644 index 000000000000..f1387f801b01 --- /dev/null +++ b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.widget.espresso; + +import android.support.test.espresso.InjectEventSecurityException; +import android.support.test.espresso.UiController; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; + +/** + * Class to wrap an UiController to overwrite source of motion events to SOURCE_MOUSE. + * Note that this doesn't change the tool type. + */ +public class MouseUiController implements UiController { + private final UiController mUiController; + + public MouseUiController(UiController uiController) { + mUiController = uiController; + } + + @Override + public boolean injectKeyEvent(KeyEvent event) throws InjectEventSecurityException { + return mUiController.injectKeyEvent(event); + } + + @Override + public boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException { + // Modify the event to mimic mouse primary button event. + event.setSource(InputDevice.SOURCE_MOUSE); + event.setButtonState(MotionEvent.BUTTON_PRIMARY); + return mUiController.injectMotionEvent(event); + } + + @Override + public boolean injectString(String str) throws InjectEventSecurityException { + return mUiController.injectString(str); + } + + @Override + public void loopMainThreadForAtLeast(long millisDelay) { + mUiController.loopMainThreadForAtLeast(millisDelay); + } + + @Override + public void loopMainThreadUntilIdle() { + mUiController.loopMainThreadUntilIdle(); + } +} diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java index 835b1b958860..4f5a72b14b2a 100644 --- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java +++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java @@ -124,6 +124,27 @@ public final class TextViewActions { } /** + * Returns an action that click then drags by mouse on text from startIndex to endIndex on the + * TextView.<br> + * <br> + * View constraints: + * <ul> + * <li>must be a TextView displayed on screen + * <ul> + * + * @param startIndex The index of the TextView's text to start a drag from + * @param endIndex The index of the TextView's text to end the drag at + */ + public static ViewAction mouseDragOnText(int startIndex, int endIndex) { + return actionWithAssertions( + new DragOnTextViewActions( + DragOnTextViewActions.Drag.MOUSE_DOWN, + new TextCoordinates(startIndex), + new TextCoordinates(endIndex), + Press.PINPOINT)); + } + + /** * A provider of the x, y coordinates of the text at the specified index in a text view. */ private static final class TextCoordinates implements CoordinatesProvider { diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java index 3bd17fa6b8f3..abcccbdbc9fb 100644 --- a/graphics/java/android/graphics/Point.java +++ b/graphics/java/android/graphics/Point.java @@ -99,7 +99,7 @@ public class Point implements Parcelable { /** @hide */ public void printShortString(PrintWriter pw) { - pw.println("["); pw.print(x); pw.print(","); pw.print(y); pw.print("]"); + pw.print("["); pw.print(x); pw.print(","); pw.print(y); pw.print("]"); } /** diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp index 94806cab2fc5..7dbba25a0ec4 100644 --- a/libs/hwui/BakedOpRenderer.cpp +++ b/libs/hwui/BakedOpRenderer.cpp @@ -25,6 +25,15 @@ namespace android { namespace uirenderer { +void BakedOpRenderer::Info::setViewport(uint32_t width, uint32_t height) { + viewportWidth = width; + viewportHeight = height; + orthoMatrix.loadOrtho(viewportWidth, viewportHeight); + + renderState.setViewport(width, height); + renderState.blend().syncEnabled(); +} + Texture* BakedOpRenderer::Info::getTexture(const SkBitmap* bitmap) { Texture* texture = renderState.assetAtlas().getEntryTexture(bitmap); if (!texture) { @@ -45,9 +54,54 @@ void BakedOpRenderer::Info::renderGlop(const BakedOpState& state, const Glop& gl didDraw = true; } -void BakedOpRenderer::startFrame(Info& info) { - info.renderState.setViewport(info.viewportWidth, info.viewportHeight); - info.renderState.blend().syncEnabled(); +Layer* BakedOpRenderer::startLayer(Info& info, uint32_t width, uint32_t height) { + info.caches.textureState().activateTexture(0); + Layer* layer = info.caches.layerCache.get(info.renderState, width, height); + LOG_ALWAYS_FATAL_IF(!layer, "need layer..."); + + info.layer = layer; + layer->texCoords.set(0.0f, width / float(layer->getHeight()), + height / float(layer->getWidth()), 0.0f); + + layer->setFbo(info.renderState.genFramebuffer()); + info.renderState.bindFramebuffer(layer->getFbo()); + layer->bindTexture(); + + // Initialize the texture if needed + if (layer->isEmpty()) { + layer->allocateTexture(); + layer->setEmpty(false); + } + + // attach the texture to the FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + layer->getTextureId(), 0); + LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED"); + LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE, + "framebuffer incomplete!"); + + // Clear the FBO + info.renderState.scissor().setEnabled(false); + glClear(GL_COLOR_BUFFER_BIT); + + // Change the viewport & ortho projection + info.setViewport(width, height); + return layer; +} + +void BakedOpRenderer::endLayer(Info& info) { + Layer* layer = info.layer; + info.layer = nullptr; + + // Detach the texture from the FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "endLayer FAILED"); + layer->removeFbo(false); +} + +void BakedOpRenderer::startFrame(Info& info, uint32_t width, uint32_t height) { + info.renderState.bindFramebuffer(0); + info.setViewport(width, height); Caches::getInstance().clearGarbage(); if (!info.opaque) { @@ -130,7 +184,31 @@ void BakedOpRenderer::onEndLayerOp(Info& info, const EndLayerOp& op, const Baked } void BakedOpRenderer::onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) { - LOG_ALWAYS_FATAL("unsupported operation"); + Layer* layer = *op.layerHandle; + + // TODO: make this work for HW layers + layer->setPaint(op.paint); + layer->setBlend(true); + float layerAlpha = (layer->getAlpha() / 255.0f) * state.alpha; + + const bool tryToSnap = state.computedState.transform.isPureTranslate(); + Glop glop; + GlopBuilder(info.renderState, info.caches, &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshTexturedUvQuad(nullptr, layer->texCoords) + .setFillLayer(layer->getTexture(), layer->getColorFilter(), layerAlpha, layer->getMode(), Blend::ModeOrderSwap::NoSwap) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds) + .build(); + info.renderGlop(state, glop); + + // return layer to cache, since each clipped savelayer is only drawn once. + layer->setConvexMask(nullptr); + if (!info.caches.layerCache.put(layer)) { + // Failing to add the layer to the cache should happen only if the layer is too large + LAYER_LOGD("Deleting layer"); + layer->decStrong(nullptr); + } } } // namespace uirenderer diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h index f45dbe41f308..616adde51aa0 100644 --- a/libs/hwui/BakedOpRenderer.h +++ b/libs/hwui/BakedOpRenderer.h @@ -25,21 +25,21 @@ namespace uirenderer { class Caches; struct Glop; +class Layer; class RenderState; class BakedOpRenderer { public: class Info { public: - Info(Caches& caches, RenderState& renderState, int viewportWidth, int viewportHeight, bool opaque) + Info(Caches& caches, RenderState& renderState, bool opaque) : renderState(renderState) , caches(caches) - , opaque(opaque) - , viewportWidth(viewportWidth) - , viewportHeight(viewportHeight) { - orthoMatrix.loadOrtho(viewportWidth, viewportHeight); + , opaque(opaque) { } + void setViewport(uint32_t width, uint32_t height); + Texture* getTexture(const SkBitmap* bitmap); void renderGlop(const BakedOpState& state, const Glop& glop); @@ -47,16 +47,19 @@ public: Caches& caches; bool didDraw = false; - bool opaque; + Layer* layer = nullptr; // where should these live? layer state object? - int viewportWidth; - int viewportHeight; + bool opaque; + uint32_t viewportWidth = 0; + uint32_t viewportHeight = 0; Matrix4 orthoMatrix; }; - static void startFrame(Info& info); + static Layer* startLayer(Info& info, uint32_t width, uint32_t height); + static void endLayer(Info& info); + static void startFrame(Info& info, uint32_t width, uint32_t height); static void endFrame(Info& info); /** diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index f99d92b89420..489ebc116a2a 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -155,8 +155,7 @@ void Layer::removeFbo(bool flush) { if (fbo) { if (flush) LayerRenderer::flushLayer(renderState, this); - // If put fails the cache will delete the FBO - caches.fboCache.put(fbo); + renderState.deleteFramebuffer(fbo); fbo = 0; } } diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index 227271d83cf8..e9e5d81f1ce2 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -189,7 +189,7 @@ Layer* LayerRenderer::createRenderLayer(RenderState& renderState, uint32_t width LAYER_RENDERER_LOGD("Requesting new render layer %dx%d", width, height); Caches& caches = Caches::getInstance(); - GLuint fbo = caches.fboCache.get(); + GLuint fbo = renderState.genFramebuffer(); if (!fbo) { ALOGW("Could not obtain an FBO"); return nullptr; @@ -204,7 +204,7 @@ Layer* LayerRenderer::createRenderLayer(RenderState& renderState, uint32_t width // We first obtain a layer before comparing against the max texture size // because layers are not allocated at the exact desired size. They are - // always created slighly larger to improve recycling + // always created slightly larger to improve recycling const uint32_t maxTextureSize = caches.maxTextureSize; if (layer->getWidth() > maxTextureSize || layer->getHeight() > maxTextureSize) { ALOGW("Layer exceeds max. dimensions supported by the GPU (%dx%d, max=%dx%d)", @@ -357,7 +357,7 @@ bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* && bitmap->width() <= caches.maxTextureSize && bitmap->height() <= caches.maxTextureSize) { - GLuint fbo = caches.fboCache.get(); + GLuint fbo = renderState.getFramebuffer(); if (!fbo) { ALOGW("Could not obtain an FBO"); return false; @@ -465,7 +465,7 @@ error: layer->setAlpha(alpha, mode); layer->setFbo(previousLayerFbo); caches.textureState().deleteTexture(texture); - caches.fboCache.put(fbo); + renderState.deleteFramebuffer(fbo); renderState.setViewport(previousViewportWidth, previousViewportHeight); return status; diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp index c1417c451895..cde42f85b0f9 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/OpReorderer.cpp @@ -203,7 +203,7 @@ private: }; // iterate back toward target to see if anything drawn since should overlap the new op -// if no target, merging ops still interate to find similar batch to insert after +// if no target, merging ops still iterate to find similar batch to insert after void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds, BatchBase** targetBatch, size_t* insertBatchIndex) const { for (int i = mBatches.size() - 1; i >= 0; i--) { @@ -292,18 +292,14 @@ void OpReorderer::LayerReorderer::dump() const { } } -OpReorderer::OpReorderer() +OpReorderer::OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight, + const std::vector< sp<RenderNode> >& nodes) : mCanvasState(*this) { - mLayerReorderers.emplace_back(); - mLayerStack.push_back(0); -} - -void OpReorderer::onViewportInitialized() {} + ATRACE_NAME("prepare drawing commands"); -void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} + mLayerReorderers.emplace_back(viewportWidth, viewportHeight); + mLayerStack.push_back(0); -void OpReorderer::defer(const SkRect& clip, int viewportWidth, int viewportHeight, - const std::vector< sp<RenderNode> >& nodes) { mCanvasState.initializeSaveStack(viewportWidth, viewportHeight, clip.fLeft, clip.fTop, clip.fRight, clip.fBottom, Vector3()); @@ -321,13 +317,22 @@ void OpReorderer::defer(const SkRect& clip, int viewportWidth, int viewportHeigh } } -void OpReorderer::defer(int viewportWidth, int viewportHeight, const DisplayList& displayList) { +OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList) + : mCanvasState(*this) { ATRACE_NAME("prepare drawing commands"); + + mLayerReorderers.emplace_back(viewportWidth, viewportHeight); + mLayerStack.push_back(0); + mCanvasState.initializeSaveStack(viewportWidth, viewportHeight, 0, 0, viewportWidth, viewportHeight, Vector3()); deferImpl(displayList); } +void OpReorderer::onViewportInitialized() {} + +void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} + /** * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods. * @@ -350,11 +355,6 @@ void OpReorderer::deferImpl(const DisplayList& displayList) { void OpReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) { ATRACE_NAME("flush drawing commands"); - // Relay through layers in reverse order, since layers - // later in the list will be drawn by earlier ones - for (int i = mLayerReorderers.size() - 1; i >= 0; i--) { - mLayerReorderers[i].replayBakedOpsImpl(arg, receivers); - } } void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) { @@ -405,15 +405,17 @@ void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) { // TODO: test rejection at defer time, where the bounds become empty void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) { + const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth(); + const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight(); + mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); mCanvasState.writableSnapshot()->transform->loadIdentity(); - mCanvasState.writableSnapshot()->initializeViewport( - (int) op.unmappedBounds.getWidth(), (int) op.unmappedBounds.getHeight()); + mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight); mCanvasState.writableSnapshot()->roundRectClipState = nullptr; // create a new layer, and push its index on the stack mLayerStack.push_back(mLayerReorderers.size()); - mLayerReorderers.emplace_back(); + mLayerReorderers.emplace_back(layerWidth, layerHeight); mLayerReorderers.back().beginLayerOp = &op; } @@ -432,7 +434,8 @@ void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) { beginLayerOp.unmappedBounds, beginLayerOp.localMatrix, beginLayerOp.localClipRect, - beginLayerOp.paint); + beginLayerOp.paint, + &mLayerReorderers[finishedLayerIndex].layer); BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp); if (bakedOpState) { diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h index 73dc9af01f71..f32b8589983b 100644 --- a/libs/hwui/OpReorderer.h +++ b/libs/hwui/OpReorderer.h @@ -63,6 +63,10 @@ class OpReorderer : public CanvasStateClient { */ class LayerReorderer { public: + LayerReorderer(uint32_t width, uint32_t height) + : width(width) + , height(height) {} + // iterate back toward target to see if anything drawn since should overlap the new op // if no target, merging ops still iterate to find similar batch to insert after void locateInsertIndex(int batchId, const Rect& clippedBounds, @@ -77,15 +81,22 @@ class OpReorderer : public CanvasStateClient { void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const; + bool empty() const { + return mBatches.empty(); + } + void clear() { mBatches.clear(); } void dump() const; + Layer* layer = nullptr; const BeginLayerOp* beginLayerOp = nullptr; - + const uint32_t width; + const uint32_t height; private: + std::vector<BatchBase*> mBatches; /** @@ -100,14 +111,13 @@ class OpReorderer : public CanvasStateClient { }; public: - OpReorderer(); - virtual ~OpReorderer() {} - // TODO: not final, just presented this way for simplicity. Layers too? - void defer(const SkRect& clip, int viewportWidth, int viewportHeight, + OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight, const std::vector< sp<RenderNode> >& nodes); - void defer(int viewportWidth, int viewportHeight, const DisplayList& displayList); + OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList); + + virtual ~OpReorderer() {} /** * replayBakedOps() is templated based on what class will receive ops being replayed. @@ -128,8 +138,21 @@ public: static BakedOpReceiver receivers[] = { MAP_OPS(BAKED_OP_RECEIVER) }; - StaticReceiver::startFrame(arg); - replayBakedOpsImpl((void*)&arg, receivers); + + // Relay through layers in reverse order, since layers + // later in the list will be drawn by earlier ones + for (int i = mLayerReorderers.size() - 1; i >= 1; i--) { + LayerReorderer& layer = mLayerReorderers[i]; + if (!layer.empty()) { + layer.layer = StaticReceiver::startLayer(arg, layer.width, layer.height); + layer.replayBakedOpsImpl((void*)&arg, receivers); + StaticReceiver::endLayer(arg); + } + } + + const LayerReorderer& fbo0 = mLayerReorderers[0]; + StaticReceiver::startFrame(arg, fbo0.width, fbo0.height); + fbo0.replayBakedOpsImpl((void*)&arg, receivers); StaticReceiver::endFrame(arg); } diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index d4f65b635d4c..8c3603b153b9 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -673,7 +673,7 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) { layer->clipRect.set(clip); - layer->setFbo(mCaches.fboCache.get()); + layer->setFbo(mRenderState.genFramebuffer()); writableSnapshot()->region = &writableSnapshot()->layer->region; writableSnapshot()->flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer; diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h index dd016372584a..6c31b429e039 100644 --- a/libs/hwui/RecordedOp.h +++ b/libs/hwui/RecordedOp.h @@ -29,6 +29,7 @@ class SkPaint; namespace android { namespace uirenderer { +class Layer; class RenderNode; struct Vertex; @@ -136,8 +137,12 @@ struct EndLayerOp : RecordedOp { }; struct LayerOp : RecordedOp { - LayerOp(BASE_PARAMS) - : SUPER(LayerOp) {} + LayerOp(BASE_PARAMS, Layer** layerHandle) + : SUPER(LayerOp) + , layerHandle(layerHandle) {} + // Records a handle to the Layer object, since the Layer itself won't be + // constructed until after this operation is constructed. + Layer** layerHandle; }; }; // namespace uirenderer diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp index cf96d44c286f..382c0bd70b52 100644 --- a/libs/hwui/microbench/OpReordererBench.cpp +++ b/libs/hwui/microbench/OpReordererBench.cpp @@ -48,8 +48,7 @@ BENCHMARK_NO_ARG(BM_OpReorderer_defer); void BM_OpReorderer_defer::Run(int iters) { StartBenchmarkTiming(); for (int i = 0; i < iters; i++) { - OpReorderer reorderer; - reorderer.defer(200, 200, *sReorderingDisplayList); + OpReorderer reorderer(200, 200, *sReorderingDisplayList); MicroBench::DoNotOptimize(&reorderer); } StopBenchmarkTiming(); @@ -60,11 +59,10 @@ void BM_OpReorderer_deferAndRender::Run(int iters) { TestUtils::runOnRenderThread([this, iters](RenderState& renderState, Caches& caches) { StartBenchmarkTiming(); for (int i = 0; i < iters; i++) { - OpReorderer reorderer; - reorderer.defer(200, 200, *sReorderingDisplayList); + OpReorderer reorderer(200, 200, *sReorderingDisplayList); MicroBench::DoNotOptimize(&reorderer); - BakedOpRenderer::Info info(caches, renderState, 200, 200, true); + BakedOpRenderer::Info info(caches, renderState, true); reorderer.replayBakedOps<BakedOpRenderer>(info); } StopBenchmarkTiming(); diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp index dfa70ace2f44..9637117bc11a 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -125,6 +125,21 @@ void RenderState::bindFramebuffer(GLuint fbo) { } } +GLuint RenderState::genFramebuffer() { + GLuint ret; + glGenFramebuffers(1, &ret); + return ret; +} + +void RenderState::deleteFramebuffer(GLuint fbo) { + if (mFramebuffer == fbo) { + // GL defines that deleting the currently bound FBO rebinds FBO 0. + // Reflect this in our cached value. + mFramebuffer = 0; + } + glDeleteFramebuffers(1, &fbo); +} + void RenderState::invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info) { if (mode == DrawGlInfo::kModeProcessNoContext) { // If there's no context we don't need to interrupt as there's diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index 9ae084506f1d..87a79966df5f 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -57,7 +57,10 @@ public: void getViewport(GLsizei* outWidth, GLsizei* outHeight); void bindFramebuffer(GLuint fbo); - GLint getFramebuffer() { return mFramebuffer; } + GLuint getFramebuffer() { return mFramebuffer; } + GLuint genFramebuffer(); + void deleteFramebuffer(GLuint fbo); + void invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info); diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index f5714265032a..7c0f0b67b8a3 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -318,10 +318,8 @@ void CanvasContext::draw() { mEglManager.damageFrame(frame, dirty); #if HWUI_NEW_OPS - OpReorderer reorderer; - reorderer.defer(dirty, frame.width(), frame.height(), mRenderNodes); - BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(), - frame.width(), frame.height(), mOpaque); + OpReorderer reorderer(dirty, frame.width(), frame.height(), mRenderNodes); + BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(), mOpaque); // TODO: profiler().draw(mCanvas); reorderer.replayBakedOps<BakedOpRenderer>(info); diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index b353ae6ec1fc..485759bd1a50 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -333,7 +333,7 @@ bool EglManager::swapBuffers(const Frame& frame, const SkRect& screenDirty) { if (CC_LIKELY(err == EGL_SUCCESS)) { return true; } - if (err == EGL_BAD_SURFACE) { + if (err == EGL_BAD_SURFACE || err == EGL_BAD_NATIVE_WINDOW) { // For some reason our surface was destroyed out from under us // This really shouldn't happen, but if it does we can recover easily // by just not trying to use the surface anymore diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp index 891af9171518..2eefd37561a6 100644 --- a/libs/hwui/tests/TreeContentAnimation.cpp +++ b/libs/hwui/tests/TreeContentAnimation.cpp @@ -54,15 +54,10 @@ public: } }; -static TestCanvas* startRecording(RenderNode* node) { - TestCanvas* renderer = new TestCanvas( - node->stagingProperties().getWidth(), node->stagingProperties().getHeight()); - return renderer; -} - -static void endRecording(TestCanvas* renderer, RenderNode* node) { - node->setStagingDisplayList(renderer->finishRecording()); - delete renderer; +static void recordNode(RenderNode& node, std::function<void(TestCanvas&)> contentCallback) { + TestCanvas canvas(node.stagingProperties().getWidth(), node.stagingProperties().getHeight()); + contentCallback(canvas); + node.setStagingDisplayList(canvas.finishRecording()); } class TreeContentAnimation { @@ -75,7 +70,7 @@ public: frameCount = fc; } } - virtual void createContent(int width, int height, TestCanvas* renderer) = 0; + virtual void createContent(int width, int height, TestCanvas* canvas) = 0; virtual void doFrame(int frameNr) = 0; template <class T> @@ -108,11 +103,9 @@ public: proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15); proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)}); - android::uirenderer::Rect DUMMY; - - TestCanvas* renderer = startRecording(rootNode); - animation.createContent(width, height, renderer); - endRecording(renderer, rootNode); + recordNode(*rootNode, [&animation, width, height](TestCanvas& canvas) { + animation.createContent(width, height, &canvas); //TODO: no& + }); // Do a few cold runs then reset the stats so that the caches are all hot for (int i = 0; i < 3; i++) { @@ -140,19 +133,19 @@ public: class ShadowGridAnimation : public TreeContentAnimation { public: std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, TestCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); + void createContent(int width, int height, TestCanvas* canvas) override { + canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas->insertReorderBarrier(true); for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { sp<RenderNode> card = createCard(x, y, dp(100), dp(100)); - renderer->drawRenderNode(card.get()); + canvas->drawRenderNode(card.get()); cards.push_back(card); } } - renderer->insertReorderBarrier(false); + canvas->insertReorderBarrier(false); } void doFrame(int frameNr) override { int curFrame = frameNr % 150; @@ -171,9 +164,9 @@ private: node->mutateStagingProperties().mutableOutline().setShouldClip(true); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); - TestCanvas* renderer = startRecording(node.get()); - renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); - endRecording(renderer, node.get()); + recordNode(*node, [](TestCanvas& canvas) { + canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); + }); return node; } }; @@ -187,19 +180,19 @@ static Benchmark _ShadowGrid(BenchmarkInfo{ class ShadowGrid2Animation : public TreeContentAnimation { public: std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, TestCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); + void createContent(int width, int height, TestCanvas* canvas) override { + canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas->insertReorderBarrier(true); for (int x = dp(8); x < (width - dp(58)); x += dp(58)) { for (int y = dp(8); y < (height - dp(58)); y += dp(58)) { sp<RenderNode> card = createCard(x, y, dp(50), dp(50)); - renderer->drawRenderNode(card.get()); + canvas->drawRenderNode(card.get()); cards.push_back(card); } } - renderer->insertReorderBarrier(false); + canvas->insertReorderBarrier(false); } void doFrame(int frameNr) override { int curFrame = frameNr % 150; @@ -218,9 +211,9 @@ private: node->mutateStagingProperties().mutableOutline().setShouldClip(true); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); - TestCanvas* renderer = startRecording(node.get()); - renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); - endRecording(renderer, node.get()); + recordNode(*node, [](TestCanvas& canvas) { + canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); + }); return node; } }; @@ -233,15 +226,30 @@ static Benchmark _ShadowGrid2(BenchmarkInfo{ class RectGridAnimation : public TreeContentAnimation { public: - sp<RenderNode> card; - void createContent(int width, int height, TestCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); + sp<RenderNode> card = new RenderNode(); + void createContent(int width, int height, TestCanvas* canvas) override { + canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas->insertReorderBarrier(true); + + card->mutateStagingProperties().setLeftTopRightBottom(50, 50, 250, 250); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + recordNode(*card, [](TestCanvas& canvas) { + canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode); + + SkRegion region; + for (int xOffset = 0; xOffset < 200; xOffset+=2) { + for (int yOffset = 0; yOffset < 200; yOffset+=2) { + region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op); + } + } - card = createCard(40, 40, 200, 200); - renderer->drawRenderNode(card.get()); + SkPaint paint; + paint.setColor(0xff00ffff); + canvas.drawRegion(region, paint); + }); + canvas->drawRenderNode(card.get()); - renderer->insertReorderBarrier(false); + canvas->insertReorderBarrier(false); } void doFrame(int frameNr) override { int curFrame = frameNr % 150; @@ -249,29 +257,6 @@ public: card->mutateStagingProperties().setTranslationY(curFrame); card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - - TestCanvas* renderer = startRecording(node.get()); - renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode); - - SkRegion region; - for (int xOffset = 0; xOffset < width; xOffset+=2) { - for (int yOffset = 0; yOffset < height; yOffset+=2) { - region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op); - } - } - - SkPaint paint; - paint.setColor(0xff00ffff); - renderer->drawRegion(region, paint); - - endRecording(renderer, node.get()); - return node; - } }; static Benchmark _RectGrid(BenchmarkInfo{ "rectgrid", @@ -282,15 +267,22 @@ static Benchmark _RectGrid(BenchmarkInfo{ class OvalAnimation : public TreeContentAnimation { public: - sp<RenderNode> card; - void createContent(int width, int height, TestCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); - - card = createCard(40, 40, 400, 400); - renderer->drawRenderNode(card.get()); + sp<RenderNode> card = new RenderNode(); + void createContent(int width, int height, TestCanvas* canvas) override { + canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas->insertReorderBarrier(true); - renderer->insertReorderBarrier(false); + card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + recordNode(*card, [](TestCanvas& canvas) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(0xFF000000); + canvas.drawOval(0, 0, 200, 200, paint); + }); + canvas->drawRenderNode(card.get()); + + canvas->insertReorderBarrier(false); } void doFrame(int frameNr) override { @@ -299,22 +291,6 @@ public: card->mutateStagingProperties().setTranslationY(curFrame); card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - - TestCanvas* renderer = startRecording(node.get()); - - SkPaint paint; - paint.setAntiAlias(true); - paint.setColor(0xFF000000); - renderer->drawOval(0, 0, width, height, paint); - - endRecording(renderer, node.get()); - return node; - } }; static Benchmark _Oval(BenchmarkInfo{ "oval", @@ -325,7 +301,7 @@ static Benchmark _Oval(BenchmarkInfo{ class PartialDamageTest : public TreeContentAnimation { public: std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, TestCanvas* renderer) override { + void createContent(int width, int height, TestCanvas* canvas) override { static SkColor COLORS[] = { 0xFFF44336, 0xFF9C27B0, @@ -333,13 +309,13 @@ public: 0xFF4CAF50, }; - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { sp<RenderNode> card = createCard(x, y, dp(100), dp(100), COLORS[static_cast<int>((y / dp(116))) % 4]); - renderer->drawRenderNode(card.get()); + canvas->drawRenderNode(card.get()); cards.push_back(card); } } @@ -350,10 +326,10 @@ public: cards[0]->mutateStagingProperties().setTranslationY(curFrame); cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - TestCanvas* renderer = startRecording(cards[0].get()); - renderer->drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0), - SkXfermode::kSrcOver_Mode); - endRecording(renderer, cards[0].get()); + recordNode(*cards[0], [curFrame](TestCanvas& canvas) { + canvas.drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0), + SkXfermode::kSrcOver_Mode); + }); } static SkColor interpolateColor(float fraction, SkColor start, SkColor end) { @@ -378,9 +354,9 @@ private: node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - TestCanvas* renderer = startRecording(node.get()); - renderer->drawColor(color, SkXfermode::kSrcOver_Mode); - endRecording(renderer, node.get()); + recordNode(*node, [color](TestCanvas& canvas) { + canvas.drawColor(color, SkXfermode::kSrcOver_Mode); + }); return node; } }; @@ -393,16 +369,24 @@ static Benchmark _PartialDamage(BenchmarkInfo{ }); -class SimpleRectGridAnimation : public TreeContentAnimation { +class SaveLayerAnimation : public TreeContentAnimation { public: - sp<RenderNode> card; - void createContent(int width, int height, TestCanvas* renderer) override { - SkPaint paint; - paint.setColor(0xFF00FFFF); - renderer->drawRect(0, 0, width, height, paint); - - card = createCard(40, 40, 200, 200); - renderer->drawRenderNode(card.get()); + sp<RenderNode> card = new RenderNode(); + void createContent(int width, int height, TestCanvas* canvas) override { + canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background + + card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + recordNode(*card, [](TestCanvas& canvas) { + canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag); + canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped + canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag); + canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); // inner, clipped + canvas.restore(); + canvas.restore(); + }); + + canvas->drawRenderNode(card.get()); } void doFrame(int frameNr) override { int curFrame = frameNr % 150; @@ -410,24 +394,10 @@ public: card->mutateStagingProperties().setTranslationY(curFrame); card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - - TestCanvas* renderer = startRecording(node.get()); - SkPaint paint; - paint.setColor(0xFFFF00FF); - renderer->drawRect(0, 0, width, height, paint); - - endRecording(renderer, node.get()); - return node; - } }; -static Benchmark _SimpleRectGrid(BenchmarkInfo{ - "simplerectgrid", - "A simple collection of rects. " - "Low CPU/GPU load.", - TreeContentAnimation::run<SimpleRectGridAnimation> +static Benchmark _SaveLayer(BenchmarkInfo{ + "savelayer", + "A nested pair of clipped saveLayer operations. " + "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.", + TreeContentAnimation::run<SaveLayerAnimation> }); diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp index 82aebeabe038..bc1b69fa3729 100644 --- a/libs/hwui/unit_tests/BakedOpStateTests.cpp +++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp @@ -85,13 +85,5 @@ TEST(BakedOpState, constructAndReject) { } } -#define UNSUPPORTED_OP(Info, Type) \ - static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); } - -class Info { -public: - int index = 0; -}; - } } diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp index d02f89dd7088..af9c3f20004e 100644 --- a/libs/hwui/unit_tests/OpReordererTests.cpp +++ b/libs/hwui/unit_tests/OpReordererTests.cpp @@ -35,19 +35,22 @@ namespace uirenderer { * for every test. * * onXXXOp methods fail by default - tests should override ops they expect + * startLayer fails by default - tests should override if expected * startFrame/endFrame do nothing by default - tests should override to intercept */ template<class CustomClient, class Arg> class TestReceiver { public: #define CLIENT_METHOD(Type) \ - virtual void on##Type(Arg&, const Type&, const BakedOpState&) { FAIL(); } + virtual void on##Type(Arg&, const Type&, const BakedOpState&) { ADD_FAILURE(); } class Client { public: virtual ~Client() {}; MAP_OPS(CLIENT_METHOD) - virtual void startFrame(Arg& info) {} + virtual Layer* startLayer(Arg& info, uint32_t width, uint32_t height) { ADD_FAILURE(); return nullptr; } + virtual void endLayer(Arg& info) { ADD_FAILURE(); } + virtual void startFrame(Arg& info, uint32_t width, uint32_t height) {} virtual void endFrame(Arg& info) {} }; @@ -57,11 +60,18 @@ public: } MAP_OPS(DISPATCHER_METHOD) - static void startFrame(Arg& info) { + static Layer* startLayer(Arg& info, uint32_t width, uint32_t height) { CustomClient client; - client.startFrame(info); + return client.startLayer(info, width, height); + } + static void endLayer(Arg& info) { + CustomClient client; + client.endLayer(info); + } + static void startFrame(Arg& info, uint32_t width, uint32_t height) { + CustomClient client; + client.startFrame(info, width, height); } - static void endFrame(Arg& info) { CustomClient client; client.endFrame(info); @@ -78,8 +88,10 @@ class FailReceiver : public TestReceiver<FailReceiver, Info>::Client {}; class SimpleReceiver : public TestReceiver<SimpleReceiver, Info>::Client { public: - void startFrame(Info& info) override { + void startFrame(Info& info, uint32_t width, uint32_t height) override { EXPECT_EQ(0, info.index++); + EXPECT_EQ(100u, width); + EXPECT_EQ(200u, height); } void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override { EXPECT_EQ(1, info.index++); @@ -97,8 +109,7 @@ TEST(OpReorderer, simple) { canvas.drawRect(0, 0, 100, 200, SkPaint()); canvas.drawBitmap(bitmap, 10, 10, nullptr); }); - OpReorderer reorderer; - reorderer.defer(200, 200, *dl); + OpReorderer reorderer(100, 200, *dl); Info info; reorderer.replayBakedOps<TestReceiver<SimpleReceiver, Info>>(info); @@ -113,8 +124,7 @@ TEST(OpReorderer, simpleRejection) { canvas.drawRect(0, 0, 400, 400, SkPaint()); canvas.restore(); }); - OpReorderer reorderer; - reorderer.defer(200, 200, *dl); + OpReorderer reorderer(200, 200, *dl); Info info; reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info); @@ -146,8 +156,7 @@ TEST(OpReorderer, simpleBatching) { canvas.restore(); }); - OpReorderer reorderer; - reorderer.defer(200, 200, *dl); + OpReorderer reorderer(200, 200, *dl); Info info; reorderer.replayBakedOps<TestReceiver<SimpleBatchingReceiver, Info>>(info); @@ -167,7 +176,7 @@ public: EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); break; default: - FAIL(); + ADD_FAILURE(); } } }; @@ -196,8 +205,7 @@ TEST(OpReorderer, renderNode) { std::vector< sp<RenderNode> > nodes; nodes.push_back(parent.get()); - OpReorderer reorderer; - reorderer.defer(SkRect::MakeWH(200, 200), 200, 200, nodes); + OpReorderer reorderer(SkRect::MakeWH(200, 200), 200, 200, nodes); Info info; reorderer.replayBakedOps<TestReceiver<RenderNodeReceiver, Info>>(info); @@ -221,8 +229,7 @@ TEST(OpReorderer, clipped) { std::vector< sp<RenderNode> > nodes; nodes.push_back(node.get()); - OpReorderer reorderer; - reorderer.defer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver + OpReorderer reorderer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver 200, 200, nodes); Info info; @@ -232,8 +239,17 @@ TEST(OpReorderer, clipped) { class SaveLayerSimpleReceiver : public TestReceiver<SaveLayerSimpleReceiver, Info>::Client { public: - void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override { + Layer* startLayer(Info& info, uint32_t width, uint32_t height) override { EXPECT_EQ(0, info.index++); + EXPECT_EQ(180u, width); + EXPECT_EQ(180u, height); + return nullptr; + } + void endLayer(Info& info) override { + EXPECT_EQ(2, info.index++); + } + void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, info.index++); EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds); EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clippedBounds); EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clipRect); @@ -243,7 +259,7 @@ public: EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform); } void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override { - EXPECT_EQ(1, info.index++); + EXPECT_EQ(3, info.index++); EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds); EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clipRect); EXPECT_TRUE(state.computedState.transform.isIdentity()); @@ -256,33 +272,61 @@ TEST(OpReorderer, saveLayerSimple) { canvas.restore(); }); - OpReorderer reorderer; - reorderer.defer(200, 200, *dl); + OpReorderer reorderer(200, 200, *dl); Info info; reorderer.replayBakedOps<TestReceiver<SaveLayerSimpleReceiver, Info>>(info); - EXPECT_EQ(2, info.index); + EXPECT_EQ(4, info.index); } -// saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as rect2, rect1, layerOp2, layerOp1 +/* saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as: + * - startLayer2, rect2 endLayer2 + * - startLayer1, rect1, drawLayer2, endLayer1 + * - startFrame, layerOp1, endFrame + */ class SaveLayerNestedReceiver : public TestReceiver<SaveLayerNestedReceiver, Info>::Client { public: - void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override { + Layer* startLayer(Info& info, uint32_t width, uint32_t height) override { const int index = info.index++; if (index == 0) { + EXPECT_EQ(400u, width); + EXPECT_EQ(400u, height); + return (Layer*) 0x400; + } else if (index == 3) { + EXPECT_EQ(800u, width); + EXPECT_EQ(800u, height); + return (Layer*) 0x800; + } else { ADD_FAILURE(); } + return (Layer*) nullptr; + } + void endLayer(Info& info) override { + int index = info.index++; + EXPECT_TRUE(index == 2 || index == 6); + } + void startFrame(Info& info, uint32_t width, uint32_t height) override { + EXPECT_EQ(7, info.index++); + } + void endFrame(Info& info) override { + EXPECT_EQ(9, info.index++); + } + void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override { + const int index = info.index++; + if (index == 1) { EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect - } else if (index == 1) { + } else if (index == 4) { EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect - } else { FAIL(); } + } else { ADD_FAILURE(); } } void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override { const int index = info.index++; - if (index == 2) { + if (index == 5) { + EXPECT_EQ((Layer*)0x400, *op.layerHandle); EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer - } else if (index == 3) { + } else if (index == 8) { + EXPECT_EQ((Layer*)0x800, *op.layerHandle); EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer - } else { FAIL(); } + } else { ADD_FAILURE(); } } }; TEST(OpReorderer, saveLayerNested) { @@ -299,12 +343,11 @@ TEST(OpReorderer, saveLayerNested) { canvas.restore(); }); - OpReorderer reorderer; - reorderer.defer(800, 800, *dl); + OpReorderer reorderer(800, 800, *dl); Info info; reorderer.replayBakedOps<TestReceiver<SaveLayerNestedReceiver, Info>>(info); - EXPECT_EQ(4, info.index); + EXPECT_EQ(10, info.index); } TEST(OpReorderer, saveLayerContentRejection) { @@ -319,8 +362,7 @@ TEST(OpReorderer, saveLayerContentRejection) { canvas.restore(); canvas.restore(); }); - OpReorderer reorderer; - reorderer.defer(200, 200, *dl); + OpReorderer reorderer(200, 200, *dl); Info info; // should see no ops, even within the layer, since the layer should be rejected diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp index c0231235027a..e8cdf461c783 100644 --- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp +++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp @@ -38,7 +38,7 @@ TEST(RecordingCanvas, emptyPlayback) { canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); canvas.restore(); }); - playbackOps(*dl, [](const RecordedOp& op) { FAIL(); }); + playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); }); } TEST(RecordingCanvas, testSimpleRectRecord) { @@ -135,7 +135,7 @@ TEST(RecordingCanvas, saveLayerSimple) { // TODO: add asserts break; default: - FAIL(); + ADD_FAILURE(); } }); EXPECT_EQ(3, count); diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html index 45fb5791dfa7..93dbf3f779ab 100644 --- a/media/java/android/media/midi/package.html +++ b/media/java/android/media/midi/package.html @@ -180,7 +180,8 @@ m.openDevice(info, new MidiManager.OnDeviceOpenedListener() { Log.e(TAG, "could not open device " + info); } else { ... - }, new Handler(Looper.getMainLooper()) + } + }, new Handler(Looper.getMainLooper()) ); </pre> diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml index 085df352bb4a..eba00a67de8a 100644 --- a/packages/DocumentsUI/res/layout/item_doc_list.xml +++ b/packages/DocumentsUI/res/layout/item_doc_list.xml @@ -14,7 +14,7 @@ limitations under the License. --> -<com.android.documentsui.DocListItem xmlns:android="http://schemas.android.com/apk/res/android" +<com.android.documentsui.ListItem xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/item_doc_list_background" @@ -137,4 +137,4 @@ </LinearLayout> -</com.android.documentsui.DocListItem> +</com.android.documentsui.ListItem> diff --git a/packages/DocumentsUI/res/values/config.xml b/packages/DocumentsUI/res/values/config.xml index 73538278c80c..ad419aabca28 100644 --- a/packages/DocumentsUI/res/values/config.xml +++ b/packages/DocumentsUI/res/values/config.xml @@ -16,6 +16,6 @@ <resources> <bool name="productivity_device">true</bool> - <!-- intentionally unset. Vendors should set this in an overlay. --> + <!-- Intentionally unset. Vendors should set this in an overlay. --> <string name="trusted_quick_viewer_package"></string> </resources> diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java index 3b985ec21cac..627ba756680f 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java @@ -334,12 +334,12 @@ public class FilesActivity extends BaseActivity { startActivity(intent); return; } catch (SecurityException e) { - // carry on to regular view mode. + // Carry on to regular view mode. Log.e(TAG, "Caught security error: " + e.getLocalizedMessage()); } } - // fallback to traditional VIEW action... + // Fallback to traditional VIEW action... intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setData(doc.derivedUri); diff --git a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java index 6a424a684a87..605c53043744 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java +++ b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java @@ -92,6 +92,8 @@ final class QuickViewIntentBuilder { intent.setClipData(mClipData); return intent; + } else { + Log.e(TAG, "Can't resolve trusted quick view package: " + trustedPkg); } } diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk b/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk new file mode 100644 index 000000000000..8baadba982dd --- /dev/null +++ b/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := TestDocumentsProvider +LOCAL_CERTIFICATE := platform +LOCAL_MODULE_TAGS := tests +#LOCAL_SDK_VERSION := current + +LOCAL_PROGUARD_ENABLED := disabled +LOCAL_DEX_PREOPT := false + +include $(BUILD_PACKAGE) diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml b/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml new file mode 100644 index 000000000000..66988a17db8b --- /dev/null +++ b/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.documentsui.testing"> + <application> + <provider android:name="TestDocumentsProvider" + android:authorities="com.android.documentsui.testing" + android:exported="true" + android:grantUriPermissions="true" + android:permission="android.permission.MANAGE_DOCUMENTS"> + <intent-filter> + <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> + </intent-filter> + </provider> + </application> +</manifest> diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java b/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java new file mode 100644 index 000000000000..63ff0de56998 --- /dev/null +++ b/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui.testing; + +import android.database.Cursor; +import android.database.MatrixCursor; +import android.database.MatrixCursor.RowBuilder; +import android.os.AsyncTask; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; +import android.provider.DocumentsContract.Document; +import android.provider.DocumentsContract.Root; +import android.provider.DocumentsProvider; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TestDocumentsProvider extends DocumentsProvider { + private static final String TAG = "TestDocumentsProvider"; + + private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { + Root.COLUMN_ROOT_ID, + Root.COLUMN_FLAGS, + Root.COLUMN_ICON, + Root.COLUMN_TITLE, + Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, + }; + + private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { + Document.COLUMN_DOCUMENT_ID, + Document.COLUMN_MIME_TYPE, + Document.COLUMN_DISPLAY_NAME, + Document.COLUMN_LAST_MODIFIED, + Document.COLUMN_FLAGS, + Document.COLUMN_SIZE, + }; + + private static String[] resolveRootProjection(String[] projection) { + return projection != null ? projection : DEFAULT_ROOT_PROJECTION; + } + + private static String[] resolveDocumentProjection(String[] projection) { + return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; + } + + @Override + public boolean onCreate() { + resetRoots(); + return true; + } + + @Override + public Cursor queryRoots(String[] projection) throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); + + RowBuilder row = result.newRow(); + row.add(Root.COLUMN_ROOT_ID, "local"); + row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY); + row.add(Root.COLUMN_TITLE, "TEST-Local"); + row.add(Root.COLUMN_SUMMARY, "TEST-LocalSummary"); + row.add(Root.COLUMN_DOCUMENT_ID, "doc:local"); + + row = result.newRow(); + row.add(Root.COLUMN_ROOT_ID, "create"); + row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD); + row.add(Root.COLUMN_TITLE, "TEST-Create"); + row.add(Root.COLUMN_DOCUMENT_ID, "doc:create"); + + return result; + } + + private Map<String, Doc> mDocs = new HashMap<>(); + + private Doc mLocalRoot; + private Doc mCreateRoot; + + private Doc buildDoc(String docId, String displayName, String mimeType) { + final Doc doc = new Doc(); + doc.docId = docId; + doc.displayName = displayName; + doc.mimeType = mimeType; + mDocs.put(doc.docId, doc); + return doc; + } + + public void resetRoots() { + Log.d(TAG, "resetRoots()"); + + mDocs.clear(); + + mLocalRoot = buildDoc("doc:local", null, Document.MIME_TYPE_DIR); + + mCreateRoot = buildDoc("doc:create", null, Document.MIME_TYPE_DIR); + mCreateRoot.flags = Document.FLAG_DIR_SUPPORTS_CREATE; + + { + Doc file1 = buildDoc("doc:file1", "FILE1", "mime1/file1"); + file1.contents = "fileone".getBytes(); + file1.flags = Document.FLAG_SUPPORTS_WRITE; + mLocalRoot.children.add(file1); + mCreateRoot.children.add(file1); + } + + { + Doc file2 = buildDoc("doc:file2", "FILE2", "mime2/file2"); + file2.contents = "filetwo".getBytes(); + file2.flags = Document.FLAG_SUPPORTS_WRITE; + mLocalRoot.children.add(file2); + mCreateRoot.children.add(file2); + } + + Doc dir1 = buildDoc("doc:dir1", "DIR1", Document.MIME_TYPE_DIR); + mLocalRoot.children.add(dir1); + + { + Doc file3 = buildDoc("doc:file3", "FILE3", "mime3/file3"); + file3.contents = "filethree".getBytes(); + file3.flags = Document.FLAG_SUPPORTS_WRITE; + dir1.children.add(file3); + } + + Doc dir2 = buildDoc("doc:dir2", "DIR2", Document.MIME_TYPE_DIR); + mCreateRoot.children.add(dir2); + + { + Doc file4 = buildDoc("doc:file4", "FILE4", "mime4/file4"); + file4.contents = "filefour".getBytes(); + file4.flags = Document.FLAG_SUPPORTS_WRITE; + dir2.children.add(file4); + } + } + + private static class Doc { + public String docId; + public int flags; + public String displayName; + public long size; + public String mimeType; + public long lastModified; + public byte[] contents; + public List<Doc> children = new ArrayList<>(); + + public void include(MatrixCursor result) { + final RowBuilder row = result.newRow(); + row.add(Document.COLUMN_DOCUMENT_ID, docId); + row.add(Document.COLUMN_DISPLAY_NAME, displayName); + row.add(Document.COLUMN_SIZE, size); + row.add(Document.COLUMN_MIME_TYPE, mimeType); + row.add(Document.COLUMN_FLAGS, flags); + row.add(Document.COLUMN_LAST_MODIFIED, lastModified); + } + } + + @Override + public boolean isChildDocument(String parentDocumentId, String documentId) { + for (Doc doc : mDocs.get(parentDocumentId).children) { + if (doc.docId.equals(documentId)) { + return true; + } + if (Document.MIME_TYPE_DIR.equals(doc.mimeType)) { + return isChildDocument(doc.docId, documentId); + } + } + return false; + } + + @Override + public String createDocument(String parentDocumentId, String mimeType, String displayName) + throws FileNotFoundException { + final String docId = "doc:" + System.currentTimeMillis(); + final Doc doc = buildDoc(docId, displayName, mimeType); + doc.flags = Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_RENAME; + mDocs.get(parentDocumentId).children.add(doc); + return docId; + } + + @Override + public String renameDocument(String documentId, String displayName) + throws FileNotFoundException { + mDocs.get(documentId).displayName = displayName; + return null; + } + + @Override + public void deleteDocument(String documentId) throws FileNotFoundException { + mDocs.remove(documentId); + for (Doc doc : mDocs.values()) { + doc.children.remove(documentId); + } + } + + @Override + public Cursor queryDocument(String documentId, String[] projection) + throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + mDocs.get(documentId).include(result); + return result; + } + + @Override + public Cursor queryChildDocuments(String parentDocumentId, String[] projection, + String sortOrder) throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + for (Doc doc : mDocs.get(parentDocumentId).children) { + doc.include(result); + } + return result; + } + + @Override + public ParcelFileDescriptor openDocument(String documentId, String mode, + CancellationSignal signal) throws FileNotFoundException { + final Doc doc = mDocs.get(documentId); + if (doc == null) { + throw new FileNotFoundException(); + } + final ParcelFileDescriptor[] pipe; + try { + pipe = ParcelFileDescriptor.createPipe(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + if (mode.contains("w")) { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + synchronized (doc) { + try { + final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream( + pipe[0]); + doc.contents = readFullyNoClose(is); + is.close(); + doc.notifyAll(); + } catch (IOException e) { + Log.w(TAG, "Failed to stream", e); + } + } + return null; + } + }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); + return pipe[1]; + } else { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + synchronized (doc) { + try { + final OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream( + pipe[1]); + while (doc.contents == null) { + doc.wait(); + } + os.write(doc.contents); + os.close(); + } catch (IOException e) { + Log.w(TAG, "Failed to stream", e); + } catch (InterruptedException e) { + Log.w(TAG, "Interuppted", e); + } + } + return null; + } + }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); + return pipe[0]; + } + } + + private static byte[] readFullyNoClose(InputStream in) throws IOException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int count; + while ((count = in.read(buffer)) != -1) { + bytes.write(buffer, 0, count); + } + return bytes.toByteArray(); + } +} diff --git a/packages/DocumentsUI/tests/Android.mk b/packages/DocumentsUI/tests/Android.mk index 3f191a91acde..cf486b1d1a5d 100644 --- a/packages/DocumentsUI/tests/Android.mk +++ b/packages/DocumentsUI/tests/Android.mk @@ -3,11 +3,12 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests +#LOCAL_SDK_VERSION := current LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_JAVA_LIBRARIES := android.test.runner -LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target guava +LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target guava ub-uiautomator LOCAL_PACKAGE_NAME := DocumentsUITests LOCAL_INSTRUMENTATION_FOR := DocumentsUI @@ -15,3 +16,5 @@ LOCAL_INSTRUMENTATION_FOR := DocumentsUI LOCAL_CERTIFICATE := platform include $(BUILD_PACKAGE) + +include $(LOCAL_PATH)/../testing/TestDocumentsProvider/Android.mk diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java new file mode 100644 index 000000000000..1f4b751abc85 --- /dev/null +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + +import android.content.Context; +import android.content.Intent; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.Configurator; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiObject2; +import android.support.test.uiautomator.Until; +import android.test.InstrumentationTestCase; +import android.view.MotionEvent; + +import java.util.concurrent.TimeoutException; + +public class FilesActivityUiTest extends InstrumentationTestCase { + + private static final String TAG = "FilesActivityUiTest"; + private static final String TARGET_PKG = "com.android.documentsui"; + private static final String LAUNCHER_PKG = "com.android.launcher"; + private static final int ONE_SECOND = 1000; + private static final int FIVE_SECONDS = 5 * ONE_SECOND; + + private ActionBar mBar; + private UiDevice mDevice; + private Context mContext; + + public void setUp() throws TimeoutException { + // Initialize UiDevice instance. + mDevice = UiDevice.getInstance(getInstrumentation()); + + Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE); + + // Start from the home screen. + mDevice.pressHome(); + mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), FIVE_SECONDS); + + // Launch app. + mContext = getInstrumentation().getContext(); + Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(TARGET_PKG); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + mContext.startActivity(intent); + + // Wait for the app to appear. + mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), FIVE_SECONDS); + mDevice.waitForIdle(); + + mBar = new ActionBar(); + } + + public void testSwitchMode() throws Exception { + UiObject2 mode = mBar.gridMode(100); + if (mode != null) { + mode.click(); + assertNotNull(mBar.listMode(ONE_SECOND)); + } else { + mBar.listMode(100).click(); + assertNotNull(mBar.gridMode(ONE_SECOND)); + } + } + + private class ActionBar { + + public UiObject2 gridMode(int timeout) { + // Note that we're using By.desc rather than By.res, because of b/25285770 + BySelector selector = By.desc("Grid view"); + if (timeout > 0) { + mDevice.wait(Until.findObject(selector), timeout); + } + return mDevice.findObject(selector); + } + + public UiObject2 listMode(int timeout) { + // Note that we're using By.desc rather than By.res, because of b/25285770 + BySelector selector = By.desc("List view"); + if (timeout > 0) { + mDevice.wait(Until.findObject(selector), timeout); + } + return mDevice.findObject(selector); + } + } +} diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java index 11e937b44476..df9d44ac3755 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java @@ -2,6 +2,7 @@ package com.android.mtp; import android.content.ContentValues; import android.content.Context; +import android.content.res.Resources; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; @@ -30,10 +31,9 @@ class MtpDatabase { private static final String TABLE_MTP_DOCUMENTS = "MtpDocuments"; - static final String COLUMN_DEVICE_ID = "deviceId"; - static final String COLUMN_STORAGE_ID = "storageId"; - static final String COLUMN_OBJECT_HANDLE = "objectHandle"; - static final String COLUMN_FULL_PATH = "fullPath"; + static final String COLUMN_DEVICE_ID = "device_id"; + static final String COLUMN_STORAGE_ID = "storage_id"; + static final String COLUMN_OBJECT_HANDLE = "object_handle"; private static class OpenHelper extends SQLiteOpenHelper { private static final String CREATE_TABLE_QUERY = @@ -43,7 +43,6 @@ class MtpDatabase { COLUMN_DEVICE_ID + " INTEGER NOT NULL," + COLUMN_STORAGE_ID + " INTEGER NOT NULL," + COLUMN_OBJECT_HANDLE + " INTEGER," + - COLUMN_FULL_PATH + " TEXT NOT NULL," + DocumentsContract.Document.COLUMN_MIME_TYPE + " TEXT," + DocumentsContract.Document.COLUMN_DISPLAY_NAME + " TEXT NOT NULL," + DocumentsContract.Document.COLUMN_SUMMARY + " TEXT," + @@ -86,17 +85,15 @@ class MtpDatabase { } @VisibleForTesting - void putRootDocument(MtpRoot root) throws Exception { + void putRootDocument(Resources resources, MtpRoot root) throws Exception { database.beginTransaction(); try { final ContentValues values = new ContentValues(); values.put(COLUMN_DEVICE_ID, root.mDeviceId); values.put(COLUMN_STORAGE_ID, root.mStorageId); values.putNull(COLUMN_OBJECT_HANDLE); - values.put( - COLUMN_FULL_PATH, "/" + root.mDeviceId + "/" + escape(root.mDescription)); values.put(Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR); - values.put(Document.COLUMN_DISPLAY_NAME, root.mDescription); + values.put(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources)); values.putNull(Document.COLUMN_SUMMARY); values.putNull(Document.COLUMN_LAST_MODIFIED); values.putNull(Document.COLUMN_ICON); @@ -113,7 +110,7 @@ class MtpDatabase { } @VisibleForTesting - void putDocument(int deviceId, String parentFullPath, MtpObjectInfo info) throws Exception { + void putDocument(int deviceId, MtpObjectInfo info) throws Exception { database.beginTransaction(); try { final String mimeType = CursorHelper.formatTypeToMimeType(info.getFormat()); @@ -134,9 +131,7 @@ class MtpDatabase { values.put(COLUMN_DEVICE_ID, deviceId); values.put(COLUMN_STORAGE_ID, info.getStorageId()); values.put(COLUMN_OBJECT_HANDLE, info.getObjectHandle()); - values.put(COLUMN_FULL_PATH, parentFullPath + "/" + escape(info.getName())); - values.put( - Document.COLUMN_MIME_TYPE, CursorHelper.formatTypeToMimeType(info.getFormat())); + values.put(Document.COLUMN_MIME_TYPE, mimeType); values.put(Document.COLUMN_DISPLAY_NAME, info.getName()); values.putNull(Document.COLUMN_SUMMARY); values.put( diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java index 7ce325423978..8e1335fb8f27 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java @@ -1,6 +1,5 @@ package com.android.mtp; - import android.database.Cursor; import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; @@ -15,7 +14,6 @@ public class MtpDatabaseTest extends AndroidTestCase { MtpDatabase.COLUMN_DEVICE_ID, MtpDatabase.COLUMN_STORAGE_ID, MtpDatabase.COLUMN_OBJECT_HANDLE, - MtpDatabase.COLUMN_FULL_PATH, DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_SUMMARY, @@ -25,6 +23,8 @@ public class MtpDatabaseTest extends AndroidTestCase { DocumentsContract.Document.COLUMN_SIZE }; + private final TestResources resources = new TestResources(); + @Override public void tearDown() { MtpDatabase.deleteDatabase(getContext()); @@ -32,35 +32,10 @@ public class MtpDatabaseTest extends AndroidTestCase { public void testPutRootDocument() throws Exception { final MtpDatabase database = new MtpDatabase(getContext()); - final MtpRoot root = new MtpRoot( - 0, - 1, - "Device A", - "Storage", - 1000, - 2000, - ""); - database.putRootDocument(root); - - final MtpRoot duplicatedNameRoot = new MtpRoot( - 0, - 2, - "Device A", - "Storage", - 1000, - 2000, - ""); - database.putRootDocument(duplicatedNameRoot); - - final MtpRoot strangeNameRoot = new MtpRoot( - 0, - 3, - "Device A", - "/@#%&<>Storage", - 1000, - 2000, - ""); - database.putRootDocument(strangeNameRoot); + database.putRootDocument(resources, new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, "")); + database.putRootDocument(resources, new MtpRoot(0, 2, "Device", "Storage", 1000, 2000, "")); + database.putRootDocument( + resources, new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 1000, 2000, "")); final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES); assertEquals(3, cursor.getCount()); @@ -70,22 +45,23 @@ public class MtpDatabaseTest extends AndroidTestCase { assertEquals("deviceId", 0, cursor.getInt(1)); assertEquals("storageId", 1, cursor.getInt(2)); assertTrue("objectHandle", cursor.isNull(3)); - assertEquals("fullPath", "/0/Storage", cursor.getString(4)); - assertEquals("mimeType", DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(5)); - assertEquals("displayName", "Storage", cursor.getString(6)); - assertTrue("summary", cursor.isNull(7)); - assertTrue("lastModified", cursor.isNull(8)); - assertTrue("icon", cursor.isNull(9)); - assertEquals("flag", 0, cursor.getInt(10)); - assertEquals("size", 1000, cursor.getInt(11)); + assertEquals("mimeType", DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(4)); + assertEquals("displayName", "Device Storage", cursor.getString(5)); + assertTrue("summary", cursor.isNull(6)); + assertTrue("lastModified", cursor.isNull(7)); + assertTrue("icon", cursor.isNull(8)); + assertEquals("flag", 0, cursor.getInt(9)); + assertEquals("size", 1000, cursor.getInt(10)); cursor.moveToNext(); assertEquals("documentId", 2, cursor.getInt(0)); - assertEquals("fullPath", "/0/Storage", cursor.getString(4)); + assertEquals("displayName", "Device Storage", cursor.getString(5)); cursor.moveToNext(); assertEquals("documentId", 3, cursor.getInt(0)); - assertEquals("fullPath", "/0/%2F%40%23%25%26%3C%3EStorage", cursor.getString(4)); + assertEquals("displayName", "Device /@#%&<>Storage", cursor.getString(5)); + + cursor.close(); } public void testPutDocument() throws Exception { @@ -96,7 +72,7 @@ public class MtpDatabaseTest extends AndroidTestCase { builder.setStorageId(5); builder.setFormat(MtpConstants.FORMAT_TEXT); builder.setCompressedSize(1000); - database.putDocument(0, "/0/Storage", builder.build()); + database.putDocument(0, builder.build()); final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES); assertEquals(1, cursor.getCount()); @@ -105,17 +81,16 @@ public class MtpDatabaseTest extends AndroidTestCase { assertEquals("deviceId", 0, cursor.getInt(1)); assertEquals("storageId", 5, cursor.getInt(2)); assertEquals("objectHandle", 100, cursor.getInt(3)); - assertEquals("fullPath", "/0/Storage/test.txt", cursor.getString(4)); - assertEquals("mimeType", "text/plain", cursor.getString(5)); - assertEquals("displayName", "test.txt", cursor.getString(6)); - assertTrue("summary", cursor.isNull(7)); - assertTrue("lastModified", cursor.isNull(8)); - assertTrue("icon", cursor.isNull(9)); + assertEquals("mimeType", "text/plain", cursor.getString(4)); + assertEquals("displayName", "test.txt", cursor.getString(5)); + assertTrue("summary", cursor.isNull(6)); + assertTrue("lastModified", cursor.isNull(7)); + assertTrue("icon", cursor.isNull(8)); assertEquals( "flag", DocumentsContract.Document.FLAG_SUPPORTS_DELETE | DocumentsContract.Document.FLAG_SUPPORTS_WRITE, - cursor.getInt(10)); - assertEquals("size", 1000, cursor.getInt(11)); + cursor.getInt(9)); + assertEquals("size", 1000, cursor.getInt(10)); } } diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index 0c03814cc4e8..5765f0a90501 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -16,9 +16,6 @@ package com.android.mtp; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; import android.database.Cursor; import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; @@ -26,7 +23,6 @@ import android.net.Uri; import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract; import android.test.AndroidTestCase; -import android.test.mock.MockResources; import android.test.suitebuilder.annotation.SmallTest; import java.io.FileNotFoundException; @@ -39,16 +35,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { private TestContentResolver mResolver; private MtpDocumentsProvider mProvider; private TestMtpManager mMtpManager; - private final MockResources mResources = new MockResources() { - @Override - public String getString(int id) throws NotFoundException { - switch (id) { - case R.string.root_name: - return "%1$s %2$s"; - } - throw new NotFoundException(); - } - }; + private final TestResources mResources = new TestResources(); @Override public void setUp() throws IOException { diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java new file mode 100644 index 000000000000..eb80e3b60e6a --- /dev/null +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mtp; + +import android.test.mock.MockResources; + +class TestResources extends MockResources { + @Override + public String getString(int id) throws NotFoundException { + switch (id) { + case R.string.root_name: + return "%1$s %2$s"; + } + throw new NotFoundException(); + } +} diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 9b1f103edfaf..c44a796082b6 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -225,4 +225,10 @@ <!-- [CHAR LIMIT=NONE] Label of a running process that represents another user --> <string name="running_process_item_user_label">User: <xliff:g id="user_name">%1$s</xliff:g></string> + + <!-- Launch defaults preference summary with some set [CHAR LIMIT=40] --> + <string name="launch_defaults_some">Some defaults set</string> + <!-- Launch defaults preference summary with none set [CHAR LIMIT=40] --> + <string name="launch_defaults_none">No defaults set</string> + </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java new file mode 100644 index 000000000000..344bf6228bf2 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.settingslib.applications; + +import android.content.ComponentName; +import android.content.Context; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.hardware.usb.IUsbManager; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.ArrayList; +import java.util.List; + +public class AppUtils { + private static final String TAG = "AppUtils"; + + public static CharSequence getLaunchByDefaultSummary(ApplicationsState.AppEntry appEntry, + IUsbManager usbManager, PackageManager pm, Context context) { + String packageName = appEntry.info.packageName; + boolean hasPreferred = hasPreferredActivities(pm, packageName) + || hasUsbDefaults(usbManager, packageName); + int status = pm.getIntentVerificationStatus(packageName, UserHandle.myUserId()); + // consider a visible current link-handling state to be any explicitly designated behavior + boolean hasDomainURLsPreference = + status != PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; + return context.getString(hasPreferred || hasDomainURLsPreference + ? R.string.launch_defaults_some + : R.string.launch_defaults_none); + } + + public static boolean hasUsbDefaults(IUsbManager usbManager, String packageName) { + try { + if (usbManager != null) { + return usbManager.hasDefaults(packageName, UserHandle.myUserId()); + } + } catch (RemoteException e) { + Log.e(TAG, "mUsbManager.hasDefaults", e); + } + return false; + } + + public static boolean hasPreferredActivities(PackageManager pm, String packageName) { + // Get list of preferred activities + List<ComponentName> prefActList = new ArrayList<>(); + // Intent list cannot be null. so pass empty list + List<IntentFilter> intentList = new ArrayList<>(); + pm.getPreferredActivities(intentList, prefActList, packageName); + Log.d(TAG, "Have " + prefActList.size() + " number of activities in preferred list"); + return prefActList.size() > 0; + } + +} diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3a5680dc209f..7c86f96ba848 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -999,9 +999,7 @@ <!-- Screen pinning dialog title. --> <string name="screen_pinning_title">Screen is pinned</string> <!-- Screen pinning dialog description. --> - <string name="screen_pinning_description">This keeps it in view until you unpin. Touch and hold Back and Overview at the same time to unpin.</string> - <!-- Screen pinning dialog description when in accessibility mode. --> - <string name="screen_pinning_description_accessible">This keeps it in view until you unpin. Touch and hold Overview to unpin.</string> + <string name="screen_pinning_description">This keeps it in view until you unpin. Touch and hold Back to unpin.</string> <!-- Screen pinning positive response. --> <string name="screen_pinning_positive">Got it</string> <!-- Screen pinning negative response. --> diff --git a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java index 1d0bfe7dc6f1..9a4cd9365283 100644 --- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java @@ -27,4 +27,9 @@ public interface RecentsComponent { void cancelPreloadingRecents(); void showNextAffiliatedTask(); void showPrevAffiliatedTask(); + + /** + * Docks the top-most task and opens recents. + */ + void dockTopTask(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index c216f9788d87..94001084b76a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -284,6 +284,11 @@ public class Recents extends SystemUI } @Override + public void dockTopTask() { + mImpl.dockTopTask(); + } + + @Override public void showNextAffiliatedTask() { mImpl.showNextAffiliatedTask(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index cea0b537e123..2c8937aa615f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -418,6 +418,16 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub showRelativeAffiliatedTask(false); } + public void dockTopTask() { + SystemServicesProxy ssp = Recents.getSystemServices(); + ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); + if (topTask != null && !ssp.isInHomeStack(topTask.id)) { + ssp.moveTaskToDockedStack(topTask.id, + ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT); + showRecents(false /* triggeredFromAltTab */); + } + } + /** * Returns the preloaded load plan and invalidates it. */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java index 31ee8ad7dd7d..28299d3217fb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java @@ -234,14 +234,6 @@ public class RecentsResizeTaskDialog extends DialogFragment { dismissAllowingStateLoss(); mRecentsActivity.dismissRecentsToHomeWithoutTransitionAnimation(); - // In debug mode, we force all task to be resizeable regardless of the - // current app configuration. - for (int i = additionalTasks; i >= 0; --i) { - if (mTasks[i] != null) { - ssp.setTaskResizeable(mTasks[i].key.id); - } - } - // Show tasks as they might not be currently visible - beginning with the oldest so that // the focus ends on the selected one. for (int i = additionalTasks; i >= 0; --i) { @@ -277,8 +269,7 @@ public class RecentsResizeTaskDialog extends DialogFragment { if (mTasks[0].key.stackId != DOCKED_STACK_ID) { int taskId = mTasks[0].key.id; SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.setTaskResizeable(taskId); - ssp.dockTask(taskId, createMode); + ssp.startTaskInDockedMode(taskId, createMode); mRecentsView.launchTask(mTasks[0], null, DOCKED_STACK_ID); } else { Toast.makeText(getContext(), "Already docked", Toast.LENGTH_SHORT); diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 231843ea2690..10075bc6861e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -214,11 +214,8 @@ public class ScreenPinningRequest implements View.OnClickListener { .setVisibility(View.INVISIBLE); } - final int description = mAccessibilityService.isEnabled() - ? R.string.screen_pinning_description_accessible - : R.string.screen_pinning_description; ((TextView) mLayout.findViewById(R.id.screen_pinning_description)) - .setText(description); + .setText(R.string.screen_pinning_description); final int backBgVisibility = mAccessibilityService.isEnabled() ? View.INVISIBLE : View.VISIBLE; mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVisibility); diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index a51e475bc164..221af1540238 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -266,36 +266,36 @@ public class SystemServicesProxy { return null; } - /** Allow a task to resize. */ - public void setTaskResizeable(int taskId) { + /** + * Resizes the given task to the new bounds. + */ + public void resizeTask(int taskId, Rect bounds) { if (mIam == null) return; try { - mIam.setTaskResizeable(taskId, true); + mIam.resizeTask(taskId, bounds, ActivityManager.RESIZE_MODE_FORCED); } catch (RemoteException e) { e.printStackTrace(); } } - /** - * Resizes the given task to the new bounds. - */ - public void resizeTask(int taskId, Rect bounds) { + /** Docks a task to the side of the screen and starts it. */ + public void startTaskInDockedMode(int taskId, int createMode) { if (mIam == null) return; try { - mIam.resizeTask(taskId, bounds, ActivityManager.RESIZE_MODE_FORCED); + mIam.startActivityFromRecents(taskId, DOCKED_STACK_ID, null); } catch (RemoteException e) { e.printStackTrace(); } } - /** Docks a task to the side of the screen. */ - public void dockTask(int taskId, int createMode) { + /** Docks an already resumed task to the side of the screen. */ + public void moveTaskToDockedStack(int taskId, int createMode) { if (mIam == null) return; try { - mIam.startActivityFromRecents(taskId, DOCKED_STACK_ID, null); + mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */); } catch (RemoteException e) { e.printStackTrace(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index ad0d7586749c..d5d07131701a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -785,8 +785,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // Dock the new task if we are hovering over a valid dock state if (event.dockState != TaskStack.DockState.NONE) { SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.setTaskResizeable(event.task.key.id); - ssp.dockTask(event.task.key.id, event.dockState.createMode); + ssp.startTaskInDockedMode(event.task.key.id, event.dockState.createMode); launchTask(event.task, null, INVALID_STACK_ID); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 893866925474..0def59993811 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -217,7 +217,7 @@ public abstract class BaseStatusBar extends SystemUI implements private boolean mDeviceProvisioned = false; - private RecentsComponent mRecents; + protected RecentsComponent mRecents; protected int mZenMode; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index cfde7913613f..cfdb01e177ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -88,7 +88,6 @@ import android.view.ViewGroup.LayoutParams; import android.view.ViewStub; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.view.accessibility.AccessibilityEvent; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; import android.view.animation.Interpolator; @@ -146,7 +145,6 @@ import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.FullscreenUserSwitcher; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.HotspotControllerImpl; -import com.android.systemui.statusbar.policy.KeyButtonView; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.LocationControllerImpl; @@ -1115,13 +1113,22 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } }; - private long mLastLockToAppLongPress; - private View.OnLongClickListener mLongPressBackRecentsListener = - new View.OnLongClickListener() { + private View.OnLongClickListener mLongPressBackListener = new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { - handleLongPressBackRecents(v); - return true; + return handleLongPressBack(); + } + }; + + private View.OnLongClickListener mRecentsLongClickListener = new View.OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + if (mRecents != null) { + mRecents.dockTopTask(); + return true; + } + return false; } }; @@ -1170,9 +1177,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener); mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener); mNavigationBarView.getRecentsButton().setLongClickable(true); - mNavigationBarView.getRecentsButton().setOnLongClickListener(mLongPressBackRecentsListener); + mNavigationBarView.getRecentsButton().setOnLongClickListener(mRecentsLongClickListener); mNavigationBarView.getBackButton().setLongClickable(true); - mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackRecentsListener); + mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackListener); mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener); mNavigationBarView.getHomeButton().setOnLongClickListener(mLongPressHomeListener); mAssistManager.onConfigurationChanged(); @@ -4048,7 +4055,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private void vibrateForCameraGesture() { // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep. - mVibrator.vibrate(new long[] { 0, 750L }, -1 /* repeat */); + mVibrator.vibrate(new long[]{0, 750L}, -1 /* repeat */); } public void onScreenTurnedOn() { @@ -4057,58 +4064,22 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } /** - * This handles long-press of both back and recents. They are - * handled together to capture them both being long-pressed - * at the same time to exit screen pinning (lock task). - * - * When accessibility mode is on, only a long-press from recents - * is required to exit. - * - * In all other circumstances we try to pass through long-press events - * for Back, so that apps can still use it. Which can be from two things. - * 1) Not currently in screen pinning (lock task). - * 2) Back is long-pressed without recents. + * Handles long press for back button. This exits screen pinning. */ - private void handleLongPressBackRecents(View v) { + private boolean handleLongPressBack() { try { - boolean sendBackLongPress = false; IActivityManager activityManager = ActivityManagerNative.getDefault(); - boolean isAccessiblityEnabled = mAccessibilityManager.isEnabled(); - if (activityManager.isInLockTaskMode() && !isAccessiblityEnabled) { - long time = System.currentTimeMillis(); - // If we recently long-pressed the other button then they were - // long-pressed 'together' - if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) { - activityManager.stopLockTaskModeOnCurrent(); - // When exiting refresh disabled flags. - mNavigationBarView.setDisabledFlags(mDisabled1, true); - } else if ((v.getId() == R.id.back) - && !mNavigationBarView.getRecentsButton().isPressed()) { - // If we aren't pressing recents right now then they presses - // won't be together, so send the standard long-press action. - sendBackLongPress = true; - } - mLastLockToAppLongPress = time; - } else { - // If this is back still need to handle sending the long-press event. - if (v.getId() == R.id.back) { - sendBackLongPress = true; - } else if (isAccessiblityEnabled && activityManager.isInLockTaskMode()) { - // When in accessibility mode a long press that is recents (not back) - // should stop lock task. - activityManager.stopLockTaskModeOnCurrent(); - // When exiting refresh disabled flags. - mNavigationBarView.setDisabledFlags(mDisabled1, true); - } - } - if (sendBackLongPress) { - KeyButtonView keyButtonView = (KeyButtonView) v; - keyButtonView.sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS); - keyButtonView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); + if (activityManager.isInLockTaskMode()) { + activityManager.stopLockTaskModeOnCurrent(); + + // When exiting refresh disabled flags. + mNavigationBarView.setDisabledFlags(mDisabled1, true); + return true; } } catch (RemoteException e) { Log.d(TAG, "Unable to reach activity manager", e); } + return false; } // Recents diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 496e4dfda6e0..927b995e5cb0 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -1500,7 +1500,7 @@ public class DeviceIdleController extends SystemService mState = STATE_SENSING; if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING."); EventLogTags.writeDeviceIdle(mState, "step"); - cancelAlarmLocked(); + scheduleAlarmLocked(mConstants.SENSING_TIMEOUT, false); cancelLocatingLocked(); mAnyMotionDetector.checkForAnyMotion(); mNotMoving = false; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c728b39209fe..257f0343b292 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -24,6 +24,7 @@ import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.HOME_STACK_ID; import static android.app.ActivityManager.INVALID_STACK_ID; +import static android.app.ActivityManager.PINNED_STACK_ID; import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static com.android.internal.util.XmlUtils.readBooleanAttribute; @@ -74,6 +75,7 @@ import android.os.WorkSource; import android.os.storage.IMountService; import android.os.storage.MountServiceInternal; import android.os.storage.StorageManager; +import android.provider.Settings.Global; import android.service.voice.IVoiceInteractionSession; import android.service.voice.VoiceInteractionSession; import android.util.ArrayMap; @@ -1188,6 +1190,7 @@ public final class ActivityManagerService extends ActivityManagerNative String mOrigDebugApp = null; boolean mOrigWaitForDebugger = false; boolean mAlwaysFinishActivities = false; + boolean mForceResizableActivites; IActivityController mController = null; String mProfileApp = null; ProcessRecord mProfileProc = null; @@ -8638,9 +8641,10 @@ public final class ActivityManagerService extends ActivityManagerNative // - a non-null bounds on a non-freeform (fullscreen OR docked) task moves // that task to freeform // - otherwise the task is not moved - // Note it's not allowed to resize a home stack task, or a docked task. + // Note it's not allowed to resize a home, docked, or pinned stack task. int stackId = task.stack.mStackId; - if (stackId == HOME_STACK_ID || stackId == DOCKED_STACK_ID) { + if (stackId == HOME_STACK_ID || stackId == DOCKED_STACK_ID + || stackId == PINNED_STACK_ID) { throw new IllegalArgumentException("trying to resizeTask on a " + "home or docked task"); } @@ -9081,6 +9085,29 @@ public final class ActivityManagerService extends ActivityManagerNative } } + /** + * Moves the top activity in the input stackId to the pinned stack. + * + * @param stackId Id of stack to move the top activity to pinned stack. + * @param bounds Bounds to use for pinned stack. + * + * @return True if the top activity of the input stack was successfully moved to the pinned + * stack. + */ + @Override + public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) { + enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, + "moveTopActivityToPinnedStack()"); + synchronized (this) { + long ident = Binder.clearCallingIdentity(); + try { + return mStackSupervisor.moveTopStackActivityToPinnedStackLocked(stackId, bounds); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + @Override public void resizeStack(int stackId, Rect bounds, boolean allowResizeInDockedMode) { enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, @@ -11613,14 +11640,17 @@ public final class ActivityManagerService extends ActivityManagerNative private void retrieveSettings() { final ContentResolver resolver = mContext.getContentResolver(); - String debugApp = Settings.Global.getString( - resolver, Settings.Global.DEBUG_APP); + String debugApp = Settings.Global.getString(resolver, Settings.Global.DEBUG_APP); boolean waitForDebugger = Settings.Global.getInt( resolver, Settings.Global.WAIT_FOR_DEBUGGER, 0) != 0; boolean alwaysFinishActivities = Settings.Global.getInt( resolver, Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0; boolean forceRtl = Settings.Global.getInt( resolver, Settings.Global.DEVELOPMENT_FORCE_RTL, 0) != 0; + int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0; + boolean forceResizable = Settings.Global.getInt( + resolver, Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, + defaultForceResizable) != 0; // Transfer any global setting for forcing RTL layout, into a System Property SystemProperties.set(Settings.Global.DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0"); @@ -11635,6 +11665,7 @@ public final class ActivityManagerService extends ActivityManagerNative mDebugApp = mOrigDebugApp = debugApp; mWaitForDebugger = mOrigWaitForDebugger = waitForDebugger; mAlwaysFinishActivities = alwaysFinishActivities; + mForceResizableActivites = forceResizable; // This happens before any activities are started, so we can // change mConfiguration in-place. updateConfigurationLocked(configuration, null, true); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 8bf1d22e4f6a..99539e4647f9 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -22,6 +22,7 @@ import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.HOME_STACK_ID; import static android.app.ActivityManager.LAST_STATIC_STACK_ID; +import static android.app.ActivityManager.PINNED_STACK_ID; import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; import static com.android.server.am.ActivityManagerDebugConfig.*; @@ -253,6 +254,9 @@ final class ActivityStack { private final LaunchingTaskPositioner mTaskPositioner; + // If the bounds of task contained in this stack should be persisted across power cycles. + final boolean mPersistTaskBounds; + static final int PAUSE_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 1; static final int DESTROY_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 2; static final int LAUNCH_TICK_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 3; @@ -354,10 +358,6 @@ final class ActivityStack { return count; } - int numTasks() { - return mTaskHistory.size(); - } - ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer, RecentTasks recentTasks) { mActivityContainer = activityContainer; @@ -370,6 +370,7 @@ final class ActivityStack { mRecentTasks = recentTasks; mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID ? new LaunchingTaskPositioner() : null; + mPersistTaskBounds = mStackId != DOCKED_STACK_ID && mStackId != PINNED_STACK_ID; } void attachDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) { @@ -1306,6 +1307,11 @@ final class ActivityStack { return true; } + if (mStackId == PINNED_STACK_ID) { + // Pinned stack is always visible if it exist. + return true; + } + final int stackIndex = mStacks.indexOf(this); if (stackIndex == mStacks.size() - 1) { @@ -1328,8 +1334,9 @@ final class ActivityStack { } final int belowFocusedIndex = mStacks.indexOf(focusedStack) - 1; - if (focusedStackId == DOCKED_STACK_ID && stackIndex == belowFocusedIndex) { - // Stacks directly behind the docked stack are always visible. + if ((focusedStackId == DOCKED_STACK_ID || focusedStackId == PINNED_STACK_ID) + && stackIndex == belowFocusedIndex) { + // Stacks directly behind the docked or pinned stack are always visible. return true; } @@ -1343,9 +1350,10 @@ final class ActivityStack { } if (belowFocusedIndex >= 0) { final ActivityStack stack = mStacks.get(belowFocusedIndex); - if (stack.mStackId == DOCKED_STACK_ID && stackIndex == (belowFocusedIndex - 1)) { - // The stack behind the docked stack is also visible so we can have a complete - // backdrop to the translucent activity when the docked stack is up. + if ((stack.mStackId == DOCKED_STACK_ID || stack.mStackId == PINNED_STACK_ID) + && stackIndex == (belowFocusedIndex - 1)) { + // The stack behind the docked or pinned stack is also visible so we can have a + // complete backdrop to the translucent activity when the docked stack is up. return true; } } @@ -2784,9 +2792,10 @@ final class ActivityStack { final String myReason = reason + " adjustFocus"; if (next != r) { if (next != null && (mStackId == FREEFORM_WORKSPACE_STACK_ID - || mStackId == DOCKED_STACK_ID)) { - // For freeform and docked stacks we always keep the focus within the stack as - // long as there is a running activity in the stack that we can adjust focus to. + || mStackId == DOCKED_STACK_ID || mStackId == PINNED_STACK_ID)) { + // For freeform, docked, and pinned stacks we always keep the focus within the + // stack as long as there is a running activity in the stack that we can adjust + // focus to. mService.setFocusedActivityLocked(next, myReason); return; } else { @@ -4633,6 +4642,51 @@ final class ActivityStack { r.taskConfigOverride = task.mOverrideConfig; } + void setFocusAndResumeStateIfNeeded( + ActivityRecord r, boolean setFocus, boolean setResume, String reason) { + // If the activity had focus before move focus to this stack. + if (setFocus) { + // If the activity owns the last resumed activity, transfer that together, + // so that we don't resume the same activity again in the new stack. + // Apps may depend on onResume()/onPause() being called in pairs. + if (setResume) { + mResumedActivity = r; + // Move the stack in which we are placing the activity to the front. We don't use + // ActivityManagerService.setFocusedActivityLocked, because if the activity is + // already focused, the call will short-circuit and do nothing. + moveToFront(reason); + } else { + // We need to not only move the stack to the front, but also have the activity + // focused. This will achieve both goals. + mService.setFocusedActivityLocked(r, reason); + } + } + } + + /** + * Moves the input activity from its current stack to this one. + * NOTE: The current task of the activity isn't moved to this stack. Instead a new task is + * created on this stack which the activity is added to. + * */ + void moveActivityToStack(ActivityRecord r) { + final ActivityStack prevStack = r.task.stack; + if (prevStack.mStackId == mStackId) { + // You are already in the right stack silly... + return; + } + + final boolean wasFocused = mStackSupervisor.isFrontStack(prevStack) + && (mStackSupervisor.topRunningActivityLocked() == r); + final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r); + + final TaskRecord task = createTaskRecord( + mStackSupervisor.getNextTaskId(), r.info, r.intent, null, null, true); + r.setTask(task, null); + task.addActivityToTop(r); + setAppTask(r, task); + setFocusAndResumeStateIfNeeded(r, wasFocused, wasResumed, "moveActivityToStack"); + } + private void setAppTask(ActivityRecord r, TaskRecord task) { final Rect bounds = task.getLaunchBounds(); task.updateOverrideConfiguration(bounds); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 99f7ec6144a3..cd2f124c715e 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -295,10 +295,6 @@ public final class ActivityStackSupervisor implements DisplayListener { /** Set when we have taken too long waiting to go to sleep. */ boolean mSleepTimeout = false; - /** Indicates if we are running on a Leanback-only (TV) device. Only initialized after - * setWindowManager is called. **/ - private boolean mLeanbackOnlyDevice; - /** * We don't want to allow the device to go to sleep while in the process * of launching an activity. This is primarily to allow alarm intent @@ -446,9 +442,6 @@ public final class ActivityStackSupervisor implements DisplayListener { mHomeStack = mFocusedStack = mLastFocusedStack = getStack(HOME_STACK_ID); mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); - - // Initialize this here, now that we can get a valid reference to PackageManager. - mLeanbackOnlyDevice = isLeanbackOnlyDevice(); } } @@ -1780,70 +1773,68 @@ public final class ActivityStackSupervisor implements DisplayListener { ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds) { final TaskRecord task = r.task; - // On leanback only devices we should keep all activities in the same stack. - if (!mLeanbackOnlyDevice && - (r.isApplicationActivity() || (task != null && task.isApplicationTask()))) { + if (!(r.isApplicationActivity() || (task != null && task.isApplicationTask()))) { + return mHomeStack; + } - ActivityStack stack; + ActivityStack stack; - if (task != null && task.stack != null) { - stack = task.stack; - if (stack.isOnHomeDisplay()) { - if (mFocusedStack != stack) { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, - "computeStackFocus: Setting " + "focused stack to r=" + r - + " task=" + task); - } else { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, - "computeStackFocus: Focused stack already=" + mFocusedStack); - } + if (task != null && task.stack != null) { + stack = task.stack; + if (stack.isOnHomeDisplay()) { + if (mFocusedStack != stack) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Setting " + "focused stack to r=" + r + + " task=" + task); + } else { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Focused stack already=" + mFocusedStack); } - return stack; - } - - final ActivityContainer container = r.mInitialActivityContainer; - if (container != null) { - // The first time put it on the desired stack, after this put on task stack. - r.mInitialActivityContainer = null; - return container.mStack; } + return stack; + } - // The fullscreen stack can contain any task regardless of if the task is resizeable - // or not. So, we let the task go in the fullscreen task if it is the focus stack. - // If the freeform stack has focus, and the activity to be launched is resizeable, - // we can also put it in the focused stack. - final boolean canUseFocusedStack = - mFocusedStack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID - || mFocusedStack.mStackId == FREEFORM_WORKSPACE_STACK_ID && r.info.resizeable; - if (canUseFocusedStack - && (!newTask || mFocusedStack.mActivityContainer.isEligibleForNewTasks())) { + final ActivityContainer container = r.mInitialActivityContainer; + if (container != null) { + // The first time put it on the desired stack, after this put on task stack. + r.mInitialActivityContainer = null; + return container.mStack; + } + + // The fullscreen stack can contain any task regardless of if the task is resizeable + // or not. So, we let the task go in the fullscreen task if it is the focus stack. + // If the freeform stack has focus, and the activity to be launched is resizeable, + // we can also put it in the focused stack. + final boolean canUseFocusedStack = + mFocusedStack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID + || mFocusedStack.mStackId == FREEFORM_WORKSPACE_STACK_ID && r.info.resizeable; + if (canUseFocusedStack + && (!newTask || mFocusedStack.mActivityContainer.isEligibleForNewTasks())) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Have a focused stack=" + mFocusedStack); + return mFocusedStack; + } + + // We first try to put the task in the first dynamic stack. + final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks; + for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) { + stack = homeDisplayStacks.get(stackNdx); + final boolean isDynamicStack = stack.mStackId >= FIRST_DYNAMIC_STACK_ID; + if (isDynamicStack) { if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, - "computeStackFocus: Have a focused stack=" + mFocusedStack); - return mFocusedStack; - } - - // We first try to put the task in the first dynamic stack. - final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks; - for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) { - stack = homeDisplayStacks.get(stackNdx); - final boolean isDynamicStack = stack.mStackId >= FIRST_DYNAMIC_STACK_ID; - if (isDynamicStack) { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, - "computeStackFocus: Setting focused stack=" + stack); - return stack; - } + "computeStackFocus: Setting focused stack=" + stack); + return stack; } - - // If there is no suitable dynamic stack then we figure out which static stack to use. - final int stackId = task != null ? task.getLaunchStackId() : - bounds != null ? FREEFORM_WORKSPACE_STACK_ID : - FULLSCREEN_WORKSPACE_STACK_ID; - stack = getStack(stackId, CREATE_IF_NEEDED, ON_TOP); - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r=" - + r + " stackId=" + stack.mStackId); - return stack; } - return mHomeStack; + + // If there is no suitable dynamic stack then we figure out which static stack to use. + final int stackId = task != null ? task.getLaunchStackId() : + bounds != null ? FREEFORM_WORKSPACE_STACK_ID : + FULLSCREEN_WORKSPACE_STACK_ID; + stack = getStack(stackId, CREATE_IF_NEEDED, ON_TOP); + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r=" + + r + " stackId=" + stack.mStackId); + return stack; } boolean setFocusedStack(ActivityRecord r, String reason) { @@ -2758,8 +2749,7 @@ public final class ActivityStackSupervisor implements DisplayListener { boolean didSomething = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; - final int numStacks = stacks.size(); - for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); if (stack.finishDisabledPackageActivitiesLocked( packageName, filterByClasses, doit, evenPersistent, userId)) { @@ -2885,13 +2875,16 @@ public final class ActivityStackSupervisor implements DisplayListener { } if (stackId != task.stack.mStackId) { - moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, FORCE_FOCUS, reason); - } else { - task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options, - task.getTopActivity() == null ? null : task.getTopActivity().appTimeTracker, - reason); + moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, reason); + + // moveTaskToStackUncheckedLocked() should already placed the task on top, + // still need moveTaskToFrontLocked() below for any transition settings. } + final ActivityRecord r = task.getTopActivity(); + task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options, + r == null ? null : r.appTimeTracker, reason); + if (DEBUG_STACK) Slog.d(TAG_STACK, "findTaskToMoveToFront: moved to front of stack=" + task.stack); } @@ -2998,84 +2991,87 @@ public final class ActivityStackSupervisor implements DisplayListener { } Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId); - - ActivityRecord r = stack.topRunningActivityLocked(); - - mTmpBounds.clear(); - mTmpConfigs.clear(); - ArrayList<TaskRecord> tasks = stack.getAllTasks(); - for (int i = tasks.size() - 1; i >= 0; i--) { - TaskRecord task = tasks.get(i); - if (task.mResizeable) { - if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) { - // For freeform stack we don't adjust the size of the tasks to match that - // of the stack, but we do try to make sure the tasks are still contained - // with the bounds of the stack. - tempRect2.set(task.mBounds); - fitWithinBounds(tempRect2, bounds); - task.updateOverrideConfiguration(tempRect2); - } else { - task.updateOverrideConfiguration(bounds); + mWindowManager.deferSurfaceLayout(); + try { + ActivityRecord r = stack.topRunningActivityLocked(); + + mTmpBounds.clear(); + mTmpConfigs.clear(); + ArrayList<TaskRecord> tasks = stack.getAllTasks(); + for (int i = tasks.size() - 1; i >= 0; i--) { + TaskRecord task = tasks.get(i); + if (task.mResizeable) { + if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) { + // For freeform stack we don't adjust the size of the tasks to match that + // of the stack, but we do try to make sure the tasks are still contained + // with the bounds of the stack. + tempRect2.set(task.mBounds); + fitWithinBounds(tempRect2, bounds); + task.updateOverrideConfiguration(tempRect2); + } else { + task.updateOverrideConfiguration(bounds); + } } - } - mTmpConfigs.put(task.taskId, task.mOverrideConfig); - mTmpBounds.put(task.taskId, task.mBounds); - } - stack.mFullscreen = mWindowManager.resizeStack(stackId, bounds, mTmpConfigs, mTmpBounds); - if (stack.mStackId == DOCKED_STACK_ID) { - // Dock stack funness...Yay! - if (stack.mFullscreen) { - // The dock stack went fullscreen which is kinda like dismissing it. - // In this case we make all other static stacks fullscreen and move all - // docked stack tasks to the fullscreen stack. - for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) { - if (i != DOCKED_STACK_ID && getStack(i) != null) { - resizeStackLocked(i, null, preserveWindows, true); + mTmpConfigs.put(task.taskId, task.mOverrideConfig); + mTmpBounds.put(task.taskId, task.mBounds); + } + stack.mFullscreen = mWindowManager.resizeStack(stackId, bounds, mTmpConfigs, mTmpBounds); + if (stack.mStackId == DOCKED_STACK_ID) { + // Dock stack funness...Yay! + if (stack.mFullscreen) { + // The dock stack went fullscreen which is kinda like dismissing it. + // In this case we make all other static stacks fullscreen and move all + // docked stack tasks to the fullscreen stack. + for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) { + if (i != DOCKED_STACK_ID && getStack(i) != null) { + resizeStackLocked(i, null, preserveWindows, true); + } } - } - final int count = tasks.size(); - for (int i = 0; i < count; i++) { - moveTaskToStackLocked(tasks.get(i).taskId, - FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS); - } + final int count = tasks.size(); + for (int i = 0; i < count; i++) { + moveTaskToStackLocked(tasks.get(i).taskId, + FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS); + } - // stack shouldn't contain anymore activities, so nothing to resume. - r = null; - } else { - // Docked stacks occupy a dedicated region on screen so the size of all other - // static stacks need to be adjusted so they don't overlap with the docked stack. - // We get the bounds to use from window manager which has been adjusted for any - // screen controls and is also the same for all stacks. - mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect); - - for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) { - if (i != DOCKED_STACK_ID) { - ActivityStack otherStack = getStack(i); - if (otherStack != null) { - resizeStackLocked(i, tempRect, PRESERVE_WINDOWS, true); + // stack shouldn't contain anymore activities, so nothing to resume. + r = null; + } else { + // Docked stacks occupy a dedicated region on screen so the size of all other + // static stacks need to be adjusted so they don't overlap with the docked stack. + // We get the bounds to use from window manager which has been adjusted for any + // screen controls and is also the same for all stacks. + mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect); + + for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) { + if (i != DOCKED_STACK_ID) { + ActivityStack otherStack = getStack(i); + if (otherStack != null) { + resizeStackLocked(i, tempRect, PRESERVE_WINDOWS, true); + } } } } + // Since we are resizing the stack, all other operations should strive to preserve + // windows. + preserveWindows = true; } - // Since we are resizing the stack, all other operations should strive to preserve - // windows. - preserveWindows = true; - } - stack.setBounds(bounds); + stack.setBounds(bounds); - if (r != null) { - final boolean updated = stack.ensureActivityConfigurationLocked(r, 0, preserveWindows); - // And we need to make sure at this point that all other activities - // are made visible with the correct configuration. - ensureActivitiesVisibleLocked(r, 0, preserveWindows); - if (!updated) { - resumeTopActivitiesLocked(stack, null, null); + if (r != null) { + final boolean updated = stack.ensureActivityConfigurationLocked(r, 0, preserveWindows); + // And we need to make sure at this point that all other activities + // are made visible with the correct configuration. + ensureActivitiesVisibleLocked(r, 0, preserveWindows); + if (!updated) { + resumeTopActivitiesLocked(stack, null, null); + } } + } finally { + mWindowManager.continueSurfaceLayout(); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } - - Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } void resizeTaskLocked(TaskRecord task, Rect bounds, int resizeMode, boolean preserveWindow) { @@ -3198,7 +3194,7 @@ public final class ActivityStackSupervisor implements DisplayListener { */ private boolean restoreRecentTaskLocked(TaskRecord task, int stackId) { if (stackId == INVALID_STACK_ID) { - stackId = mLeanbackOnlyDevice ? mHomeStack.mStackId : task.getLaunchStackId(); + stackId = task.getLaunchStackId(); } if (task.stack != null) { // Task has already been restored once. See if we need to do anything more @@ -3265,23 +3261,8 @@ public final class ActivityStackSupervisor implements DisplayListener { // If the task had focus before (or we're requested to move focus), // move focus to the new stack. - if (forceFocus || wasFocused) { - // If the task owns the last resumed activity, transfer that together, - // so that we don't resume the same activity again in the new stack. - // Apps may depend on onResume()/onPause() being called in pairs. - if (wasResumed) { - stack.mResumedActivity = r; - // Move the stack in which we are placing the task to the front. We don't use - // ActivityManagerService.setFocusedActivityLocked, because if the activity is - // already focused, the call will short-circuit and do nothing. - stack.moveToFront(reason); - } else { - // We need to not only move the stack to the front, but also have the activity - // focused. This will achieve both goals. - mService.setFocusedActivityLocked(r, reason); - } - - } + stack.setFocusAndResumeStateIfNeeded( + r, forceFocus || wasFocused, wasResumed, reason); return stack; } @@ -3293,7 +3274,8 @@ public final class ActivityStackSupervisor implements DisplayListener { return; } final String reason = "moveTaskToStack"; - if (stackId == DOCKED_STACK_ID || stackId == FULLSCREEN_WORKSPACE_STACK_ID) { + if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID + || stackId == FULLSCREEN_WORKSPACE_STACK_ID) { // We are about to relaunch the activity because its configuration changed due to // being maximized, i.e. size change. The activity will first remove the old window // and then add a new one. This call will tell window manager about this, so it can @@ -3314,7 +3296,7 @@ public final class ActivityStackSupervisor implements DisplayListener { && task.mBounds == null && task.mLastNonFullscreenBounds != null) { resizeTaskLocked(task, task.mLastNonFullscreenBounds, RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS); - } else if (stackId == DOCKED_STACK_ID) { + } else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) { resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS); } @@ -3325,6 +3307,46 @@ public final class ActivityStackSupervisor implements DisplayListener { resumeTopActivitiesLocked(); } + boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect bounds) { + final ActivityStack stack = getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP); + if (stack == null) { + throw new IllegalArgumentException( + "moveTopStackActivityToPinnedStackLocked: Unknown stackId=" + stackId); + } + + final ActivityRecord r = stack.topRunningActivityLocked(); + if (r == null) { + Slog.w(TAG, "moveTopStackActivityToPinnedStackLocked: No top running activity" + + " in stack=" + stack); + return false; + } + + if (!r.info.supportsPip) { + Slog.w(TAG, + "moveTopStackActivityToPinnedStackLocked: Picture-In-Picture not supported for " + + " r=" + r); + return false; + } + + final TaskRecord task = r.task; + if (task.mActivities.size() == 1) { + // There is only one activity in the task. So, we can just move the task over to the + // pinned stack without re-parenting the activity in a different task. + moveTaskToStackLocked(task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS); + } else { + final ActivityStack pinnedStack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP); + pinnedStack.moveActivityToStack(r); + } + + resizeStackLocked(PINNED_STACK_ID, bounds, PRESERVE_WINDOWS, true); + + // The task might have already been running and its visibility needs to be synchronized with + // the visibility of the stack / windows. + ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); + resumeTopActivitiesLocked(); + return true; + } + void positionTaskInStackLocked(int taskId, int stackId, int position) { final TaskRecord task = anyTaskForIdLocked(taskId); if (task == null) { @@ -4924,18 +4946,6 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - private boolean isLeanbackOnlyDevice() { - boolean onLeanbackOnly = false; - try { - onLeanbackOnly = AppGlobals.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_LEANBACK_ONLY); - } catch (RemoteException e) { - // noop - } - - return onLeanbackOnly; - } - /** * Adjust bounds to stay within stack bounds. * diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index fe87a9332309..120b40c73c13 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -20,6 +20,7 @@ import static android.app.ActivityManager.DOCKED_STACK_ID; import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.HOME_STACK_ID; +import static android.app.ActivityManager.PINNED_STACK_ID; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS; @@ -317,7 +318,7 @@ final class TaskRecord { mNextAffiliateTaskId = nextTaskId; mCallingUid = callingUid; mCallingPackage = callingPackage; - mResizeable = resizeable; + mResizeable = resizeable || mService.mForceResizableActivites; mPrivileged = privileged; ActivityInfo info = mActivities.get(0).info; mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1; @@ -419,7 +420,7 @@ final class TaskRecord { } else { autoRemoveRecents = false; } - mResizeable = info.resizeable; + mResizeable = info.resizeable || mService.mForceResizableActivites; mLockTaskMode = info.lockTaskLaunchMode; mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0; setLockTaskAuth(); @@ -625,6 +626,9 @@ final class TaskRecord { // Only set this based on the first activity if (mActivities.isEmpty()) { taskType = r.mActivityType; + if (taskType == HOME_ACTIVITY_TYPE && mService.mForceResizableActivites) { + mResizeable = r.info.resizeable; + } isPersistable = r.isPersistable(); mCallingUid = r.launchedFromUid; mCallingPackage = r.launchedFromPackage; @@ -1189,14 +1193,14 @@ final class TaskRecord { mFullscreen = bounds == null; if (mFullscreen) { - if (mBounds != null && stack.mStackId != DOCKED_STACK_ID) { + if (mBounds != null && stack.mPersistTaskBounds) { mLastNonFullscreenBounds = mBounds; } mBounds = null; mOverrideConfig = Configuration.EMPTY; } else { mBounds = new Rect(bounds); - if (stack == null || stack.mStackId != DOCKED_STACK_ID) { + if (stack == null || stack.mPersistTaskBounds) { mLastNonFullscreenBounds = mBounds; } @@ -1235,11 +1239,12 @@ final class TaskRecord { /** Returns the bounds that should be used to launch this task. */ Rect getLaunchBounds() { + final int stackId = stack.mStackId; if (stack == null - || stack.mStackId == HOME_STACK_ID - || stack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID) { + || stackId == HOME_STACK_ID + || stackId == FULLSCREEN_WORKSPACE_STACK_ID) { return (mResizeable && stack != null) ? stack.mBounds : null; - } else if (stack.mStackId == DOCKED_STACK_ID) { + } else if (!stack.mPersistTaskBounds) { return stack.mBounds; } return mLastNonFullscreenBounds; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b3e7f5f01010..aed2f0b9219e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2276,7 +2276,7 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings.enableSystemPackageLPw(packageName); try { - scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, UserHandle.SYSTEM); + scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null); } catch (PackageManagerException e) { Slog.e(TAG, "Failed to parse original system package: " + e.getMessage()); @@ -5685,7 +5685,7 @@ public class PackageManagerService extends IPackageManager.Stub { } try { scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK, - scanFlags, currentTime, UserHandle.SYSTEM); + scanFlags, currentTime, null); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage()); @@ -5791,8 +5791,6 @@ public class PackageManagerService extends IPackageManager.Stub { */ private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { - Preconditions.checkNotNull(user); - if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile); parseFlags |= mDefParseFlags; PackageParser pp = new PackageParser(); @@ -12141,7 +12139,7 @@ public class PackageManagerService extends IPackageManager.Stub { int oldScanFlags = SCAN_UPDATE_SIGNATURE | SCAN_UPDATE_TIME; try { scanPackageTracedLI(restoreFile, oldParseFlags, oldScanFlags, origUpdateTime, - UserHandle.SYSTEM); + null); } catch (PackageManagerException e) { Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade: " + e.getMessage()); @@ -13276,8 +13274,7 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageParser.Package newPkg; try { - newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, SCAN_NO_PATHS, 0, - UserHandle.SYSTEM); + newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, SCAN_NO_PATHS, 0, null); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to restore system package:" + newPs.name + ": " + e.getMessage()); return false; @@ -15841,8 +15838,7 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mInstallLock) { PackageParser.Package pkg = null; try { - pkg = scanPackageTracedLI(new File(codePath), parseFlags, 0, 0, - UserHandle.SYSTEM); + pkg = scanPackageTracedLI(new File(codePath), parseFlags, 0, 0, null); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to scan " + codePath + ": " + e.getMessage()); } @@ -16005,8 +16001,7 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mInstallLock) { final PackageParser.Package pkg; try { - pkg = scanPackageTracedLI(ps.codePath, parseFlags, SCAN_INITIAL, 0, - UserHandle.SYSTEM); + pkg = scanPackageTracedLI(ps.codePath, parseFlags, SCAN_INITIAL, 0, null); loaded.add(pkg.applicationInfo); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to scan " + ps.codePath + ": " + e.getMessage()); diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 2365a4591310..dc34904d98a9 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -456,10 +456,12 @@ public class AppTransition implements Dump { return -startPos / denom; } - private Animation createScaleUpAnimationLocked( - int transit, boolean enter, int appWidth, int appHeight) { - Animation a = null; + private Animation createScaleUpAnimationLocked(int transit, boolean enter, + Rect containingFrame) { + Animation a; getDefaultNextAppTransitionStartRect(mTmpStartRect); + final int appWidth = containingFrame.width(); + final int appHeight = containingFrame.height(); if (enter) { // Entering app zooms out from the center of the initial rect. float scaleW = mTmpStartRect.width() / (float) appWidth; @@ -746,10 +748,11 @@ public class AppTransition implements Dump { * activity that is leaving, and the activity that is entering. */ Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState, - int appWidth, int appHeight, int orientation, int transit, Rect containingFrame, - Rect contentInsets, @Nullable Rect surfaceInsets, boolean resizedWindow, - int taskId) { + int orientation, int transit, Rect containingFrame, Rect contentInsets, + @Nullable Rect surfaceInsets, boolean freeform, int taskId) { Animation a; + final int appWidth = containingFrame.width(); + final int appHeight = containingFrame.height(); getDefaultNextAppTransitionStartRect(mTmpStartRect); final int thumbWidthI = mTmpStartRect.width(); final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; @@ -762,7 +765,7 @@ public class AppTransition implements Dump { switch (thumbTransitState) { case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: { - if (resizedWindow) { + if (freeform) { a = createAspectScaledThumbnailEnterNonFullscreenAnimationLocked( containingFrame, surfaceInsets, taskId); } else { @@ -953,8 +956,10 @@ public class AppTransition implements Dump { * This animation is created when we are doing a thumbnail transition, for the activity that is * leaving, and the activity that is entering. */ - Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, int appWidth, - int appHeight, int transit, int taskId) { + Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, Rect containingFrame, + int transit, int taskId) { + final int appWidth = containingFrame.width(); + final int appHeight = containingFrame.height(); Bitmap thumbnailHeader = getAppTransitionThumbnailHeader(taskId); Animation a; getDefaultNextAppTransitionStartRect(mTmpStartRect); @@ -1016,18 +1021,22 @@ public class AppTransition implements Dump { return prepareThumbnailAnimation(a, appWidth, appHeight, transit); } - private Animation createRelaunchAnimation(int appWidth, int appHeight, - Rect containingFrame) { + private Animation createRelaunchAnimation(Rect containingFrame, Rect contentInsets) { getDefaultNextAppTransitionStartRect(mTmpFromClipRect); final int left = mTmpFromClipRect.left; final int top = mTmpFromClipRect.top; mTmpFromClipRect.offset(-left, -top); - mTmpToClipRect.set(0, 0, appWidth, appHeight); + // TODO: Isn't that strange that we ignore exact position of the containingFrame? + mTmpToClipRect.set(0, 0, containingFrame.width(), containingFrame.height()); AnimationSet set = new AnimationSet(true); float fromWidth = mTmpFromClipRect.width(); float toWidth = mTmpToClipRect.width(); float fromHeight = mTmpFromClipRect.height(); - float toHeight = mTmpToClipRect.height(); + // While the window might span the whole display, the actual content will be cropped to the + // system decoration frame, for example when the window is docked. We need to take into + // account the visible height when constructing the animation. + float toHeight = mTmpToClipRect.height() - contentInsets.top - contentInsets.bottom; + int translateAdjustment = 0; if (fromWidth <= toWidth && fromHeight <= toHeight) { // The final window is larger in both dimensions than current window (e.g. we are // maximizing), so we can simply unclip the new window and there will be no disappearing @@ -1037,12 +1046,17 @@ public class AppTransition implements Dump { // The disappearing window has one larger dimension. We need to apply scaling, so the // first frame of the entry animation matches the old window. set.addAnimation(new ScaleAnimation(fromWidth / toWidth, 1, fromHeight / toHeight, 1)); + // We might not be going exactly full screen, but instead be aligned under the status + // bar using cropping. We still need to account for the cropped part, which will also + // be scaled. + translateAdjustment = (int) (contentInsets.top * fromHeight / toHeight); } - // We might not be going exactly full screen, but instead be aligned under the status bar. - // We need to take this into account when creating the translate animation. + // We animate the translation from the old position of the removed window, to the new + // position of the added window. The latter might not be full screen, for example docked for + // docked windows. TranslateAnimation translate = new TranslateAnimation(left - containingFrame.left, - 0, top - containingFrame.top, 0); + 0, top - containingFrame.top - translateAdjustment, 0); set.addAnimation(translate); set.setDuration(DEFAULT_APP_TRANSITION_DURATION); set.setZAdjustment(Animation.ZORDER_TOP); @@ -1060,10 +1074,30 @@ public class AppTransition implements Dump { && mNextAppTransitionType != NEXT_TRANSIT_TYPE_CLIP_REVEAL; } + /** + * + * @param frame These are the bounds of the window when it finishes the animation. This is where + * the animation must usually finish in entrance animation, as the next frame will + * display the window at these coordinates. In case of exit animation, this is + * where the animation must start, as the frame before the animation is displaying + * the window at these bounds. + * @param insets Knowing where the window will be positioned is not enough. Some parts of the + * window might be obscured, usually by the system windows (status bar and + * navigation bar) and we use content insets to convey that information. This + * usually affects the animation aspects vertically, as the system decoration is + * at the top and the bottom. For example when we animate from full screen to + * recents, we want to exclude the covered parts, because they won't match the + * thumbnail after the last frame is executed. + * @param surfaceInsets In rare situation the surface is larger than the content and we need to + * know about this to make the animation frames match. We currently use + * this for freeform windows, which have larger surfaces to display + * shadows. When we animate them from recents, we want to match the content + * to the recents thumbnail and hence need to account for the surface being + * bigger. + */ Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, - int appWidth, int appHeight, int orientation, Rect containingFrame, Rect contentInsets, - @Nullable Rect surfaceInsets, Rect appFrame, boolean isVoiceInteraction, - boolean resizedWindow, int taskId) { + int orientation, Rect frame, Rect insets, @Nullable Rect surfaceInsets, + boolean isVoiceInteraction, boolean freeform, int taskId) { Animation a; if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN || transit == TRANSIT_TASK_OPEN @@ -1086,7 +1120,7 @@ public class AppTransition implements Dump { + " anim=" + a + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3)); } else if (transit == TRANSIT_ACTIVITY_RELAUNCH) { - a = createRelaunchAnimation(appWidth, appHeight, containingFrame); + a = createRelaunchAnimation(frame, insets); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, "applyAnimation:" + " anim=" + a + " nextAppTransition=" + mNextAppTransition @@ -1108,14 +1142,14 @@ public class AppTransition implements Dump { + " transit=" + appTransitionToString(transit) + " Callers=" + Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) { - a = createClipRevealAnimationLocked(transit, enter, appFrame); + a = createClipRevealAnimationLocked(transit, enter, frame); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, "applyAnimation:" + " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL" + " transit=" + appTransitionToString(transit) + " Callers=" + Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) { - a = createScaleUpAnimationLocked(transit, enter, appWidth, appHeight); + a = createScaleUpAnimationLocked(transit, enter, frame); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, "applyAnimation:" + " anim=" + a + " nextAppTransition=ANIM_SCALE_UP" @@ -1126,7 +1160,7 @@ public class AppTransition implements Dump { mNextAppTransitionScaleUp = (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP); a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter), - appWidth, appHeight, transit, taskId); + frame, transit, taskId); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) { String animName = mNextAppTransitionScaleUp ? "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN"; @@ -1140,8 +1174,8 @@ public class AppTransition implements Dump { mNextAppTransitionScaleUp = (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP); a = createAspectScaledThumbnailEnterExitAnimationLocked( - getThumbnailTransitionState(enter), appWidth, appHeight, orientation, transit, - containingFrame, contentInsets, surfaceInsets, resizedWindow, taskId); + getThumbnailTransitionState(enter), orientation, transit, frame, + insets, surfaceInsets, freeform, taskId); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) { String animName = mNextAppTransitionScaleUp ? "ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN"; diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java index bf7063fba67e..a61e83fc12df 100644 --- a/services/core/java/com/android/server/wm/AppWindowAnimator.java +++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java @@ -300,7 +300,8 @@ public class AppWindowAnimator { return false; } - if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed) + if ((mAppToken.allDrawn || mAppToken.mAnimatingWithSavedSurface + || animating || mAppToken.startingDisplayed) && animation != null) { if (!animating) { if (DEBUG_ANIM) Slog.v(TAG, diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 5a1ed582358c..943e3ea736d4 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -52,6 +52,13 @@ class AppWindowToken extends WindowToken { final boolean voiceInteraction; + // Whether the window has a saved surface from last pause, which can be + // used to start an entering animation earlier. + boolean mHasSavedSurface; + + // Whether we're performing an entering animation with a saved surface. + boolean mAnimatingWithSavedSurface; + Task mTask; boolean appFullscreen; int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -114,11 +121,14 @@ class AppWindowToken extends WindowToken { // This application will have its window replaced due to relaunch. This allows window manager // to differentiate between simple removal of a window and replacement. In the latter case it // will preserve the old window until the new one is drawn. - boolean mReplacingWindow; + boolean mWillReplaceWindow; // If true, the replaced window was already requested to be removed. boolean mReplacingRemoveRequested; // Whether the replacement of the window should trigger app transition animation. boolean mAnimateReplacingWindow; + // If not null, the window that will be used to replace the old one. This is being set when + // the window is added and unset when this window reports its first draw. + WindowState mReplacingWindow; AppWindowToken(WindowManagerService _service, IApplicationToken _token, boolean _voiceInteraction) { @@ -257,10 +267,13 @@ class AppWindowToken extends WindowToken { final int N = allAppWindows.size(); for (int i=0; i<N; i++) { WindowState win = allAppWindows.get(i); + // If we're animating with a saved surface, we're already visible. + // Return true so that the alpha doesn't get cleared. if (!win.mAppFreezing - && (win.mViewVisibility == View.VISIBLE || - (win.mWinAnimator.isAnimating() && - !service.mAppTransition.isTransitionSet())) + && (win.mViewVisibility == View.VISIBLE + || mAnimatingWithSavedSurface + || (win.mWinAnimator.isAnimating() && + !service.mAppTransition.isTransitionSet())) && !win.mDestroying && win.isDrawnLw()) { return true; } diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index f5e97e5a5d7c..f35ea669a71d 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -77,7 +77,6 @@ class DragState { mSurfaceControl = surface; mFlags = flags; mLocalWin = localWin; - mUid = Binder.getCallingUid(); mNotifiedWindows = new ArrayList<WindowState>(); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 7c561800ed03..4d11452a8c0c 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -17,12 +17,15 @@ package com.android.server.wm; import static android.app.ActivityManager.DOCKED_STACK_ID; +import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.HOME_STACK_ID; +import static android.app.ActivityManager.PINNED_STACK_ID; import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION; import static com.android.server.wm.WindowManagerService.TAG; import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE; import static com.android.server.wm.WindowManagerService.DEBUG_STACK; import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK; -import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID; + import android.content.res.Configuration; import android.graphics.Rect; @@ -187,7 +190,8 @@ class Task implements DimLayer.DimLayerUser { bounds = mTmpRect; mFullscreen = true; } else { - if (mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID || bounds.isEmpty()) { + if ((mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID + && mStack.mStackId != PINNED_STACK_ID) || bounds.isEmpty()) { // ensure bounds are entirely within the display rect if (!bounds.intersect(mTmpRect)) { // Can't set bounds outside the containing display...Sorry! @@ -241,8 +245,7 @@ class Task implements DimLayer.DimLayerUser { private boolean useCurrentBounds() { final DisplayContent displayContent = mStack.getDisplayContent(); if (mFullscreen - || mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID - || mStack.mStackId == DOCKED_STACK_ID + || mStack.allowTaskResize() || displayContent == null || displayContent.getDockedStackLocked() != null) { return true; @@ -322,6 +325,10 @@ class Task implements DimLayer.DimLayerUser { return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers; } + boolean inHomeStack() { + return mStack != null && mStack.mStackId == HOME_STACK_ID; + } + boolean inFreeformWorkspace() { return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID; } diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 6734fd621216..9b3d47823d7a 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -22,6 +22,7 @@ import static android.app.ActivityManager.FIRST_STATIC_STACK_ID; import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.LAST_STATIC_STACK_ID; +import static android.app.ActivityManager.PINNED_STACK_ID; import static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerService.H.RESIZE_STACK; import static com.android.server.wm.WindowManagerService.TAG; @@ -123,7 +124,8 @@ public class TaskStack implements DimLayer.DimLayerUser { boolean allowTaskResize() { return mStackId == FREEFORM_WORKSPACE_STACK_ID - || mStackId == DOCKED_STACK_ID; + || mStackId == DOCKED_STACK_ID + || mStackId == PINNED_STACK_ID; } /** @@ -202,6 +204,7 @@ public class TaskStack implements DimLayer.DimLayerUser { private boolean useCurrentBounds() { if (mFullscreen || mStackId == DOCKED_STACK_ID + || mStackId == PINNED_STACK_ID || mDisplayContent == null || mDisplayContent.getDockedStackLocked() != null) { return true; @@ -393,7 +396,7 @@ public class TaskStack implements DimLayer.DimLayerUser { Rect bounds = null; final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID); - if (mStackId == DOCKED_STACK_ID || (dockedStack != null + if (mStackId == DOCKED_STACK_ID || (dockedStack != null && mStackId != PINNED_STACK_ID && mStackId >= FIRST_STATIC_STACK_ID && mStackId <= LAST_STATIC_STACK_ID)) { // The existence of a docked stack affects the size of any static stack created since // the docked stack occupies a dedicated region on screen. @@ -422,6 +425,7 @@ public class TaskStack implements DimLayer.DimLayerUser { void getStackDockedModeBoundsLocked(Rect outBounds) { if (mStackId == DOCKED_STACK_ID + || mStackId == PINNED_STACK_ID || mStackId > LAST_STATIC_STACK_ID || mDisplayContent == null) { outBounds.set(mBounds); @@ -533,7 +537,7 @@ public class TaskStack implements DimLayer.DimLayerUser { for (int i = 0; i < count; i++) { final TaskStack otherStack = mService.mStackIdToStack.valueAt(i); final int otherStackId = otherStack.mStackId; - if (otherStackId != DOCKED_STACK_ID + if (otherStackId != DOCKED_STACK_ID && mStackId != PINNED_STACK_ID && otherStackId >= FIRST_STATIC_STACK_ID && otherStackId <= LAST_STATIC_STACK_ID) { mService.mH.sendMessage( diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 928a1170625c..be86e2f9cf6d 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -413,7 +413,7 @@ public class WindowAnimator { final AppWindowToken atoken = win.mAppToken; if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) { - if (atoken == null || atoken.allDrawn) { + if (atoken == null || atoken.allDrawn || atoken.mAnimatingWithSavedSurface) { if (winAnimator.performShowLocked()) { setPendingLayoutChanges(displayId, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index cd5fbb0a6b31..62553cf6a2b0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1327,8 +1327,14 @@ public class WindowManagerService extends IWindowManager.Stub addAttachedWindowToListLocked(win, addToToken); } - if (win.mAppToken != null && addToToken) { - win.mAppToken.allAppWindows.add(win); + final AppWindowToken appToken = win.mAppToken; + if (appToken != null) { + if (addToToken) { + appToken.allAppWindows.add(win); + } + if (appToken.mWillReplaceWindow) { + appToken.mReplacingWindow = win; + } } } @@ -2053,7 +2059,7 @@ public class WindowManagerService extends IWindowManager.Stub } private void prepareWindowReplacementTransition(AppWindowToken atoken) { - if (atoken == null || !atoken.mReplacingWindow || !atoken.mAnimateReplacingWindow) { + if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) { return; } atoken.allDrawn = false; @@ -2157,6 +2163,8 @@ public class WindowManagerService extends IWindowManager.Stub + " isAnimating=" + win.mWinAnimator.isAnimating() + " app-animation=" + (win.mAppToken != null ? win.mAppToken.mAppAnimator.animation : null) + + " mWillReplaceWindow=" + + (win.mAppToken != null ? win.mAppToken.mWillReplaceWindow : false) + " inPendingTransaction=" + (win.mAppToken != null ? win.mAppToken.inPendingTransaction : false) + " mDisplayFrozen=" + mDisplayFrozen); @@ -2168,8 +2176,8 @@ public class WindowManagerService extends IWindowManager.Stub // animation wouldn't be seen. if (win.mHasSurface && okToDisplay()) { final AppWindowToken appToken = win.mAppToken; - if (appToken != null && appToken.mReplacingWindow) { - // This window is going to be replaced. We need to kepp it around until the new one + if (appToken != null && appToken.mWillReplaceWindow) { + // This window is going to be replaced. We need to keep it around until the new one // gets added, then we will get rid of this one. if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Preserving " + win + " until the new one is " + "added"); @@ -2679,9 +2687,16 @@ public class WindowManagerService extends IWindowManager.Stub if (winAnimator.mSurfaceControl != null) { if (DEBUG_VISIBILITY) Slog.i(TAG, "Relayout invis " + win + ": mExiting=" + win.mExiting); + // If we are using a saved surface to do enter animation, just let the + // animation run and don't destroy the surface. This could happen when + // the app sets visibility to invisible for the first time after resume, + // or when the user exits immediately after a resume. In both cases, we + // don't want to destroy the saved surface. + final boolean isAnimatingWithSavedSurface = + win.mAppToken != null && win.mAppToken.mAnimatingWithSavedSurface; // If we are not currently running the exit animation, we // need to see about starting one. - if (!win.mExiting) { + if (!win.mExiting && !isAnimatingWithSavedSurface) { surfaceChanged = true; // Try starting an animation; if there isn't one, we // can destroy the surface right away. @@ -2833,12 +2848,19 @@ public class WindowManagerService extends IWindowManager.Stub try { synchronized (mWindowMap) { WindowState win = windowForClientLocked(session, client, false); - if (DEBUG_ADD_REMOVE) Slog.d(TAG, "finishDrawingWindow: " + win); + if (DEBUG_ADD_REMOVE) Slog.d(TAG, "finishDrawingWindow: " + win + " mDrawState=" + + (win != null ? win.mWinAnimator.drawStateToString() : "null")); if (win != null && win.mWinAnimator.finishDrawingLocked()) { if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) { getDefaultDisplayContentLocked().pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; } + if (win.mAppToken != null) { + // App has drawn something to its windows, we're no longer animating with + // the saved surfaces. If the user exits now, we only want to save again + // if allDrawn is true. + win.mAppToken.mAnimatingWithSavedSurface = false; + } win.setDisplayLayoutNeeded(); mWindowPlacerLocked.requestTraversal(); } @@ -2862,46 +2884,44 @@ public class WindowManagerService extends IWindowManager.Stub "applyAnimation: atoken=" + atoken); // Determine the visible rect to calculate the thumbnail clip - WindowState win = atoken.findMainWindow(); - Rect containingFrame = new Rect(0, 0, width, height); - Rect contentInsets = new Rect(); - Rect appFrame = new Rect(0, 0, width, height); + final WindowState win = atoken.findMainWindow(); + final Rect frame = new Rect(0, 0, width, height); + final Rect insets = new Rect(); Rect surfaceInsets = null; final boolean fullscreen = win != null && win.isFullscreen(width, height); final boolean freeform = win != null && win.inFreeformWorkspace(); + final boolean docked = win != null && win.inDockedWorkspace(); if (win != null) { // Containing frame will usually cover the whole screen, including dialog windows. // For freeform workspace windows it will not cover the whole screen and it also // won't exactly match the final freeform window frame (e.g. when overlapping with // the status bar). In that case we need to use the final frame. if (freeform) { - containingFrame.set(win.mFrame); + frame.set(win.mFrame); } else { - containingFrame.set(win.mContainingFrame); + frame.set(win.mContainingFrame); } surfaceInsets = win.getAttrs().surfaceInsets; - if (fullscreen) { + if (fullscreen || docked) { // For fullscreen windows use the window frames and insets to set the thumbnail - // clip. For none-fullscreen windows we use the app display region so the clip + // clip. For non-fullscreen windows we use the app display region so the clip // isn't affected by the window insets. - contentInsets.set(win.mContentInsets); - appFrame.set(win.mFrame); - } else { - appFrame.set(containingFrame); + insets.set(win.mContentInsets); } } - final int containingWidth = containingFrame.width(); - final int containingHeight = containingFrame.height(); if (atoken.mLaunchTaskBehind) { // Differentiate the two animations. This one which is briefly on the screen // gets the !enter animation, and the other activity which remains on the // screen gets the enter animation. Both appear in the mOpeningApps set. enter = false; } - Animation a = mAppTransition.loadAnimation(lp, transit, enter, containingWidth, - containingHeight, mCurConfiguration.orientation, containingFrame, contentInsets, - surfaceInsets, appFrame, isVoiceInteraction, freeform, atoken.mTask.mTaskId); + if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "Loading animation for app transition." + + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter + + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets); + Animation a = mAppTransition.loadAnimation(lp, transit, enter, + mCurConfiguration.orientation, frame, insets, surfaceInsets, isVoiceInteraction, + freeform, atoken.mTask.mTaskId); if (a != null) { if (DEBUG_ANIM) { RuntimeException e = null; @@ -2911,6 +2931,8 @@ public class WindowManagerService extends IWindowManager.Stub } Slog.v(TAG, "Loaded animation " + a + " for " + atoken, e); } + final int containingWidth = frame.width(); + final int containingHeight = frame.height(); atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight, mAppTransition.canSkipFirstFrame()); } @@ -3909,7 +3931,7 @@ public class WindowManagerService extends IWindowManager.Stub // transition animation // * or this is an opening app and windows are being replaced. if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) || - (visible && wtoken.mReplacingWindow)) { + (visible && wtoken.mWillReplaceWindow)) { boolean changed = false; if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Changing app " + wtoken + " hidden=" + wtoken.hidden @@ -4657,7 +4679,7 @@ public class WindowManagerService extends IWindowManager.Stub throw new IllegalArgumentException("resizeStack: stackId " + stackId + " not found."); } - if (stack.setBounds(bounds, configs, taskBounds)) { + if (stack.setBounds(bounds, configs, taskBounds) && stack.isVisibleLocked()) { stack.resizeWindows(); stack.getDisplayContent().layoutNeeded = true; mWindowPlacerLocked.performSurfacePlacement(); @@ -4710,6 +4732,26 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** + * Starts deferring layout passes. Useful when doing multiple changes but to optimize + * performance, only one layout pass should be done. This can be called multiple times, and + * layouting will be resumed once the last caller has called {@link #continueSurfaceLayout} + */ + public void deferSurfaceLayout() { + synchronized (mWindowMap) { + mWindowPlacerLocked.deferLayout(); + } + } + + /** + * Resumes layout passes after deferring them. See {@link #deferSurfaceLayout()} + */ + public void continueSurfaceLayout() { + synchronized (mWindowMap) { + mWindowPlacerLocked.continueLayout(); + } + } + public void getTaskBounds(int taskId, Rect bounds) { synchronized (mWindowMap) { Task task = mTaskIdToTask.get(taskId); @@ -6972,7 +7014,7 @@ public class WindowManagerService extends IWindowManager.Stub + " asbinder=" + window.asBinder()); } - final int callerPid = Binder.getCallingPid(); + final int callerUid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); IBinder token = null; @@ -6997,6 +7039,7 @@ public class WindowManagerService extends IWindowManager.Stub final IBinder winBinder = window.asBinder(); token = new Binder(); mDragState = new DragState(this, token, surface, flags, winBinder); + mDragState.mUid = callerUid; token = mDragState.mToken = new Binder(); // 5 second timeout for this window to actually begin the drag @@ -8346,7 +8389,7 @@ public class WindowManagerService extends IWindowManager.Stub final int numTokens = tokens.size(); for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) { final AppWindowToken wtoken = tokens.get(tokenNdx); - if (wtoken.mIsExiting && !wtoken.mReplacingWindow) { + if (wtoken.mIsExiting && !wtoken.mWillReplaceWindow) { continue; } i = reAddAppWindowsLocked(displayContent, i, wtoken); @@ -8416,7 +8459,8 @@ public class WindowManagerService extends IWindowManager.Stub } else if (wtoken != null) { winAnimator.mAnimLayer = w.mLayer + wtoken.mAppAnimator.animLayerAdjustment; - if (wtoken.mReplacingWindow && wtoken.mAnimateReplacingWindow) { + if (wtoken.mWillReplaceWindow && wtoken.mAnimateReplacingWindow && + wtoken.mReplacingWindow != w) { // We know that we will be animating a relaunching window in the near future, // which will receive a z-order increase. We want the replaced window to // immediately receive the same treatment, e.g. to be above the dock divider. @@ -9962,7 +10006,9 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Attempted to set replacing window on non-existing app token " + token); return; } - appWindowToken.mReplacingWindow = true; + if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Marking app token " + appWindowToken + + " as replacing window."); + appWindowToken.mWillReplaceWindow = true; appWindowToken.mAnimateReplacingWindow = animate; } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 7ea64f153061..e9ea3a89ff19 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -28,6 +28,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION; import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT; import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION; @@ -35,6 +36,7 @@ import static com.android.server.wm.WindowManagerService.DEBUG_POWER; import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE; import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY; +import android.app.ActivityManager; import android.app.AppOpsManager; import android.graphics.Point; import android.os.PowerManager; @@ -55,7 +57,6 @@ import android.content.res.Configuration; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Rect; -import android.graphics.RectF; import android.graphics.Region; import android.os.IBinder; import android.os.RemoteException; @@ -446,7 +447,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { + WindowManagerService.TYPE_LAYER_OFFSET; mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type); mAttachedWindow = attachedWindow; - if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + mAttachedWindow); + if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + + mAttachedWindow); final WindowList childWindows = mAttachedWindow.mChildWindows; final int numChildWindows = childWindows.size(); @@ -552,7 +554,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { @Override public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf, Rect osf) { - if (mAppToken != null && mAppToken.mReplacingWindow + if (mAppToken != null && mAppToken.mWillReplaceWindow && (mExiting || !mAppToken.mReplacingRemoveRequested)) { // This window is being replaced and either already got information that it's being // removed or we are still waiting for some information. Because of this we don't @@ -1307,10 +1309,12 @@ final class WindowState implements WindowManagerPolicy.WindowState { void maybeRemoveReplacedWindow() { AppWindowToken token = mAppToken; - if (token != null && token.mReplacingWindow) { - token.mReplacingWindow = false; + if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == this) { + if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this); + token.mWillReplaceWindow = false; token.mAnimateReplacingWindow = false; token.mReplacingRemoveRequested = false; + token.mReplacingWindow = null; for (int i = token.allAppWindows.size() - 1; i >= 0; i--) { WindowState win = token.allAppWindows.get(i); if (win.mExiting) { @@ -1501,6 +1505,31 @@ final class WindowState implements WindowManagerPolicy.WindowState { return mExiting || (mService.mClosingApps.contains(mAppToken)); } + boolean saveSurfaceIfNeeded() { + if (ActivityManager.isLowRamDeviceStatic()) { + // Don't save surfaces on Svelte devices. + return false; + } + + Task task = getTask(); + if (task == null || task.inHomeStack()) { + // Don't save surfaces for home stack apps. These usually resume and draw + // first frame very fast. Saving surfaces are mostly a waste of memory. + return false; + } + + // We want to save surface if the app's windows are "allDrawn", or if we're + // currently animating with save surfaces. (If the app didn't even finish + // drawing when the user exits, but we have a saved surface from last time, + // we still want to keep that surface.) + if (mAppToken.allDrawn || mAppToken.mAnimatingWithSavedSurface) { + mAppToken.mHasSavedSurface = true; + return true; + } + + return false; + } + @Override public boolean isDefaultDisplay() { final DisplayContent displayContent = getDisplayContent(); diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 1754123d0546..68c39ba432e3 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -38,7 +38,6 @@ import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; -import android.graphics.RectF; import android.graphics.Region; import android.os.Debug; import android.os.RemoteException; @@ -536,6 +535,7 @@ class WindowStateAnimator { mDrawState = COMMIT_DRAW_PENDING; return true; } + return false; } @@ -555,7 +555,8 @@ class WindowStateAnimator { mDrawState = READY_TO_SHOW; boolean result = false; final AppWindowToken atoken = mWin.mAppToken; - if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) { + if (atoken == null || atoken.allDrawn || atoken.mAnimatingWithSavedSurface || + mWin.mAttrs.type == TYPE_APPLICATION_STARTING) { result = performShowLocked(); } if (mDestroyPreservedSurfaceUponRedraw && result) { @@ -996,12 +997,16 @@ class WindowStateAnimator { } void destroySurfaceLocked() { - if (mWin.mAppToken != null && mWin == mWin.mAppToken.startingWindow) { - mWin.mAppToken.startingDisplayed = false; + final AppWindowToken wtoken = mWin.mAppToken; + if (wtoken != null) { + wtoken.mHasSavedSurface = false; + wtoken.mAnimatingWithSavedSurface = false; + if (mWin == wtoken.startingWindow) { + wtoken.startingDisplayed = false; + } } if (mSurfaceControl != null) { - int i = mWin.mChildWindows.size(); while (i > 0) { i--; @@ -1428,7 +1433,7 @@ class WindowStateAnimator { // living in a different stack. If we suddenly crop it to the new stack bounds, it might // get cut off. We don't want it to happen, so we let it ignore the stack bounds until it // gets removed. The window that will replace it will abide them. - if (appToken != null && appToken.mCropWindowsToStack && !appToken.mReplacingWindow) { + if (appToken != null && appToken.mCropWindowsToStack && !appToken.mWillReplaceWindow) { TaskStack stack = w.getTask().mStack; stack.getBounds(mTmpStackBounds); // When we resize we use the big surface approach, which means we can't trust the @@ -1854,8 +1859,7 @@ class WindowStateAnimator { } } - if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING - && mWin.mAppToken != null) { + if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING && mWin.mAppToken != null) { mWin.mAppToken.firstWindowDrawn = true; if (mWin.mAppToken.startingData != null) { diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index 0c004b2046a2..b267860ce80e 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -17,6 +17,7 @@ import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.WindowManagerService.DEBUG; import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE; +import static com.android.server.wm.WindowManagerService.DEBUG_ANIM; import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS; import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT; import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT_REPEATS; @@ -111,13 +112,34 @@ class WindowSurfacePlacer { private int mPreferredModeId = 0; private boolean mTraversalScheduled; + private int mDeferDepth = 0; public WindowSurfacePlacer(WindowManagerService service) { mService = service; mWallpaperControllerLocked = mService.mWallpaperControllerLocked; } + /** + * See {@link WindowManagerService#deferSurfaceLayout()} + */ + void deferLayout() { + mDeferDepth++; + } + + /** + * See {@link WindowManagerService#continueSurfaceLayout()} + */ + void continueLayout() { + mDeferDepth--; + if (mDeferDepth <= 0) { + performSurfacePlacement(); + } + } + final void performSurfacePlacement() { + if (mDeferDepth > 0) { + return; + } int loopCount = 6; do { mTraversalScheduled = false; @@ -340,6 +362,10 @@ class WindowSurfacePlacer { // Don't remove this window until rotation has completed. continue; } + // Discard the saved surface if window size is changed, it can't be reused. + if (win.mAppToken != null && win.mAppToken.mHasSavedSurface) { + win.mWinAnimator.destroySurfaceLocked(); + } win.reportResized(); mService.mResizingWindows.remove(i); } @@ -371,7 +397,9 @@ class WindowSurfacePlacer { if (mWallpaperControllerLocked.isWallpaperTarget(win)) { wallpaperDestroyed = true; } - win.mWinAnimator.destroySurfaceLocked(); + if (!win.saveSurfaceIfNeeded()) { + win.mWinAnimator.destroySurfaceLocked(); + } } while (i > 0); mService.mDestroySurface.clear(); } @@ -1174,6 +1202,18 @@ class WindowSurfacePlacer { } } createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer); + + if (wtoken.mHasSavedSurface) { + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, + "Early start of animation: " + wtoken + ", allDrawn=" + wtoken.allDrawn); + + for (int j = 0; j < wtoken.windows.size(); j++) { + WindowState ws = wtoken.windows.get(j); + ws.mWinAnimator.mDrawState = WindowStateAnimator.READY_TO_SHOW; + } + wtoken.mHasSavedSurface = false; + wtoken.mAnimatingWithSavedSurface = true; + } } AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ? null : @@ -1219,6 +1259,10 @@ class WindowSurfacePlacer { + wtoken.allDrawn + " startingDisplayed=" + wtoken.startingDisplayed + " startingMoved=" + wtoken.startingMoved); + + if (wtoken.mHasSavedSurface || wtoken.mAnimatingWithSavedSurface) { + continue; + } if (!wtoken.allDrawn && !wtoken.startingDisplayed && !wtoken.startingMoved) { return false; } |