Merge "Allow preloading of freefore multi window drawables."
diff --git a/api/current.txt b/api/current.txt
index c58219d..0263927 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13466,6 +13466,7 @@
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_INFO_TIMESTAMP_SOURCE;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_INFO_WHITE_LEVEL;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_MAX_ANALOG_SENSITIVITY;
+ field public static final android.hardware.camera2.CameraCharacteristics.Key<android.graphics.Rect[]> SENSOR_OPTICAL_BLACK_REGIONS;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_ORIENTATION;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_REFERENCE_ILLUMINANT1;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Byte> SENSOR_REFERENCE_ILLUMINANT2;
@@ -13878,6 +13879,8 @@
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
field public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> SCALER_CROP_REGION;
+ field public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_DYNAMIC_BLACK_LEVEL;
+ field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_DYNAMIC_WHITE_LEVEL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_EXPOSURE_TIME;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_FRAME_DURATION;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> SENSOR_GREEN_SPLIT;
@@ -36774,6 +36777,7 @@
method public void stopNestedScroll();
method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
method public void unscheduleDrawable(android.graphics.drawable.Drawable);
+ method public final void updateDragShadow(android.view.View.DragShadowBuilder);
method protected boolean verifyDrawable(android.graphics.drawable.Drawable);
method public boolean willNotCacheDrawing();
method public boolean willNotDraw();
diff --git a/api/system-current.txt b/api/system-current.txt
index 5b976a9..621a282 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -13814,6 +13814,7 @@
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_INFO_TIMESTAMP_SOURCE;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_INFO_WHITE_LEVEL;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_MAX_ANALOG_SENSITIVITY;
+ field public static final android.hardware.camera2.CameraCharacteristics.Key<android.graphics.Rect[]> SENSOR_OPTICAL_BLACK_REGIONS;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_ORIENTATION;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_REFERENCE_ILLUMINANT1;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Byte> SENSOR_REFERENCE_ILLUMINANT2;
@@ -14226,6 +14227,8 @@
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
field public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> SCALER_CROP_REGION;
+ field public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_DYNAMIC_BLACK_LEVEL;
+ field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_DYNAMIC_WHITE_LEVEL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_EXPOSURE_TIME;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_FRAME_DURATION;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> SENSOR_GREEN_SPLIT;
@@ -39097,6 +39100,7 @@
method public void stopNestedScroll();
method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
method public void unscheduleDrawable(android.graphics.drawable.Drawable);
+ method public final void updateDragShadow(android.view.View.DragShadowBuilder);
method protected boolean verifyDrawable(android.graphics.drawable.Drawable);
method public boolean willNotCacheDrawing();
method public boolean willNotDraw();
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 0b7b6fc..303078b 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -751,7 +751,8 @@
int taskId = data.readInt();
int createMode = data.readInt();
boolean toTop = data.readInt() != 0;
- moveTaskToDockedStack(taskId, createMode, toTop);
+ boolean animate = data.readInt() != 0;
+ moveTaskToDockedStack(taskId, createMode, toTop, animate);
reply.writeNoException();
return true;
}
@@ -3577,7 +3578,7 @@
reply.recycle();
}
@Override
- public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop)
+ public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate)
throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -3586,6 +3587,7 @@
data.writeInt(taskId);
data.writeInt(createMode);
data.writeInt(toTop ? 1 : 0);
+ data.writeInt(animate ? 1 : 0);
mRemote.transact(MOVE_TASK_TO_DOCKED_STACK_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index db4f5c1..6594990 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -142,7 +142,7 @@
public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException;
public void moveTaskBackwards(int task) throws RemoteException;
public void moveTaskToStack(int taskId, int stackId, boolean toTop) throws RemoteException;
- public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop)
+ public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate)
throws RemoteException;
public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) throws RemoteException;
public void resizeStack(int stackId, Rect bounds, boolean allowResizeInDockedMode) throws RemoteException;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 848b33f..7fdb972 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3282,12 +3282,17 @@
tombstone ? getActionTombstoneLayoutResource()
: getActionLayoutResource());
final Icon ai = action.getIcon();
- button.setTextViewCompoundDrawablesRelative(R.id.action0, ai, null, null, null);
button.setTextViewText(R.id.action0, processLegacyText(action.title));
if (!tombstone) {
button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
}
button.setContentDescription(R.id.action0, action.title);
+ if (action.mRemoteInputs != null) {
+ button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
+ }
+ if (mN.color != COLOR_DEFAULT) {
+ button.setTextColor(R.id.action0, mN.color);
+ }
processLegacyAction(action, button);
return button;
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 68f660f..1c65c94 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2864,24 +2864,6 @@
}
/**
- * Returns the device owner user id. Note this method will still return the device owner user id
- * even if it's running on a different user. If there is no device owner this method return
- * {@link UserHandle.USER_NULL}.
- *
- * @hide
- */
- public int getDeviceOwnerUserId() {
- if (mService != null) {
- try {
- return mService.getDeviceOwnerUserId();
- } catch (RemoteException re) {
- Log.w(TAG, "Failed to get device owner user id");
- }
- }
- return UserHandle.USER_NULL;
- }
-
- /**
* @hide
* @deprecated Do not use
* @removed
@@ -4576,4 +4558,37 @@
return false;
}
}
+
+ /**
+ * @hide
+ * Return if this user is a managed profile of another user. An admin can become the profile
+ * owner of a managed profile with {@link #ACTION_PROVISION_MANAGED_PROFILE} and of a managed
+ * user with {@link #ACTION_PROVISION_MANAGED_USER}.
+ * @param admin Which profile owner this request is associated with.
+ * @return if this user is a managed profile of another user.
+ */
+ public boolean isManagedProfile(@NonNull ComponentName admin) {
+ try {
+ return mService.isManagedProfile(admin);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed talking with device policy service", re);
+ return false;
+ }
+ }
+
+ /**
+ * @hide
+ * Return if this user is a system-only user. An admin can manage a device from a system only
+ * user by calling {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE}.
+ * @param admin Which device owner this request is associated with.
+ * @return if this user is a system-only user.
+ */
+ public boolean isSystemOnlyUser(@NonNull ComponentName admin) {
+ try {
+ return mService.isSystemOnlyUser(admin);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed talking with device policy service", re);
+ return false;
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index ba34c3d..fc7c2b3 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -116,7 +116,6 @@
boolean setDeviceOwner(in ComponentName who, String ownerName, int userId);
ComponentName getDeviceOwner();
String getDeviceOwnerName();
- int getDeviceOwnerUserId();
void clearDeviceOwner(String packageName);
boolean setProfileOwner(in ComponentName who, String ownerName, int userHandle);
@@ -234,4 +233,6 @@
boolean isProvisioningAllowed(String action);
void setKeepUninstalledPackages(in ComponentName admin,in List<String> packageList);
List<String> getKeepUninstalledPackages(in ComponentName admin);
+ boolean isManagedProfile(in ComponentName admin);
+ boolean isSystemOnlyUser(in ComponentName admin);
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a2ef078..6fc998f 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2178,11 +2178,18 @@
* (8-14 bits is expected), or by the point where the sensor response
* becomes too non-linear to be useful. The default value for this is
* maximum representable value for a 16-bit raw sample (2^16 - 1).</p>
+ * <p>The white level values of captured images may vary for different
+ * capture settings (e.g., {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}). This key
+ * represents a coarse approximation for such case. It is recommended
+ * to use {@link CaptureResult#SENSOR_DYNAMIC_WHITE_LEVEL android.sensor.dynamicWhiteLevel} for captures when supported
+ * by the camera device, which provides more accurate white level values.</p>
* <p><b>Range of valid values:</b><br>
* > 255 (8-bit output)</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
*
* @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN
+ * @see CaptureResult#SENSOR_DYNAMIC_WHITE_LEVEL
+ * @see CaptureRequest#SENSOR_SENSITIVITY
*/
@PublicKey
public static final Key<Integer> SENSOR_INFO_WHITE_LEVEL =
@@ -2520,12 +2527,24 @@
* layout key (see {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT android.sensor.info.colorFilterArrangement}), i.e. the
* nth value given corresponds to the black level offset for the nth
* color channel listed in the CFA.</p>
+ * <p>The black level values of captured images may vary for different
+ * capture settings (e.g., {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}). This key
+ * represents a coarse approximation for such case. It is recommended to
+ * use {@link CaptureResult#SENSOR_DYNAMIC_BLACK_LEVEL android.sensor.dynamicBlackLevel} or use pixels from
+ * {@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions} directly for captures when
+ * supported by the camera device, which provides more accurate black
+ * level values. For raw capture in particular, it is recommended to use
+ * pixels from {@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions} to calculate black
+ * level values for each frame.</p>
* <p><b>Range of valid values:</b><br>
* >= 0 for each.</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
*
+ * @see CaptureResult#SENSOR_DYNAMIC_BLACK_LEVEL
* @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
* @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL
+ * @see CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS
+ * @see CaptureRequest#SENSOR_SENSITIVITY
*/
@PublicKey
public static final Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_BLACK_LEVEL_PATTERN =
@@ -2580,6 +2599,32 @@
new Key<int[]>("android.sensor.availableTestPatternModes", int[].class);
/**
+ * <p>List of disjoint rectangles indicating the sensor
+ * optically shielded black pixel regions.</p>
+ * <p>In most camera sensors, the active array is surrounded by some
+ * optically shielded pixel areas. By blocking light, these pixels
+ * provides a reliable black reference for black level compensation
+ * in active array region.</p>
+ * <p>This key provides a list of disjoint rectangles specifying the
+ * regions of optically shielded (with metal shield) black pixel
+ * regions if the camera device is capable of reading out these black
+ * pixels in the output raw images. In comparison to the fixed black
+ * level values reported by {@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern}, this key
+ * may provide a more accurate way for the application to calculate
+ * black level of each captured raw images.</p>
+ * <p>When this key is reported, the {@link CaptureResult#SENSOR_DYNAMIC_BLACK_LEVEL android.sensor.dynamicBlackLevel} and
+ * {@link CaptureResult#SENSOR_DYNAMIC_WHITE_LEVEL android.sensor.dynamicWhiteLevel} will also be reported.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN
+ * @see CaptureResult#SENSOR_DYNAMIC_BLACK_LEVEL
+ * @see CaptureResult#SENSOR_DYNAMIC_WHITE_LEVEL
+ */
+ @PublicKey
+ public static final Key<android.graphics.Rect[]> SENSOR_OPTICAL_BLACK_REGIONS =
+ new Key<android.graphics.Rect[]>("android.sensor.opticalBlackRegions", android.graphics.Rect[].class);
+
+ /**
* <p>List of lens shading modes for {@link CaptureRequest#SHADING_MODE android.shading.mode} that are supported by this camera device.</p>
* <p>This list contains lens shading modes that can be set for the camera device.
* Camera devices that support the MANUAL_POST_PROCESSING capability will always
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index b3acf2b..5f27bca 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -3292,6 +3292,69 @@
new Key<Long>("android.sensor.rollingShutterSkew", long.class);
/**
+ * <p>A per-frame dynamic black level offset for each of the color filter
+ * arrangement (CFA) mosaic channels.</p>
+ * <p>Camera sensor black levels may vary dramatically for different
+ * capture settings (e.g. {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}). The fixed black
+ * level reported by {@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern} may be too
+ * inaccurate to represent the actual value on a per-frame basis. The
+ * camera device internal pipeline relies on reliable black level values
+ * to process the raw images appropriately. To get the best image
+ * quality, the camera device may choose to estimate the per frame black
+ * level values either based on optically shielded black regions
+ * ({@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions}) or its internal model.</p>
+ * <p>This key reports the camera device estimated per-frame zero light
+ * value for each of the CFA mosaic channels in the camera sensor. The
+ * {@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern} may only represent a coarse
+ * approximation of the actual black level values. This value is the
+ * black level used in camera device internal image processing pipeline
+ * and generally more accurate than the fixed black level values.
+ * However, since they are estimated values by the camera device, they
+ * may not be as accurate as the black level values calculated from the
+ * optical black pixels reported by {@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions}.</p>
+ * <p>The values are given in the same order as channels listed for the CFA
+ * layout key (see {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT android.sensor.info.colorFilterArrangement}), i.e. the
+ * nth value given corresponds to the black level offset for the nth
+ * color channel listed in the CFA.</p>
+ * <p>This key will be available if {@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions} is
+ * available or the camera device advertises this key via
+ * {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureRequestKeys }.</p>
+ * <p><b>Range of valid values:</b><br>
+ * >= 0 for each.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN
+ * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
+ * @see CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS
+ * @see CaptureRequest#SENSOR_SENSITIVITY
+ */
+ @PublicKey
+ public static final Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_DYNAMIC_BLACK_LEVEL =
+ new Key<android.hardware.camera2.params.BlackLevelPattern>("android.sensor.dynamicBlackLevel", android.hardware.camera2.params.BlackLevelPattern.class);
+
+ /**
+ * <p>Maximum raw value output by sensor for this frame.</p>
+ * <p>Since the android.sensor.blackLevel may change for different
+ * capture settings (e.g., {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}), the white
+ * level will change accordingly. This key is similar to
+ * {@link CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL android.sensor.info.whiteLevel}, but specifies the camera device
+ * estimated white level for each frame.</p>
+ * <p>This key will be available if {@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions} is
+ * available or the camera device advertises this key via
+ * {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureRequestKeys }.</p>
+ * <p><b>Range of valid values:</b><br>
+ * >= 0</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL
+ * @see CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS
+ * @see CaptureRequest#SENSOR_SENSITIVITY
+ */
+ @PublicKey
+ public static final Key<Integer> SENSOR_DYNAMIC_WHITE_LEVEL =
+ new Key<Integer>("android.sensor.dynamicWhiteLevel", int.class);
+
+ /**
* <p>Quality of lens shading correction applied
* to the image data.</p>
* <p>When set to OFF mode, no lens shading correction will be applied by the
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index b5bbbbb..c71d6cc 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -56,7 +56,6 @@
Bundle getUserRestrictions(int userHandle);
boolean hasUserRestriction(in String restrictionKey, int userHandle);
void setUserRestriction(String key, boolean value, int userId);
- void setSystemControlledUserRestriction(String key, boolean value, int userId);
void setApplicationRestrictions(in String packageName, in Bundle restrictions,
int userHandle);
Bundle getApplicationRestrictions(in String packageName);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7b5f5ab..64a046e 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -289,8 +289,12 @@
/**
* Create a screenshot of the applications currently displayed.
+ *
+ * @param frameScale the scale to apply to the frame, only used when width = -1 and
+ * height = -1
*/
- Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight);
+ Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight,
+ float frameScale);
/**
* Called by the status bar to notify Views of changes to System UI visiblity.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 66381f9..ab1943c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -807,6 +807,12 @@
private static boolean sAlwaysRemeasureExactly = false;
/**
+ * Relax constraints around whether setLayoutParams() must be called after
+ * modifying the layout params.
+ */
+ private static boolean sLayoutParamsAlwaysChanged = false;
+
+ /**
* This view does not want keystrokes. Use with TAKES_FOCUS_MASK when
* calling setFlags.
*/
@@ -3975,6 +3981,11 @@
// modes, so we always need to run an additional EXACTLY pass.
sAlwaysRemeasureExactly = targetSdkVersion <= M;
+ // Prior to N, layout params could change without requiring a
+ // subsequent call to setLayoutParams() and they would usually
+ // work. Partial layout breaks this assumption.
+ sLayoutParamsAlwaysChanged = targetSdkVersion <= M;
+
sCompatibilityDone = true;
}
}
@@ -16904,6 +16915,9 @@
* @return true if this view's LayoutParams changed since last layout.
*/
public final boolean didLayoutParamsChange() {
+ if (sLayoutParamsAlwaysChanged) {
+ return true;
+ }
return (mPrivateFlags3 & PFLAG3_LAYOUT_PARAMS_CHANGED) == PFLAG3_LAYOUT_PARAMS_CHANGED;
}
@@ -19978,19 +19992,22 @@
Log.d(VIEW_LOG_TAG, "drag shadow: width=" + shadowSize.x + " height=" + shadowSize.y
+ " shadowX=" + shadowTouchPoint.x + " shadowY=" + shadowTouchPoint.y);
}
- Surface surface = new Surface();
+ if (mAttachInfo.mDragSurface != null) {
+ mAttachInfo.mDragSurface.release();
+ }
+ mAttachInfo.mDragSurface = new Surface();
try {
mAttachInfo.mDragToken = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
- flags, shadowSize.x, shadowSize.y, surface);
+ flags, shadowSize.x, shadowSize.y, mAttachInfo.mDragSurface);
if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token="
- + mAttachInfo.mDragToken + " surface=" + surface);
+ + mAttachInfo.mDragToken + " surface=" + mAttachInfo.mDragSurface);
if (mAttachInfo.mDragToken != null) {
- Canvas canvas = surface.lockCanvas(null);
+ Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null);
try {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
shadowBuilder.onDrawShadow(canvas);
} finally {
- surface.unlockCanvasAndPost(canvas);
+ mAttachInfo.mDragSurface.unlockCanvasAndPost(canvas);
}
final ViewRootImpl root = getViewRootImpl();
@@ -20005,14 +20022,11 @@
shadowSize.x, shadowSize.y,
shadowTouchPoint.x, shadowTouchPoint.y, data);
if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
-
- // Off and running! Release our local surface instance; the drag
- // shadow surface is now managed by the system process.
- surface.release();
}
} catch (Exception e) {
Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
- surface.destroy();
+ mAttachInfo.mDragSurface.destroy();
+ mAttachInfo.mDragSurface = null;
}
return okay;
@@ -20051,6 +20065,27 @@
}
}
+ public final void updateDragShadow(DragShadowBuilder shadowBuilder) {
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "updateDragShadow");
+ }
+ if (mAttachInfo.mDragToken != null) {
+ try {
+ Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null);
+ try {
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ shadowBuilder.onDrawShadow(canvas);
+ } finally {
+ mAttachInfo.mDragSurface.unlockCanvasAndPost(canvas);
+ }
+ } catch (Exception e) {
+ Log.e(VIEW_LOG_TAG, "Unable to update drag shadow", e);
+ }
+ } else {
+ Log.e(VIEW_LOG_TAG, "No active drag");
+ }
+ }
+
/**
* Starts a move from {startX, startY}, the amount of the movement will be the offset
* between {startX, startY} and the new cursor positon.
@@ -22342,6 +22377,11 @@
IBinder mDragToken;
/**
+ * The drag shadow surface for the current drag operation.
+ */
+ public Surface mDragSurface;
+
+ /**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2c0cd7a..b503e12 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5376,6 +5376,10 @@
if (what == DragEvent.ACTION_DRAG_ENDED) {
setLocalDragState(null);
mAttachInfo.mDragToken = null;
+ if (mAttachInfo.mDragSurface != null) {
+ mAttachInfo.mDragSurface.release();
+ mAttachInfo.mDragSurface = null;
+ }
}
}
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index ca1b211..ce1c108 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -21,6 +21,7 @@
import android.app.ActivityThread;
import android.app.Application;
import android.app.PendingIntent;
+import android.app.RemoteInput;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.content.ContextWrapper;
@@ -153,6 +154,13 @@
};
/**
+ * @hide
+ */
+ public void setRemoteInputs(int viewId, RemoteInput[] remoteInputs) {
+ mActions.add(new SetRemoteInputsAction(viewId, remoteInputs));
+ }
+
+ /**
* Handle with care!
*/
static class MutablePair<F, S> {
@@ -1699,6 +1707,43 @@
}
/**
+ * Helper action to add a view tag with RemoteInputs.
+ */
+ private class SetRemoteInputsAction extends Action {
+
+ public SetRemoteInputsAction(int viewId, RemoteInput[] remoteInputs) {
+ this.viewId = viewId;
+ this.remoteInputs = remoteInputs;
+ }
+
+ public SetRemoteInputsAction(Parcel parcel) {
+ viewId = parcel.readInt();
+ remoteInputs = parcel.readParcelableArray(RemoteInput.class.getClassLoader());
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(TAG);
+ dest.writeInt(viewId);
+ dest.writeParcelableArray(remoteInputs, flags);
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+ final TextView target = (TextView) root.findViewById(viewId);
+ if (target == null) return;
+
+ target.setTagInternal(R.id.remote_input_tag, remoteInputs);
+ }
+
+ public String getActionName() {
+ return "SetRemoteInputsAction";
+ }
+
+ final Parcelable[] remoteInputs;
+ public final static int TAG = 18;
+ }
+
+ /**
* Simple class used to keep track of memory usage in a RemoteViews.
*
*/
@@ -1894,6 +1939,9 @@
case TextViewDrawableColorFilterAction.TAG:
mActions.add(new TextViewDrawableColorFilterAction(parcel));
break;
+ case SetRemoteInputsAction.TAG:
+ mActions.add(new SetRemoteInputsAction(parcel));
+ break;
default:
throw new ActionException("Tag " + tag + " not found");
}
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index a24fb40..1d6e52c 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -880,8 +880,12 @@
public boolean onTouch(View view, MotionEvent motionEvent) {
final int actionMasked = motionEvent.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
- mInitialTouchTarget = findNearestChild((ViewGroup) view,
- (int) motionEvent.getX(), (int) motionEvent.getY());
+ if (view instanceof ViewGroup) {
+ mInitialTouchTarget = findNearestChild((ViewGroup) view,
+ (int) motionEvent.getX(), (int) motionEvent.getY());
+ } else {
+ mInitialTouchTarget = null;
+ }
}
final View child = mInitialTouchTarget;
diff --git a/core/res/res/layout/notification_material_action.xml b/core/res/res/layout/notification_material_action.xml
index da8b2e7..f4bc918 100644
--- a/core/res/res/layout/notification_material_action.xml
+++ b/core/res/res/layout/notification_material_action.xml
@@ -18,15 +18,11 @@
<Button xmlns:android="http://schemas.android.com/apk/res/android"
style="@android:style/Widget.Material.Light.Button.Borderless.Small"
android:id="@+id/action0"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="48dp"
- android:layout_weight="1"
- android:layout_margin="0dp"
- android:gravity="start|center_vertical"
- android:drawablePadding="8dp"
- android:paddingStart="8dp"
+ android:layout_gravity="center"
+ android:layout_marginStart="8dp"
android:textColor="@color/secondary_text_material_light"
- android:textSize="13sp"
android:singleLine="true"
android:ellipsize="end"
android:background="@drawable/notification_material_action_background"
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index 2a36949..edaf020 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -14,14 +14,20 @@
limitations under the License.
-->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/actions"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:visibility="gone"
- android:layout_marginBottom="8dp"
- >
- <!-- actions will be added here -->
-</LinearLayout>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/actions_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:id="@+id/actions"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ android:paddingEnd="8dp"
+ android:orientation="horizontal"
+ android:visibility="gone"
+ android:background="#ffeeeeee"
+ >
+ <!-- actions will be added here -->
+ </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/values-sw720dp/config.xml b/core/res/res/values-sw720dp/config.xml
index 9792835..1f5791a 100644
--- a/core/res/res/values-sw720dp/config.xml
+++ b/core/res/res/values-sw720dp/config.xml
@@ -19,4 +19,7 @@
used for picking activities to handle an intent. -->
<integer name="config_maxResolverActivityColumns">4</integer>
+ <!-- Enable cascading submenus. -->
+ <bool name="config_enableCascadingSubmenus">true</bool>
+
</resources>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 9d5e5ac..c03fbeb 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -122,4 +122,6 @@
<!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_CONTEXT_CLICK}. -->
<item type="id" name="accessibilityActionContextClick" />
+
+ <item type="id" name="remote_input_tag" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1964bec..1dd502f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3623,6 +3623,8 @@
<string name="user_switched">Current user <xliff:g id="name" example="Bob">%1$s</xliff:g>.</string>
<!-- Message shown when switching to a user [CHAR LIMIT=none] -->
<string name="user_switching_message">Switching to <xliff:g id="name" example="Bob">%1$s</xliff:g>\u2026</string>
+ <!-- Message when logging out a user on a split user system -->
+ <string name="user_logging_out_message">Logging out <xliff:g id="name" example="Bob">%1$s</xliff:g>\u2026</string>
<!-- Default name of the owner user [CHAR LIMIT=20] -->
<string name="owner_name" msgid="3879126011135546571">Owner</string>
<!-- Error message title [CHAR LIMIT=35] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 49243e1..c66dd18 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -933,6 +933,7 @@
<java-symbol type="string" name="upload_file" />
<java-symbol type="string" name="user_switched" />
<java-symbol type="string" name="user_switching_message" />
+ <java-symbol type="string" name="user_logging_out_message" />
<java-symbol type="string" name="volume_alarm" />
<java-symbol type="string" name="volume_icon_description_bluetooth" />
<java-symbol type="string" name="volume_icon_description_incall" />
@@ -2304,6 +2305,9 @@
<java-symbol type="string" name="notification_inbox_ellipsis" />
<java-symbol type="bool" name="config_mainBuiltInDisplayIsRound" />
+ <java-symbol type="id" name="actions_container" />
+ <java-symbol type="id" name="remote_input_tag" />
+
<java-symbol type="attr" name="seekBarDialogPreferenceStyle" />
<java-symbol type="string" name="ext_media_status_removed" />
<java-symbol type="string" name="ext_media_status_unmounted" />
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 27c6620..ab37519 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -143,7 +143,9 @@
access while in power save mode, even if they aren't in the foreground. -->
<allow-in-power-save-except-idle package="com.android.providers.downloads" />
+ <!-- These are the packages that are white-listed to be able to run as system user -->
+ <system-user-whitelisted-app package="com.android.settings" />
+
<!-- These are the packages that shouldn't run as system user -->
<system-user-blacklisted-app package="com.android.wallpaper.livepicker" />
- <system-user-blacklisted-app package="com.android.settings" />
</permissions>
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 5ad1bce..f8c6f3f 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -87,7 +87,7 @@
return;
}
- long startTime = System.currentTimeMillis();
+ long startTime = System.nanoTime();
switch (msg.what) {
case DO_RELEASE: {
mTvInputSessionImpl.release();
@@ -185,18 +185,18 @@
break;
}
}
- long duration = System.currentTimeMillis() - startTime;
- if (duration > EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS) {
+ long durationMs = (System.nanoTime() - startTime) / (1000 * 1000);
+ if (durationMs > EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS) {
Log.w(TAG, "Handling message (" + msg.what + ") took too long time (duration="
- + duration + "ms)");
- if (msg.what == DO_TUNE && duration > EXECUTE_MESSAGE_TUNE_TIMEOUT_MILLIS) {
- throw new RuntimeException("Too much time to handle tune request. (" + duration
+ + durationMs + "ms)");
+ if (msg.what == DO_TUNE && durationMs > EXECUTE_MESSAGE_TUNE_TIMEOUT_MILLIS) {
+ throw new RuntimeException("Too much time to handle tune request. (" + durationMs
+ "ms > " + EXECUTE_MESSAGE_TUNE_TIMEOUT_MILLIS + "ms) "
+ "Consider handling the tune request in a separate thread.");
}
- if (duration > EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS) {
+ if (durationMs > EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS) {
throw new RuntimeException("Too much time to handle a request. (type=" + msg.what +
- ", " + duration + "ms > " + EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS + "ms).");
+ ", " + durationMs + "ms > " + EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS + "ms).");
}
}
}
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 368f9f7..f2920e5 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -12,12 +12,23 @@
-keep class com.android.systemui.statusbar.phone.PhoneStatusBar
-keep class com.android.systemui.statusbar.tv.TvStatusBar
--keep class com.android.systemui.recents.*
-keepclassmembers class ** {
public void onBusEvent(**);
public void onInterprocessBusEvent(**);
}
-keepclassmembers class ** extends **.EventBus$InterprocessEvent {
- public <init>(android.os.Bundle);
-}
\ No newline at end of file
+ public <init>(android.os.Bundle);
+}
+
+-keep class com.android.systemui.recents.views.TaskStackLayoutAlgorithm {
+ public float getFocusState();
+ public void setFocusState(float);
+}
+
+-keep class com.android.systemui.recents.views.TaskView {
+ public int getDim();
+ public void setDim(int);
+ public float getTaskProgress();
+ public void setTaskProgress(float);
+}
diff --git a/packages/SystemUI/res/drawable/ic_send.xml b/packages/SystemUI/res/drawable/ic_send.xml
new file mode 100644
index 0000000..b1c7914
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_send.xml
@@ -0,0 +1,27 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4.02,42.0L46.0,24.0 4.02,6.0 4.0,20.0l30.0,4.0 -30.0,4.0z"/>
+ <path
+ android:pathData="M0 0h48v48H0z"
+ android:fillColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/recents_dismiss_button.xml b/packages/SystemUI/res/layout/recents_dismiss_button.xml
deleted file mode 100644
index 6a2f782..0000000
--- a/packages/SystemUI/res/layout/recents_dismiss_button.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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.
--->
-<!-- Extends Framelayout -->
-<com.android.systemui.statusbar.DismissView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/recents_dismiss_all_button_size"
- android:visibility="gone"
- android:clipChildren="false"
- android:clipToPadding="false">
- <com.android.systemui.statusbar.DismissViewButton
- android:id="@+id/dismiss_text"
- android:layout_width="@dimen/recents_dismiss_all_button_size"
- android:layout_height="@dimen/recents_dismiss_all_button_size"
- android:layout_gravity="end"
- android:alpha="1.0"
- android:background="@drawable/recents_button_bg"
- android:contentDescription="@string/recents_dismiss_all_message"/>
-</com.android.systemui.statusbar.DismissView>
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
index 8ca5634..74092c1 100644
--- a/packages/SystemUI/res/layout/remote_input.xml
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -16,31 +16,61 @@
~ limitations under the License
-->
-<!-- FrameLayout -->
+<!-- LinearLayout -->
<com.android.systemui.statusbar.policy.RemoteInputView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:theme="@style/systemui_theme_light"
+ android:theme="@style/systemui_theme_remote_input"
+ android:id="@+id/remote_input"
android:layout_height="match_parent"
android:layout_width="match_parent"
- android:paddingStart="4dp"
- android:paddingEnd="2dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="12dp"
android:paddingBottom="4dp"
android:paddingTop="2dp">
<view class="com.android.systemui.statusbar.policy.RemoteInputView$RemoteEditText"
android:id="@+id/remote_input_text"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:paddingEnd="12dp"
+ android:gravity="start|center_vertical"
+ android:textAppearance="?android:attr/textAppearance"
+ android:textColor="#deffffff"
+ android:textSize="16sp"
+ android:background="@null"
android:singleLine="true"
+ android:ellipsize="start"
android:imeOptions="actionSend" />
- <ProgressBar
- android:id="@+id/remote_input_progress"
- android:layout_width="match_parent"
+ <FrameLayout
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:visibility="invisible"
- android:indeterminate="true"
- style="?android:attr/progressBarStyleHorizontal" />
+ android:layout_gravity="center_vertical">
+
+ <ImageButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:id="@+id/remote_input_send"
+ android:src="@drawable/ic_send"
+ android:tint="@android:color/white"
+ android:tintMode="src_atop"
+ android:background="@drawable/ripple_drawable" />
+
+ <ProgressBar
+ android:id="@+id/remote_input_progress"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center"
+ android:visibility="invisible"
+ android:indeterminate="true"
+ style="?android:attr/progressBarStyleSmall" />
+
+ </FrameLayout>
</com.android.systemui.statusbar.policy.RemoteInputView>
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index f7e2344..43e7bac 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -28,4 +28,14 @@
<!-- We have only space for one notification on phone landscape layouts. -->
<integer name="keyguard_max_notification_count">1</integer>
+
+ <!-- Recents: The relative range of visible tasks from the current scroll position
+ while the stack is focused. -->
+ <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
+ <item name="recents_layout_focused_range_max" format="float" type="integer">2</item>
+
+ <!-- Recents: The relative range of visible tasks from the current scroll position
+ while the stack is not focused. -->
+ <item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
+ <item name="recents_layout_unfocused_range_max" format="float" type="integer">1.5</item>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 4f6d209..6795da4 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -32,4 +32,14 @@
<!-- Set to true to enable the user switcher on the keyguard. -->
<bool name="config_keyguardUserSwitcher">true</bool>
+
+ <!-- Recents: The relative range of visible tasks from the current scroll position
+ while the stack is focused. -->
+ <item name="recents_layout_focused_range_min" format="float" type="integer">-4</item>
+ <item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
+
+ <!-- Recents: The relative range of visible tasks from the current scroll position
+ while the stack is not focused. -->
+ <item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
+ <item name="recents_layout_unfocused_range_max" format="float" type="integer">2.5</item>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 0c638a2..d8193ab 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -138,12 +138,6 @@
<!-- The duration in seconds to wait before the dismiss buttons are shown. -->
<integer name="recents_task_bar_dismiss_delay_seconds">1000</integer>
- <!-- The min animation duration for animating views that are currently visible. -->
- <integer name="recents_filter_animate_current_views_duration">250</integer>
-
- <!-- The min animation duration for animating views that are newly visible. -->
- <integer name="recents_filter_animate_new_views_duration">250</integer>
-
<!-- The duration of the window transition when coming to Recents from an app.
In order to defer the in-app animations until after the transition is complete,
we also need to use this value as the starting delay when animating the first
@@ -192,6 +186,16 @@
<!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
<integer name="recents_svelte_level">0</integer>
+ <!-- Recents: The relative range of visible tasks from the current scroll position
+ while the stack is focused. -->
+ <item name="recents_layout_focused_range_min" format="float" type="integer">-4</item>
+ <item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
+
+ <!-- Recents: The relative range of visible tasks from the current scroll position
+ while the stack is not focused. -->
+ <item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
+ <item name="recents_layout_unfocused_range_max" format="float" type="integer">2.5</item>
+
<!-- Whether to enable KeyguardService or not -->
<bool name="config_enableKeyguardService">true</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 73f63a9..c85ada8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -247,6 +247,12 @@
<!-- The amount to allow the stack to overscroll. -->
<dimen name="recents_stack_overscroll">24dp</dimen>
+ <!-- The size of the peek area at the top of the stack. -->
+ <dimen name="recents_layout_focused_peek_size">@dimen/recents_history_button_height</dimen>
+
+ <!-- The height of the history button. -->
+ <dimen name="recents_history_button_height">48dp</dimen>
+
<!-- Space reserved for the cards behind the top card in the top stack -->
<dimen name="top_stack_peek_amount">12dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 00b10a1..e0c0f6a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -698,8 +698,6 @@
<string name="recents_search_bar_label">search</string>
<!-- Recents: Launch error string. [CHAR LIMIT=NONE] -->
<string name="recents_launch_error_message">Could not start <xliff:g id="app" example="Calendar">%s</xliff:g>.</string>
- <!-- Recents: Dismiss all button. [CHAR LIMIT=NONE] -->
- <string name="recents_dismiss_all_message">Dismiss all applications</string>
<!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] -->
<string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string>
@@ -1159,10 +1157,20 @@
<!-- Option to use new paging layout in quick settings [CHAR LIMIT=60] -->
<string name="qs_paging" translatable="false">Use the new Quick Settings</string>
+ <!-- Toggles paging recents via the recents button -->
+ <string name="overview_page_on_toggle">Enable paging</string>
+ <!-- Description for the toggle for fast-toggling recents via the recents button -->
+ <string name="overview_page_on_toggle_desc">Enable paging via the Overview button</string>
+
<!-- Toggles fast-toggling recents via the recents button -->
<string name="overview_fast_toggle_via_button">Enable fast toggle</string>
<!-- Description for the toggle for fast-toggling recents via the recents button -->
- <string name="overview_fast_toggle_via_button_desc">Enable paging via the Overview button</string>
+ <string name="overview_fast_toggle_via_button_desc">Enable launch timeout while paging</string>
+
+ <!-- Toggles fullscreen screenshots -->
+ <string name="overview_fullscreen_thumbnails">Enable fullscreen screenshots</string>
+ <!-- Description for the toggle for fullscreen screenshots -->
+ <string name="overview_fullscreen_thumbnails_desc">Enable fullscreen screenshots in Overview</string>
<!-- Category in the System UI Tuner settings, where new/experimental
settings are -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4462a03..2fd0fe5 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -220,9 +220,8 @@
<item name="android:colorControlActivated">@color/system_accent_color</item>
</style>
- <style name="systemui_theme_light" parent="@android:style/Theme.DeviceDefault.Light">
- <item name="android:colorPrimary">@color/system_primary_color</item>
- <item name="android:colorControlActivated">@color/system_accent_color</item>
+ <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:colorControlActivated">@android:color/white</item>
</style>
<style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog">
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 585f3c7..4d07d5f 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -88,10 +88,20 @@
android:title="@string/overview" >
<com.android.systemui.tuner.TunerSwitch
+ android:key="overview_page_on_toggle"
+ android:title="@string/overview_page_on_toggle"
+ android:summary="@string/overview_page_on_toggle_desc" />
+
+ <com.android.systemui.tuner.TunerSwitch
android:key="overview_fast_toggle"
android:title="@string/overview_fast_toggle_via_button"
android:summary="@string/overview_fast_toggle_via_button_desc" />
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="overview_fullscreen_thumbnails"
+ android:title="@string/overview_fullscreen_thumbnails"
+ android:summary="@string/overview_fullscreen_thumbnails_desc" />
+
</PreferenceScreen>
<SwitchPreference
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
index 7cfe38e..69e9359 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
@@ -24,7 +24,7 @@
oneway interface IRecentsNonSystemUserCallbacks {
void preloadRecents();
void cancelPreloadingRecents();
- void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents);
+ void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, boolean animate);
void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
void toggleRecents();
void onConfigurationChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 348bd87..186cace 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -214,14 +214,15 @@
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
- mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */);
+ mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */, true /* animate */);
} else {
if (mSystemUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
- callbacks.showRecents(triggeredFromAltTab, false /* draggingInRecents */);
+ callbacks.showRecents(triggeredFromAltTab, false /* draggingInRecents */,
+ true /* animate */);
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 04def86..92978f2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -43,9 +43,9 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
+import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
@@ -53,9 +53,12 @@
import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.ResizeTaskEvent;
import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
+import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
+import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
@@ -79,40 +82,40 @@
/**
* The main Recents activity that is started from AlternateRecentsComponent.
*/
-public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
- ViewTreeObserver.OnPreDrawListener {
+public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreDrawListener {
private final static String TAG = "RecentsActivity";
private final static boolean DEBUG = false;
public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
- RecentsPackageMonitor mPackageMonitor;
- long mLastTabKeyEventTime;
- boolean mFinishedOnStartup;
+ private RecentsPackageMonitor mPackageMonitor;
+ private long mLastTabKeyEventTime;
+ private boolean mFinishedOnStartup;
+ private boolean mIgnoreAltTabRelease;
// Top level views
- RecentsView mRecentsView;
- SystemBarScrimViews mScrimViews;
- ViewStub mEmptyViewStub;
- View mEmptyView;
+ private RecentsView mRecentsView;
+ private SystemBarScrimViews mScrimViews;
+ private ViewStub mEmptyViewStub;
+ private View mEmptyView;
// Resize task debug
- RecentsResizeTaskDialog mResizeTaskDebugDialog;
+ private RecentsResizeTaskDialog mResizeTaskDebugDialog;
// Search AppWidget
- AppWidgetProviderInfo mSearchWidgetInfo;
- RecentsAppWidgetHost mAppWidgetHost;
- RecentsAppWidgetHostView mSearchWidgetHostView;
+ private AppWidgetProviderInfo mSearchWidgetInfo;
+ private RecentsAppWidgetHost mAppWidgetHost;
+ private RecentsAppWidgetHostView mSearchWidgetHostView;
// Runnables to finish the Recents activity
- FinishRecentsRunnable mFinishLaunchHomeRunnable;
+ private FinishRecentsRunnable mFinishLaunchHomeRunnable;
// The trigger to automatically launch the current task
- DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() {
+ private DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() {
@Override
public void run() {
- dismissRecentsToFocusedTask(false);
+ dismissRecentsToFocusedTask();
}
});
@@ -259,12 +262,9 @@
/**
* Dismisses recents if we are already visible and the intent is to toggle the recents view.
*/
- boolean dismissRecentsToFocusedTask(boolean checkFilteredStackState) {
+ boolean dismissRecentsToFocusedTask() {
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
- // If we currently have filtered stacks, then unfilter those first
- if (checkFilteredStackState &&
- mRecentsView.unfilterFilteredStacks()) return true;
// If we have a focused Task, launch that Task now
if (mRecentsView.launchFocusedTask()) return true;
}
@@ -274,12 +274,9 @@
/**
* Dismisses recents if we are already visible and the intent is to toggle the recents view.
*/
- boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
+ boolean dismissRecentsToFocusedTaskOrHome() {
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
- // If we currently have filtered stacks, then unfilter those first
- if (checkFilteredStackState &&
- mRecentsView.unfilterFilteredStacks()) return true;
// If we have a focused Task, launch that Task now
if (mRecentsView.launchFocusedTask()) return true;
// If none of the other cases apply, then just go Home
@@ -348,7 +345,6 @@
// Set the Recents layout
setContentView(R.layout.recents);
mRecentsView = (RecentsView) findViewById(R.id.recents_view);
- mRecentsView.setCallbacks(this);
mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
@@ -406,13 +402,6 @@
mRecentsView.disableLayersForOneFrame();
}
- if (launchState.startHidden) {
- launchState.startHidden = false;
- mRecentsView.setStackViewVisibility(View.INVISIBLE);
- } else {
- mRecentsView.setStackViewVisibility(View.VISIBLE);
- }
-
// Notify that recents is now visible
SystemServicesProxy ssp = Recents.getSystemServices();
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, true));
@@ -441,6 +430,9 @@
protected void onStop() {
super.onStop();
+ // Reset some states
+ mIgnoreAltTabRelease = false;
+
// Notify that recents is now hidden
SystemServicesProxy ssp = Recents.getSystemServices();
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, false));
@@ -511,10 +503,6 @@
boolean hasRepKeyTimeElapsed = (SystemClock.elapsedRealtime() -
mLastTabKeyEventTime) > altTabKeyDelay;
if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
- // As we iterate to the next/previous task, cancel any current/lagging window
- // transition animations
- EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
-
// Focus the next task in the stack
final boolean backward = event.isShiftPressed();
if (backward) {
@@ -523,6 +511,11 @@
EventBus.getDefault().send(new FocusNextTaskViewEvent());
}
mLastTabKeyEventTime = SystemClock.elapsedRealtime();
+
+ // In the case of another ALT event, don't ignore the next release
+ if (event.isAltPressed()) {
+ mIgnoreAltTabRelease = false;
+ }
}
return true;
}
@@ -557,7 +550,7 @@
@Override
public void onBackPressed() {
// Dismiss Recents to the focused Task or Home
- dismissRecentsToFocusedTaskOrHome(true);
+ dismissRecentsToFocusedTaskOrHome();
}
/**** RecentsResizeTaskDialog ****/
@@ -569,23 +562,25 @@
return mResizeTaskDebugDialog;
}
- /**** RecentsView.RecentsViewCallbacks Implementation ****/
-
- @Override
- public void onAllTaskViewsDismissed() {
- mFinishLaunchHomeRunnable.run();
- }
-
/**** EventBus events ****/
public final void onBusEvent(ToggleRecentsEvent event) {
- dismissRecentsToFocusedTaskOrHome(true /* checkFilteredStackState */);
+ dismissRecentsToFocusedTaskOrHome();
}
public final void onBusEvent(IterateRecentsEvent event) {
// Focus the next task
EventBus.getDefault().send(new FocusNextTaskViewEvent());
- mIterateTrigger.poke();
+
+ // Start dozing after the recents button is clicked
+ RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+ if (debugFlags.isFastToggleRecentsEnabled()) {
+ if (!mIterateTrigger.isDozing()) {
+ mIterateTrigger.startDozing();
+ } else {
+ mIterateTrigger.poke();
+ }
+ }
}
public final void onBusEvent(UserInteractionEvent event) {
@@ -595,10 +590,12 @@
public final void onBusEvent(HideRecentsEvent event) {
if (event.triggeredFromAltTab) {
// If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
- dismissRecentsToFocusedTaskOrHome(false /* checkFilteredStackState */);
+ if (!mIgnoreAltTabRelease) {
+ dismissRecentsToFocusedTaskOrHome();
+ }
} else if (event.triggeredFromHomeKey) {
// Otherwise, dismiss Recents to Home
- dismissRecentsToHome(true /* checkFilteredStackState */);
+ dismissRecentsToHome(true /* animated */);
} else {
// Do nothing
}
@@ -622,31 +619,22 @@
});
}
}
- ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
- @Override
- public void run() {
- // If we are not launching with alt-tab and fast-toggle is enabled, then start
- // the dozer now
- RecentsConfiguration config = Recents.getConfiguration();
- RecentsActivityLaunchState launchState = config.getLaunchState();
- RecentsDebugFlags flags = Recents.getDebugFlags();
- if (flags.isFastToggleRecentsEnabled() && !launchState.launchedWithAltTab) {
- mIterateTrigger.startDozing();
- }
- }
- });
mRecentsView.startEnterRecentsAnimation(ctx);
ctx.postAnimationTrigger.decrement();
}
public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) {
- mRecentsView.setStackViewVisibility(View.VISIBLE);
+ EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(true));
mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
+ mRecentsView.invalidate();
}
public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) {
- mRecentsView.setStackViewVisibility(View.INVISIBLE);
+ if (mRecentsView.isLastTaskLaunchedFreeform()) {
+ EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(false));
+ }
mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
+ mRecentsView.invalidate();
}
public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) {
@@ -687,6 +675,14 @@
ssp.removeTask(event.task.key.id);
}
+ public final void onBusEvent(AllTaskViewsDismissedEvent event) {
+ // Just go straight home (no animation necessary because there are no more task views)
+ dismissRecentsToHome(false /* animated */);
+
+ // Keep track of all-deletions
+ MetricsLogger.count(this, "overview_task_all_dismissed", 1);
+ }
+
public final void onBusEvent(ResizeTaskEvent event) {
getResizeTaskDebugDialog().showResizeTaskDialog(event.task, mRecentsView);
}
@@ -723,6 +719,12 @@
finish();
}
+ public final void onBusEvent(StackViewScrolledEvent event) {
+ // Once the user has scrolled while holding alt-tab, then we should ignore the release of
+ // the key
+ mIgnoreAltTabRelease = true;
+ }
+
private void refreshSearchWidgetView() {
if (mSearchWidgetInfo != null) {
SystemServicesProxy ssp = Recents.getSystemServices();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 7f7dbce..a1e5118 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -34,7 +34,7 @@
public boolean launchedFromSearchHome;
public boolean launchedReuseTaskStackViews;
public boolean launchedHasConfigurationChanged;
- public boolean startHidden;
+ public boolean launchedViaDragGesture;
public int launchedToTaskId;
public int launchedNumVisibleTasks;
public int launchedNumVisibleThumbnails;
@@ -75,7 +75,7 @@
*/
public int getInitialFocusTaskIndex(int numTasks) {
RecentsDebugFlags flags = Recents.getDebugFlags();
- if (flags.isFastToggleRecentsEnabled() && !launchedWithAltTab) {
+ if (flags.isPageOnToggleEnabled() && !launchedWithAltTab) {
// If we are fast toggling, then focus the next task depending on when you are on home
// or coming in from another app
if (launchedFromHome) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 8f952be..440ed6b1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -180,7 +180,8 @@
* Constrain the width of the landscape stack to the smallest width of the device.
*/
private int getInsetToSmallestWidth(int availableWidth) {
- if (availableWidth > smallestWidth) {
+ RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+ if (!debugFlags.isFullscreenThumbnailsEnabled() && (availableWidth > smallestWidth)) {
return (availableWidth - smallestWidth) / 2;
}
return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index 6c74a4e..67d7115 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -27,6 +27,8 @@
public class RecentsDebugFlags implements TunerService.Tunable {
private static final String KEY_FAST_TOGGLE = "overview_fast_toggle";
+ private static final String KEY_PAGE_ON_TOGGLE = "overview_page_on_toggle";
+ private static final String KEY_FULLSCREEN_THUMBNAILS = "overview_fullscreen_thumbnails";
public static class Static {
// Enables debug drawing for the transition thumbnail
@@ -47,8 +49,9 @@
public static final int SystemServicesProxyMockTaskCount = 100;
}
- private boolean mForceEnableFreeformWorkspace;
- private boolean mEnableFastToggleRecents;
+ private boolean mFastToggleRecents;
+ private boolean mPageOnToggle;
+ private boolean mUseFullscreenThumbnails;
/**
* We read the prefs once when we start the activity, then update them as the tuner changes
@@ -57,21 +60,44 @@
public RecentsDebugFlags(Context context) {
// Register all our flags, this will also call onTuningChanged() for each key, which will
// initialize the current state of each flag
- TunerService.get(context).addTunable(this, KEY_FAST_TOGGLE);
+ TunerService.get(context).addTunable(this, KEY_FAST_TOGGLE, KEY_PAGE_ON_TOGGLE,
+ KEY_FULLSCREEN_THUMBNAILS);
}
/**
* @return whether we are enabling fast toggling.
*/
public boolean isFastToggleRecentsEnabled() {
- return mEnableFastToggleRecents;
+ return mPageOnToggle && mFastToggleRecents;
+ }
+
+ /**
+ * @return whether the recents button toggles pages.
+ */
+ public boolean isPageOnToggleEnabled() {
+ return mPageOnToggle;
+ }
+
+ /**
+ * @return whether we should show fullscreen thumbnails
+ */
+ public boolean isFullscreenThumbnailsEnabled() {
+ return mUseFullscreenThumbnails;
}
@Override
public void onTuningChanged(String key, String newValue) {
switch (key) {
case KEY_FAST_TOGGLE:
- mEnableFastToggleRecents = (newValue != null) &&
+ mFastToggleRecents = (newValue != null) &&
+ (Integer.parseInt(newValue) != 0);
+ break;
+ case KEY_PAGE_ON_TOGGLE:
+ mPageOnToggle = (newValue != null) &&
+ (Integer.parseInt(newValue) != 0);
+ break;
+ case KEY_FULLSCREEN_THUMBNAILS:
+ mUseFullscreenThumbnails = (newValue != null) &&
(Integer.parseInt(newValue) != 0);
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 618eb6f..e0ff4cc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -35,6 +35,7 @@
import android.view.AppTransitionAnimationSpec;
import android.view.LayoutInflater;
import android.view.View;
+
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Prefs;
import com.android.systemui.R;
@@ -167,7 +168,7 @@
public void run() {
// When this fires, then the user has not released alt-tab for at least
// FAST_ALT_TAB_DELAY_MS milliseconds
- showRecents(mTriggeredFromAltTab, false /* draggingInRecents */);
+ showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */);
}
});
@@ -250,7 +251,8 @@
}
@Override
- public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents) {
+ public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
+ boolean animate) {
mTriggeredFromAltTab = triggeredFromAltTab;
mDraggingInRecents = draggingInRecents;
if (mFastAltTabTrigger.hasTriggered()) {
@@ -283,7 +285,7 @@
ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
MutableBoolean isTopTaskHome = new MutableBoolean(true);
if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
- startRecentsActivity(topTask, isTopTaskHome.value);
+ startRecentsActivity(topTask, isTopTaskHome.value, animate);
}
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to launch RecentsActivity", e);
@@ -329,7 +331,7 @@
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
RecentsDebugFlags flags = Recents.getDebugFlags();
- if (flags.isFastToggleRecentsEnabled() && !launchState.launchedWithAltTab) {
+ if (flags.isPageOnToggleEnabled() && !launchState.launchedWithAltTab) {
// Notify recents to move onto the next task
EventBus.getDefault().post(new IterateRecentsEvent());
} else {
@@ -355,7 +357,7 @@
}
// Otherwise, start the recents activity
- startRecentsActivity(topTask, isTopTaskHome.value);
+ startRecentsActivity(topTask, isTopTaskHome.value, true /* animate */);
mLastToggleTime = SystemClock.elapsedRealtime();
}
} catch (ActivityNotFoundException e) {
@@ -539,9 +541,9 @@
SystemServicesProxy ssp = Recents.getSystemServices();
ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
if (topTask != null && !SystemServicesProxy.isHomeStack(topTask.stackId)) {
- ssp.startTaskInDockedMode(topTask.id,
+ ssp.moveTaskToDockedStack(topTask.id,
ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
- showRecents(false /* triggeredFromAltTab */, draggingInRecents);
+ showRecents(false /* triggeredFromAltTab */, draggingInRecents, false /* animate */);
}
}
@@ -791,18 +793,19 @@
* Shows the recents activity
*/
private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
- boolean isTopTaskHome) {
+ boolean isTopTaskHome, boolean animate) {
RecentsTaskLoader loader = Recents.getTaskLoader();
// Update the header bar if necessary
reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
- if (sInstanceLoadPlan == null) {
- // Create a new load plan if onPreloadRecents() was never triggered
+ // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
+ // should always preload the tasks now
+ if (mTriggeredFromAltTab ||sInstanceLoadPlan == null) {
+ // Create a new load plan if preloadRecents() was never triggered
sInstanceLoadPlan = loader.createLoadPlan(mContext);
}
-
- if (!sInstanceLoadPlan.hasTasks()) {
+ if (mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
}
TaskStack stack = sInstanceLoadPlan.getTaskStack();
@@ -811,6 +814,14 @@
mDummyStackView.updateLayoutForStack(stack);
TaskStackLayoutAlgorithm.VisibilityReport stackVr =
mDummyStackView.computeStackVisibilityReport();
+
+ if (!animate) {
+ ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1);
+ startRecentsActivity(topTask, opts, false /* fromHome */,
+ false /* fromSearchHome */, false /* fromThumbnail*/, stackVr);
+ return;
+ }
+
boolean hasRecentTasks = stack.getTaskCount() > 0;
boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
@@ -870,8 +881,7 @@
launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
launchState.launchedHasConfigurationChanged = false;
- launchState.startHidden = topTask != null && topTask.stackId == FREEFORM_WORKSPACE_STACK_ID
- || mDraggingInRecents;
+ launchState.launchedViaDragGesture = mDraggingInRecents;
Intent intent = new Intent();
intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
index 9d96d8e..f9ccfc8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
@@ -18,7 +18,6 @@
import android.content.Context;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.misc.SystemServicesProxy;
/**
* This is sent when we want to start screen pinning.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/AllTaskViewsDismissedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/AllTaskViewsDismissedEvent.java
new file mode 100644
index 0000000..cf74519
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/AllTaskViewsDismissedEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent whenever all the task views in a stack have been dismissed.
+ */
+public class AllTaskViewsDismissedEvent extends EventBus.Event {
+ // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java
new file mode 100644
index 0000000..cb5011a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent whenever a new scroll gesture happens on a stack view.
+ */
+public class StackViewScrolledEvent extends EventBus.Event {
+
+ public final int yMovement;
+
+ public StackViewScrolledEvent(int yMovement) {
+ this.yMovement = yMovement;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java
new file mode 100644
index 0000000..b42da9c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent to update the visibility of all visible freeform task views.
+ */
+public class UpdateFreeformTaskViewVisibilityEvent extends EventBus.Event {
+
+ public final boolean visible;
+
+ public UpdateFreeformTaskViewVisibilityEvent(boolean visible) {
+ this.visible = visible;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/FreePathInterpolator.java b/packages/SystemUI/src/com/android/systemui/recents/misc/FreePathInterpolator.java
new file mode 100644
index 0000000..720c952
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/FreePathInterpolator.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+import android.graphics.Path;
+import android.view.animation.BaseInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * An interpolator that can traverse a Path. The x coordinate along the <code>Path</code>
+ * is the input value and the output is the y coordinate of the line at that point.
+ * This means that the Path must conform to a function <code>y = f(x)</code>.
+ *
+ * <p>The <code>Path</code> must not have gaps in the x direction and must not
+ * loop back on itself such that there can be two points sharing the same x coordinate.
+ * It is alright to have a disjoint line in the vertical direction:</p>
+ * <p><blockquote><pre>
+ * Path path = new Path();
+ * path.lineTo(0.25f, 0.25f);
+ * path.moveTo(0.25f, 0.5f);
+ * path.lineTo(1f, 1f);
+ * </pre></blockquote></p>
+ */
+public class FreePathInterpolator extends BaseInterpolator {
+
+ // This governs how accurate the approximation of the Path is.
+ private static final float PRECISION = 0.002f;
+
+ private float[] mX;
+ private float[] mY;
+ private float mArcLength;
+
+ /**
+ * Create an interpolator for an arbitrary <code>Path</code>.
+ *
+ * @param path The <code>Path</code> to use to make the line representing the interpolator.
+ */
+ public FreePathInterpolator(Path path) {
+ initPath(path);
+ }
+
+ private void initPath(Path path) {
+ float[] pointComponents = path.approximate(PRECISION);
+
+ int numPoints = pointComponents.length / 3;
+
+ mX = new float[numPoints];
+ mY = new float[numPoints];
+ mArcLength = 0;
+ float prevX = 0;
+ float prevY = 0;
+ float prevFraction = 0;
+ int componentIndex = 0;
+ for (int i = 0; i < numPoints; i++) {
+ float fraction = pointComponents[componentIndex++];
+ float x = pointComponents[componentIndex++];
+ float y = pointComponents[componentIndex++];
+ if (fraction == prevFraction && x != prevX) {
+ throw new IllegalArgumentException(
+ "The Path cannot have discontinuity in the X axis.");
+ }
+ if (x < prevX) {
+ throw new IllegalArgumentException("The Path cannot loop back on itself.");
+ }
+ mX[i] = x;
+ mY[i] = y;
+ mArcLength += Math.hypot(x - prevX, y - prevY);
+ prevX = x;
+ prevY = y;
+ prevFraction = fraction;
+ }
+ }
+
+ /**
+ * Using the line in the Path in this interpolator that can be described as
+ * <code>y = f(x)</code>, finds the y coordinate of the line given <code>t</code>
+ * as the x coordinate.
+ *
+ * @param t Treated as the x coordinate along the line.
+ * @return The y coordinate of the Path along the line where x = <code>t</code>.
+ * @see Interpolator#getInterpolation(float)
+ */
+ @Override
+ public float getInterpolation(float t) {
+ int startIndex = 0;
+ int endIndex = mX.length - 1;
+
+ // Return early if out of bounds
+ if (t <= 0) {
+ return mY[startIndex];
+ } else if (t >= 1) {
+ return mY[endIndex];
+ }
+
+ // Do a binary search for the correct x to interpolate between.
+ while (endIndex - startIndex > 1) {
+ int midIndex = (startIndex + endIndex) / 2;
+ if (t < mX[midIndex]) {
+ endIndex = midIndex;
+ } else {
+ startIndex = midIndex;
+ }
+ }
+
+ float xRange = mX[endIndex] - mX[startIndex];
+ if (xRange == 0) {
+ return mY[startIndex];
+ }
+
+ float tInRange = t - mX[startIndex];
+ float fraction = tInRange / xRange;
+
+ float startY = mY[startIndex];
+ float endY = mY[endIndex];
+ return startY + (fraction * (endY - startY));
+ }
+
+ /**
+ * Finds the x that provides the given <code>y = f(x)</code>.
+ *
+ * @param y a value from (0,1) that is in this path.
+ */
+ public float getX(float y) {
+ int startIndex = 0;
+ int endIndex = mY.length - 1;
+
+ // Return early if out of bounds
+ if (y <= 0) {
+ return mX[endIndex];
+ } else if (y >= 1) {
+ return mX[startIndex];
+ }
+
+ // Do a binary search for index that bounds the y
+ while (endIndex - startIndex > 1) {
+ int midIndex = (startIndex + endIndex) / 2;
+ if (y < mY[midIndex]) {
+ startIndex = midIndex;
+ } else {
+ endIndex = midIndex;
+ }
+ }
+
+ float yRange = mY[endIndex] - mY[startIndex];
+ if (yRange == 0) {
+ return mX[startIndex];
+ }
+
+ float tInRange = y - mY[startIndex];
+ float fraction = tInRange / yRange;
+
+ float startX = mX[startIndex];
+ float endX = mX[endIndex];
+ return startX + (fraction * (endX - startX));
+ }
+
+ /**
+ * Returns the arclength of the path we are interpolating.
+ */
+ public float getArcLength() {
+ return mArcLength;
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
deleted file mode 100644
index 515c3bd..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.misc;
-
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.util.Log;
-
-/**
- * Represents a 2d curve that is parameterized along the arc length of the curve (p), and allows the
- * conversions of x->p and p->x.
- */
-public class ParametricCurve {
-
- private static final boolean DEBUG = false;
- private static final String TAG = "ParametricCurve";
-
- private static final int PrecisionSteps = 250;
-
- /**
- * A 2d function, representing the curve.
- */
- public interface CurveFunction {
- float f(float x);
- float invF(float y);
- }
-
- /**
- * A function that returns a value given a parametric value.
- */
- public interface ParametricCurveFunction {
- float f(float p);
- }
-
- float[] xp;
- float[] px;
- float mLength;
-
- CurveFunction mFn;
- ParametricCurveFunction mScaleFn;
-
- public ParametricCurve(CurveFunction fn, ParametricCurveFunction scaleFn) {
- long t1;
- if (DEBUG) {
- t1 = SystemClock.currentThreadTimeMicro();
- Log.d(TAG, "initializeCurve");
- }
- mFn = fn;
- mScaleFn = scaleFn;
- xp = new float[PrecisionSteps + 1];
- px = new float[PrecisionSteps + 1];
-
- // Approximate f(x)
- float[] fx = new float[PrecisionSteps + 1];
- float step = 1f / PrecisionSteps;
- float x = 0;
- for (int xStep = 0; xStep <= PrecisionSteps; xStep++) {
- fx[xStep] = fn.f(x);
- x += step;
- }
- // Calculate the arc length for x:1->0
- float pLength = 0;
- float[] dx = new float[PrecisionSteps + 1];
- dx[0] = 0;
- for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
- dx[xStep] = (float) Math.hypot(fx[xStep] - fx[xStep - 1], step);
- pLength += dx[xStep];
- }
- mLength = pLength;
- // Approximate p(x), a function of cumulative progress with x, normalized to 0..1
- float p = 0;
- px[0] = 0f;
- px[PrecisionSteps] = 1f;
- if (DEBUG) {
- Log.d(TAG, "p[0]=0");
- Log.d(TAG, "p[" + PrecisionSteps + "]=1");
- }
- for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
- p += Math.abs(dx[xStep] / pLength);
- px[xStep] = p;
- if (DEBUG) {
- Log.d(TAG, "p[" + xStep + "]=" + p);
- }
- }
- // Given p(x), calculate the inverse function x(p). This assumes that x(p) is also a valid
- // function.
- int xStep = 0;
- p = 0;
- xp[0] = 0f;
- xp[PrecisionSteps] = 1f;
- if (DEBUG) {
- Log.d(TAG, "x[0]=0");
- Log.d(TAG, "x[" + PrecisionSteps + "]=1");
- }
- for (int pStep = 0; pStep < PrecisionSteps; pStep++) {
- // Walk forward in px and find the x where px <= p && p < px+1
- while (xStep < PrecisionSteps) {
- if (px[xStep] > p) break;
- xStep++;
- }
- // Now, px[xStep-1] <= p < px[xStep]
- if (xStep == 0) {
- xp[pStep] = 0;
- } else {
- // Find x such that proportionally, x is correct
- float fraction = (p - px[xStep - 1]) / (px[xStep] - px[xStep - 1]);
- x = (xStep - 1 + fraction) * step;
- xp[pStep] = x;
- }
- if (DEBUG) {
- Log.d(TAG, "x[" + pStep + "]=" + xp[pStep]);
- }
- p += step;
- }
- if (DEBUG) {
- Log.d(TAG, "\t1t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
- }
- }
-
- /**
- * Converts from the progress along the arc-length of the curve to a coordinate within the
- * bounds. Note, p=0 represents the top of the bounds, and p=1 represents the bottom.
- */
- public int pToX(float p, Rect bounds) {
- int top = bounds.top;
- int height = bounds.height();
-
- if (p <= 0f) return top;
- if (p >= 1f) return top + (int) (p * height);
-
- float pIndex = p * PrecisionSteps;
- int pFloorIndex = (int) Math.floor(pIndex);
- int pCeilIndex = (int) Math.ceil(pIndex);
- float x = xp[pFloorIndex];
- if (pFloorIndex < PrecisionSteps && (pCeilIndex != pFloorIndex)) {
- // Interpolate between the two precalculated positions
- x += (xp[pCeilIndex] - xp[pFloorIndex]) * (pIndex - pFloorIndex);
- }
- return top + (int) (x * height);
- }
-
- /**
- * Converts from the progress along the arc-length of the curve to a scale.
- */
- public float pToScale(float p) {
- return mScaleFn.f(p);
- }
-
- /**
- * Converts from a bounds coordinate to the progress along the arc-length of the curve.
- * Note, p=0 represents the top of the bounds, and p=1 represents the bottom.
- */
- public float xToP(int x, Rect bounds) {
- int top = bounds.top;
-
- float xf = (float) (x - top) / bounds.height();
- if (xf <= 0f) return 0f;
- if (xf >= 1f) return xf;
-
- float xIndex = xf * PrecisionSteps;
- int xFloorIndex = (int) Math.floor(xIndex);
- int xCeilIndex = (int) Math.ceil(xIndex);
- float p = px[xFloorIndex];
- if (xFloorIndex < PrecisionSteps && (xCeilIndex != xFloorIndex)) {
- // Interpolate between the two precalculated positions
- p += (px[xCeilIndex] - px[xFloorIndex]) * (xIndex - xFloorIndex);
- }
- return p;
- }
-
- /**
- * Computes the progress offset from the bottom of the curve (p=1) such that the given height
- * is visible when scaled at the that progress.
- */
- public float computePOffsetForScaledHeight(int height, Rect bounds) {
- int top = bounds.top;
- int bottom = bounds.bottom;
- height = Math.min(height, bottom - top);
-
- if (bounds.height() == 0) {
- return 0;
- }
-
- // Find the next p(x) such that (bottom-x) == scale(p(x))*height
- int minX = top;
- int maxX = bottom;
- long t1;
- if (DEBUG) {
- t1 = SystemClock.currentThreadTimeMicro();
- Log.d(TAG, "computePOffsetForScaledHeight: " + height);
- }
- while (minX <= maxX) {
- int midX = minX + ((maxX - minX) / 2);
- float pMidX = xToP(midX, bounds);
- float scaleMidX = mScaleFn.f(pMidX);
- int scaledHeight = (int) (scaleMidX * height);
- if ((bottom - midX) < scaledHeight) {
- maxX = midX - 1;
- } else if ((bottom - midX) > scaledHeight) {
- minX = midX + 1;
- } else {
- if (DEBUG) {
- Log.d(TAG, "\t1t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
- }
- return 1f - pMidX;
- }
- }
- if (DEBUG) {
- Log.d(TAG, "\t2t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
- }
- return 1f - xToP(maxX, bounds);
- }
-
- /**
- * Computes the progress offset from the bottom of the curve (p=1) that allows the given height,
- * unscaled at the progress, will be visible.
- */
- public float computePOffsetForHeight(int height, Rect bounds) {
- int top = bounds.top;
- int bottom = bounds.bottom;
- height = Math.min(height, bottom - top);
-
- if (bounds.height() == 0) {
- return 0;
- }
-
- // Find the next p(x) such that (bottom-x) == height
- int minX = top;
- int maxX = bottom;
- long t1;
- if (DEBUG) {
- t1 = SystemClock.currentThreadTimeMicro();
- Log.d(TAG, "computePOffsetForHeight: " + height);
- }
- while (minX <= maxX) {
- int midX = minX + ((maxX - minX) / 2);
- if ((bottom - midX) < height) {
- maxX = midX - 1;
- } else if ((bottom - midX) > height) {
- minX = midX + 1;
- } else {
- if (DEBUG) {
- Log.d(TAG, "\t1t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
- }
- return 1f - xToP(midX, bounds);
- }
- }
- if (DEBUG) {
- Log.d(TAG, "\t2t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
- }
- return 1f - xToP(maxX, bounds);
- }
-
- /**
- * Returns the length of this curve.
- */
- public float getArcLength() {
- return mLength;
- }
-}
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 16d6929..5026c790 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -311,6 +311,17 @@
}
}
+ /** Docks an already resumed task to the side of the screen. */
+ public void moveTaskToDockedStack(int taskId, int createMode) {
+ if (mIam == null) return;
+
+ try {
+ mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */, false /* animate */);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
/** Returns the focused stack id. */
public int getFocusedStack() {
if (mIam == null) return -1;
@@ -331,6 +342,13 @@
}
/**
+ * Returns whether the given stack id is the docked stack id.
+ */
+ public static boolean isDockedStack(int stackId) {
+ return stackId == DOCKED_STACK_ID;
+ }
+
+ /**
* Returns whether the given stack id is the freeform workspace stack id.
*/
public static boolean isFreeformStack(int stackId) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index f26dcde..0970252 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -59,7 +59,7 @@
class FilteredTaskList {
private static final String TAG = "FilteredTaskList";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
ArrayList<Task> mTasks = new ArrayList<>();
ArrayList<Task> mFilteredTasks = new ArrayList<>();
@@ -74,18 +74,17 @@
if (!prevFilteredTasks.equals(mFilteredTasks)) {
return true;
} else {
- // If the tasks are exactly the same pre/post filter, then just reset it
- mFilter = null;
return false;
}
}
- /** Resets this FilteredTaskList. */
+ /**
+ * Resets the task list, but does not remove the filter.
+ */
void reset() {
mTasks.clear();
mFilteredTasks.clear();
mTaskIndices.clear();
- mFilter = null;
}
/** Removes the task filter and returns the previous touch state */
@@ -200,16 +199,10 @@
/** Task stack callbacks */
public interface TaskStackCallbacks {
/* Notifies when a task has been added to the stack */
- public void onStackTaskAdded(TaskStack stack, Task t);
+ void onStackTaskAdded(TaskStack stack, Task t);
/* Notifies when a task has been removed from the stack */
- public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
+ void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
Task newFrontMostTask);
- /* Notifies when all task has been removed from the stack */
- public void onStackAllTasksRemoved(TaskStack stack, ArrayList<Task> removedTasks);
- /** Notifies when the stack was filtered */
- public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t);
- /** Notifies when the stack was un-filtered */
- public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks);
}
@@ -300,8 +293,18 @@
FilteredTaskList mTaskList = new FilteredTaskList();
TaskStackCallbacks mCb;
- ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>();
- HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>();
+ ArrayList<TaskGrouping> mGroups = new ArrayList<>();
+ HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<>();
+
+ public TaskStack() {
+ // Ensure that we only show non-docked tasks
+ mTaskList.setFilter(new TaskFilter() {
+ @Override
+ public boolean acceptTask(Task t, int index) {
+ return !SystemServicesProxy.isDockedStack(t.key.stackId);
+ }
+ });
+ }
/** Sets the callbacks for this task stack. */
public void setCallbacks(TaskStackCallbacks cb) {
@@ -377,20 +380,6 @@
}
}
- /** Removes all tasks */
- public void removeAllTasks() {
- ArrayList<Task> taskList = new ArrayList<Task>(mTaskList.getTasks());
- int taskCount = taskList.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- Task t = taskList.get(i);
- removeTaskImpl(t);
- }
- if (mCb != null) {
- // Notify that all tasks have been removed
- mCb.onStackAllTasksRemoved(this, taskList);
- }
- }
-
/** Sets a few tasks in one go */
public void setTasks(List<Task> tasks) {
ArrayList<Task> taskList = mTaskList.getTasks();
@@ -419,7 +408,7 @@
/** Gets the task keys */
public ArrayList<Task.TaskKey> getTaskKeys() {
- ArrayList<Task.TaskKey> taskKeys = new ArrayList<Task.TaskKey>();
+ ArrayList<Task.TaskKey> taskKeys = new ArrayList<>();
ArrayList<Task> tasks = mTaskList.getTasks();
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
@@ -486,41 +475,6 @@
return false;
}
- /******** Filtering ********/
-
- /** Filters the stack into tasks similar to the one specified */
- public void filterTasks(final Task t) {
- ArrayList<Task> oldStack = new ArrayList<Task>(mTaskList.getTasks());
-
- // Set the task list filter
- boolean filtered = mTaskList.setFilter(new TaskFilter() {
- @Override
- public boolean acceptTask(Task at, int i) {
- return t.key.getComponent().getPackageName().equals(
- at.key.getComponent().getPackageName());
- }
- });
- if (filtered && mCb != null) {
- mCb.onStackFiltered(this, oldStack, t);
- }
- }
-
- /** Unfilters the current stack */
- public void unfilterTasks() {
- ArrayList<Task> oldStack = new ArrayList<Task>(mTaskList.getTasks());
-
- // Unset the filter, then update the virtual scroll
- mTaskList.removeFilter();
- if (mCb != null) {
- mCb.onStackUnfiltered(this, oldStack);
- }
- }
-
- /** Returns whether tasks are currently filtered */
- public boolean hasFilteredTasks() {
- return mTaskList.hasFilter();
- }
-
/******** Grouping ********/
/** Adds a group to the set */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index f3c66a5..ff3aef9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -17,7 +17,6 @@
package com.android.systemui.recents.views;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
import android.content.Context;
@@ -49,7 +48,6 @@
import java.util.ArrayList;
import java.util.List;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
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 d18389f..311ee65 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -23,24 +23,23 @@
import android.os.Handler;
import android.util.ArraySet;
import android.util.AttributeSet;
-import android.view.AppTransitionAnimationSpec;
import android.view.LayoutInflater;
import android.view.MotionEvent;
-import android.view.View;
import android.view.WindowInsets;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
+import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsAppWidgetHostView;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
@@ -64,20 +63,13 @@
private static final String TAG = "RecentsView";
private static final boolean DEBUG = false;
- private int mStackViewVisibility = View.VISIBLE;
-
- /** The RecentsView callbacks */
- public interface RecentsViewCallbacks {
- public void onAllTaskViewsDismissed();
- }
-
LayoutInflater mInflater;
Handler mHandler;
- ArrayList<TaskStack> mStacks;
TaskStackView mTaskStackView;
RecentsAppWidgetHostView mSearchBar;
- RecentsViewCallbacks mCb;
+ boolean mAwaitingFirstLayout = true;
+ boolean mLastTaskLaunchedWasFreeform;
RecentsTransitionHelper mTransitionHelper;
RecentsViewTouchHandler mTouchHandler;
@@ -116,11 +108,6 @@
mTouchHandler = new RecentsViewTouchHandler(this);
}
- /** Sets the callbacks */
- public void setCallbacks(RecentsViewCallbacks cb) {
- mCb = cb;
- }
-
/** Set/get the bsp root node */
public void setTaskStack(TaskStack stack) {
RecentsConfiguration config = Recents.getConfiguration();
@@ -142,12 +129,18 @@
mTaskStackView.setCallbacks(this);
addView(mTaskStackView);
}
- mTaskStackView.setVisibility(mStackViewVisibility);
// Trigger a new layout
requestLayout();
}
+ /**
+ * Returns whether the last task launched was in the freeform stack or not.
+ */
+ public boolean isLastTaskLaunchedFreeform() {
+ return mLastTaskLaunchedWasFreeform;
+ }
+
/** Gets the next task in the stack - or if the last - the top task */
public Task getNextTaskOrTopTask(Task taskToSearch) {
Task returnTask = null;
@@ -336,6 +329,17 @@
mDragView.layout(left, top, left + mDragView.getMeasuredWidth(),
top + mDragView.getMeasuredHeight());
}
+
+ if (mAwaitingFirstLayout) {
+ mAwaitingFirstLayout = false;
+
+ // If launched via dragging from the nav bar, then we should translate the whole view
+ // down offscreen
+ RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+ if (launchState.launchedViaDragGesture) {
+ setTranslationY(getMeasuredHeight());
+ }
+ }
}
@Override
@@ -377,24 +381,6 @@
return super.verifyDrawable(who);
}
- /** Unfilters any filtered stacks */
- public boolean unfilterFilteredStacks() {
- if (mStacks != null) {
- // Check if there are any filtered stacks and unfilter them before we back out of Recents
- boolean stacksUnfiltered = false;
- int numStacks = mStacks.size();
- for (int i = 0; i < numStacks; i++) {
- TaskStack stack = mStacks.get(i);
- if (stack.hasFilteredTasks()) {
- stack.unfilterTasks();
- stacksUnfiltered = true;
- }
- }
- return stacksUnfiltered;
- }
- return false;
- }
-
public void disableLayersForOneFrame() {
if (mTaskStackView != null) {
mTaskStackView.disableLayersForOneFrame();
@@ -407,59 +393,11 @@
public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
final TaskStack stack, final Task task, final boolean lockToTask,
final Rect bounds, int destinationStack) {
+ mLastTaskLaunchedWasFreeform = SystemServicesProxy.isFreeformStack(task.key.stackId);
mTransitionHelper.launchTaskFromRecents(stack, task, stackView, tv, lockToTask, bounds,
destinationStack);
}
- @Override
- public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks) {
- /* TODO: Not currently enabled
- if (removedTasks != null) {
- int taskCount = removedTasks.size();
- for (int i = 0; i < taskCount; i++) {
- onTaskViewDismissed(removedTasks.get(i));
- }
- }
- */
-
- mCb.onAllTaskViewsDismissed();
-
- // Keep track of all-deletions
- MetricsLogger.count(getContext(), "overview_task_all_dismissed", 1);
- }
-
- @Override
- public void onTaskStackFilterTriggered() {
- // Hide the search bar
- if (mSearchBar != null) {
- int filterDuration = getResources().getInteger(
- R.integer.recents_filter_animate_current_views_duration);
- mSearchBar.animate()
- .alpha(0f)
- .setStartDelay(0)
- .setInterpolator(mFastOutSlowInInterpolator)
- .setDuration(filterDuration)
- .withLayer()
- .start();
- }
- }
-
- @Override
- public void onTaskStackUnfilterTriggered() {
- // Show the search bar
- if (mSearchBar != null) {
- int filterDuration = getResources().getInteger(
- R.integer.recents_filter_animate_new_views_duration);
- mSearchBar.animate()
- .alpha(1f)
- .setStartDelay(0)
- .setInterpolator(mFastOutSlowInInterpolator)
- .setDuration(filterDuration)
- .withLayer()
- .start();
- }
- }
-
/**** EventBus Events ****/
public final void onBusEvent(DragStartEvent event) {
@@ -545,7 +483,6 @@
}
public final void onBusEvent(DraggingInRecentsEvent event) {
- setStackViewVisibility(View.VISIBLE);
setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
}
@@ -579,11 +516,11 @@
}
}
- public void setStackViewVisibility(int stackViewVisibility) {
- mStackViewVisibility = stackViewVisibility;
- if (mTaskStackView != null) {
- mTaskStackView.setVisibility(stackViewVisibility);
- invalidate();
+ public final void onBusEvent(RecentsVisibilityChangedEvent event) {
+ if (!event.visible) {
+ // Reset the view state
+ mAwaitingFirstLayout = true;
+ mLastTaskLaunchedWasFreeform = false;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index b3bd6ed..f8f0ea1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -16,14 +16,22 @@
package com.android.systemui.recents.views;
+import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Path;
import android.graphics.Rect;
+import android.util.FloatProperty;
import android.util.Log;
+import android.util.Property;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.misc.ParametricCurve;
+import com.android.systemui.recents.RecentsDebugFlags;
+import com.android.systemui.recents.misc.FreePathInterpolator;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
@@ -32,21 +40,96 @@
import java.util.ArrayList;
import java.util.HashMap;
+/**
+ * Used to describe a visible range that can be normalized to [0, 1].
+ */
+class Range {
+ final float relativeMin;
+ final float relativeMax;
+ float origin;
+ float min;
+ float max;
+
+ public Range(float relMin, float relMax) {
+ min = relativeMin = relMin;
+ max = relativeMax = relMax;
+ }
+
+ /**
+ * Offsets this range to a given absolute position.
+ */
+ public void offset(float x) {
+ this.origin = x;
+ min = x + relativeMin;
+ max = x + relativeMax;
+ }
+
+ /**
+ * Returns x normalized to the range 0 to 1 such that 0 = min, 0.5 = origin and 1 = max
+ *
+ * @param x is an absolute value in the same domain as origin
+ */
+ public float getNormalizedX(float x) {
+ if (x < origin) {
+ return 0.5f + 0.5f * (x - origin) / -relativeMin;
+ } else {
+ return 0.5f + 0.5f * (x - origin) / relativeMax;
+ }
+ }
+
+ /**
+ * Given a normalized {@param x} value in this range, projected onto the full range to get an
+ * absolute value about the given {@param origin}.
+ */
+ public float getAbsoluteX(float normX) {
+ if (normX < 0.5f) {
+ return (normX - 0.5f) / 0.5f * -relativeMin;
+ } else {
+ return (normX - 0.5f) / 0.5f * relativeMax;
+ }
+ }
+
+ /**
+ * Returns whether a value at an absolute x would be within range.
+ */
+ public boolean isInRange(float absX) {
+ return (absX >= Math.floor(min)) && (absX <= Math.ceil(max));
+ }
+}
/**
- * The layout logic for a TaskStackView.
+ * The layout logic for a TaskStackView. This layout can have two states focused and unfocused,
+ * and in the focused state, there is a task that is displayed more prominently in the stack.
*/
public class TaskStackLayoutAlgorithm {
private static final String TAG = "TaskStackViewLayoutAlgorithm";
private static final boolean DEBUG = false;
- // The min scale of the last task at the top of the curve
- private static final float STACK_PEEK_MIN_SCALE = 0.85f;
- // The scale of the last task
- private static final float SINGLE_TASK_SCALE = 0.95f;
- // The percentage of height of task to show between tasks
- private static final float VISIBLE_TASK_HEIGHT_BETWEEN_TASKS = 0.5f;
+ // The scale factor to apply to the user movement in the stack to unfocus it
+ private static final float UNFOCUS_MULTIPLIER = 0.8f;
+
+ // The various focus states
+ public static final float STATE_FOCUSED = 1f;
+ public static final float STATE_UNFOCUSED = 0f;
+
+ /**
+ * A Property wrapper around the <code>focusState</code> functionality handled by the
+ * {@link TaskStackLayoutAlgorithm#setFocusState(float)} and
+ * {@link TaskStackLayoutAlgorithm#getFocusState()} methods.
+ */
+ private static final Property<TaskStackLayoutAlgorithm, Float> FOCUS_STATE =
+ new FloatProperty<TaskStackLayoutAlgorithm>("focusState") {
+ @Override
+ public void setValue(TaskStackLayoutAlgorithm object, float value) {
+ object.setFocusState(value);
+ }
+
+ @Override
+ public Float get(TaskStackLayoutAlgorithm object) {
+ return object.getFocusState();
+ }
+ };
// A report of the visibility state of the stack
public class VisibilityReport {
@@ -61,6 +144,8 @@
}
Context mContext;
+ private TaskStackView mStackView;
+ private Interpolator mFastOutSlowInInterpolator;
// The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot.
public Rect mTaskRect = new Rect();
@@ -74,10 +159,33 @@
private Rect mStackRect = new Rect();
// The current stack rect, can either by mFreeformStackRect or mStackRect depending on whether
// there is a freeform workspace
- public Rect mCurrentStackRect;
+ public Rect mCurrentStackRect = new Rect();
// This is the current system insets
public Rect mSystemInsets = new Rect();
+ // The visible ranges when the stack is focused and unfocused
+ private Range mUnfocusedRange;
+ private Range mFocusedRange;
+
+ // The offset from the top when scrolled to the top of the stack
+ private int mFocusedPeekHeight;
+
+ // The offset from the bottom of the stack to the bottom of the bounds
+ private int mStackBottomOffset;
+
+ // The paths defining the motion of the tasks when the stack is focused and unfocused
+ private Path mUnfocusedCurve;
+ private Path mFocusedCurve;
+ private FreePathInterpolator mUnfocusedCurveInterpolator;
+ private FreePathInterpolator mFocusedCurveInterpolator;
+
+ // The state of the stack focus (0..1), which controls the transition of the stack from the
+ // focused to non-focused state
+ private float mFocusState;
+
+ // The animator used to reset the focused state
+ private ObjectAnimator mFocusStateAnimator;
+
// The smallest scroll progress, at this value, the back most task will be visible
float mMinScrollP;
// The largest scroll progress, at this value, the front most task will be visible above the
@@ -88,78 +196,44 @@
// The task progress for the front-most task in the stack
float mFrontMostTaskP;
- // The relative progress to ensure that the height between affiliated tasks is respected
- float mWithinAffiliationPOffset;
- // The relative progress to ensure that the height between non-affiliated tasks is
- // respected
- float mBetweenAffiliationPOffset;
- // The relative progress to ensure that the task height is respected
- float mTaskHeightPOffset;
- // The relative progress to ensure that the half task height is respected
- float mTaskHalfHeightPOffset;
- // The front-most task bottom offset
- int mStackBottomOffset;
- // The relative progress to ensure that the offset from the bottom of the stack to the bottom
- // of the task is respected
- float mStackBottomPOffset;
-
// The last computed task counts
int mNumStackTasks;
int mNumFreeformTasks;
+
// The min/max z translations
int mMinTranslationZ;
int mMaxTranslationZ;
- // Optimization, allows for quick lookup of task -> progress
- HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<>();
+ // Optimization, allows for quick lookup of task -> index
+ private HashMap<Task.TaskKey, Integer> mTaskIndexMap = new HashMap<>();
// The freeform workspace layout
FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
- // Log function
- static ParametricCurve sCurve;
-
- public TaskStackLayoutAlgorithm(Context context) {
+ public TaskStackLayoutAlgorithm(Context context, TaskStackView stackView) {
Resources res = context.getResources();
+ mStackView = stackView;
+
+ mFocusedRange = new Range(res.getFloat(R.integer.recents_layout_focused_range_min),
+ res.getFloat(R.integer.recents_layout_focused_range_max));
+ mUnfocusedRange = new Range(res.getFloat(R.integer.recents_layout_unfocused_range_min),
+ res.getFloat(R.integer.recents_layout_unfocused_range_max));
+ mFocusState = getDefaultFocusState();
+ mFocusedPeekHeight = res.getDimensionPixelSize(R.dimen.recents_layout_focused_peek_size);
+
mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
mContext = context;
mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm();
- if (sCurve == null) {
- sCurve = new ParametricCurve(new ParametricCurve.CurveFunction() {
- // The large the XScale, the longer the flat area of the curve
- private static final float XScale = 1.75f;
- private static final float LogBase = 3000;
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ com.android.internal.R.interpolator.fast_out_slow_in);
+ }
- float reverse(float x) {
- return (-x * XScale) + 1;
- }
-
- @Override
- public float f(float x) {
- return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase);
- }
-
- @Override
- public float invF(float y) {
- return (float) (Math.log(1f - reverse(y)) / (-Math.log(LogBase) * XScale));
- }
- }, new ParametricCurve.ParametricCurveFunction() {
- @Override
- public float f(float p) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.hasFreeformWorkspaceSupport()) {
- return 1f;
- }
-
- if (p < 0) return STACK_PEEK_MIN_SCALE;
- if (p > 1) return 1f;
- float scaleRange = (1f - STACK_PEEK_MIN_SCALE);
- float scale = STACK_PEEK_MIN_SCALE + (p * scaleRange);
- return scale;
- }
- });
- }
+ /**
+ * Resets this layout when the stack view is reset.
+ */
+ public void reset() {
+ setFocusState(getDefaultFocusState());
}
/**
@@ -173,16 +247,32 @@
}
/**
+ * Sets the focused state.
+ */
+ public void setFocusState(float focusState) {
+ mFocusState = focusState;
+ mStackView.requestSynchronizeStackViewsWithModel();
+ }
+
+ /**
+ * Gets the focused state.
+ */
+ public float getFocusState() {
+ return mFocusState;
+ }
+
+ /**
* Computes the stack and task rects. The given task stack bounds is the whole bounds not
* including the search bar.
*/
public void initialize(Rect taskStackBounds) {
SystemServicesProxy ssp = Recents.getSystemServices();
-
+ RecentsDebugFlags debugFlags = Recents.getDebugFlags();
RecentsConfiguration config = Recents.getConfiguration();
int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
int heightPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_stack_top_padding);
+ Rect lastStackRect = new Rect(mCurrentStackRect);
// The freeform height is the visible height (not including system insets) - padding above
// freeform and below stack - gap between the freeform and stack
@@ -200,43 +290,34 @@
taskStackBounds.top + heightPadding,
taskStackBounds.right - widthPadding,
taskStackBounds.bottom);
+
// Anchor the task rect to the top-center of the non-freeform stack rect
- int size = mStackRect.width();
+ float aspect = (float) (taskStackBounds.width() - mSystemInsets.left - mSystemInsets.right)
+ / (taskStackBounds.height() - mSystemInsets.bottom);
+ int width = mStackRect.width();
+ int height = debugFlags.isFullscreenThumbnailsEnabled() ? (int) (width / aspect) : width;
mTaskRect.set(mStackRect.left, mStackRect.top,
- mStackRect.left + size, mStackRect.top + size);
+ mStackRect.left + width, mStackRect.top + height);
mCurrentStackRect = ssp.hasFreeformWorkspaceSupport() ? mFreeformStackRect : mStackRect;
- // Compute the progress offsets
- int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
- R.dimen.recents_task_bar_height);
- int betweenAffiliationOffset = (int) (VISIBLE_TASK_HEIGHT_BETWEEN_TASKS * mTaskRect.height());
- mWithinAffiliationPOffset = sCurve.computePOffsetForScaledHeight(withinAffiliationOffset,
- mCurrentStackRect);
- mBetweenAffiliationPOffset = sCurve.computePOffsetForScaledHeight(betweenAffiliationOffset,
- mCurrentStackRect);
- mTaskHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height(),
- mCurrentStackRect);
- mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2,
- mCurrentStackRect);
- mStackBottomPOffset = sCurve.computePOffsetForHeight(mStackBottomOffset, mCurrentStackRect);
+ // Short circuit here if the stack rects haven't changed so we don't do all the work below
+ if (lastStackRect.equals(mCurrentStackRect)) {
+ return;
+ }
+
+ // Reinitialize the focused and unfocused curves
+ mUnfocusedCurve = constructUnfocusedCurve();
+ mUnfocusedCurveInterpolator = new FreePathInterpolator(mUnfocusedCurve);
+ mFocusedCurve = constructFocusedCurve();
+ mFocusedCurveInterpolator = new FreePathInterpolator(mFocusedCurve);
if (DEBUG) {
Log.d(TAG, "initialize");
- Log.d(TAG, "\tarclength: " + sCurve.getArcLength());
Log.d(TAG, "\tmFreeformRect: " + mFreeformRect);
Log.d(TAG, "\tmFreeformStackRect: " + mFreeformStackRect);
Log.d(TAG, "\tmStackRect: " + mStackRect);
Log.d(TAG, "\tmTaskRect: " + mTaskRect);
Log.d(TAG, "\tmSystemInsets: " + mSystemInsets);
-
- Log.d(TAG, "\tpWithinAffiliateOffset: " + mWithinAffiliationPOffset);
- Log.d(TAG, "\tpBetweenAffiliateOffset: " + mBetweenAffiliationPOffset);
- Log.d(TAG, "\tmTaskHeightPOffset: " + mTaskHeightPOffset);
- Log.d(TAG, "\tmTaskHalfHeightPOffset: " + mTaskHalfHeightPOffset);
- Log.d(TAG, "\tmStackBottomPOffset: " + mStackBottomPOffset);
-
- Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mCurrentStackRect));
- Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mCurrentStackRect));
}
}
@@ -248,7 +329,7 @@
SystemServicesProxy ssp = Recents.getSystemServices();
// Clear the progress map
- mTaskProgressMap.clear();
+ mTaskIndexMap.clear();
// Return early if we have no tasks
ArrayList<Task> tasks = stack.getTasks();
@@ -273,41 +354,28 @@
mNumStackTasks = stackTasks.size();
mNumFreeformTasks = freeformTasks.size();
- float pAtBackMostTaskTop = 0;
- float pAtFrontMostTaskTop = pAtBackMostTaskTop;
- if (!stackTasks.isEmpty()) {
- // Update the for each task from back to front.
- int taskCount = stackTasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = stackTasks.get(i);
- mTaskProgressMap.put(task.key, pAtFrontMostTaskTop);
+ // Put each of the tasks in the progress map at a fixed index (does not need to actually
+ // map to a scroll position, just by index)
+ int taskCount = stackTasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = stackTasks.get(i);
+ mTaskIndexMap.put(task.key, i);
+ }
- if (DEBUG) {
- Log.d(TAG, "Update: " + task.activityLabel + " p: " + pAtFrontMostTaskTop);
- }
-
- if (i < (taskCount - 1)) {
- // Increment the peek height
- float pPeek = task.group == null || task.group.isFrontMostTask(task) ?
- mBetweenAffiliationPOffset : mWithinAffiliationPOffset;
- pAtFrontMostTaskTop += pPeek;
- }
- }
-
- mFrontMostTaskP = pAtFrontMostTaskTop;
- if (mNumStackTasks > 1) {
- // Set the stack end scroll progress to the point at which the bottom of the front-most
- // task is aligned to the bottom of the stack
- mMaxScrollP = alignToStackBottom(pAtFrontMostTaskTop,
- mStackBottomPOffset + (ssp.hasFreeformWorkspaceSupport() ?
- mTaskHalfHeightPOffset : mTaskHeightPOffset));
- // Basically align the back-most task such that the last two tasks would be visible
- mMinScrollP = alignToStackBottom(pAtBackMostTaskTop,
- mStackBottomPOffset + (ssp.hasFreeformWorkspaceSupport() ?
- mTaskHalfHeightPOffset : mTaskHeightPOffset));
- } else {
- // When there is a single item, then just make all the stack progresses the same
+ // Calculate the min/max scroll
+ if (getDefaultFocusState() > 0f) {
+ mMinScrollP = 0;
+ mMaxScrollP = Math.max(mMinScrollP, mNumStackTasks - 1);
+ } else {
+ if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
mMinScrollP = mMaxScrollP = 0;
+ } else {
+ float bottomOffsetPct = (float) (mStackBottomOffset + mTaskRect.height()) /
+ mCurrentStackRect.height();
+ float normX = mUnfocusedCurveInterpolator.getX(bottomOffsetPct);
+ mMinScrollP = 0;
+ mMaxScrollP = Math.max(mMinScrollP,
+ (mNumStackTasks - 1) - mUnfocusedRange.getAbsoluteX(normX));
}
}
@@ -315,7 +383,20 @@
mFreeformLayoutAlgorithm.update(freeformTasks, this);
mInitialScrollP = mMaxScrollP;
} else {
- mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset);
+ if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
+ mInitialScrollP = mMinScrollP;
+ } else if (getDefaultFocusState() > 0f) {
+ RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+ if (launchState.launchedFromHome) {
+ mInitialScrollP = Math.max(mMinScrollP, mNumStackTasks - 1);
+ } else {
+ mInitialScrollP = Math.max(mMinScrollP, mNumStackTasks - 2);
+ }
+ } else {
+ float offsetPct = (float) (mTaskRect.height() / 2) / mCurrentStackRect.height();
+ float normX = mUnfocusedCurveInterpolator.getX(offsetPct);
+ mInitialScrollP = (mNumStackTasks - 1) - mUnfocusedRange.getAbsoluteX(normX);
+ }
}
if (DEBUG) {
@@ -327,8 +408,45 @@
}
/**
- * Computes the maximum number of visible tasks and thumbnails. Requires that
- * update() is called first.
+ * Updates this stack when a scroll happens.
+ */
+ public void updateFocusStateOnScroll(int yMovement) {
+ Utilities.cancelAnimationWithoutCallbacks(mFocusStateAnimator);
+ if (mFocusState > STATE_UNFOCUSED) {
+ float delta = (float) yMovement / (UNFOCUS_MULTIPLIER * mCurrentStackRect.height());
+ mFocusState -= Math.min(mFocusState, Math.abs(delta));
+ }
+ }
+
+ /**
+ * Aniamtes the focused state back to its orginal state.
+ */
+ public void animateFocusState(float newState) {
+ Utilities.cancelAnimationWithoutCallbacks(mFocusStateAnimator);
+ if (Float.compare(newState, getFocusState()) != 0) {
+ mFocusStateAnimator = ObjectAnimator.ofFloat(this, FOCUS_STATE, getFocusState(),
+ newState);
+ mFocusStateAnimator.setDuration(200);
+ mFocusStateAnimator.setInterpolator(mFastOutSlowInInterpolator);
+ mFocusStateAnimator.start();
+ }
+ }
+
+ /**
+ * Returns the default focus state.
+ */
+ public float getDefaultFocusState() {
+ RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+ RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+ if (debugFlags.isPageOnToggleEnabled() || launchState.launchedWithAltTab) {
+ return 1f;
+ }
+ return 0f;
+ }
+
+ /**
+ * Computes the maximum number of visible tasks and thumbnails when the scroll is at the initial
+ * stack scroll. Requires that update() is called first.
*/
public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
// Ensure minimum visibility count
@@ -344,29 +462,32 @@
// Otherwise, walk backwards in the stack and count the number of tasks and visible
// thumbnails and add that to the total freeform task count
- int taskHeight = mTaskRect.height();
+ TaskViewTransform tmpTransform = new TaskViewTransform();
+ Range currentRange = getDefaultFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
+ currentRange.offset(mInitialScrollP);
int taskBarHeight = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_task_bar_height);
int numVisibleTasks = Math.max(mNumFreeformTasks, 1);
int numVisibleThumbnails = Math.max(mNumFreeformTasks, 1);
- Task firstNonFreeformTask = tasks.get(tasks.size() - mNumFreeformTasks - 1);
- float progress = mTaskProgressMap.get(firstNonFreeformTask.key) - mInitialScrollP;
- int prevScreenY = sCurve.pToX(progress, mCurrentStackRect);
- for (int i = tasks.size() - 2; i >= 0; i--) {
+ float prevScreenY = Integer.MAX_VALUE;
+ for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
+
+ // Skip freeform
if (task.isFreeformTask()) {
continue;
}
- progress = mTaskProgressMap.get(task.key) - mInitialScrollP;
- if (progress < 0) {
- break;
+ // Skip invisible
+ float taskProgress = getStackScrollForTask(task);
+ if (!currentRange.isInRange(taskProgress)) {
+ continue;
}
+
boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
if (isFrontMostTaskInGroup) {
- float scaleAtP = sCurve.pToScale(progress);
- int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
- int screenY = sCurve.pToX(progress, mCurrentStackRect) + scaleYOffsetAtP;
+ getStackTransform(taskProgress, mInitialScrollP, tmpTransform, null);
+ float screenY = tmpTransform.rect.top;
boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
if (hasVisibleThumbnail) {
numVisibleThumbnails++;
@@ -374,12 +495,12 @@
prevScreenY = screenY;
} else {
// Once we hit the next front most task that does not have a visible thumbnail,
- // w alk through remaining visible set
+ // walk through remaining visible set
for (int j = i; j >= 0; j--) {
numVisibleTasks++;
- progress = mTaskProgressMap.get(tasks.get(j).key) - mInitialScrollP;
- if (progress < 0) {
- break;
+ taskProgress = getStackScrollForTask(tasks.get(j));
+ if (!currentRange.isInRange(taskProgress)) {
+ continue;
}
}
break;
@@ -397,86 +518,82 @@
* is what the view is measured and laid out with.
*/
public TaskViewTransform getStackTransform(Task task, float stackScroll,
- TaskViewTransform transformOut, TaskViewTransform prevTransform) {
+ TaskViewTransform transformOut, TaskViewTransform frontTransform) {
if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
return transformOut;
} else {
// Return early if we have an invalid index
- if (task == null || !mTaskProgressMap.containsKey(task.key)) {
+ if (task == null || !mTaskIndexMap.containsKey(task.key)) {
transformOut.reset();
return transformOut;
}
- return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
- prevTransform);
+ return getStackTransform(mTaskIndexMap.get(task.key), stackScroll, transformOut,
+ frontTransform);
}
}
/** Update/get the transform */
public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
- TaskViewTransform transformOut, TaskViewTransform prevTransform) {
+ TaskViewTransform transformOut, TaskViewTransform frontTransform) {
SystemServicesProxy ssp = Recents.getSystemServices();
- if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
- // Center the task in the stack, changing the scale will not follow the curve, but just
- // modulate some values directly
- float pTaskRelative = mMinScrollP - stackScroll;
- float scale = ssp.hasFreeformWorkspaceSupport() ? 1f : SINGLE_TASK_SCALE;
- int topOffset = (mCurrentStackRect.top - mTaskRect.top) +
- (mCurrentStackRect.height() - mTaskRect.height()) / 2;
- transformOut.scale = scale;
- transformOut.translationX = (mStackRect.width() - mTaskRect.width()) / 2;
- transformOut.translationY = (int) (topOffset + (pTaskRelative * mCurrentStackRect.height()));
- transformOut.translationZ = mMaxTranslationZ;
- transformOut.rect.set(mTaskRect);
- transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
- Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
- transformOut.visible = true;
- transformOut.p = pTaskRelative;
- return transformOut;
+ // Compute the focused and unfocused offset
+ mUnfocusedRange.offset(stackScroll);
+ float p = mUnfocusedRange.getNormalizedX(taskProgress);
+ float yp = mUnfocusedCurveInterpolator.getInterpolation(p);
+ float unfocusedP = p;
+ int unFocusedY = (int) (Math.max(0f, (1f - yp)) * mCurrentStackRect.height());
+ boolean unfocusedVisible = mUnfocusedRange.isInRange(taskProgress);
+ int focusedY = 0;
+ boolean focusedVisible = true;
+ if (mFocusState > 0f) {
+ mFocusedRange.offset(stackScroll);
+ p = mFocusedRange.getNormalizedX(taskProgress);
+ yp = mFocusedCurveInterpolator.getInterpolation(p);
+ focusedY = (int) (Math.max(0f, (1f - yp)) * mCurrentStackRect.height());
+ focusedVisible = mFocusedRange.isInRange(taskProgress);
+ }
- } else {
- float pTaskRelative = taskProgress - stackScroll;
- float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
- if (DEBUG) {
- Log.d(TAG, "getStackTransform (normal): " + taskProgress + ", " + stackScroll);
- }
-
- // If the task top is outside of the bounds below the screen, then immediately reset it
- if (pTaskRelative > 1f) {
- transformOut.reset();
- transformOut.rect.set(mTaskRect);
- return transformOut;
- }
- // The check for the top is trickier, since we want to show the next task if it is at
- // all visible, even if p < 0.
- if (pTaskRelative < 0f) {
- if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) {
- transformOut.reset();
- transformOut.rect.set(mTaskRect);
- return transformOut;
- }
- }
- float scale = sCurve.pToScale(pBounded);
- int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
- transformOut.scale = scale;
- transformOut.translationX = (mStackRect.width() - mTaskRect.width()) / 2;
- transformOut.translationY = (mCurrentStackRect.top - mTaskRect.top) +
- (sCurve.pToX(pBounded, mCurrentStackRect) - mCurrentStackRect.top) -
- scaleYOffset;
- transformOut.translationZ = Math.max(mMinTranslationZ,
- mMinTranslationZ + (pBounded * (mMaxTranslationZ - mMinTranslationZ)));
- transformOut.rect.set(mTaskRect);
- transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
- Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
- transformOut.visible = true;
- transformOut.p = pTaskRelative;
- if (DEBUG) {
- Log.d(TAG, "\t" + transformOut);
- }
-
+ // Skip if the task is not visible
+ if (!unfocusedVisible && !focusedVisible) {
+ transformOut.reset();
return transformOut;
}
+
+ int y;
+ float z;
+ float relP;
+ if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
+ // When there is exactly one task, then decouple the task from the stack and just move
+ // in screen space
+ p = (mMinScrollP - stackScroll) / mNumStackTasks;
+ int centerYOffset = (mCurrentStackRect.top - mTaskRect.top) +
+ (mCurrentStackRect.height() - mTaskRect.height()) / 2;
+ y = (int) (centerYOffset + (p * mCurrentStackRect.height()));
+ z = mMaxTranslationZ;
+ relP = p;
+
+ } else {
+ // Otherwise, update the task to the stack layout
+ y = unFocusedY + (int) (mFocusState * (focusedY - unFocusedY));
+ y += (mCurrentStackRect.top - mTaskRect.top);
+ z = Math.max(mMinTranslationZ, Math.min(mMaxTranslationZ,
+ mMinTranslationZ + (p * (mMaxTranslationZ - mMinTranslationZ))));
+ relP = unfocusedP;
+ }
+
+ // Fill out the transform
+ transformOut.scale = 1f;
+ transformOut.translationX = (mCurrentStackRect.width() - mTaskRect.width()) / 2;
+ transformOut.translationY = y;
+ transformOut.translationZ = z;
+ transformOut.rect.set(mTaskRect);
+ transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
+ Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+ transformOut.visible = true;
+ transformOut.p = relP;
+ return transformOut;
}
/**
@@ -491,8 +608,8 @@
* stack.
*/
float getStackScrollForTask(Task t) {
- if (!mTaskProgressMap.containsKey(t.key)) return 0f;
- return mTaskProgressMap.get(t.key);
+ if (!mTaskIndexMap.containsKey(t.key)) return 0f;
+ return mTaskIndexMap.get(t.key);
}
/**
@@ -501,7 +618,8 @@
* screen along the arc-length proportionally (1/arclength).
*/
public float getDeltaPForY(int downY, int y) {
- float deltaP = (float) (y - downY) / mCurrentStackRect.height() * (1f / sCurve.getArcLength());
+ float deltaP = (float) (y - downY) / mCurrentStackRect.height() *
+ mUnfocusedCurveInterpolator.getArcLength();
return -deltaP;
}
@@ -510,18 +628,61 @@
* of the curve, map back to the screen y.
*/
public int getYForDeltaP(float downScrollP, float p) {
- int y = (int) ((p - downScrollP) * mCurrentStackRect.height() * sCurve.getArcLength());
+ int y = (int) ((p - downScrollP) * mCurrentStackRect.height() *
+ (1f / mUnfocusedCurveInterpolator.getArcLength()));
return -y;
}
- private float alignToStackTop(float p) {
- // At scroll progress == p, then p is at the top of the stack
+ /**
+ * Creates a new path for the focused curve.
+ */
+ private Path constructFocusedCurve() {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ int taskBarHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.recents_task_bar_height);
+
+ // Initialize the focused curve. This curve is a piecewise curve composed of several
+ // quadradic beziers that goes from (0,1) through (0.5, peek height offset),
+ // (0.667, next task offset), (0.833, bottom task offset), and (1,0).
+ float peekHeightPct = 0f;
+ if (!ssp.hasFreeformWorkspaceSupport()) {
+ peekHeightPct = (float) mFocusedPeekHeight / mCurrentStackRect.height();
+ }
+ Path p = new Path();
+ p.moveTo(0f, 1f);
+ p.lineTo(0.5f, 1f - peekHeightPct);
+ p.lineTo(0.66666667f, (float) (taskBarHeight * 3) / mCurrentStackRect.height());
+ p.lineTo(0.83333333f, (float) (taskBarHeight / 2) / mCurrentStackRect.height());
+ p.lineTo(1f, 0f);
return p;
}
- private float alignToStackBottom(float p, float pOffsetFromBottom) {
- // At scroll progress == p, then p is at the top of the stack
- // At scroll progress == p + 1, then p is at the bottom of the stack
- return p - (1 - pOffsetFromBottom);
+ /**
+ * Creates a new path for the unfocused curve.
+ */
+ private Path constructUnfocusedCurve() {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+
+ // Initialize the unfocused curve. This curve is a piecewise curve composed of two quadradic
+ // beziers that goes from (0,1) through (0.5, peek height offset) and ends at (1,0). This
+ // ensures that we match the range, at which 0.5 represents the stack scroll at the current
+ // task progress. Because the height offset can change depending on a resource, we compute
+ // the control point of the second bezier such that between it and a first known point,
+ // there is a tangent at (0.5, peek height offset).
+ float cpoint1X = 0.4f;
+ float cpoint1Y = 1f;
+ float peekHeightPct = 0f;
+ if (!ssp.hasFreeformWorkspaceSupport()) {
+ peekHeightPct = (float) mFocusedPeekHeight / mCurrentStackRect.height();
+ }
+ float slope = ((1f - peekHeightPct) - cpoint1Y) / (0.5f - cpoint1X);
+ float b = 1f - slope * cpoint1X;
+ float cpoint2X = 0.75f;
+ float cpoint2Y = slope * cpoint2X + b;
+ Path p = new Path();
+ p.moveTo(0f, 1f);
+ p.cubicTo(0f, 1f, cpoint1X, 1f, 0.5f, 1f - peekHeightPct);
+ p.cubicTo(0.5f, 1f - peekHeightPct, cpoint2X, cpoint2Y, 1f, 0f);
+ return p;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 7250d6a..67710bf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -38,10 +38,17 @@
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
+import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
+import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
@@ -60,7 +67,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
@@ -80,14 +86,10 @@
interface TaskStackViewCallbacks {
public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
boolean lockToTask, Rect bounds, int destinationStack);
- public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks);
- public void onTaskStackFilterTriggered();
- public void onTaskStackUnfilterTriggered();
}
TaskStack mStack;
TaskStackLayoutAlgorithm mLayoutAlgorithm;
- TaskStackViewFilterAlgorithm mFilterAlgorithm;
TaskStackViewScroller mStackScroller;
TaskStackViewTouchHandler mTouchHandler;
TaskStackViewCallbacks mCb;
@@ -101,6 +103,7 @@
boolean mStackViewsDirty = true;
boolean mStackViewsClipDirty = true;
boolean mAwaitingFirstLayout = true;
+ boolean mEnterAnimationComplete = false;
boolean mStartEnterAnimationRequestedAfterLayout;
ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
@@ -151,8 +154,7 @@
setStack(stack);
mViewPool = new ViewPool<>(context, this);
mInflater = LayoutInflater.from(context);
- mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context);
- mFilterAlgorithm = new TaskStackViewFilterAlgorithm(this, mViewPool);
+ mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, this);
mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm);
mStackScroller.setCallbacks(this);
mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller);
@@ -290,11 +292,14 @@
mStackViewsDirty = true;
mStackViewsClipDirty = true;
mAwaitingFirstLayout = true;
+ mEnterAnimationComplete = false;
if (mUIDozeTrigger != null) {
mUIDozeTrigger.stopDozing();
mUIDozeTrigger.resetTrigger();
}
mStackScroller.reset();
+ mLayoutAlgorithm.reset();
+ requestLayout();
}
/** Requests that the views be synchronized with the model */
@@ -353,7 +358,7 @@
}
// Update the stack transforms
- TaskViewTransform prevTransform = null;
+ TaskViewTransform frontTransform = null;
for (int i = taskCount - 1; i >= 0; i--) {
Task task = tasks.get(i);
if (task.isFreeformTask()) {
@@ -361,7 +366,7 @@
}
TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll,
- taskTransforms.get(i), prevTransform);
+ taskTransforms.get(i), frontTransform);
if (DEBUG) {
Log.d(TAG, "updateStackTransform: " + i + ", " + transform.visible);
}
@@ -386,7 +391,7 @@
transform.translationY = Math.min(transform.translationY,
mLayoutAlgorithm.mCurrentStackRect.bottom);
}
- prevTransform = transform;
+ frontTransform = transform;
}
if (visibleRangeOut != null) {
visibleRangeOut[0] = frontMostVisibleIndex;
@@ -605,15 +610,19 @@
/**
* Sets the focused task to the provided (bounded taskIndex).
+ *
+ * @return whether or not the stack will scroll as a part of this focus change
*/
- private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated) {
- setFocusedTask(taskIndex, scrollToTask, animated, true);
+ private boolean setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated) {
+ return setFocusedTask(taskIndex, scrollToTask, animated, true);
}
/**
* Sets the focused task to the provided (bounded taskIndex).
+ *
+ * @return whether or not the stack will scroll as a part of this focus change
*/
- private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated,
+ private boolean setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated,
final boolean requestViewFocus) {
// Find the next task to focus
int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
@@ -629,6 +638,7 @@
}
}
+ boolean willScroll = false;
mFocusedTaskIndex = newFocusedTaskIndex;
if (mFocusedTaskIndex != -1) {
Runnable focusTaskRunnable = new Runnable() {
@@ -643,19 +653,39 @@
if (scrollToTask) {
// TODO: Center the newly focused task view, only if not freeform
- float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask) - 0.5f;
+ RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+ float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask);
+ if (!debugFlags.isFullscreenThumbnailsEnabled()) {
+ newScroll -= 0.5f;
+ }
newScroll = mStackScroller.getBoundedStackScroll(newScroll);
- mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
- focusTaskRunnable);
+ if (Float.compare(newScroll, mStackScroller.getStackScroll()) != 0) {
+ mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
+ focusTaskRunnable);
+ willScroll = true;
+
+ // Cancel any running enter animations at this point when we scroll as well
+ if (!mEnterAnimationComplete) {
+ final List<TaskView> taskViews = getTaskViews();
+ for (TaskView tv : taskViews) {
+ tv.cancelEnterRecentsAnimation();
+ }
+ }
+ } else {
+ focusTaskRunnable.run();
+ }
+ mLayoutAlgorithm.animateFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED);
} else {
focusTaskRunnable.run();
}
}
+ return willScroll;
}
/**
* Sets the focused task relative to the currently focused task.
*
+ * @param forward whether to go to the next task in the stack (along the curve) or the previous
* @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
* if the currently focused task is not a stack task, will set the focus
* to the first visible stack task
@@ -663,6 +693,23 @@
* focus.
*/
public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated) {
+ setRelativeFocusedTask(forward, stackTasksOnly, animated, false);
+ }
+
+ /**
+ * Sets the focused task relative to the currently focused task.
+ *
+ * @param forward whether to go to the next task in the stack (along the curve) or the previous
+ * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
+ * if the currently focused task is not a stack task, will set the focus
+ * to the first visible stack task
+ * @param animated determines whether to actually draw the highlight along with the change in
+ * focus.
+ * @param cancelWindowAnimations if set, will attempt to cancel window animations if a scroll
+ * happens
+ */
+ public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated,
+ boolean cancelWindowAnimations) {
int newIndex = -1;
if (mFocusedTaskIndex != -1) {
if (stackTasksOnly) {
@@ -686,8 +733,10 @@
}
}
} else {
- // No restrictions, lets just move to the new task
- newIndex = mFocusedTaskIndex + (forward ? -1 : 1);
+ // No restrictions, lets just move to the new task (looping forward/backwards if
+ // necessary)
+ int taskCount = mStack.getTaskCount();
+ newIndex = (mFocusedTaskIndex + (forward ? -1 : 1) + taskCount) % taskCount;
}
} else {
// We don't have a focused task, so focus the first visible task view
@@ -697,7 +746,12 @@
}
}
if (newIndex != -1) {
- setFocusedTask(newIndex, true, animated);
+ boolean willScroll = setFocusedTask(newIndex, true, animated);
+ if (willScroll && cancelWindowAnimations) {
+ // As we iterate to the next/previous task, cancel any current/lagging window
+ // transition animations
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
+ }
}
}
@@ -933,9 +987,15 @@
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
- boolean occludesLaunchTarget = (launchTargetTask != null) &&
- launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
- tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget,
+ boolean hideTask = false;
+ boolean occludesLaunchTarget = false;
+ if (launchTargetTask != null) {
+ occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(task,
+ launchTargetTask);
+ hideTask = SystemServicesProxy.isFreeformStack(launchTargetTask.key.stackId) &&
+ SystemServicesProxy.isFreeformStack(task.key.stackId);
+ }
+ tv.prepareEnterRecentsAnimation(task.isLaunchTarget, hideTask, occludesLaunchTarget,
offscreenY);
}
@@ -1030,20 +1090,6 @@
}
}
- /** Requests this task stack to start it's dismiss-all animation. */
- public void startDismissAllAnimation(final Runnable postAnimationRunnable) {
- // Clear the focused task
- resetFocusedTask();
- List<TaskView> taskViews = getTaskViews();
- int taskViewCount = taskViews.size();
- int count = 0;
- for (int i = taskViewCount - 1; i >= 0; i--) {
- TaskView tv = taskViews.get(i);
- tv.startDeleteTaskAnimation(i > 0 ? null : postAnimationRunnable, count * 50);
- count++;
- }
- }
-
/** Animates a task view in this stack as it launches. */
public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) {
Task launchTargetTask = tv.getTask();
@@ -1071,8 +1117,11 @@
mLayersDisabled = false;
// Draw the freeform workspace background
- if (mFreeformWorkspaceBackground.getAlpha() > 0) {
- mFreeformWorkspaceBackground.draw(canvas);
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp.hasFreeformWorkspaceSupport()) {
+ if (mFreeformWorkspaceBackground.getAlpha() > 0) {
+ mFreeformWorkspaceBackground.draw(canvas);
+ }
}
super.dispatchDraw(canvas);
@@ -1116,8 +1165,8 @@
mViewPool.returnViewToPool(tv);
}
- // Get the stack scroll of the task to anchor to (since we are removing something, the front
- // most task will be our anchor task)
+ // Get the stack scroll of the task to anchor to (since we are removing something, the
+ // front most task will be our anchor task)
Task anchorTask = null;
float prevAnchorTaskScroll = 0;
boolean pullStackForward = stack.getTaskCount() > 0;
@@ -1134,11 +1183,16 @@
// to ensure that the new front most task is now fully visible
mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP);
} else if (pullStackForward) {
- // Otherwise, offset the scroll by half the movement of the anchor task to allow the
- // tasks behind the removed task to move forward, and the tasks in front to move back
+ // Otherwise, offset the scroll by the movement of the anchor task
float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
- mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
- - prevAnchorTaskScroll) / 2);
+ float newStackScroll = mStackScroller.getStackScroll() +
+ (anchorTaskScroll - prevAnchorTaskScroll);
+ if (mLayoutAlgorithm.getFocusState() != TaskStackLayoutAlgorithm.STATE_FOCUSED) {
+ // If we are focused, we don't want the front task to move, but otherwise, we
+ // allow the back task to move up, and the front task to move back
+ newStackScroll /= 2;
+ }
+ mStackScroller.setStackScroll(newStackScroll);
mStackScroller.boundScroll();
}
@@ -1169,99 +1223,15 @@
}
}
- // If there are no remaining tasks, then either unfilter the current stack, or just close
- // the activity if there are no filtered stacks
+ // If there are no remaining tasks, then just close recents
if (mStack.getTaskCount() == 0) {
- boolean shouldFinishActivity = true;
- if (mStack.hasFilteredTasks()) {
- mStack.unfilterTasks();
- shouldFinishActivity = (mStack.getTaskCount() == 0);
- }
+ boolean shouldFinishActivity = (mStack.getTaskCount() == 0);
if (shouldFinishActivity) {
- mCb.onAllTaskViewsDismissed(null);
+ EventBus.getDefault().send(new AllTaskViewsDismissedEvent());
}
}
}
- @Override
- public void onStackAllTasksRemoved(TaskStack stack, final ArrayList<Task> removedTasks) {
- // Announce for accessibility
- String msg = getContext().getString(R.string.accessibility_recents_all_items_dismissed);
- announceForAccessibility(msg);
-
- startDismissAllAnimation(new Runnable() {
- @Override
- public void run() {
- // Notify that all tasks have been removed
- mCb.onAllTaskViewsDismissed(removedTasks);
- }
- });
- }
-
- @Override
- public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
- Task filteredTask) {
- /*
- // Stash the scroll and filtered task for us to restore to when we unfilter
- mStashedScroll = getStackScroll();
-
- // Calculate the current task transforms
- ArrayList<TaskViewTransform> curTaskTransforms =
- getStackTransforms(curTasks, getStackScroll(), null, true);
-
- // Update the task offsets
- mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks());
-
- // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
- updateLayout(false);
- float overlapHeight = mLayoutAlgorithm.getTaskOverlapHeight();
- setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
- boundScrollRaw();
-
- // Compute the transforms of the items in the new stack after setting the new scroll
- final ArrayList<Task> tasks = mStack.getTasks();
- final ArrayList<TaskViewTransform> taskTransforms =
- getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
-
- // Animate
- mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
-
- // Notify any callbacks
- mCb.onTaskStackFilterTriggered();
- */
- }
-
- @Override
- public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
- /*
- // Calculate the current task transforms
- final ArrayList<TaskViewTransform> curTaskTransforms =
- getStackTransforms(curTasks, getStackScroll(), null, true);
-
- // Update the task offsets
- mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks());
-
- // Restore the stashed scroll
- updateLayout(false);
- setStackScrollRaw(mStashedScroll);
- boundScrollRaw();
-
- // Compute the transforms of the items in the new stack after restoring the stashed scroll
- final ArrayList<Task> tasks = mStack.getTasks();
- final ArrayList<TaskViewTransform> taskTransforms =
- getStackTransforms(tasks, getStackScroll(), null, true);
-
- // Animate
- mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
-
- // Clear the saved vars
- mStashedScroll = 0;
-
- // Notify any callbacks
- mCb.onTaskStackUnfilterTriggered();
- */
- }
-
/**** ViewPoolConsumer Implementation ****/
@Override
@@ -1502,6 +1472,30 @@
requestSynchronizeStackViewsWithModel(175);
}
+ public final void onBusEvent(StackViewScrolledEvent event) {
+ mLayoutAlgorithm.updateFocusStateOnScroll(event.yMovement);
+ }
+
+ public final void onBusEvent(IterateRecentsEvent event) {
+ mLayoutAlgorithm.animateFocusState(mLayoutAlgorithm.getDefaultFocusState());
+ }
+
+ public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
+ mEnterAnimationComplete = true;
+ }
+
+ public final void onBusEvent(UpdateFreeformTaskViewVisibilityEvent event) {
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
+ Task task = tv.getTask();
+ if (SystemServicesProxy.isFreeformStack(task.key.stackId)) {
+ tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+ }
+
/**
* Removes the task from the stack, and updates the focus to the next task in the stack if the
* removed TaskView was focused.
@@ -1521,16 +1515,5 @@
// Remove the task from the stack
mStack.removeTask(task);
-
- if (taskWasFocused || ssp.isTouchExplorationEnabled()) {
- // If the dismissed task was focused or if we are in touch exploration mode, then focus
- // the next task
- RecentsConfiguration config = Recents.getConfiguration();
- RecentsActivityLaunchState launchState = config.getLaunchState();
- boolean isFreeformTask = taskIndex > 0 ?
- mStack.getTasks().get(taskIndex - 1).isFreeformTask() : false;
- setFocusedTask(taskIndex - 1, !isFreeformTask /* scrollToTask */,
- launchState.launchedWithAltTab);
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
deleted file mode 100644
index 45f573d..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.model.Task;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/* The layout logic for a TaskStackView */
-public class TaskStackViewFilterAlgorithm {
-
- TaskStackView mStackView;
- ViewPool<TaskView, Task> mViewPool;
-
- public TaskStackViewFilterAlgorithm(TaskStackView stackView, ViewPool<TaskView, Task> viewPool) {
- mStackView = stackView;
- mViewPool = viewPool;
- }
-
- /** Orchestrates the animations of the current child views and any new views. */
- void startFilteringAnimation(ArrayList<Task> curTasks,
- ArrayList<TaskViewTransform> curTaskTransforms,
- final ArrayList<Task> tasks,
- final ArrayList<TaskViewTransform> taskTransforms) {
- // Calculate the transforms to animate out all the existing views if they are not in the
- // new visible range (or to their final positions in the stack if they are)
- final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>();
- final HashMap<TaskView, TaskViewTransform> childViewTransforms =
- new HashMap<TaskView, TaskViewTransform>();
- int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks,
- taskTransforms, childViewTransforms, childrenToRemove);
-
- // If all the current views are in the visible range of the new stack, then don't wait for
- // views to animate out and animate all the new views into their place
- final boolean unifyNewViewAnimation = childrenToRemove.isEmpty();
- if (unifyNewViewAnimation) {
- int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms,
- childViewTransforms);
- duration = Math.max(duration, inDuration);
- }
-
- // Animate all the views to their final transforms
- for (final TaskView tv : childViewTransforms.keySet()) {
- TaskViewTransform t = childViewTransforms.get(tv);
- tv.animate().cancel();
- tv.animate()
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- childViewTransforms.remove(tv);
- if (childViewTransforms.isEmpty()) {
- // Return all the removed children to the view pool
- for (TaskView tv : childrenToRemove) {
- mViewPool.returnViewToPool(tv);
- }
-
- if (!unifyNewViewAnimation) {
- // For views that are not already visible, animate them in
- childViewTransforms.clear();
- int duration = getEnterTransformsForFilterAnimation(tasks,
- taskTransforms, childViewTransforms);
- for (final TaskView tv : childViewTransforms.keySet()) {
- TaskViewTransform t = childViewTransforms.get(tv);
- tv.updateViewPropertiesToTaskTransform(t, duration);
- }
- }
- }
- }
- });
- tv.updateViewPropertiesToTaskTransform(t, duration);
- }
- }
-
- /**
- * Creates the animations for all the children views that need to be animated in when we are
- * un/filtering a stack, and returns the duration for these animations.
- */
- int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks,
- ArrayList<TaskViewTransform> taskTransforms,
- HashMap<TaskView, TaskViewTransform> childViewTransformsOut) {
- int offset = 0;
- int movement = 0;
- int taskCount = tasks.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- Task task = tasks.get(i);
- TaskViewTransform toTransform = taskTransforms.get(i);
- if (toTransform.visible) {
- TaskView tv = mStackView.getChildViewForTask(task);
- if (tv == null) {
- // For views that are not already visible, animate them in
- tv = mViewPool.pickUpViewFromPool(task, task);
-
- // Compose a new transform to fade and slide the new task in
- TaskViewTransform fromTransform = new TaskViewTransform(toTransform);
- tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
- tv.updateViewPropertiesToTaskTransform(fromTransform, 0);
-
- toTransform.startDelay = offset * 25;
- childViewTransformsOut.put(tv, toTransform);
-
- // Use the movement of the new views to calculate the duration of the animation
- movement = Math.max(movement,
- Math.abs(toTransform.translationY - fromTransform.translationY));
- offset++;
- }
- }
- }
- return mStackView.getResources().getInteger(
- R.integer.recents_filter_animate_new_views_duration);
- }
-
- /**
- * Creates the animations for all the children views that need to be removed or to move views
- * to their un/filtered position when we are un/filtering a stack, and returns the duration
- * for these animations.
- */
- int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks,
- ArrayList<TaskViewTransform> curTaskTransforms,
- ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms,
- HashMap<TaskView, TaskViewTransform> childViewTransformsOut,
- ArrayList<TaskView> childrenToRemoveOut) {
- // Animate all of the existing views out of view (if they are not in the visible range in
- // the new stack) or to their final positions in the new stack
- int offset = 0;
- int movement = 0;
- List<TaskView> taskViews = mStackView.getTaskViews();
- int taskViewCount = taskViews.size();
- for (int i = 0; i < taskViewCount; i++) {
- TaskView tv = taskViews.get(i);
- Task task = tv.getTask();
- int taskIndex = tasks.indexOf(task);
- TaskViewTransform toTransform;
-
- // If the view is no longer visible, then we should just animate it out
- boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible;
- if (willBeInvisible) {
- if (taskIndex < 0) {
- toTransform = curTaskTransforms.get(curTasks.indexOf(task));
- } else {
- toTransform = new TaskViewTransform(taskTransforms.get(taskIndex));
- }
- tv.prepareTaskTransformForFilterTaskVisible(toTransform);
- childrenToRemoveOut.add(tv);
- } else {
- toTransform = taskTransforms.get(taskIndex);
- // Use the movement of the visible views to calculate the duration of the animation
- movement = Math.max(movement, Math.abs(toTransform.translationY -
- (int) tv.getTranslationY()));
- }
-
- toTransform.startDelay = offset * 25;
- childViewTransformsOut.put(tv, toTransform);
- offset++;
- }
- return mStackView.getResources().getInteger(
- R.integer.recents_filter_animate_current_views_duration);
- }
-
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 3a2ed0f..62b640e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -166,12 +166,6 @@
mScrollAnimator.setDuration(mContext.getResources().getInteger(
R.integer.recents_animate_task_stack_scroll_duration));
mScrollAnimator.setInterpolator(mLinearOutSlowInInterpolator);
- mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- setStackScroll((Float) animation.getAnimatedValue());
- }
- });
mScrollAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 81c89a1..907ed2f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -31,10 +31,10 @@
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -59,6 +59,7 @@
boolean mIsScrolling;
float mDownScrollP;
int mDownX, mDownY;
+ int mLastY;
int mActivePointerId = INACTIVE_POINTER_ID;
int mOverscrollSize;
TaskView mActiveTaskView = null;
@@ -150,11 +151,6 @@
if (mSv.getTaskViews().size() == 0) {
return false;
}
- // Short circuit while we are alt-tabbing
- RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
- if (launchState.launchedWithAltTab) {
- return false;
- }
final TaskStackLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
int action = ev.getAction();
@@ -163,6 +159,7 @@
// Save the touch down info
mDownX = (int) ev.getX();
mDownY = (int) ev.getY();
+ mLastY = mDownY;
mDownScrollP = mScroller.getStackScroll();
mActivePointerId = ev.getPointerId(0);
mActiveTaskView = findViewAtPoint(mDownX, mDownY);
@@ -181,6 +178,7 @@
final int index = ev.getActionIndex();
mDownX = (int) ev.getX();
mDownY = (int) ev.getY();
+ mLastY = mDownY;
mDownScrollP = mScroller.getStackScroll();
mActivePointerId = ev.getPointerId(index);
mVelocityTracker.addMovement(ev);
@@ -209,8 +207,10 @@
if (DEBUG) {
Log.d(TAG, "scroll: " + curScrollP);
}
+ EventBus.getDefault().send(new StackViewScrolledEvent(y - mLastY));
}
+ mLastY = y;
mVelocityTracker.addMovement(ev);
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 2c8f316..523f84f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.views;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -29,7 +30,6 @@
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
@@ -40,12 +40,10 @@
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
@@ -155,7 +153,7 @@
}
/** Gets the task */
- Task getTask() {
+ public Task getTask() {
return mTask;
}
@@ -200,7 +198,7 @@
// Measure the content
mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
+ MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
// Measure the bar view, and action button
mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
@@ -211,7 +209,7 @@
// Measure the thumbnail to be square
mThumbnailView.measure(
MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
+ MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
mThumbnailView.updateClipToTaskBar(mHeaderView);
setMeasuredDimension(width, height);
invalidateOutline();
@@ -253,6 +251,7 @@
mActionButtonView.setAlpha(1f);
mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
}
+ setVisibility(View.VISIBLE);
}
/**
@@ -277,12 +276,14 @@
/** Prepares this task view for the enter-recents animations. This is called earlier in the
* first layout because the actual animation into recents may take a long time. */
- void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
- boolean occludesLaunchTarget, int offscreenY) {
+ void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, boolean hideTask,
+ boolean occludesLaunchTarget, int offscreenY) {
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
int initialDim = getDim();
- if (launchState.launchedHasConfigurationChanged) {
+ if (hideTask) {
+ setVisibility(View.INVISIBLE);
+ } else if (launchState.launchedHasConfigurationChanged) {
// Just load the views as-is
} else if (launchState.launchedFromAppWithThumbnail) {
if (isTaskViewLaunchTargetTask) {
@@ -340,15 +341,21 @@
animate().alpha(1f)
.translationY(transform.translationY)
.setUpdateListener(null)
- .setInterpolator(mFastOutSlowInInterpolator)
- .setDuration(taskViewEnterFromHomeDuration)
- .withEndAction(new Runnable() {
+ .setListener(new AnimatorListenerAdapter() {
+ private boolean hasEnded;
+
+ // We use the animation listener instead of withEndAction() to
+ // ensure that onAnimationEnd() is called when the animator is
+ // cancelled
@Override
- public void run() {
- // Decrement the post animation trigger
+ public void onAnimationEnd(Animator animation) {
+ if (hasEnded) return;
ctx.postAnimationTrigger.decrement();
+ hasEnded = true;
}
})
+ .setInterpolator(mFastOutSlowInInterpolator)
+ .setDuration(taskViewEnterFromHomeDuration)
.start();
ctx.postAnimationTrigger.increment();
}
@@ -368,21 +375,30 @@
.translationY(transform.translationY)
.setStartDelay(delay)
.setUpdateListener(ctx.updateListener)
+ .setListener(new AnimatorListenerAdapter() {
+ private boolean hasEnded;
+
+ // We use the animation listener instead of withEndAction() to ensure that
+ // onAnimationEnd() is called when the animator is cancelled
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (hasEnded) return;
+ ctx.postAnimationTrigger.decrement();
+ hasEnded = true;
+ }
+ })
.setInterpolator(mQuintOutInterpolator)
.setDuration(taskViewEnterFromHomeDuration +
frontIndex * taskViewEnterFromHomeStaggerDelay)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- // Decrement the post animation trigger
- ctx.postAnimationTrigger.decrement();
- }
- })
.start();
ctx.postAnimationTrigger.increment();
}
}
+ public void cancelEnterRecentsAnimation() {
+ animate().cancel();
+ }
+
public void fadeInActionButton(int duration) {
// Hide the action button
mActionButtonView.setAlpha(0f);
@@ -409,11 +425,6 @@
ctx.postAnimationTrigger.increment();
}
- /** Animates this task view away when dismissing all tasks. */
- void startDismissAllAnimation() {
- dismissTask();
- }
-
/** Animates this task view as it exits recents */
void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask,
boolean occludesLaunchTarget, boolean lockToTask) {
@@ -619,23 +630,6 @@
setDim(getDimFromTaskProgress());
}
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
-
- RecentsDebugFlags flags = Recents.getDebugFlags();
- if (flags.isFastToggleRecentsEnabled() && mIsFocused) {
- Paint tmpPaint = new Paint();
- Rect tmpRect = new Rect();
- tmpRect.set(0, 0, getWidth(), getHeight());
- tmpPaint.setColor(0xFFFF0000);
- tmpPaint.setStrokeWidth(35);
- tmpPaint.setStyle(Paint.Style.STROKE);
- canvas.drawRect(tmpRect, tmpPaint);
- }
- }
-
/**** View focus state ****/
/**
@@ -666,10 +660,6 @@
clearAccessibilityFocus();
}
}
- RecentsDebugFlags flags = Recents.getDebugFlags();
- if (flags.isFastToggleRecentsEnabled()) {
- invalidate();
- }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 649199e..76c6691 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -16,12 +16,7 @@
package com.android.systemui.recents.views;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
@@ -69,16 +64,13 @@
TextView mActivityDescription;
// Header drawables
- boolean mCurrentPrimaryColorIsDark;
- int mCurrentPrimaryColor;
- int mBackgroundColor;
int mCornerRadius;
int mHighlightHeight;
Drawable mLightDismissDrawable;
Drawable mDarkDismissDrawable;
RippleDrawable mBackground;
GradientDrawable mBackgroundColorDrawable;
- AnimatorSet mFocusAnimator;
+ ObjectAnimator mFocusAnimator;
String mDismissContentDescription;
// Static highlight that we draw at the top of each view
@@ -223,15 +215,12 @@
((ColorDrawable) getBackground()).getColor() : 0;
if (existingBgColor != t.colorPrimary) {
mBackgroundColorDrawable.setColor(t.colorPrimary);
- mBackgroundColor = t.colorPrimary;
}
int taskBarViewLightTextColor = getResources().getColor(
R.color.recents_task_bar_light_text_color);
int taskBarViewDarkTextColor = getResources().getColor(
R.color.recents_task_bar_dark_text_color);
- mCurrentPrimaryColor = t.colorPrimary;
- mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor;
mActivityDescription.setTextColor(t.useLightOnPrimaryColor ?
taskBarViewLightTextColor : taskBarViewDarkTextColor);
mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
@@ -258,7 +247,6 @@
// Stop any focus animations
Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
- mBackground.jumpToCurrentState();
}
/** Updates the resize task bar button. */
@@ -376,84 +364,22 @@
isRunning = mFocusAnimator.isRunning();
}
Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
- mBackground.jumpToCurrentState();
if (focused) {
// If we are not animating the visible state, just return
if (!animateFocusedState) return;
- int currentColor = mBackgroundColor;
- int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
- int[][] states = new int[][] {
- new int[] {},
- new int[] { android.R.attr.state_enabled },
- new int[] { android.R.attr.state_pressed }
- };
- int[] newStates = new int[]{
- 0,
- android.R.attr.state_enabled,
- android.R.attr.state_pressed
- };
- int[] colors = new int[] {
- currentColor,
- secondaryColor,
- secondaryColor
- };
- mBackground.setColor(new ColorStateList(states, colors));
- mBackground.setState(newStates);
- // Pulse the background color
- int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
- ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
- currentColor, lightPrimaryColor);
- backgroundColor.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mBackground.setState(new int[]{});
- }
- });
- backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int color = (int) animation.getAnimatedValue();
- mBackgroundColorDrawable.setColor(color);
- mBackgroundColor = color;
- }
- });
- backgroundColor.setRepeatCount(ValueAnimator.INFINITE);
- backgroundColor.setRepeatMode(ValueAnimator.REVERSE);
- // Pulse the translation
- ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f);
- translation.setRepeatCount(ValueAnimator.INFINITE);
- translation.setRepeatMode(ValueAnimator.REVERSE);
-
- mFocusAnimator = new AnimatorSet();
- mFocusAnimator.playTogether(backgroundColor, translation);
- mFocusAnimator.setStartDelay(150);
- mFocusAnimator.setDuration(750);
+ // Bump up the translation
+ mFocusAnimator = ObjectAnimator.ofFloat(this, "translationZ", 8f);
+ mFocusAnimator.setDuration(200);
mFocusAnimator.start();
} else {
if (isRunning) {
- // Restore the background color
- int currentColor = mBackgroundColor;
- ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
- currentColor, mCurrentPrimaryColor);
- backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int color = (int) animation.getAnimatedValue();
- mBackgroundColorDrawable.setColor(color);
- mBackgroundColor = color;
- }
- });
// Restore the translation
- ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f);
-
- mFocusAnimator = new AnimatorSet();
- mFocusAnimator.playTogether(backgroundColor, translation);
+ mFocusAnimator = ObjectAnimator.ofFloat(this, "translationZ", 0f);
mFocusAnimator.setDuration(150);
mFocusAnimator.start();
} else {
- mBackground.setState(new int[] {});
setTranslationZ(0f);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index f8ab35f..723989a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -80,6 +80,7 @@
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AnimationUtils;
import android.widget.DateTimeView;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -280,6 +281,10 @@
@Override
public boolean onClickHandler(
final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
+ if (handleRemoteInput(view, pendingIntent, fillInIntent)) {
+ return true;
+ }
+
if (DEBUG) {
Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
}
@@ -368,6 +373,65 @@
Intent fillInIntent) {
return super.onClickHandler(view, pendingIntent, fillInIntent);
}
+
+ private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) {
+ Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
+ RemoteInput[] inputs = null;
+ if (tag instanceof RemoteInput[]) {
+ inputs = (RemoteInput[]) tag;
+ }
+
+ if (inputs == null) {
+ return false;
+ }
+
+ RemoteInput input = null;
+
+ for (RemoteInput i : inputs) {
+ if (i.getAllowFreeFormInput()) {
+ input = i;
+ }
+ }
+
+ if (input == null) {
+ return false;
+ }
+
+ ViewParent p = view.getParent();
+ RemoteInputView riv = null;
+ while (p != null) {
+ if (p instanceof View) {
+ View pv = (View) p;
+ if (pv.isRootNamespace()) {
+ riv = (RemoteInputView) pv.findViewWithTag(RemoteInputView.VIEW_TAG);
+ break;
+ }
+ }
+ p = p.getParent();
+ }
+
+ if (riv == null) {
+ return false;
+ }
+
+ riv.setVisibility(View.VISIBLE);
+ int cx = view.getLeft() + view.getWidth() / 2;
+ int cy = view.getTop() + view.getHeight() / 2;
+ int w = riv.getWidth();
+ int h = riv.getHeight();
+ int r = Math.max(
+ Math.max(cx + cy, cx + (h - cy)),
+ Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+ ViewAnimationUtils.createCircularReveal(riv, cx, cy, 0, r)
+ .start();
+
+ riv.setPendingIntent(pendingIntent);
+ riv.setRemoteInput(inputs, input);
+ riv.focus();
+
+ return true;
+ }
+
};
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -1551,15 +1615,15 @@
RemoteInput remoteInput = null;
- // See if the notification has exactly one action and this action allows free-form input
- // TODO: relax restrictions once we support more than one remote input action.
Notification.Action[] actions = entry.notification.getNotification().actions;
- if (actions != null && actions.length == 1) {
- if (actions[0].getRemoteInputs() != null) {
- for (RemoteInput ri : actions[0].getRemoteInputs()) {
- if (ri.getAllowFreeFormInput()) {
- remoteInput = ri;
- break;
+ if (actions != null) {
+ for (Notification.Action a : actions) {
+ if (a.getRemoteInputs() != null) {
+ for (RemoteInput ri : a.getRemoteInputs()) {
+ if (ri.getAllowFreeFormInput()) {
+ remoteInput = ri;
+ break;
+ }
}
}
}
@@ -1569,32 +1633,36 @@
if (remoteInput != null) {
View bigContentView = entry.getExpandedContentView();
if (bigContentView != null) {
- inflateRemoteInput(bigContentView, entry, remoteInput, actions);
+ inflateRemoteInput(bigContentView, entry);
}
View headsUpContentView = entry.getHeadsUpContentView();
if (headsUpContentView != null) {
- inflateRemoteInput(headsUpContentView, entry, remoteInput, actions);
+ inflateRemoteInput(headsUpContentView, entry);
}
}
}
- private void inflateRemoteInput(View view, Entry entry, RemoteInput remoteInput,
- Notification.Action[] actions) {
- View actionContainerCandidate = view.findViewById(com.android.internal.R.id.actions);
- if (actionContainerCandidate instanceof ViewGroup) {
- ViewGroup actionContainer = (ViewGroup) actionContainerCandidate;
- RemoteInputView riv = inflateRemoteInputView(actionContainer, entry,
- actions[0], remoteInput);
+ private RemoteInputView inflateRemoteInput(View view, Entry entry) {
+ View actionContainerCandidate = view.findViewById(
+ com.android.internal.R.id.actions_container);
+ if (actionContainerCandidate instanceof FrameLayout) {
+ ViewGroup actionContainer = (FrameLayout) actionContainerCandidate;
+ RemoteInputView riv = inflateRemoteInputView(actionContainer, entry);
if (riv != null) {
- actionContainer.removeAllViews();
- actionContainer.addView(riv);
+ riv.setVisibility(View.INVISIBLE);
+ actionContainer.addView(riv, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT)
+ );
+ riv.setBackgroundColor(entry.notification.getNotification().color);
+ return riv;
}
}
+ return null;
}
- protected RemoteInputView inflateRemoteInputView(ViewGroup root, Entry entry,
- Notification.Action action, RemoteInput remoteInput) {
+ protected RemoteInputView inflateRemoteInputView(ViewGroup root, Entry entry) {
return null;
}
@@ -2024,7 +2092,7 @@
Notification n = notification.getNotification();
mNotificationData.updateRanking(ranking);
- boolean applyInPlace = !entry.cacheContentViews(mContext, notification.getNotification());
+ boolean applyInPlace = entry.cacheContentViews(mContext, notification.getNotification());
boolean shouldInterrupt = shouldInterrupt(entry, notification);
boolean alertAgain = alertAgain(entry, n);
if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 0466c14..742be02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -197,8 +197,8 @@
? (int) event.getRawY()
: (int) event.getRawX(),
!mIsVertical
- ? mVelocityTracker.getXVelocity()
- : mVelocityTracker.getYVelocity());
+ ? mVelocityTracker.getYVelocity()
+ : mVelocityTracker.getXVelocity());
} else if (mDragMode == DRAG_MODE_RECENTS) {
mRecentsComponent.onDraggingInRecentsEnded(mVelocityTracker.getYVelocity());
}
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 781d651..0f182ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1102,10 +1102,8 @@
}
@Override
- protected RemoteInputView inflateRemoteInputView(ViewGroup root, Entry entry,
- Notification.Action action, RemoteInput remoteInput) {
- return RemoteInputView.inflate(mContext, root, entry, action, remoteInput,
- mRemoteInputController);
+ protected RemoteInputView inflateRemoteInputView(ViewGroup root, Entry entry) {
+ return RemoteInputView.inflate(mContext, root, entry, mRemoteInputController);
}
public int getStatusBarHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 2ad9287..acfe54d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -34,11 +34,13 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
-import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -47,16 +49,21 @@
/**
* Host for the remote input.
*/
-public class RemoteInputView extends FrameLayout implements View.OnClickListener {
+public class RemoteInputView extends LinearLayout implements View.OnClickListener {
private static final String TAG = "RemoteInput";
+ // A marker object that let's us easily find views of this class.
+ public static final Object VIEW_TAG = new Object();
+
private RemoteEditText mEditText;
+ private ImageButton mSendButton;
private ProgressBar mProgressBar;
private PendingIntent mPendingIntent;
+ private RemoteInput[] mRemoteInputs;
private RemoteInput mRemoteInput;
- private Notification.Action mAction;
private RemoteInputController mController;
+
private NotificationData.Entry mEntry;
public RemoteInputView(Context context, AttributeSet attrs) {
@@ -69,6 +76,9 @@
mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress);
+ mSendButton = (ImageButton) findViewById(R.id.remote_input_send);
+ mSendButton.setOnClickListener(this);
+
mEditText = (RemoteEditText) getChildAt(0);
mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
@@ -99,10 +109,11 @@
Bundle results = new Bundle();
results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- RemoteInput.addResultsToIntent(mAction.getRemoteInputs(), fillInIntent,
+ RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
results);
mEditText.setEnabled(false);
+ mSendButton.setVisibility(INVISIBLE);
mProgressBar.setVisibility(VISIBLE);
try {
@@ -113,17 +124,13 @@
}
public static RemoteInputView inflate(Context context, ViewGroup root,
- NotificationData.Entry entry, Notification.Action action, RemoteInput remoteInput,
+ NotificationData.Entry entry,
RemoteInputController controller) {
RemoteInputView v = (RemoteInputView)
LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
-
- v.mEditText.setHint(action.title);
- v.mPendingIntent = action.actionIntent;
- v.mRemoteInput = remoteInput;
- v.mAction = action;
v.mController = controller;
v.mEntry = entry;
+ v.setTag(VIEW_TAG);
return v;
}
@@ -132,15 +139,16 @@
public void onClick(View v) {
if (v == mEditText) {
if (!mEditText.isFocusable()) {
- mEditText.setInnerFocusable(true);
- mController.addRemoteInput(mEntry);
- mEditText.mShowImeOnInputConnection = true;
+ focus();
}
+ } else if (v == mSendButton) {
+ sendRemoteInput();
}
}
public void onDefocus() {
mController.removeRemoteInput(mEntry);
+ setVisibility(INVISIBLE);
}
@Override
@@ -149,6 +157,23 @@
mController.removeRemoteInput(mEntry);
}
+ public void setPendingIntent(PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ }
+
+ public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
+ mRemoteInputs = remoteInputs;
+ mRemoteInput = remoteInput;
+ mEditText.setHint(mRemoteInput.getLabel());
+ }
+
+ public void focus() {
+ mEditText.setInnerFocusable(true);
+ mController.addRemoteInput(mEntry);
+ mEditText.mShowImeOnInputConnection = true;
+ mEditText.requestFocus();
+ }
+
/**
* An EditText that changes appearance based on whether it's focusable and becomes
* un-focusable whenever the user navigates away from it or it becomes invisible.
@@ -220,6 +245,13 @@
return inputConnection;
}
+ @Override
+ public void onCommitCompletion(CompletionInfo text) {
+ clearComposingText();
+ setText(text.getText());
+ setSelection(getText().length());
+ }
+
void setInnerFocusable(boolean focusable) {
setFocusableInTouchMode(focusable);
setFocusable(focusable);
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index c5ce2fd..f6af942 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -2251,7 +2251,7 @@
public boolean matches(PendingIntent pi, IAlarmListener rec) {
return (operation != null)
? operation.equals(pi)
- : listener.asBinder().equals(rec.asBinder());
+ : rec != null && listener.asBinder().equals(rec.asBinder());
}
public boolean matches(String packageName) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 29173a5..94a908c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1274,6 +1274,7 @@
boolean mAlwaysFinishActivities = false;
boolean mForceResizableActivities;
boolean mSupportsFreeformWindowManagement;
+ boolean mTakeFullscreenScreenshots;
IActivityController mController = null;
String mProfileApp = null;
ProcessRecord mProfileProc = null;
@@ -1646,7 +1647,7 @@
break;
}
case START_USER_SWITCH_UI_MSG: {
- mUserController.showUserSwitchDialog(msg.arg1, (String) msg.obj);
+ mUserController.showUserSwitchDialog((Pair<UserInfo, UserInfo>) msg.obj);
break;
}
case DISMISS_DIALOG_UI_MSG: {
@@ -4320,7 +4321,8 @@
}
if (task.stack.mStackId != launchStackId) {
mStackSupervisor.moveTaskToStackLocked(
- taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents");
+ taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents",
+ true /* animate */);
}
}
@@ -9239,7 +9241,7 @@
if (DEBUG_STACK) Slog.d(TAG_STACK, "moveActivityToStack: moving r=" + r
+ " to stackId=" + stackId);
mStackSupervisor.moveTaskToStackLocked(r.task.taskId, stackId, ON_TOP, !FORCE_FOCUS,
- "moveActivityToStack");
+ "moveActivityToStack", true /* animate */);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -9260,7 +9262,7 @@
if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId
+ " to stackId=" + stackId + " toTop=" + toTop);
mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop, !FORCE_FOCUS,
- "moveTaskToStack");
+ "moveTaskToStack", true /* animate */);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -9277,9 +9279,10 @@
* and
* {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT}
* @param toTop If the task and stack should be moved to the top.
+ * @param animate Whether we should play an animation for the moving the task
*/
@Override
- public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop) {
+ public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate) {
enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
"moveTaskToDockedStack()");
synchronized (this) {
@@ -9289,7 +9292,8 @@
+ " to createMode=" + createMode + " toTop=" + toTop);
mWindowManager.setDockedStackCreateMode(createMode);
mStackSupervisor.moveTaskToStackLocked(
- taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS, "moveTaskToDockedStack");
+ taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS, "moveTaskToDockedStack",
+ animate);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -11878,9 +11882,13 @@
mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT);
final String debugApp = Settings.Global.getString(resolver, DEBUG_APP);
+ final String fsScreenshots = Settings.Secure.getString(resolver,
+ "overview_fullscreen_thumbnails");
final boolean waitForDebugger = Settings.Global.getInt(resolver, WAIT_FOR_DEBUGGER, 0) != 0;
final boolean alwaysFinishActivities =
Settings.Global.getInt(resolver, ALWAYS_FINISH_ACTIVITIES, 0) != 0;
+ final boolean takeFullscreenScreenshots = fsScreenshots != null &&
+ Integer.parseInt(fsScreenshots) != 0;
final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0;
final int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0;
final boolean forceResizable = Settings.Global.getInt(
@@ -11901,6 +11909,7 @@
mAlwaysFinishActivities = alwaysFinishActivities;
mForceResizableActivities = forceResizable;
mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable;
+ mTakeFullscreenScreenshots = takeFullscreenScreenshots;
// This happens before any activities are started, so we can
// change mConfiguration in-place.
updateConfigurationLocked(configuration, null, true);
@@ -20248,25 +20257,27 @@
}
@Override
- public boolean switchUser(final int userId) {
- enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
- String userName;
+ public boolean switchUser(final int targetUserId) {
+ enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId);
+ UserInfo currentUserInfo;
+ UserInfo targetUserInfo;
synchronized (this) {
- UserInfo userInfo = mUserController.getUserInfo(userId);
- if (userInfo == null) {
- Slog.w(TAG, "No user info for user #" + userId);
+ int currentUserId = mUserController.getCurrentUserIdLocked();
+ currentUserInfo = mUserController.getUserInfo(currentUserId);
+ targetUserInfo = mUserController.getUserInfo(targetUserId);
+ if (targetUserInfo == null) {
+ Slog.w(TAG, "No user info for user #" + targetUserId);
return false;
}
- if (userInfo.isManagedProfile()) {
- Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
+ if (targetUserInfo.isManagedProfile()) {
+ Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
return false;
}
- userName = userInfo.name;
- mUserController.setTargetUserIdLocked(userId);
+ mUserController.setTargetUserIdLocked(targetUserId);
}
+ Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
- mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_UI_MSG, userId, 0,
- userName));
+ mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_UI_MSG, userNames));
return true;
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index e28d198..6c68f88 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -54,6 +54,7 @@
import android.app.ResultInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
@@ -72,6 +73,7 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
import android.util.EventLog;
import android.util.Slog;
@@ -836,8 +838,8 @@
}
}
- public final Bitmap screenshotActivities(ActivityRecord who) {
- if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "screenshotActivities: " + who);
+ public final Bitmap screenshotActivitiesLocked(ActivityRecord who) {
+ if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "screenshotActivitiesLocked: " + who);
if (who.noDisplay) {
if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tNo display");
return null;
@@ -852,10 +854,21 @@
int w = mService.mThumbnailWidth;
int h = mService.mThumbnailHeight;
+ float scale = 1f;
if (w > 0) {
if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tTaking screenshot");
+
+ // When this flag is set, we currently take the fullscreen screenshot of the activity
+ // but scaled inversely by the density. This gives us a "good-enough" fullscreen
+ // thumbnail to use within SystemUI without using enormous amounts of memory on high
+ // density devices.
+ if (mService.mTakeFullscreenScreenshots) {
+ Context context = mService.mContext;
+ w = h = -1;
+ scale = (1f / Math.max(1f, context.getResources().getDisplayMetrics().density));
+ }
return mWindowManager.screenshotApplications(who.appToken, Display.DEFAULT_DISPLAY,
- w, h);
+ w, h, scale);
}
Slog.e(TAG, "Invalid thumbnail dimensions: " + w + "x" + h);
return null;
@@ -914,7 +927,7 @@
final ActivityRecord next = mStackSupervisor.topRunningActivityLocked();
if (mService.mHasRecents
&& (next == null || next.noDisplay || next.task != prev.task || uiSleeping)) {
- prev.updateThumbnailLocked(screenshotActivities(prev), null);
+ prev.updateThumbnailLocked(screenshotActivitiesLocked(prev), null);
}
stopFullyDrawnTraceIfNeeded();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 124d2ef..507d76d 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3100,7 +3100,8 @@
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, "resizeStack");
+ FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS, "resizeStack",
+ true /* animate */);
}
// stack shouldn't contain anymore activities, so nothing to resume.
@@ -3336,7 +3337,7 @@
}
void moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
- String reason) {
+ String reason, boolean animate) {
final TaskRecord task = anyTaskForIdLocked(taskId);
if (task == null) {
Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
@@ -3357,7 +3358,7 @@
// preserve the old window until the new one is drawn. This prevents having a gap
// between the removal and addition, in which no window is visible. We also want the
// entrance of the new window to be properly animated.
- mWindowManager.setReplacingWindow(topActivity.appToken);
+ mWindowManager.setReplacingWindow(topActivity.appToken, animate);
}
final ActivityStack stack = moveTaskToStackUncheckedLocked(
task, stackId, toTop, forceFocus, "moveTaskToStack:" + reason);
@@ -3405,7 +3406,7 @@
// 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,
- "moveTopActivityToPinnedStack");
+ "moveTopActivityToPinnedStack", true /* animate */);
} else {
final ActivityStack pinnedStack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
pinnedStack.moveActivityToStack(r);
@@ -3690,7 +3691,7 @@
void handleLaunchTaskBehindCompleteLocked(ActivityRecord r) {
r.mLaunchTaskBehind = false;
final TaskRecord task = r.task;
- task.setLastThumbnailLocked(task.stack.screenshotActivities(r));
+ task.setLastThumbnailLocked(task.stack.screenshotActivitiesLocked(r));
mRecentTasks.addLocked(task);
mService.notifyTaskStackChangedLocked();
mWindowManager.setAppVisibility(r.appToken, false);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 02cf87a..755db6f 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -834,7 +834,7 @@
if (stack != null) {
final ActivityRecord resumedActivity = stack.mResumedActivity;
if (resumedActivity != null && resumedActivity.task == this) {
- final Bitmap thumbnail = stack.screenshotActivities(resumedActivity);
+ final Bitmap thumbnail = stack.screenshotActivitiesLocked(resumedActivity);
setLastThumbnailLocked(thumbnail);
}
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 4b2a55f..b30905e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -61,6 +61,7 @@
import android.os.UserManager;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -641,10 +642,10 @@
return true;
}
- void showUserSwitchDialog(int userId, String userName) {
+ void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
// The dialog will show and then initiate the user switch by calling startUserInForeground
- Dialog d = new UserSwitchingDialog(mService, mService.mContext, userId, userName,
- true /* above system */);
+ Dialog d = new UserSwitchingDialog(mService, mService.mContext, fromToUserPair.first,
+ fromToUserPair.second, true /* above system */);
d.show();
}
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 28b4096..10e88e6 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -18,9 +18,12 @@
import android.app.AlertDialog;
import android.content.Context;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -49,20 +52,26 @@
@GuardedBy("this")
private boolean mStartedUser;
- public UserSwitchingDialog(ActivityManagerService service, Context context,
- int userId, String userName, boolean aboveSystem) {
+ public UserSwitchingDialog(ActivityManagerService service, Context context, UserInfo oldUser,
+ UserInfo newUser, boolean aboveSystem) {
super(context);
mService = service;
- mUserId = userId;
+ mUserId = newUser.id;
// Set up the dialog contents
setCancelable(false);
Resources res = getContext().getResources();
// Custom view due to alignment and font size requirements
View view = LayoutInflater.from(getContext()).inflate(R.layout.user_switching_dialog, null);
- ((TextView) view.findViewById(R.id.message)).setText(
- res.getString(com.android.internal.R.string.user_switching_message, userName));
+
+ String viewMessage;
+ if (UserManager.isSplitSystemUser() && newUser.id == UserHandle.USER_SYSTEM) {
+ viewMessage = res.getString(R.string.user_logging_out_message, oldUser.name);
+ } else {
+ viewMessage = res.getString(R.string.user_switching_message, newUser.name);
+ }
+ ((TextView) view.findViewById(R.id.message)).setText(viewMessage);
setView(view);
if (aboveSystem) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fe9fe50..75886aa 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5151,12 +5151,12 @@
UserInfo userInfo = UserManagerService.getInstance().getUserInfo(userId);
killBackgroundUserProcessesWithRecordAudioPermission(userInfo);
}
- UserManagerService.getInstance().setSystemControlledUserRestriction(
+ UserManagerService.getInstance().setUserRestriction(
UserManager.DISALLOW_RECORD_AUDIO, true, userId);
} else if (action.equals(Intent.ACTION_USER_FOREGROUND)) {
// Enable audio recording for foreground user/profile
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- UserManagerService.getInstance().setSystemControlledUserRestriction(
+ UserManagerService.getInstance().setUserRestriction(
UserManager.DISALLOW_RECORD_AUDIO, false, userId);
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fd036a7..558ea58 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -781,18 +781,6 @@
@Override
public void setUserRestriction(String key, boolean value, int userId) {
checkManageUsersPermission("setUserRestriction");
- if (!UserRestrictionsUtils.isSystemControlled(key)) {
- setUserRestrictionNoCheck(key, value, userId);
- }
- }
-
- @Override
- public void setSystemControlledUserRestriction(String key, boolean value, int userId) {
- checkSystemOrRoot("setSystemControlledUserRestriction");
- setUserRestrictionNoCheck(key, value, userId);
- }
-
- private void setUserRestrictionNoCheck(String key, boolean value, int userId) {
synchronized (mRestrictionsLock) {
// Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create
// a copy.
@@ -1561,8 +1549,6 @@
final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
final long ident = Binder.clearCallingIdentity();
- final DevicePolicyManager devicePolicyManager = (DevicePolicyManager) mContext
- .getSystemService(Context.DEVICE_POLICY_SERVICE);
UserInfo userInfo;
final int userId;
try {
@@ -1605,22 +1591,13 @@
return null;
}
}
- if (devicePolicyManager != null) {
- int deviceOwnerUserId = devicePolicyManager.getDeviceOwnerUserId();
- // If there is a device owner, completely disallow multiple user in non-split
- // user devices. In split user devices, no further users can be added If there
- // is a device owner outside of the system user.
- if (deviceOwnerUserId != UserHandle.USER_NULL
- && (!UserManager.isSplitSystemUser()
- || deviceOwnerUserId != UserHandle.USER_SYSTEM)) {
- return null;
- }
- }
// In split system user mode, we assign the first human user the primary flag.
// And if there is no device owner, we also assign the admin flag to primary user.
if (UserManager.isSplitSystemUser()
&& !isGuest && !isManagedProfile && getPrimaryUser() == null) {
flags |= UserInfo.FLAG_PRIMARY;
+ DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
if (devicePolicyManager == null
|| devicePolicyManager.getDeviceOwner() == null) {
flags |= UserInfo.FLAG_ADMIN;
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 129cbd3..85453a8 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -50,7 +50,7 @@
private UserRestrictionsUtils() {
}
- public static final String[] USER_RESTRICTIONS = {
+ public static final Set<String> USER_RESTRICTIONS = Sets.newArraySet(
UserManager.DISALLOW_CONFIG_WIFI,
UserManager.DISALLOW_MODIFY_ACCOUNTS,
UserManager.DISALLOW_INSTALL_APPS,
@@ -84,14 +84,7 @@
UserManager.DISALLOW_SAFE_BOOT,
UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
UserManager.DISALLOW_RECORD_AUDIO,
- UserManager.DISALLOW_CAMERA,
- };
-
- /**
- * Set of user restrictions, which can only be enforced by the system.
- */
- public static final Set<String> SYSTEM_CONTROLLED_USER_RESTRICTIONS = Sets.newArraySet(
- UserManager.DISALLOW_RECORD_AUDIO
+ UserManager.DISALLOW_CAMERA
);
/**
@@ -143,11 +136,17 @@
}
serializer.startTag(null, tag);
- for (String key : USER_RESTRICTIONS) {
- if (restrictions.getBoolean(key)
- && !NON_PERSIST_USER_RESTRICTIONS.contains(key)) {
- serializer.attribute(null, key, "true");
+ for (String key : restrictions.keySet()) {
+ if (NON_PERSIST_USER_RESTRICTIONS.contains(key)) {
+ continue; // Don't persist.
}
+ if (USER_RESTRICTIONS.contains(key)) {
+ if (restrictions.getBoolean(key)) {
+ serializer.attribute(null, key, "true");
+ }
+ continue;
+ }
+ Log.w(TAG, "Unknown user restriction detected: " + key);
}
serializer.endTag(null, tag);
}
@@ -198,14 +197,6 @@
}
/**
- * @return true if a restriction is "system controlled"; i.e. can not be overwritten via
- * {@link UserManager#setUserRestriction}.
- */
- public static boolean isSystemControlled(String restriction) {
- return SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(restriction);
- }
-
- /**
* @return true if a restriction is settable by device owner.
*/
public static boolean canDeviceOwnerChange(String restriction) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 3f4eaac..425ff9b 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -128,6 +128,8 @@
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;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 74750a9..fdccc3f 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -281,7 +281,7 @@
}
}
- void broadcastDragEndedLw() {
+ private void broadcastDragEndedLw() {
final int myPid = Process.myPid();
if (WindowManagerService.DEBUG_DRAG) {
@@ -313,6 +313,9 @@
}
void endDragLw() {
+ if (mAnimation != null) {
+ return;
+ }
if (!mDragResult) {
mAnimation = createReturnAnimationLocked();
mService.scheduleAnimationLocked();
@@ -322,7 +325,7 @@
}
- void cleanUpDragLw() {
+ private void cleanUpDragLw() {
broadcastDragEndedLw();
// stop intercepting input
@@ -336,6 +339,9 @@
}
void notifyMoveLw(float x, float y) {
+ if (mAnimation != null) {
+ return;
+ }
mCurrentX = x;
mCurrentY = y;
@@ -410,6 +416,9 @@
// result from the recipient.
boolean notifyDropLw(WindowState touchedWin, DropPermissionHolder dropPermissionHolder,
float x, float y) {
+ if (mAnimation != null) {
+ return false;
+ }
mCurrentX = x;
mCurrentY = y;
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index f5e4e3b..804830e 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -196,7 +196,7 @@
? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
: DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
mService.mActivityManager.moveTaskToDockedStack(
- mTask.mTaskId, createMode, true /*toTop*/);
+ mTask.mTaskId, createMode, true /*toTop*/, true /* animate */);
}
} catch(RemoteException e) {}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e741c45..c7fccc3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2079,7 +2079,7 @@
}
private void prepareWindowReplacementTransition(AppWindowToken atoken) {
- if (atoken == null || !atoken.mWillReplaceWindow) {
+ if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) {
return;
}
atoken.allDrawn = false;
@@ -2951,7 +2951,7 @@
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 fullscreen = win != null && win.isFrameFullscreen(displayInfo);
final boolean freeform = win != null && win.inFreeformWorkspace();
final boolean docked = win != null && win.inDockedWorkspace();
if (win != null) {
@@ -5717,7 +5717,7 @@
@Override
public void run() {
Bitmap bm = screenshotApplicationsInner(null, Display.DEFAULT_DISPLAY, -1, -1,
- true);
+ true, 1f);
try {
receiver.send(bm);
} catch (RemoteException e) {
@@ -5736,18 +5736,20 @@
* @param displayId the Display to take a screenshot of.
* @param width the width of the target bitmap
* @param height the height of the target bitmap
+ * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
*/
@Override
- public Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height) {
+ public Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height,
+ float frameScale) {
if (!checkCallingPermission(Manifest.permission.READ_FRAME_BUFFER,
"screenshotApplications()")) {
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
- return screenshotApplicationsInner(appToken, displayId, width, height, false);
+ return screenshotApplicationsInner(appToken, displayId, width, height, false, frameScale);
}
Bitmap screenshotApplicationsInner(IBinder appToken, int displayId, int width, int height,
- boolean includeFullDisplay) {
+ boolean includeFullDisplay, float frameScale) {
final DisplayContent displayContent;
synchronized(mWindowMap) {
displayContent = getDisplayContentLocked(displayId);
@@ -5876,7 +5878,7 @@
screenshotReady = true;
}
- if (ws.isFullscreen(dw, dh) && ws.isOpaqueDrawn()){
+ if (ws.isObscuringFullscreen(displayInfo)){
break;
}
}
@@ -5927,10 +5929,10 @@
}
if (width < 0) {
- width = frame.width();
+ width = (int) (frame.width() * frameScale);
}
if (height < 0) {
- height = frame.height();
+ height = (int) (frame.height() * frameScale);
}
// Tell surface flinger what part of the image to crop. Take the top
@@ -8569,7 +8571,8 @@
} else if (wtoken != null) {
winAnimator.mAnimLayer =
w.mLayer + wtoken.mAppAnimator.animLayerAdjustment;
- if (wtoken.mWillReplaceWindow && wtoken.mReplacingWindow != w) {
+ if (wtoken.mWillReplaceWindow && wtoken.mReplacingWindow != w
+ && wtoken.mAnimateReplacingWindow) {
// 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.
@@ -10107,7 +10110,7 @@
* a window.
* @param token Application token for which the activity will be relaunched.
*/
- public void setReplacingWindow(IBinder token) {
+ public void setReplacingWindow(IBinder token, boolean animate) {
synchronized (mWindowMap) {
AppWindowToken appWindowToken = findAppWindowToken(token);
if (appWindowToken == null) {
@@ -10118,13 +10121,16 @@
+ " as replacing window.");
appWindowToken.mWillReplaceWindow = true;
appWindowToken.mHasReplacedWindow = false;
+ appWindowToken.mAnimateReplacingWindow = animate;
- // Set-up dummy animation so we can start treating windows associated with this token
- // like they are in transition before the new app window is ready for us to run the
- // real transition animation.
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
- "setReplacingWindow() Setting dummy animation on: " + appWindowToken);
- appWindowToken.mAppAnimator.setDummyAnimation();
+ if (animate) {
+ // Set-up dummy animation so we can start treating windows associated with this
+ // token like they are in transition before the new app window is ready for us to
+ // run the real transition animation.
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+ "setReplacingWindow() Setting dummy animation on: " + appWindowToken);
+ appWindowToken.mAppAnimator.setDummyAnimation();
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c6f7f4c..01db707 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1250,9 +1250,20 @@
&& (mAttachedWindow == null || !mAttachedWindow.hasMoved());
}
- boolean isFullscreen(int screenWidth, int screenHeight) {
- return mFrame.left <= 0 && mFrame.top <= 0 &&
- mFrame.right >= screenWidth && mFrame.bottom >= screenHeight;
+ boolean isObscuringFullscreen(final DisplayInfo displayInfo) {
+ Task task = getTask();
+ if (task != null && task.mStack != null && !task.mStack.isFullscreen()) {
+ return false;
+ }
+ if (!isOpaqueDrawn() || !isFrameFullscreen(displayInfo)) {
+ return false;
+ }
+ return true;
+ }
+
+ boolean isFrameFullscreen(final DisplayInfo displayInfo) {
+ return mFrame.left <= 0 && mFrame.top <= 0
+ && mFrame.right >= displayInfo.appWidth && mFrame.bottom >= displayInfo.appHeight;
}
boolean isConfigChanged() {
@@ -1389,6 +1400,7 @@
&& token.mHasReplacedWindow) {
if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this);
token.mWillReplaceWindow = false;
+ token.mAnimateReplacingWindow = false;
token.mReplacingRemoveRequested = false;
token.mReplacingWindow = null;
token.mHasReplacedWindow = false;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 004307e..93c2ff6 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1106,7 +1106,7 @@
applyDecorRect(w.mDecorFrame);
}
- final boolean fullscreen = w.isFullscreen(displayInfo.appWidth, displayInfo.appHeight);
+ final boolean fullscreen = w.isFrameFullscreen(displayInfo);
final boolean isFreeformResizing =
w.isDragResizing() && w.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
final Rect clipRect = mTmpClipRect;
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 979f55b..6292c21 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -661,7 +661,7 @@
// Update effect.
w.mObscured = mObscured;
if (!mObscured) {
- handleNotObscuredLocked(w, innerDw, innerDh);
+ handleNotObscuredLocked(w, displayInfo);
}
w.applyDimLayerIfNeeded();
@@ -1332,16 +1332,14 @@
/**
* @param w WindowState this method is applied to.
- * @param innerDw Width of app window.
- * @param innerDh Height of app window.
+ * @param dispInfo info of the display that the window's obscuring state is checked against.
*/
- private void handleNotObscuredLocked(final WindowState w, final int innerDw, final int innerDh) {
+ private void handleNotObscuredLocked(final WindowState w, final DisplayInfo dispInfo) {
final WindowManager.LayoutParams attrs = w.mAttrs;
final int attrFlags = attrs.flags;
final boolean canBeSeen = w.isDisplayedLw();
- final boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn();
- if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) {
+ if (canBeSeen && w.isObscuringFullscreen(dispInfo)) {
// This window completely covers everything behind it,
// so we want to leave all of them as undimmed (for
// performance reasons).
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3ae2751..8beee73 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1386,11 +1386,11 @@
}
migrated = true;
- // Migrate user 0 restrictions to DO, except for "system" restrictions.
+ // Migrate user 0 restrictions to DO.
final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked();
migrateUserRestrictionsForUser(UserHandle.SYSTEM, deviceOwnerAdmin,
- /* exceptionList =*/ UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS);
+ /* exceptionList =*/ null);
// Push DO user restrictions to user manager.
pushUserRestrictions(UserHandle.USER_SYSTEM);
@@ -1402,7 +1402,6 @@
final Set<String> normalExceptionList = Sets.newArraySet(
UserManager.DISALLOW_OUTGOING_CALLS,
UserManager.DISALLOW_SMS);
- normalExceptionList.addAll(UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS);
final Set<String> managedExceptionList = new ArraySet<>(normalExceptionList.size() + 1);
managedExceptionList.addAll(normalExceptionList);
@@ -1446,15 +1445,15 @@
final Bundle origRestrictions = mUserManagerInternal.getBaseUserRestrictions(
user.getIdentifier());
- final Bundle newSystemRestrictions = new Bundle();
+ final Bundle newBaseRestrictions = new Bundle();
final Bundle newOwnerRestrictions = new Bundle();
for (String key : origRestrictions.keySet()) {
if (!origRestrictions.getBoolean(key)) {
continue;
}
- if (exceptionList.contains(key)) {
- newSystemRestrictions.putBoolean(key, true);
+ if (exceptionList!= null && exceptionList.contains(key)) {
+ newBaseRestrictions.putBoolean(key, true);
} else {
newOwnerRestrictions.putBoolean(key, true);
}
@@ -1462,11 +1461,11 @@
if (VERBOSE_LOG) {
Log.v(LOG_TAG, "origRestrictions=" + origRestrictions);
- Log.v(LOG_TAG, "newSystemRestrictions=" + newSystemRestrictions);
+ Log.v(LOG_TAG, "newBaseRestrictions=" + newBaseRestrictions);
Log.v(LOG_TAG, "newOwnerRestrictions=" + newOwnerRestrictions);
}
mUserManagerInternal.setBaseUserRestrictionsByDpmsForMigration(user.getIdentifier(),
- newSystemRestrictions);
+ newBaseRestrictions);
if (admin != null) {
admin.ensureUserRestrictions().clear();
@@ -4628,11 +4627,6 @@
}
}
- @Override
- public int getDeviceOwnerUserId() {
- return mOwners.hasDeviceOwner() ? mOwners.getDeviceOwnerUserId() : UserHandle.USER_NULL;
- }
-
// Returns the active device owner or null if there is no device owner.
@VisibleForTesting
ActiveAdmin getDeviceOwnerAdminLocked() {
@@ -6847,4 +6841,30 @@
final int targetSdkVersion = ai == null ? 0 : ai.targetSdkVersion;
return targetSdkVersion;
}
+
+ @Override
+ public boolean isManagedProfile(ComponentName admin) {
+ synchronized (this) {
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ }
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
+ final UserInfo user;
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ user = mUserManager.getUserInfo(callingUserId);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ return user != null && user.isManagedProfile();
+ }
+
+ @Override
+ public boolean isSystemOnlyUser(ComponentName admin) {
+ synchronized (this) {
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
+ return UserManager.isSplitSystemUser() && callingUserId == UserHandle.USER_SYSTEM;
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index dfa9f8f..f32f209 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -145,16 +145,13 @@
// Check the new base restrictions.
DpmTestUtils.assertRestrictions(
- DpmTestUtils.newRestrictions(
- UserManager.DISALLOW_RECORD_AUDIO
- ),
+ DpmTestUtils.newRestrictions(),
newBaseRestrictions.get(UserHandle.USER_SYSTEM));
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
UserManager.DISALLOW_SMS,
- UserManager.DISALLOW_OUTGOING_CALLS,
- UserManager.DISALLOW_RECORD_AUDIO
+ UserManager.DISALLOW_OUTGOING_CALLS
),
newBaseRestrictions.get(10));
@@ -162,28 +159,30 @@
DpmTestUtils.newRestrictions(
UserManager.DISALLOW_SMS,
UserManager.DISALLOW_OUTGOING_CALLS,
- UserManager.DISALLOW_WALLPAPER,
- UserManager.DISALLOW_RECORD_AUDIO
+ UserManager.DISALLOW_WALLPAPER
),
newBaseRestrictions.get(11));
// Check the new owner restrictions.
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
- UserManager.DISALLOW_ADD_USER
+ UserManager.DISALLOW_ADD_USER,
+ UserManager.DISALLOW_RECORD_AUDIO
),
dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions());
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
UserManager.DISALLOW_REMOVE_USER,
- UserManager.DISALLOW_WALLPAPER
+ UserManager.DISALLOW_WALLPAPER,
+ UserManager.DISALLOW_RECORD_AUDIO
),
dpms.getProfileOwnerAdminLocked(10).ensureUserRestrictions());
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
- UserManager.DISALLOW_REMOVE_USER
+ UserManager.DISALLOW_REMOVE_USER,
+ UserManager.DISALLOW_RECORD_AUDIO
),
dpms.getProfileOwnerAdminLocked(11).ensureUserRestrictions());
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
new file mode 100644
index 0000000..5542a4f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.server.pm;
+
+import com.android.server.devicepolicy.DpmTestUtils;
+
+import android.os.Bundle;
+import android.os.UserManager;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+/**
+ * Tests for {@link com.android.server.pm.UserRestrictionsUtils}.
+ *
+ * <p>Run with:<pre>
+ m FrameworksServicesTests &&
+ adb install \
+ -r out/target/product/hammerhead/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+ adb shell am instrument -e class com.android.server.pm.UserRestrictionsUtilsTest \
+ -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
+public class UserRestrictionsUtilsTest extends AndroidTestCase {
+ public void testNonNull() {
+ Bundle out = UserRestrictionsUtils.nonNull(null);
+ assertNotNull(out);
+ out.putBoolean("a", true); // Should not be Bundle.EMPTY.
+
+ Bundle in = new Bundle();
+ assertSame(in, UserRestrictionsUtils.nonNull(in));
+ }
+
+ public void testIsEmpty() {
+ assertTrue(UserRestrictionsUtils.isEmpty(null));
+ assertTrue(UserRestrictionsUtils.isEmpty(new Bundle()));
+ assertFalse(UserRestrictionsUtils.isEmpty(DpmTestUtils.newRestrictions("a")));
+ }
+
+ public void testClone() {
+ Bundle in = new Bundle();
+ Bundle out = UserRestrictionsUtils.clone(in);
+ assertNotSame(in, out);
+ DpmTestUtils.assertRestrictions(out, new Bundle());
+
+ out = UserRestrictionsUtils.clone(null);
+ assertNotNull(out);
+ out.putBoolean("a", true); // Should not be Bundle.EMPTY.
+ }
+
+ public void testMerge() {
+ Bundle a = DpmTestUtils.newRestrictions("a", "d");
+ Bundle b = DpmTestUtils.newRestrictions("b", "d", "e");
+
+ UserRestrictionsUtils.merge(a, b);
+
+ DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a);
+
+ UserRestrictionsUtils.merge(a, null);
+
+ DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a);
+
+ try {
+ UserRestrictionsUtils.merge(a, a);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testCanDeviceOwnerChange() {
+ assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
+ assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_WALLPAPER));
+ assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_ADD_USER));
+ }
+
+ public void testCanProfileOwnerChange() {
+ assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
+ assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_WALLPAPER));
+ assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_ADD_USER));
+ assertTrue(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_ADJUST_VOLUME));
+ }
+
+ public void testSortToGlobalAndLocal() {
+ final Bundle local = new Bundle();
+ final Bundle global = new Bundle();
+
+ UserRestrictionsUtils.sortToGlobalAndLocal(null, global, local);
+ assertEquals(0, global.size());
+ assertEquals(0, local.size());
+
+ UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, global, local);
+ assertEquals(0, global.size());
+ assertEquals(0, local.size());
+
+ UserRestrictionsUtils.sortToGlobalAndLocal(DpmTestUtils.newRestrictions(
+ UserManager.DISALLOW_ADJUST_VOLUME,
+ UserManager.DISALLOW_UNMUTE_MICROPHONE,
+ UserManager.DISALLOW_USB_FILE_TRANSFER,
+ UserManager.DISALLOW_CONFIG_TETHERING,
+ UserManager.DISALLOW_OUTGOING_BEAM,
+ UserManager.DISALLOW_APPS_CONTROL
+ ), global, local);
+
+
+ DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(
+ // These can be set by PO too, but when DO sets them, they're global.
+ UserManager.DISALLOW_ADJUST_VOLUME,
+ UserManager.DISALLOW_UNMUTE_MICROPHONE,
+
+ // These can only be set by DO.
+ UserManager.DISALLOW_USB_FILE_TRANSFER,
+ UserManager.DISALLOW_CONFIG_TETHERING
+ ), global);
+
+ DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(
+ // They can be set by both DO/PO.
+ UserManager.DISALLOW_OUTGOING_BEAM,
+ UserManager.DISALLOW_APPS_CONTROL
+ ), local);
+ }
+
+ public void testAreEqual() {
+ assertTrue(UserRestrictionsUtils.areEqual(
+ null,
+ null));
+
+ assertTrue(UserRestrictionsUtils.areEqual(
+ null,
+ Bundle.EMPTY));
+
+ assertTrue(UserRestrictionsUtils.areEqual(
+ Bundle.EMPTY,
+ null));
+
+ assertTrue(UserRestrictionsUtils.areEqual(
+ Bundle.EMPTY,
+ Bundle.EMPTY));
+
+ assertTrue(UserRestrictionsUtils.areEqual(
+ new Bundle(),
+ Bundle.EMPTY));
+
+ assertFalse(UserRestrictionsUtils.areEqual(
+ null,
+ DpmTestUtils.newRestrictions("a")));
+
+ assertFalse(UserRestrictionsUtils.areEqual(
+ DpmTestUtils.newRestrictions("a"),
+ null));
+
+ assertTrue(UserRestrictionsUtils.areEqual(
+ DpmTestUtils.newRestrictions("a"),
+ DpmTestUtils.newRestrictions("a")));
+
+ assertFalse(UserRestrictionsUtils.areEqual(
+ DpmTestUtils.newRestrictions("a"),
+ DpmTestUtils.newRestrictions("a", "b")));
+
+ assertFalse(UserRestrictionsUtils.areEqual(
+ DpmTestUtils.newRestrictions("a", "b"),
+ DpmTestUtils.newRestrictions("a")));
+
+ assertFalse(UserRestrictionsUtils.areEqual(
+ DpmTestUtils.newRestrictions("b", "a"),
+ DpmTestUtils.newRestrictions("a", "a")));
+
+ // Make sure false restrictions are handled correctly.
+ final Bundle a = DpmTestUtils.newRestrictions("a");
+ a.putBoolean("b", true);
+
+ final Bundle b = DpmTestUtils.newRestrictions("a");
+ b.putBoolean("b", false);
+
+ assertFalse(UserRestrictionsUtils.areEqual(a, b));
+ assertFalse(UserRestrictionsUtils.areEqual(b, a));
+ }
+}
diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h
index 7ea26b3..ab4d284 100644
--- a/tools/aapt2/Diagnostics.h
+++ b/tools/aapt2/Diagnostics.h
@@ -18,7 +18,6 @@
#define AAPT_DIAGNOSTICS_H
#include "Source.h"
-
#include "util/StringPiece.h"
#include "util/Util.h"
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 02fe59c..c2ddb5c 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -711,6 +711,46 @@
}
}
+ Maybe<int32_t> maybeMin, maybeMax;
+
+ if (Maybe<StringPiece16> maybeMinStr = xml::findAttribute(parser, u"min")) {
+ StringPiece16 minStr = util::trimWhitespace(maybeMinStr.value());
+ if (!minStr.empty()) {
+ android::Res_value value;
+ if (android::ResTable::stringToInt(minStr.data(), minStr.size(), &value)) {
+ maybeMin = static_cast<int32_t>(value.data);
+ }
+ }
+
+ if (!maybeMin) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "invalid 'min' value '" << minStr << "'");
+ return false;
+ }
+ }
+
+ if (Maybe<StringPiece16> maybeMaxStr = xml::findAttribute(parser, u"max")) {
+ StringPiece16 maxStr = util::trimWhitespace(maybeMaxStr.value());
+ if (!maxStr.empty()) {
+ android::Res_value value;
+ if (android::ResTable::stringToInt(maxStr.data(), maxStr.size(), &value)) {
+ maybeMax = static_cast<int32_t>(value.data);
+ }
+ }
+
+ if (!maybeMax) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "invalid 'max' value '" << maxStr << "'");
+ return false;
+ }
+ }
+
+ if ((maybeMin || maybeMax) && (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "'min' and 'max' can only be used when format='integer'");
+ return false;
+ }
+
struct SymbolComparator {
bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
return a.symbol.name.value() < b.symbol.name.value();
@@ -794,6 +834,13 @@
std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
+ if (maybeMin) {
+ attr->minInt = maybeMin.value();
+ }
+
+ if (maybeMax) {
+ attr->maxInt = maybeMax.value();
+ }
outResource->value = std::move(attr);
return true;
}
@@ -872,6 +919,7 @@
}
transformReferenceFromNamespace(parser, u"", &maybeKey.value());
+ maybeKey.value().setSource(source);
std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
if (!value) {
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index ab16424..6e0812b 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -138,6 +138,22 @@
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
}
+TEST_F(ResourceParserTest, ParseAttrWithMinMax) {
+ std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>";
+ ASSERT_TRUE(testParse(input));
+
+ Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask);
+ EXPECT_EQ(10, attr->minInt);
+ EXPECT_EQ(23, attr->maxInt);
+}
+
+TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) {
+ std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>";
+ ASSERT_FALSE(testParse(input));
+}
+
TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
std::string input = "<declare-styleable name=\"Styleable\">\n"
" <attr name=\"foo\" />\n"
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 5550f19..04c375f 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -15,9 +15,9 @@
*/
#include "Resource.h"
+#include "ResourceUtils.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
-
#include "util/Util.h"
#include "flatten/ResourceTypeExtensions.h"
@@ -216,7 +216,7 @@
*out << "(null)";
break;
case android::Res_value::TYPE_INT_DEC:
- *out << "(integer) " << value.data;
+ *out << "(integer) " << static_cast<int32_t>(value.data);
break;
case android::Res_value::TYPE_INT_HEX:
*out << "(integer) " << std::hex << value.data << std::dec;
@@ -237,7 +237,10 @@
}
}
-Attribute::Attribute(bool w, uint32_t t) : weak(w), typeMask(t) {
+Attribute::Attribute(bool w, uint32_t t) :
+ weak(w), typeMask(t),
+ minInt(std::numeric_limits<int32_t>::min()),
+ maxInt(std::numeric_limits<int32_t>::max()) {
}
bool Attribute::isWeak() const {
@@ -361,6 +364,81 @@
}
}
+static void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
+ const Item* value) {
+ *msg << "expected";
+ if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+ *msg << " boolean";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
+ *msg << " color";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
+ *msg << " dimension";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
+ *msg << " enum";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
+ *msg << " flags";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
+ *msg << " float";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
+ *msg << " fraction";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
+ *msg << " integer";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
+ *msg << " reference";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
+ *msg << " string";
+ }
+
+ *msg << " but got " << *value;
+}
+
+bool Attribute::matches(const Item* item, DiagMessage* outMsg) const {
+ android::Res_value val = {};
+ item->flatten(&val);
+
+ // Always allow references.
+ const uint32_t mask = typeMask | android::ResTable_map::TYPE_REFERENCE;
+ if (!(mask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
+ if (outMsg) {
+ buildAttributeMismatchMessage(outMsg, this, item);
+ }
+ return false;
+
+ } else if (ResourceUtils::androidTypeToAttributeTypeMask(val.dataType) &
+ android::ResTable_map::TYPE_INTEGER) {
+ if (static_cast<int32_t>(util::deviceToHost32(val.data)) < minInt) {
+ if (outMsg) {
+ *outMsg << *item << " is less than minimum integer " << minInt;
+ }
+ return false;
+ } else if (static_cast<int32_t>(util::deviceToHost32(val.data)) > maxInt) {
+ if (outMsg) {
+ *outMsg << *item << " is greater than maximum integer " << maxInt;
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
Style* Style::clone(StringPool* newPool) const {
Style* style = new Style();
style->parent = parent;
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 7ae346a..a038282 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -17,9 +17,10 @@
#ifndef AAPT_RESOURCE_VALUES_H
#define AAPT_RESOURCE_VALUES_H
-#include "util/Maybe.h"
+#include "Diagnostics.h"
#include "Resource.h"
#include "StringPool.h"
+#include "util/Maybe.h"
#include <array>
#include <androidfw/ResourceTypes.h>
@@ -233,8 +234,8 @@
bool weak;
uint32_t typeMask;
- uint32_t minInt;
- uint32_t maxInt;
+ int32_t minInt;
+ int32_t maxInt;
std::vector<Symbol> symbols;
Attribute(bool w, uint32_t t = 0u);
@@ -243,6 +244,7 @@
Attribute* clone(StringPool* newPool) const override;
void printMask(std::ostream* out) const;
void print(std::ostream* out) const override;
+ bool matches(const Item* item, DiagMessage* outMsg) const;
};
struct Style : public BaseValue<Style> {
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
index 8af203c..319528e 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -58,6 +58,10 @@
return out;
}
+inline bool operator==(const Source& lhs, const Source& rhs) {
+ return lhs.path == rhs.path && lhs.line == rhs.line;
+}
+
inline bool operator<(const Source& lhs, const Source& rhs) {
int cmp = lhs.path.compare(rhs.path);
if (cmp < 0) return true;
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index acf5bb5..02bff2c 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -67,6 +67,28 @@
};
/**
+ * New types for a ResTable_map.
+ */
+struct ExtendedResTableMapTypes {
+ enum {
+ /**
+ * Type that contains the source path of the next item in the map.
+ */
+ ATTR_SOURCE_PATH = Res_MAKEINTERNAL(0xffff),
+
+ /**
+ * Type that contains the source line of the next item in the map.
+ */
+ ATTR_SOURCE_LINE = Res_MAKEINTERNAL(0xfffe),
+
+ /**
+ * Type that contains the comment of the next item in the map.
+ */
+ ATTR_COMMENT = Res_MAKEINTERNAL(0xfffd)
+ };
+};
+
+/**
* Followed by exportedSymbolCount ExportedSymbol structs, followed by the string pool.
*/
struct FileExport_header {
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 636e977..f42e6b7 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -108,15 +108,19 @@
SymbolWriter* mSymbols;
FlatEntry* mEntry;
BigBuffer* mBuffer;
+ StringPool* mSourcePool;
+ StringPool* mCommentPool;
bool mUseExtendedChunks;
+
size_t mEntryCount = 0;
Maybe<uint32_t> mParentIdent;
Maybe<ResourceNameRef> mParentName;
MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer,
+ StringPool* sourcePool, StringPool* commentPool,
bool useExtendedChunks) :
- mSymbols(symbols), mEntry(entry), mBuffer(buffer),
- mUseExtendedChunks(useExtendedChunks) {
+ mSymbols(symbols), mEntry(entry), mBuffer(buffer), mSourcePool(sourcePool),
+ mCommentPool(commentPool), mUseExtendedChunks(useExtendedChunks) {
}
void flattenKey(Reference* key, ResTable_map* outEntry) {
@@ -158,6 +162,32 @@
mEntryCount++;
}
+ void flattenMetaData(Value* value) {
+ if (!mUseExtendedChunks) {
+ return;
+ }
+
+ Reference key(ResourceId{ ExtendedResTableMapTypes::ATTR_SOURCE_PATH });
+ StringPool::Ref sourcePathRef = mSourcePool->makeRef(
+ util::utf8ToUtf16(value->getSource().path));
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC,
+ static_cast<uint32_t>(sourcePathRef.getIndex()));
+ flattenEntry(&key, &val);
+
+ if (value->getSource().line) {
+ key.id = ResourceId(ExtendedResTableMapTypes::ATTR_SOURCE_LINE);
+ val.value.data = static_cast<uint32_t>(value->getSource().line.value());
+ flattenEntry(&key, &val);
+ }
+
+ if (!value->getComment().empty()) {
+ key.id = ResourceId(ExtendedResTableMapTypes::ATTR_COMMENT);
+ StringPool::Ref commentRef = mCommentPool->makeRef(value->getComment());
+ val.value.data = static_cast<uint32_t>(commentRef.getIndex());
+ flattenEntry(&key, &val);
+ }
+ }
+
void visit(Attribute* attr) override {
{
Reference key(ResourceId{ ResTable_map::ATTR_TYPE });
@@ -165,6 +195,18 @@
flattenEntry(&key, &val);
}
+ if (attr->minInt != std::numeric_limits<int32_t>::min()) {
+ Reference key(ResourceId{ ResTable_map::ATTR_MIN });
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->minInt));
+ flattenEntry(&key, &val);
+ }
+
+ if (attr->maxInt != std::numeric_limits<int32_t>::max()) {
+ Reference key(ResourceId{ ResTable_map::ATTR_MAX });
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->maxInt));
+ flattenEntry(&key, &val);
+ }
+
for (Attribute::Symbol& s : attr->symbols) {
BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
flattenEntry(&s.symbol, &val);
@@ -199,6 +241,7 @@
for (Style::Entry& entry : style->entries) {
flattenEntry(&entry.key, entry.value.get());
+ flattenMetaData(&entry.key);
}
}
@@ -206,6 +249,7 @@
for (auto& attrRef : styleable->entries) {
BinaryPrimitive val(Res_value{});
flattenEntry(&attrRef, &val);
+ flattenMetaData(&attrRef);
}
}
@@ -215,6 +259,7 @@
flattenValue(item.get(), outEntry);
outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
mEntryCount++;
+ flattenMetaData(item.get());
}
}
@@ -258,6 +303,7 @@
Reference key(q);
flattenEntry(&key, plural->values[i].get());
+ flattenMetaData(plural->values[i].get());
}
}
};
@@ -377,7 +423,8 @@
} else {
const size_t beforeEntry = buffer->size();
ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
- MapFlattenVisitor visitor(mSymbols, entry, buffer, mOptions.useExtendedChunks);
+ MapFlattenVisitor visitor(mSymbols, entry, buffer, mSourcePool, mSourcePool,
+ mOptions.useExtendedChunks);
entry->value->accept(&visitor);
outEntry->count = util::hostToDevice32(visitor.mEntryCount);
if (visitor.mParentName) {
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
index 4ffb980..7030603 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -262,4 +262,55 @@
EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@com.app.test:color/green"));
}
+TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
+ Attribute attr(false);
+ attr.typeMask = android::ResTable_map::TYPE_INTEGER;
+ attr.minInt = 10;
+ attr.maxInt = 23;
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addValue(u"@android:attr/foo", ResourceId(0x01010000),
+ util::make_unique<Attribute>(attr))
+ .build();
+
+ ResourceTable result;
+ ASSERT_TRUE(flatten(table.get(), &result));
+
+ Attribute* actualAttr = test::getValue<Attribute>(&result, u"@android:attr/foo");
+ ASSERT_NE(nullptr, actualAttr);
+ EXPECT_EQ(attr.isWeak(), actualAttr->isWeak());
+ EXPECT_EQ(attr.typeMask, actualAttr->typeMask);
+ EXPECT_EQ(attr.minInt, actualAttr->minInt);
+ EXPECT_EQ(attr.maxInt, actualAttr->maxInt);
+}
+
+TEST_F(TableFlattenerTest, FlattenSourceAndCommentsForChildrenOfCompoundValues) {
+ Style style;
+ Reference key(test::parseNameOrDie(u"@android:attr/foo"));
+ key.id = ResourceId(0x01010000);
+ key.setSource(Source("test").withLine(2));
+ key.setComment(StringPiece16(u"comment"));
+ style.entries.push_back(Style::Entry{ key, util::make_unique<Id>() });
+
+ test::ResourceTableBuilder builder = test::ResourceTableBuilder();
+ std::unique_ptr<ResourceTable> table = builder
+ .setPackageId(u"android", 0x01)
+ .addValue(u"@android:attr/foo", ResourceId(0x01010000),
+ test::AttributeBuilder().build())
+ .addValue(u"@android:style/foo", ResourceId(0x01020000),
+ std::unique_ptr<Style>(style.clone(builder.getStringPool())))
+ .build();
+
+ ResourceTable result;
+ ASSERT_TRUE(flatten(table.get(), &result));
+
+ Style* actualStyle = test::getValue<Style>(&result, u"@android:style/foo");
+ ASSERT_NE(nullptr, actualStyle);
+ ASSERT_EQ(1u, actualStyle->entries.size());
+
+ Reference* actualKey = &actualStyle->entries[0].key;
+ EXPECT_EQ(key.getSource(), actualKey->getSource());
+ EXPECT_EQ(key.getComment(), actualKey->getComment());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 4b82537..2743539 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -78,52 +78,6 @@
return value;
}
- void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
- const Item* value) {
- *msg << "expected";
- if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
- *msg << " boolean";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
- *msg << " color";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
- *msg << " dimension";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
- *msg << " enum";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
- *msg << " flags";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
- *msg << " float";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
- *msg << " fraction";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
- *msg << " integer";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
- *msg << " reference";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
- *msg << " string";
- }
-
- *msg << " but got " << *value;
- }
-
public:
using ValueVisitor::visit;
@@ -175,32 +129,25 @@
entry.value->accept(this);
// Now verify that the type of this item is compatible with the attribute it
- // is defined for.
- android::Res_value val = {};
- entry.value->flatten(&val);
-
- // Always allow references.
- const uint32_t typeMask = symbol->attribute->typeMask |
- android::ResTable_map::TYPE_REFERENCE;
-
- if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
+ // is defined for. We pass `nullptr` as the DiagMessage so that this check is
+ // fast and we avoid creating a DiagMessage when the match is successful.
+ if (!symbol->attribute->matches(entry.value.get(), nullptr)) {
// The actual type of this item is incompatible with the attribute.
- DiagMessage msg(style->getSource());
- buildAttributeMismatchMessage(&msg, symbol->attribute.get(), entry.value.get());
+ DiagMessage msg(entry.key.getSource());
+
+ // Call the matches method again, this time with a DiagMessage so we fill
+ // in the actual error message.
+ symbol->attribute->matches(entry.value.get(), &msg);
mContext->getDiagnostics()->error(msg);
mError = true;
}
+
} else {
- DiagMessage msg(style->getSource());
+ DiagMessage msg(entry.key.getSource());
msg << "style attribute '";
- if (entry.key.name) {
- msg << entry.key.name.value().package << ":" << entry.key.name.value().entry;
- } else {
- msg << entry.key.id.value();
- }
+ ReferenceLinker::writeResourceName(&msg, entry.key, transformedReference);
msg << "' " << errStr;
mContext->getDiagnostics()->error(msg);
- mContext->getDiagnostics()->note(DiagMessage(style->getSource()) << entry.key);
mError = true;
}
}
@@ -249,16 +196,12 @@
CallSite* callSite, std::string* outError) {
const ISymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
if (!symbol) {
- std::stringstream errStr;
- errStr << "not found";
- if (outError) *outError = errStr.str();
+ if (outError) *outError = "not found";
return nullptr;
}
if (!isSymbolVisible(*symbol, reference, *callSite)) {
- std::stringstream errStr;
- errStr << "is private";
- if (outError) *outError = errStr.str();
+ if (outError) *outError = "is private";
return nullptr;
}
return symbol;
@@ -275,9 +218,7 @@
}
if (!symbol->attribute) {
- std::stringstream errStr;
- errStr << "is not an attribute";
- if (outError) *outError = errStr.str();
+ if (outError) *outError = "is not an attribute";
return nullptr;
}
return symbol;
@@ -294,14 +235,26 @@
}
if (!symbol->attribute) {
- std::stringstream errStr;
- errStr << "is not an attribute";
- if (outError) *outError = errStr.str();
+ if (outError) *outError = "is not an attribute";
return {};
}
return xml::AaptAttribute{ symbol->id, *symbol->attribute };
}
+void ReferenceLinker::writeResourceName(DiagMessage* outMsg, const Reference& orig,
+ const Reference& transformed) {
+ assert(outMsg);
+
+ if (orig.name) {
+ *outMsg << orig.name.value();
+ if (transformed.name.value() != orig.name.value()) {
+ *outMsg << " (aka " << transformed.name.value() << ")";
+ }
+ } else {
+ *outMsg << orig.id.value();
+ }
+}
+
bool ReferenceLinker::linkReference(Reference* reference, IAaptContext* context,
ISymbolTable* symbols, xml::IPackageDeclStack* decls,
CallSite* callSite) {
@@ -322,15 +275,7 @@
DiagMessage errorMsg(reference->getSource());
errorMsg << "resource ";
- if (reference->name) {
- errorMsg << reference->name.value();
- if (transformedReference.name.value() != reference->name.value()) {
- errorMsg << " (aka " << transformedReference.name.value() << ")";
- }
- } else {
- errorMsg << reference->id.value();
- }
-
+ writeResourceName(&errorMsg, *reference, transformedReference);
errorMsg << " " << errStr;
context->getDiagnostics()->error(errorMsg);
return false;
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
index 6f11d58..a0eb00c 100644
--- a/tools/aapt2/link/ReferenceLinker.h
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -80,6 +80,13 @@
std::string* outError);
/**
+ * Writes the resource name to the DiagMessage, using the "orig_name (aka <transformed_name>)"
+ * syntax.
+ */
+ static void writeResourceName(DiagMessage* outMsg, const Reference& orig,
+ const Reference& transformed);
+
+ /**
* Transforms the package name of the reference to the fully qualified package name using
* the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible
* to the reference at the callsite, the reference is updated with an ID.
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index d04181d..6ad2f9c 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -61,7 +61,7 @@
if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) {
// This resource has an Attribute.
if (Attribute* attr = valueCast<Attribute>(iter->value.get())) {
- symbol->attribute = std::unique_ptr<Attribute>(attr->clone(nullptr));
+ symbol->attribute = util::make_unique<Attribute>(*attr);
} else {
return {};
}
@@ -77,7 +77,6 @@
return symbol.get();
}
-
static std::shared_ptr<ISymbolTable::Symbol> lookupAttributeInTable(const android::ResTable& table,
ResourceId id) {
// Try as a bag.
@@ -103,29 +102,40 @@
if (s->attribute) {
for (size_t i = 0; i < (size_t) count; i++) {
- if (!Res_INTERNALID(entry[i].map.name.ident)) {
- android::ResTable::resource_name entryName;
- if (!table.getResourceName(entry[i].map.name.ident, false, &entryName)) {
- table.unlockBag(entry);
- return nullptr;
+ const android::ResTable_map& mapEntry = entry[i].map;
+ if (Res_INTERNALID(mapEntry.name.ident)) {
+ switch (mapEntry.name.ident) {
+ case android::ResTable_map::ATTR_MIN:
+ s->attribute->minInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
+ case android::ResTable_map::ATTR_MAX:
+ s->attribute->maxInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
}
-
- const ResourceType* parsedType = parseResourceType(
- StringPiece16(entryName.type, entryName.typeLen));
- if (!parsedType) {
- table.unlockBag(entry);
- return nullptr;
- }
-
- Attribute::Symbol symbol;
- symbol.symbol.name = ResourceNameRef(
- StringPiece16(entryName.package, entryName.packageLen),
- *parsedType,
- StringPiece16(entryName.name, entryName.nameLen)).toResourceName();
- symbol.symbol.id = ResourceId(entry[i].map.name.ident);
- symbol.value = entry[i].map.value.data;
- s->attribute->symbols.push_back(std::move(symbol));
+ continue;
}
+
+ android::ResTable::resource_name entryName;
+ if (!table.getResourceName(mapEntry.name.ident, false, &entryName)) {
+ table.unlockBag(entry);
+ return nullptr;
+ }
+
+ const ResourceType* parsedType = parseResourceType(
+ StringPiece16(entryName.type, entryName.typeLen));
+ if (!parsedType) {
+ table.unlockBag(entry);
+ return nullptr;
+ }
+
+ Attribute::Symbol symbol;
+ symbol.symbol.name = ResourceName(
+ StringPiece16(entryName.package, entryName.packageLen),
+ *parsedType,
+ StringPiece16(entryName.name, entryName.nameLen));
+ symbol.symbol.id = ResourceId(mapEntry.name.ident);
+ symbol.value = mapEntry.value.data;
+ s->attribute->symbols.push_back(std::move(symbol));
}
}
table.unlockBag(entry);
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 9ca694a..f8e3d03 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -36,6 +36,10 @@
public:
ResourceTableBuilder() = default;
+ StringPool* getStringPool() {
+ return &mTable->stringPool;
+ }
+
ResourceTableBuilder& setPackageId(const StringPiece16& packageName, uint8_t id) {
ResourceTablePackage* package = mTable->createPackage(packageName, id);
assert(package);
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 49625b5..5cc7aa7 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -575,11 +575,10 @@
Source source = mSource;
if (sourceBlock) {
- size_t len;
- const char* str = mSourcePool.string8At(util::deviceToHost32(sourceBlock->path.index),
- &len);
- if (str) {
- source.path.assign(str, len);
+ StringPiece path = util::getString8(mSourcePool,
+ util::deviceToHost32(sourceBlock->path.index));
+ if (!path.empty()) {
+ source.path = path.toString();
}
source.line = util::deviceToHost32(sourceBlock->line);
}
@@ -728,6 +727,16 @@
}
for (const ResTable_map& mapEntry : map) {
+ if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
+ if (style->entries.empty()) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "out-of-sequence meta data in style");
+ return {};
+ }
+ collectMetaData(mapEntry, &style->entries.back().key);
+ continue;
+ }
+
style->entries.emplace_back();
Style::Entry& styleEntry = style->entries.back();
@@ -771,12 +780,20 @@
attr->typeMask = util::deviceToHost32(typeMaskIter->value.data);
}
- if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
- for (const ResTable_map& mapEntry : map) {
- if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
- continue;
+ for (const ResTable_map& mapEntry : map) {
+ if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
+ switch (util::deviceToHost32(mapEntry.name.ident)) {
+ case ResTable_map::ATTR_MIN:
+ attr->minInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
+ case ResTable_map::ATTR_MAX:
+ attr->maxInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
}
+ continue;
+ }
+ if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
Attribute::Symbol symbol;
symbol.value = util::deviceToHost32(mapEntry.value.data);
if (util::deviceToHost32(mapEntry.name.ident) == 0) {
@@ -799,15 +816,57 @@
}
}
- // TODO(adamlesinski): Find min, max, i80n, etc attributes.
+ // TODO(adamlesinski): Find i80n, attributes.
return attr;
}
+static bool isMetaDataEntry(const ResTable_map& mapEntry) {
+ switch (util::deviceToHost32(mapEntry.name.ident)) {
+ case ExtendedResTableMapTypes::ATTR_SOURCE_PATH:
+ case ExtendedResTableMapTypes::ATTR_SOURCE_LINE:
+ case ExtendedResTableMapTypes::ATTR_COMMENT:
+ return true;
+ }
+ return false;
+}
+
+bool BinaryResourceParser::collectMetaData(const ResTable_map& mapEntry, Value* value) {
+ switch (util::deviceToHost32(mapEntry.name.ident)) {
+ case ExtendedResTableMapTypes::ATTR_SOURCE_PATH:
+ value->setSource(Source(util::getString8(mSourcePool,
+ util::deviceToHost32(mapEntry.value.data))));
+ return true;
+ break;
+
+ case ExtendedResTableMapTypes::ATTR_SOURCE_LINE:
+ value->setSource(value->getSource().withLine(util::deviceToHost32(mapEntry.value.data)));
+ return true;
+ break;
+
+ case ExtendedResTableMapTypes::ATTR_COMMENT:
+ value->setComment(util::getString(mSourcePool, util::deviceToHost32(mapEntry.value.data)));
+ return true;
+ break;
+ }
+ return false;
+}
+
std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
const ConfigDescription& config,
const ResTable_map_entry* map) {
std::unique_ptr<Array> array = util::make_unique<Array>();
+ Source source;
for (const ResTable_map& mapEntry : map) {
+ if (isMetaDataEntry(mapEntry)) {
+ if (array->items.empty()) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "out-of-sequence meta data in array");
+ return {};
+ }
+ collectMetaData(mapEntry, array->items.back().get());
+ continue;
+ }
+
array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
}
return array;
@@ -818,6 +877,16 @@
const ResTable_map_entry* map) {
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
for (const ResTable_map& mapEntry : map) {
+ if (isMetaDataEntry(mapEntry)) {
+ if (styleable->entries.empty()) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "out-of-sequence meta data in styleable");
+ return {};
+ }
+ collectMetaData(mapEntry, &styleable->entries.back());
+ continue;
+ }
+
if (util::deviceToHost32(mapEntry.name.ident) == 0) {
// The map entry's key (attribute) is not set. This must be
// a symbol reference, so resolve it.
@@ -841,29 +910,42 @@
const ConfigDescription& config,
const ResTable_map_entry* map) {
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ Item* lastEntry = nullptr;
for (const ResTable_map& mapEntry : map) {
+ if (isMetaDataEntry(mapEntry)) {
+ if (!lastEntry) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "out-of-sequence meta data in plural");
+ return {};
+ }
+ collectMetaData(mapEntry, lastEntry);
+ continue;
+ }
+
std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
if (!item) {
return {};
}
+ lastEntry = item.get();
+
switch (util::deviceToHost32(mapEntry.name.ident)) {
- case android::ResTable_map::ATTR_ZERO:
+ case ResTable_map::ATTR_ZERO:
plural->values[Plural::Zero] = std::move(item);
break;
- case android::ResTable_map::ATTR_ONE:
+ case ResTable_map::ATTR_ONE:
plural->values[Plural::One] = std::move(item);
break;
- case android::ResTable_map::ATTR_TWO:
+ case ResTable_map::ATTR_TWO:
plural->values[Plural::Two] = std::move(item);
break;
- case android::ResTable_map::ATTR_FEW:
+ case ResTable_map::ATTR_FEW:
plural->values[Plural::Few] = std::move(item);
break;
- case android::ResTable_map::ATTR_MANY:
+ case ResTable_map::ATTR_MANY:
plural->values[Plural::Many] = std::move(item);
break;
- case android::ResTable_map::ATTR_OTHER:
+ case ResTable_map::ATTR_OTHER:
plural->values[Plural::Other] = std::move(item);
break;
}
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 73fcf52..0745a59 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -91,6 +91,13 @@
const ConfigDescription& config,
const android::ResTable_map_entry* map);
+ /**
+ * If the mapEntry is a special type that denotes meta data (source, comment), then it is
+ * read and added to the Value.
+ * Returns true if the mapEntry was meta data.
+ */
+ bool collectMetaData(const android::ResTable_map& mapEntry, Value* value);
+
IAaptContext* mContext;
ResourceTable* mTable;
diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h
index 1f7d5ce..aa409ea 100644
--- a/tools/aapt2/util/Maybe.h
+++ b/tools/aapt2/util/Maybe.h
@@ -275,6 +275,29 @@
return Maybe<T>();
}
+/**
+ * Define the == operator between Maybe<T> and Maybe<U> if the operator T == U is defined.
+ * Otherwise this won't be defined and the compiler will yell at the callsite instead of inside
+ * Maybe.h.
+ */
+template <typename T, typename U>
+auto operator==(const Maybe<T>& a, const Maybe<U>& b)
+-> decltype(std::declval<T> == std::declval<U>) {
+ if (a && b) {
+ return a.value() == b.value();
+ }
+ return false;
+}
+
+/**
+ * Same as operator== but negated.
+ */
+template <typename T, typename U>
+auto operator!=(const Maybe<T>& a, const Maybe<U>& b)
+-> decltype(std::declval<T> == std::declval<U>) {
+ return !(a == b);
+}
+
} // namespace aapt
#endif // AAPT_MAYBE_H
diff --git a/tools/aapt2/util/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp
index d2c33ca..9cca40e 100644
--- a/tools/aapt2/util/Maybe_test.cpp
+++ b/tools/aapt2/util/Maybe_test.cpp
@@ -119,4 +119,14 @@
}
}
+TEST(MaybeTest, Equality) {
+ Maybe<int> a = 1;
+ Maybe<int> b = 1;
+ Maybe<int> c;
+
+ EXPECT_EQ(a, b);
+ EXPECT_EQ(b, a);
+ EXPECT_NE(a, c);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index a898619..0dacbd7 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -158,6 +158,15 @@
return StringPiece16();
}
+inline StringPiece getString8(const android::ResStringPool& pool, size_t idx) {
+ size_t len;
+ const char* str = pool.string8At(idx, &len);
+ if (str != nullptr) {
+ return StringPiece(str, len);
+ }
+ return StringPiece();
+}
+
/**
* Checks that the Java string format contains no non-positional arguments (arguments without
* explicitly specifying an index) when there are more than one argument. This is an error
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 3c260a8..6951ede 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -298,7 +298,7 @@
@Override
public Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth,
- int maxHeight) throws RemoteException {
+ int maxHeight, float frameScale) throws RemoteException {
// TODO Auto-generated method stub
return null;
}