diff options
113 files changed, 2328 insertions, 1531 deletions
diff --git a/api/current.txt b/api/current.txt index c4160443380f..dd70a3298aeb 100644 --- a/api/current.txt +++ b/api/current.txt @@ -416,9 +416,11 @@ package android { field public static final int contentAuthority = 16843408; // 0x1010290 field public static final int contentDescription = 16843379; // 0x1010273 field public static final int contentInsetEnd = 16843860; // 0x1010454 + field public static final int contentInsetEndWithActions = 16844070; // 0x1010526 field public static final int contentInsetLeft = 16843861; // 0x1010455 field public static final int contentInsetRight = 16843862; // 0x1010456 field public static final int contentInsetStart = 16843859; // 0x1010453 + field public static final int contentInsetStartWithNavigation = 16844069; // 0x1010525 field public static final int contextClickable = 16844007; // 0x10104e7 field public static final int contextPopupMenuStyle = 16844034; // 0x1010502 field public static final int controlX1 = 16843772; // 0x10103fc @@ -48290,9 +48292,15 @@ package android.widget { method public void collapseActionView(); method public void dismissPopupMenus(); method public int getContentInsetEnd(); + method public int getContentInsetEndWithActions(); method public int getContentInsetLeft(); method public int getContentInsetRight(); method public int getContentInsetStart(); + method public int getContentInsetStartWithNavigation(); + method public int getCurrentContentInsetEnd(); + method public int getCurrentContentInsetLeft(); + method public int getCurrentContentInsetRight(); + method public int getCurrentContentInsetStart(); method public android.graphics.drawable.Drawable getLogo(); method public java.lang.CharSequence getLogoDescription(); method public android.view.Menu getMenu(); @@ -48311,6 +48319,8 @@ package android.widget { method public void inflateMenu(int); method public boolean isOverflowMenuShowing(); method protected void onLayout(boolean, int, int, int, int); + method public void setContentInsetEndWithActions(int); + method public void setContentInsetStartWithNavigation(int); method public void setContentInsetsAbsolute(int, int); method public void setContentInsetsRelative(int, int); method public void setLogo(int); diff --git a/api/system-current.txt b/api/system-current.txt index e372d98a92a1..0fe632d27d88 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -511,9 +511,11 @@ package android { field public static final int contentAuthority = 16843408; // 0x1010290 field public static final int contentDescription = 16843379; // 0x1010273 field public static final int contentInsetEnd = 16843860; // 0x1010454 + field public static final int contentInsetEndWithActions = 16844070; // 0x1010526 field public static final int contentInsetLeft = 16843861; // 0x1010455 field public static final int contentInsetRight = 16843862; // 0x1010456 field public static final int contentInsetStart = 16843859; // 0x1010453 + field public static final int contentInsetStartWithNavigation = 16844069; // 0x1010525 field public static final int contextClickable = 16844007; // 0x10104e7 field public static final int contextPopupMenuStyle = 16844034; // 0x1010502 field public static final int controlX1 = 16843772; // 0x10103fc @@ -51354,9 +51356,15 @@ package android.widget { method public void collapseActionView(); method public void dismissPopupMenus(); method public int getContentInsetEnd(); + method public int getContentInsetEndWithActions(); method public int getContentInsetLeft(); method public int getContentInsetRight(); method public int getContentInsetStart(); + method public int getContentInsetStartWithNavigation(); + method public int getCurrentContentInsetEnd(); + method public int getCurrentContentInsetLeft(); + method public int getCurrentContentInsetRight(); + method public int getCurrentContentInsetStart(); method public android.graphics.drawable.Drawable getLogo(); method public java.lang.CharSequence getLogoDescription(); method public android.view.Menu getMenu(); @@ -51375,6 +51383,8 @@ package android.widget { method public void inflateMenu(int); method public boolean isOverflowMenuShowing(); method protected void onLayout(boolean, int, int, int, int); + method public void setContentInsetEndWithActions(int); + method public void setContentInsetStartWithNavigation(int); method public void setContentInsetsAbsolute(int, int); method public void setContentInsetsRelative(int, int); method public void setLogo(int); diff --git a/api/test-current.txt b/api/test-current.txt index e4153f126cb8..d59fa272088d 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -416,9 +416,11 @@ package android { field public static final int contentAuthority = 16843408; // 0x1010290 field public static final int contentDescription = 16843379; // 0x1010273 field public static final int contentInsetEnd = 16843860; // 0x1010454 + field public static final int contentInsetEndWithActions = 16844070; // 0x1010526 field public static final int contentInsetLeft = 16843861; // 0x1010455 field public static final int contentInsetRight = 16843862; // 0x1010456 field public static final int contentInsetStart = 16843859; // 0x1010453 + field public static final int contentInsetStartWithNavigation = 16844069; // 0x1010525 field public static final int contextClickable = 16844007; // 0x10104e7 field public static final int contextPopupMenuStyle = 16844034; // 0x1010502 field public static final int controlX1 = 16843772; // 0x10103fc @@ -48364,9 +48366,15 @@ package android.widget { method public void collapseActionView(); method public void dismissPopupMenus(); method public int getContentInsetEnd(); + method public int getContentInsetEndWithActions(); method public int getContentInsetLeft(); method public int getContentInsetRight(); method public int getContentInsetStart(); + method public int getContentInsetStartWithNavigation(); + method public int getCurrentContentInsetEnd(); + method public int getCurrentContentInsetLeft(); + method public int getCurrentContentInsetRight(); + method public int getCurrentContentInsetStart(); method public android.graphics.drawable.Drawable getLogo(); method public java.lang.CharSequence getLogoDescription(); method public android.view.Menu getMenu(); @@ -48385,6 +48393,8 @@ package android.widget { method public void inflateMenu(int); method public boolean isOverflowMenuShowing(); method protected void onLayout(boolean, int, int, int, int); + method public void setContentInsetEndWithActions(int); + method public void setContentInsetStartWithNavigation(int); method public void setContentInsetsAbsolute(int, int); method public void setContentInsetsRelative(int, int); method public void setLogo(int); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 887932a7dbbb..0d387e660f8a 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5920,6 +5920,9 @@ public class Activity extends ContextThemeWrapper * @return true if this is the topmost, non-finishing activity in its task. */ private boolean isTopOfTask() { + if (mToken == null || mWindow == null || !mWindowAdded) { + return false; + } try { return ActivityManagerNative.getDefault().isTopOfTask(mToken); } catch (RemoteException e) { diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java index 68442ea40701..f6d2268343a3 100644 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ b/core/java/android/bluetooth/BluetoothGatt.java @@ -545,7 +545,6 @@ public final class BluetoothGatt implements BluetoothProfile { /*package*/ BluetoothGattCharacteristic getCharacteristicById(BluetoothDevice device, int instanceId) { for(BluetoothGattService svc : mServices) { for(BluetoothGattCharacteristic charac : svc.getCharacteristics()) { - Log.w(TAG, "getCharacteristicById() comparing " + charac.getInstanceId() + " and " + instanceId); if (charac.getInstanceId() == instanceId) return charac; } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 66e0ada27b52..10259be8d430 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1581,14 +1581,6 @@ public class Intent implements Parcelable, Cloneable { = "android.intent.extra.UNINSTALL_ALL_USERS"; /** - * Specified when the uninstall confirmation dialog is not required to be shown. - * Use with {@link #ACTION_UNINSTALL_PACKAGE} - * @hide - */ - public static final String EXTRA_SKIP_UNINSTALL_CONFIRMATION = - "android.intent.extra.SKIP_UNINSTALL_CONFIRMATION"; - - /** * A string associated with a {@link #ACTION_UPGRADE_SETUP} activity * describing the last run version of the platform that was setup. * @hide diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index e3fb161cf0ab..a0238fb07e5e 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -316,6 +316,12 @@ interface IPackageManager { int getApplicationEnabledSetting(in String packageName, int userId); /** + * Logs process start information (including APK hash) to the security log. + */ + void logAppProcessStartIfNeeded(String processName, int uid, String seinfo, String apkFile, + int pid); + + /** * As per {@link android.content.pm.PackageManager#flushPackageRestrictionsAsUser}. */ void flushPackageRestrictionsAsUser(in int userId); diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl index 8f9dcfc15d63..31d377b554f8 100644 --- a/core/java/android/content/pm/IShortcutService.aidl +++ b/core/java/android/content/pm/IShortcutService.aidl @@ -47,4 +47,8 @@ interface IShortcutService { int getIconMaxDimensions(String packageName, int userId); void resetThrottling(); // system only API for developer opsions + + byte[] getBackupPayload(int user); + + void applyRestore(in byte[] payload, int user); }
\ No newline at end of file diff --git a/services/net/java/android/net/metrics/CaptivePortalCheckResultEvent.java b/core/java/android/net/metrics/CaptivePortalCheckResultEvent.java index 163f7e40c7a0..2239a254c8a0 100644 --- a/services/net/java/android/net/metrics/CaptivePortalCheckResultEvent.java +++ b/core/java/android/net/metrics/CaptivePortalCheckResultEvent.java @@ -19,6 +19,9 @@ package android.net.metrics; import android.os.Parcel; import android.os.Parcelable; +/** + * {@hide} + */ public class CaptivePortalCheckResultEvent extends IpConnectivityEvent implements Parcelable { public static final String TAG = "CaptivePortalCheckResultEvent"; diff --git a/services/net/java/android/net/metrics/CaptivePortalStateChangeEvent.java b/core/java/android/net/metrics/CaptivePortalStateChangeEvent.java index d0cc120c5c5a..00808c180069 100644 --- a/services/net/java/android/net/metrics/CaptivePortalStateChangeEvent.java +++ b/core/java/android/net/metrics/CaptivePortalStateChangeEvent.java @@ -19,6 +19,9 @@ package android.net.metrics; import android.os.Parcel; import android.os.Parcelable; +/** + * {@hide} + */ public class CaptivePortalStateChangeEvent extends IpConnectivityEvent implements Parcelable { public static final String TAG = "CaptivePortalStateChangeEvent"; diff --git a/services/net/java/android/net/metrics/ConnectivityServiceChangeEvent.java b/core/java/android/net/metrics/ConnectivityServiceChangeEvent.java index 92b376c66444..c6fcb2d5c713 100644 --- a/services/net/java/android/net/metrics/ConnectivityServiceChangeEvent.java +++ b/core/java/android/net/metrics/ConnectivityServiceChangeEvent.java @@ -19,6 +19,9 @@ package android.net.metrics; import android.os.Parcel; import android.os.Parcelable; +/** + * {@hide} + */ public class ConnectivityServiceChangeEvent extends IpConnectivityEvent implements Parcelable { public static final String TAG = "ConnectivityServiceChangeEvent"; diff --git a/services/net/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java index 2c240344871b..7b4466439e68 100644 --- a/services/net/java/android/net/metrics/DhcpClientEvent.java +++ b/core/java/android/net/metrics/DhcpClientEvent.java @@ -19,6 +19,9 @@ package android.net.metrics; import android.os.Parcel; import android.os.Parcelable; +/** + * {@hide} + */ public class DhcpClientEvent extends IpConnectivityEvent implements Parcelable { public static final String TAG = "DhcpClientEvent"; diff --git a/services/net/java/android/net/metrics/IpConnectivityEvent.java b/core/java/android/net/metrics/IpConnectivityEvent.java index f277bd07bdab..ec428909e1cb 100644 --- a/services/net/java/android/net/metrics/IpConnectivityEvent.java +++ b/core/java/android/net/metrics/IpConnectivityEvent.java @@ -20,6 +20,9 @@ import android.net.ConnectivityMetricsLogger; import android.os.Parcel; import android.os.Parcelable; +/** + * {@hide} + */ public class IpConnectivityEvent implements Parcelable { // IPRM = IpReachabilityMonitor // DHCP = DhcpClient diff --git a/services/net/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java b/core/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java index a8c18d6f7d9e..e71b0bedd36e 100644 --- a/services/net/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java +++ b/core/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java @@ -19,6 +19,9 @@ package android.net.metrics; import android.os.Parcel; import android.os.Parcelable; +/** + * {@hide} + */ public class IpReachabilityMonitorMessageEvent extends IpConnectivityEvent implements Parcelable { public static final String TAG = "IpReachabilityMonitorMessageEvent"; diff --git a/services/net/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java b/core/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java index 172cbf858c8b..182b7780241c 100644 --- a/services/net/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java +++ b/core/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java @@ -19,6 +19,9 @@ package android.net.metrics; import android.os.Parcel; import android.os.Parcelable; +/** + * {@hide} + */ public class IpReachabilityMonitorProbeEvent extends IpConnectivityEvent implements Parcelable { public static final String TAG = "IpReachabilityMonitorProbeEvent"; diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index a1e2e946c48a..8e1609c587a3 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -116,14 +116,11 @@ interface IWindowSession { * @param top The new top position * @param right The new right position * @param bottom The new bottom position - * @param requestedWidth The new requested width - * @param requestedHeight The new requested height * @param deferTransactionUntilFrame Frame number from our parent (attached) to * defer this action until. * @param outFrame Rect in which is placed the new position/size on screen. */ void repositionChild(IWindow childWindow, int left, int top, int right, int bottom, - int requestedWidth, int requestedHeight, long deferTransactionUntilFrame, out Rect outFrame); /* diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 477ffd9b5ba4..8a8fb43cc3d4 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -490,7 +490,7 @@ public class SurfaceView extends View { | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE ; - if (!creating && !force && !mUpdateWindowNeeded) { + if (!creating && !force && !mUpdateWindowNeeded && !sizeChanged) { mLayout.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY; } else { @@ -584,18 +584,6 @@ public class SurfaceView extends View { mSurface.transferFrom(mNewSurface); if (visible && mSurface.isValid()) { - // We set SCALING_MODE_NO_SCALE_CROP to allow the WindowManager - // to update our Surface crop without requiring a new buffer from - // us. In the default mode of SCALING_MODE_FREEZE, surface geometry - // state (which includes crop) is only applied when a buffer - // with appropriate geometry is available. During drag resize - // it is quite frequent that a matching buffer will not be available - // (because we are constantly being resized and have fallen behind). - // However in such situations the WindowManager still needs to be able - // to update our crop to ensure we stay within the bounds of the containing - // window. - mSurface.setScalingMode(Surface.SCALING_MODE_NO_SCALE_CROP); - if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) { mSurfaceCreated = true; mIsCreating = true; @@ -666,7 +654,6 @@ public class SurfaceView extends View { mLocation[0], mLocation[1])); mSession.repositionChild(mWindow, mWindowSpaceLeft, mWindowSpaceTop, mLocation[0], mLocation[1], - mWindowSpaceWidth, mWindowSpaceHeight, -1, mWinFrame); } catch (RemoteException ex) { Log.e(TAG, "Exception from relayout", ex); @@ -703,7 +690,6 @@ public class SurfaceView extends View { } // Just using mRTLastReportedPosition as a dummy rect here session.repositionChild(window, left, top, right, bottom, - mWindowSpaceWidth, mWindowSpaceHeight, frameNumber, mRTLastReportedPosition); // Now overwrite mRTLastReportedPosition with our values diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index f18b7acf64f0..c5b8849e88ab 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -1113,19 +1113,19 @@ public class ViewPropertyAnimator { if (mListener != null) { mListener.onAnimationEnd(animation); } - if (mAnimatorOnEndMap != null) { - Runnable r = mAnimatorOnEndMap.get(animation); + if (mAnimatorCleanupMap != null) { + Runnable r = mAnimatorCleanupMap.get(animation); if (r != null) { r.run(); } - mAnimatorOnEndMap.remove(animation); + mAnimatorCleanupMap.remove(animation); } - if (mAnimatorCleanupMap != null) { - Runnable r = mAnimatorCleanupMap.get(animation); + if (mAnimatorOnEndMap != null) { + Runnable r = mAnimatorOnEndMap.get(animation); if (r != null) { r.run(); } - mAnimatorCleanupMap.remove(animation); + mAnimatorOnEndMap.remove(animation); } mAnimatorMap.remove(animation); } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index bdaf291a2d3c..14821118f728 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -2926,8 +2926,10 @@ public class AccessibilityNodeInfo implements Parcelable { mInputType = other.mInputType; mLiveRegion = other.mLiveRegion; mDrawingOrderInParent = other.mDrawingOrderInParent; - if (other.mExtras != null && !other.mExtras.isEmpty()) { - getExtras().putAll(other.mExtras); + if (other.mExtras != null) { + mExtras = new Bundle(other.mExtras); + } else { + mExtras = null; } mRangeInfo = (other.mRangeInfo != null) ? RangeInfo.obtain(other.mRangeInfo) : null; @@ -3006,7 +3008,9 @@ public class AccessibilityNodeInfo implements Parcelable { mDrawingOrderInParent = parcel.readInt(); if (parcel.readInt() == 1) { - getExtras().putAll(parcel.readBundle()); + mExtras = parcel.readBundle(); + } else { + mExtras = null; } if (parcel.readInt() == 1) { @@ -3073,9 +3077,7 @@ public class AccessibilityNodeInfo implements Parcelable { mTextSelectionEnd = UNDEFINED_SELECTION_INDEX; mInputType = InputType.TYPE_NULL; mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE; - if (mExtras != null) { - mExtras.clear(); - } + mExtras = null; if (mRangeInfo != null) { mRangeInfo.recycle(); mRangeInfo = null; diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 18687c975432..92631da2da90 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -173,9 +173,6 @@ public class PopupWindow { private int mHeight = LayoutParams.WRAP_CONTENT; private int mLastHeight; - private int mPopupWidth; - private int mPopupHeight; - private float mElevation; private Drawable mBackground; @@ -1298,8 +1295,6 @@ public class PopupWindow { mPopupViewInitialLayoutDirectionInherited = (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); - mPopupWidth = p.width; - mPopupHeight = p.height; } /** @@ -2006,7 +2001,7 @@ public class PopupWindow { * @param height the new height, must be >= 0 or -1 to ignore */ public void update(View anchor, int width, int height) { - update(anchor, false, 0, 0, true, width, height); + update(anchor, false, 0, 0, width, height); } /** @@ -2026,11 +2021,11 @@ public class PopupWindow { * @param height the new height, must be >= 0 or -1 to ignore */ public void update(View anchor, int xoff, int yoff, int width, int height) { - update(anchor, true, xoff, yoff, true, width, height); + update(anchor, true, xoff, yoff, width, height); } private void update(View anchor, boolean updateLocation, int xoff, int yoff, - boolean updateDimension, int width, int height) { + int width, int height) { if (!isShowing() || mContentView == null) { return; @@ -2055,13 +2050,13 @@ public class PopupWindow { final int oldX = p.x; final int oldY = p.y; - if (updateDimension) { - if (width == -1) { - width = mPopupWidth; - } - if (height == -1) { - height = mPopupHeight; - } + // If an explicit width/height has not specified, use the most recent + // explicitly specified value (either from setWidth/Height or update). + if (width == -1) { + width = mWidth; + } + if (height == -1) { + height = mHeight; } final boolean aboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index a9af654b3ae9..4a68d3c4977e 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -3411,17 +3411,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Sets whether the movement method will automatically be set to {@link LinkMovementMethod} - * after {@link #setText} or {@link #append} is called. The movement method is set if one of the - * following is true: - * <ul> - * <li>{@link #setAutoLinkMask} has been set to nonzero and links are detected in - * {@link #setText} or {@link #append}. - * <li>The input for {@link #setText} or {@link #append} contains a {@link ClickableSpan}. - * </ul> - * - * <p>This function does not have an immediate effect, movement method will be set only after a - * call to {@link #setText} or {@link #append}. The default is true.</p> + * Sets whether the movement method will automatically be set to + * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been + * set to nonzero and links are detected in {@link #setText}. + * The default is true. * * @attr ref android.R.styleable#TextView_linksClickable */ @@ -3431,14 +3424,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Returns whether the movement method will automatically be set to {@link LinkMovementMethod} - * after {@link #setText} or {@link #append} is called. - * - * See {@link #setLinksClickable} for details. - * - * <p>The default is true.</p> - * - * @see #setLinksClickable + * Returns whether the movement method will automatically be set to + * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been + * set to nonzero and links are detected in {@link #setText}. + * The default is true. * * @attr ref android.R.styleable#TextView_linksClickable */ @@ -4032,19 +4021,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ((Editable) mText).append(text, start, end); - boolean hasClickableSpans = false; if (mAutoLinkMask != 0) { - hasClickableSpans = Linkify.addLinks((Spannable) mText, mAutoLinkMask); - } else if (mLinksClickable && text instanceof Spanned) { - ClickableSpan[] clickableSpans = - ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class); - hasClickableSpans = clickableSpans != null && clickableSpans.length > 0; - } - - // Do not change the movement method for text that supports text selection as it - // would prevent an arbitrary cursor displacement. - if (hasClickableSpans && mLinksClickable && !textCanBeSelected()) { - setMovementMethod(LinkMovementMethod.getInstance()); + boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask); + // Do not change the movement method for text that support text selection as it + // would prevent an arbitrary cursor displacement. + if (linksWereAdded && mLinksClickable && !textCanBeSelected()) { + setMovementMethod(LinkMovementMethod.getInstance()); + } } } @@ -4397,7 +4380,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener text = TextUtils.stringOrSpannedString(text); } - boolean hasClickableSpans = false; if (mAutoLinkMask != 0) { Spannable s2; @@ -4407,32 +4389,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener s2 = mSpannableFactory.newSpannable(text); } - hasClickableSpans = Linkify.addLinks(s2, mAutoLinkMask); - if (hasClickableSpans) { + if (Linkify.addLinks(s2, mAutoLinkMask)) { text = s2; - } - } else if (mLinksClickable && text instanceof Spanned) { - ClickableSpan[] clickableSpans = - ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class); - hasClickableSpans = clickableSpans != null && clickableSpans.length > 0; - if (hasClickableSpans && !(text instanceof Spannable)) { - text = mSpannableFactory.newSpannable(text); - } - } + type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; - if (hasClickableSpans) { - type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; - /* - * We must go ahead and set the text before changing the - * movement method, because setMovementMethod() may call - * setText() again to try to upgrade the buffer type. - */ - mText = text; + /* + * We must go ahead and set the text before changing the + * movement method, because setMovementMethod() may call + * setText() again to try to upgrade the buffer type. + */ + mText = text; - // Do not change the movement method for text that supports text selection as it - // would prevent an arbitrary cursor displacement. - if (mLinksClickable && !textCanBeSelected()) { - setMovementMethod(LinkMovementMethod.getInstance()); + // Do not change the movement method for text that support text selection as it + // would prevent an arbitrary cursor displacement. + if (mLinksClickable && !textCanBeSelected()) { + setMovementMethod(LinkMovementMethod.getInstance()); + } } } diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java index 06daf61937c1..5b0a90adde33 100644 --- a/core/java/android/widget/Toolbar.java +++ b/core/java/android/widget/Toolbar.java @@ -106,6 +106,8 @@ import java.util.List; * @attr ref android.R.styleable#Toolbar_contentInsetLeft * @attr ref android.R.styleable#Toolbar_contentInsetRight * @attr ref android.R.styleable#Toolbar_contentInsetStart + * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation + * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions * @attr ref android.R.styleable#Toolbar_gravity * @attr ref android.R.styleable#Toolbar_logo * @attr ref android.R.styleable#Toolbar_logoDescription @@ -159,6 +161,8 @@ public class Toolbar extends ViewGroup { private int mTitleMarginBottom; private final RtlSpacingHelper mContentInsets = new RtlSpacingHelper(); + private int mContentInsetStartWithNavigation; + private int mContentInsetEndWithActions; private int mGravity = Gravity.START | Gravity.CENTER_VERTICAL; @@ -272,6 +276,11 @@ public class Toolbar extends ViewGroup { mContentInsets.setRelative(contentInsetStart, contentInsetEnd); } + mContentInsetStartWithNavigation = a.getDimensionPixelOffset( + R.styleable.Toolbar_contentInsetStartWithNavigation, RtlSpacingHelper.UNDEFINED); + mContentInsetEndWithActions = a.getDimensionPixelOffset( + R.styleable.Toolbar_contentInsetEndWithActions, RtlSpacingHelper.UNDEFINED); + mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon); mCollapseDescription = a.getText(R.styleable.Toolbar_collapseContentDescription); @@ -1055,7 +1064,7 @@ public class Toolbar extends ViewGroup { } /** - * Set the content insets for this toolbar relative to layout direction. + * Sets the content insets for this toolbar relative to layout direction. * * <p>The content inset affects the valid area for Toolbar content other than * the navigation button and menu. Insets define the minimum margin for these components @@ -1069,13 +1078,15 @@ public class Toolbar extends ViewGroup { * @see #getContentInsetEnd() * @see #getContentInsetLeft() * @see #getContentInsetRight() + * @attr ref android.R.styleable#Toolbar_contentInsetEnd + * @attr ref android.R.styleable#Toolbar_contentInsetStart */ public void setContentInsetsRelative(int contentInsetStart, int contentInsetEnd) { mContentInsets.setRelative(contentInsetStart, contentInsetEnd); } /** - * Get the starting content inset for this toolbar. + * Gets the starting content inset for this toolbar. * * <p>The content inset affects the valid area for Toolbar content other than * the navigation button and menu. Insets define the minimum margin for these components @@ -1088,13 +1099,14 @@ public class Toolbar extends ViewGroup { * @see #getContentInsetEnd() * @see #getContentInsetLeft() * @see #getContentInsetRight() + * @attr ref android.R.styleable#Toolbar_contentInsetStart */ public int getContentInsetStart() { return mContentInsets.getStart(); } /** - * Get the ending content inset for this toolbar. + * Gets the ending content inset for this toolbar. * * <p>The content inset affects the valid area for Toolbar content other than * the navigation button and menu. Insets define the minimum margin for these components @@ -1107,13 +1119,14 @@ public class Toolbar extends ViewGroup { * @see #getContentInsetStart() * @see #getContentInsetLeft() * @see #getContentInsetRight() + * @attr ref android.R.styleable#Toolbar_contentInsetEnd */ public int getContentInsetEnd() { return mContentInsets.getEnd(); } /** - * Set the content insets for this toolbar. + * Sets the content insets for this toolbar. * * <p>The content inset affects the valid area for Toolbar content other than * the navigation button and menu. Insets define the minimum margin for these components @@ -1127,13 +1140,15 @@ public class Toolbar extends ViewGroup { * @see #getContentInsetEnd() * @see #getContentInsetLeft() * @see #getContentInsetRight() + * @attr ref android.R.styleable#Toolbar_contentInsetLeft + * @attr ref android.R.styleable#Toolbar_contentInsetRight */ public void setContentInsetsAbsolute(int contentInsetLeft, int contentInsetRight) { mContentInsets.setAbsolute(contentInsetLeft, contentInsetRight); } /** - * Get the left content inset for this toolbar. + * Gets the left content inset for this toolbar. * * <p>The content inset affects the valid area for Toolbar content other than * the navigation button and menu. Insets define the minimum margin for these components @@ -1146,13 +1161,14 @@ public class Toolbar extends ViewGroup { * @see #getContentInsetStart() * @see #getContentInsetEnd() * @see #getContentInsetRight() + * @attr ref android.R.styleable#Toolbar_contentInsetLeft */ public int getContentInsetLeft() { return mContentInsets.getLeft(); } /** - * Get the right content inset for this toolbar. + * Gets the right content inset for this toolbar. * * <p>The content inset affects the valid area for Toolbar content other than * the navigation button and menu. Insets define the minimum margin for these components @@ -1165,11 +1181,160 @@ public class Toolbar extends ViewGroup { * @see #getContentInsetStart() * @see #getContentInsetEnd() * @see #getContentInsetLeft() + * @attr ref android.R.styleable#Toolbar_contentInsetRight */ public int getContentInsetRight() { return mContentInsets.getRight(); } + /** + * Gets the start content inset to use when a navigation button is present. + * + * <p>Different content insets are often called for when additional buttons are present + * in the toolbar, as well as at different toolbar sizes. The larger value of + * {@link #getContentInsetStart()} and this value will be used during layout.</p> + * + * @return the start content inset used when a navigation icon has been set in pixels + * + * @see #setContentInsetStartWithNavigation(int) + * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation + */ + public int getContentInsetStartWithNavigation() { + return mContentInsetStartWithNavigation != RtlSpacingHelper.UNDEFINED + ? mContentInsetStartWithNavigation + : getContentInsetStart(); + } + + /** + * Sets the start content inset to use when a navigation button is present. + * + * <p>Different content insets are often called for when additional buttons are present + * in the toolbar, as well as at different toolbar sizes. The larger value of + * {@link #getContentInsetStart()} and this value will be used during layout.</p> + * + * @param insetStartWithNavigation the inset to use when a navigation icon has been set + * in pixels + * + * @see #getContentInsetStartWithNavigation() + * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation + */ + public void setContentInsetStartWithNavigation(int insetStartWithNavigation) { + if (insetStartWithNavigation < 0) { + insetStartWithNavigation = RtlSpacingHelper.UNDEFINED; + } + if (insetStartWithNavigation != mContentInsetStartWithNavigation) { + mContentInsetStartWithNavigation = insetStartWithNavigation; + if (getNavigationIcon() != null) { + requestLayout(); + } + } + } + + /** + * Gets the end content inset to use when action buttons are present. + * + * <p>Different content insets are often called for when additional buttons are present + * in the toolbar, as well as at different toolbar sizes. The larger value of + * {@link #getContentInsetEnd()} and this value will be used during layout.</p> + * + * @return the end content inset used when a menu has been set in pixels + * + * @see #setContentInsetEndWithActions(int) + * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions + */ + public int getContentInsetEndWithActions() { + return mContentInsetEndWithActions != RtlSpacingHelper.UNDEFINED + ? mContentInsetEndWithActions + : getContentInsetEnd(); + } + + /** + * Sets the start content inset to use when action buttons are present. + * + * <p>Different content insets are often called for when additional buttons are present + * in the toolbar, as well as at different toolbar sizes. The larger value of + * {@link #getContentInsetEnd()} and this value will be used during layout.</p> + * + * @param insetEndWithActions the inset to use when a menu has been set in pixels + * + * @see #setContentInsetEndWithActions(int) + * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions + */ + public void setContentInsetEndWithActions(int insetEndWithActions) { + if (insetEndWithActions < 0) { + insetEndWithActions = RtlSpacingHelper.UNDEFINED; + } + if (insetEndWithActions != mContentInsetEndWithActions) { + mContentInsetEndWithActions = insetEndWithActions; + if (getNavigationIcon() != null) { + requestLayout(); + } + } + } + + /** + * Gets the content inset that will be used on the starting side of the bar in the current + * toolbar configuration. + * + * @return the current content inset start in pixels + * + * @see #getContentInsetStartWithNavigation() + */ + public int getCurrentContentInsetStart() { + return getNavigationIcon() != null + ? Math.max(getContentInsetStart(), Math.max(mContentInsetStartWithNavigation, 0)) + : getContentInsetStart(); + } + + /** + * Gets the content inset that will be used on the ending side of the bar in the current + * toolbar configuration. + * + * @return the current content inset end in pixels + * + * @see #getContentInsetEndWithActions() + */ + public int getCurrentContentInsetEnd() { + boolean hasActions = false; + if (mMenuView != null) { + final MenuBuilder mb = mMenuView.peekMenu(); + hasActions = mb != null && mb.hasVisibleItems(); + } + return hasActions + ? Math.max(getContentInsetEnd(), Math.max(mContentInsetEndWithActions, 0)) + : getContentInsetEnd(); + } + + /** + * Gets the content inset that will be used on the left side of the bar in the current + * toolbar configuration. + * + * @return the current content inset left in pixels + * + * @see #getContentInsetStartWithNavigation() + * @see #getContentInsetEndWithActions() + */ + public int getCurrentContentInsetLeft() { + return isLayoutRtl() + ? getCurrentContentInsetEnd() + : getCurrentContentInsetStart(); + } + + /** + * Gets the content inset that will be used on the right side of the bar in the current + * toolbar configuration. + * + * @return the current content inset right in pixels + * + * @see #getContentInsetStartWithNavigation() + * @see #getContentInsetEndWithActions() + */ + public int getCurrentContentInsetRight() { + return isLayoutRtl() + ? getCurrentContentInsetStart() + : getCurrentContentInsetEnd(); + } + private void ensureNavButtonView() { if (mNavButtonView == null) { mNavButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle); @@ -1406,7 +1571,7 @@ public class Toolbar extends ViewGroup { childState = combineMeasuredStates(childState, mCollapseButtonView.getMeasuredState()); } - final int contentInsetStart = getContentInsetStart(); + final int contentInsetStart = getCurrentContentInsetStart(); width += Math.max(contentInsetStart, navWidth); collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth); @@ -1420,7 +1585,7 @@ public class Toolbar extends ViewGroup { childState = combineMeasuredStates(childState, mMenuView.getMeasuredState()); } - final int contentInsetEnd = getContentInsetEnd(); + final int contentInsetEnd = getCurrentContentInsetEnd(); width += Math.max(contentInsetEnd, menuWidth); collapsingMargins[marginEndIndex] = Math.max(0, contentInsetEnd - menuWidth); @@ -1543,10 +1708,12 @@ public class Toolbar extends ViewGroup { } } - collapsingMargins[0] = Math.max(0, getContentInsetLeft() - left); - collapsingMargins[1] = Math.max(0, getContentInsetRight() - (width - paddingRight - right)); - left = Math.max(left, getContentInsetLeft()); - right = Math.min(right, width - paddingRight - getContentInsetRight()); + final int contentInsetLeft = getCurrentContentInsetLeft(); + final int contentInsetRight = getCurrentContentInsetRight(); + collapsingMargins[0] = Math.max(0, contentInsetLeft - left); + collapsingMargins[1] = Math.max(0, contentInsetRight - (width - paddingRight - right)); + left = Math.max(left, contentInsetLeft); + right = Math.min(right, width - paddingRight - contentInsetRight); if (shouldLayout(mExpandedActionView)) { if (isRtl) { diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 4b695b93d0e9..ea0fbdab1b89 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -209,6 +209,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private Drawable mResizingBackgroundDrawable; private Drawable mCaptionBackgroundDrawable; private Drawable mUserCaptionBackgroundDrawable; + private Drawable mOriginalBackgroundDrawable; private float mAvailableWidth; @@ -888,6 +889,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mBackgroundPadding.setEmpty(); } drawableChanged(); + + // Make sure we don't reset to the old drawable when finishing resizing. + if (mResizeMode != RESIZE_MODE_INVALID) { + mOriginalBackgroundDrawable = null; + } } } @@ -1950,6 +1956,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind updateElevation(); updateColorViews(null /* insets */, false); + + mOriginalBackgroundDrawable = getBackground(); + setBackgroundDrawable(null); } mResizeMode = resizeMode; getViewRootImpl().requestInvalidateRootRenderNode(); @@ -1961,6 +1970,10 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind updateColorViews(null /* insets */, false); mResizeMode = RESIZE_MODE_INVALID; getViewRootImpl().requestInvalidateRootRenderNode(); + if (mOriginalBackgroundDrawable != null) { + setBackgroundDrawable(mOriginalBackgroundDrawable); + mOriginalBackgroundDrawable = null; + } } @Override diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index 31b2f9619ed6..df57639a819e 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -65,7 +65,6 @@ public class MenuBuilder implements Menu { private final Context mContext; private final Resources mResources; - private final boolean mShowCascadingMenus; /** * Whether the shortcuts should be qwerty-accessible. Use isQwertyMode() @@ -188,9 +187,6 @@ public class MenuBuilder implements Menu { public MenuBuilder(Context context) { mContext = context; mResources = context.getResources(); - mShowCascadingMenus = context.getResources().getBoolean( - com.android.internal.R.bool.config_enableCascadingSubmenus); - mItems = new ArrayList<MenuItemImpl>(); mVisibleItems = new ArrayList<MenuItemImpl>(); @@ -915,10 +911,6 @@ public class MenuBuilder implements Menu { close(true /* closeAllMenus */); } } else if (itemImpl.hasSubMenu() || providerHasSubMenu) { - if (!mShowCascadingMenus) { - close(false /* closeAllMenus */); - } - if (!itemImpl.hasSubMenu()) { itemImpl.setSubMenu(new SubMenuBuilder(getContext(), this, itemImpl)); } diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java index 2cb224edf1d5..8ced36f78d75 100644 --- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java +++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java @@ -240,7 +240,10 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On mTreeObserver = null; } mShownAnchorView.removeOnAttachStateChangeListener(mAttachStateChangeListener); - mOnDismissListener.onDismiss(); + + if (mOnDismissListener != null) { + mOnDismissListener.onDismiss(); + } } @Override @@ -265,6 +268,13 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On subPopup.setPresenterCallback(mPresenterCallback); subPopup.setForceShowIcon(mAdapter.getForceShowIcon()); + // Pass responsibility for handling onDismiss to the submenu. + subPopup.setOnDismissListener(mOnDismissListener); + mOnDismissListener = null; + + // Close this menu popup to make room for the submenu popup. + dismiss(); + // Show the new sub-menu popup at the same location as this popup. if (subPopup.tryShow(mXOffset, mYOffset)) { if (mPresenterCallback != null) { diff --git a/core/res/res/values-sw600dp/dimens_material.xml b/core/res/res/values-sw600dp/dimens_material.xml index 3bbb352bca8e..1ec5c0fa5cb8 100644 --- a/core/res/res/values-sw600dp/dimens_material.xml +++ b/core/res/res/values-sw600dp/dimens_material.xml @@ -23,6 +23,8 @@ <dimen name="action_bar_default_height_material">64dp</dimen> <!-- Default content inset of an action bar. --> <dimen name="action_bar_content_inset_material">24dp</dimen> + <!-- Default content inset of an action bar with navigation present. --> + <dimen name="action_bar_content_inset_with_nav">80dp</dimen> <!-- Default start padding of an action bar. --> <dimen name="action_bar_default_padding_start_material">8dp</dimen> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 00eb81a8ed98..44290010b746 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4384,8 +4384,7 @@ i <attr name="autoLink" /> <!-- If set to false, keeps the movement method from being set to the link movement method even if autoLink causes links - to be found or the input text contains a - {@link android.text.style.ClickableSpan ClickableSpan}. --> + to be found. --> <attr name="linksClickable" format="boolean" /> <!-- If set, specifies that this TextView has a numeric input method. The default is false. @@ -7644,6 +7643,12 @@ i <!-- Minimum inset for content views within a bar. Navigation buttons and menu views are excepted. Only valid for some themes and configurations. --> <attr name="contentInsetRight" format="dimension" /> + <!-- Minimum inset for content views within a bar when a navigation button + is present, such as the Up button. Only valid for some themes and configurations. --> + <attr name="contentInsetStartWithNavigation" format="dimension" /> + <!-- Minimum inset for content views within a bar when actions from a menu + are present. Only valid for some themes and configurations. --> + <attr name="contentInsetEndWithActions" format="dimension" /> <!-- Elevation for the action bar itself --> <attr name="elevation" /> <!-- Reference to a theme that should be used to inflate popups @@ -8011,6 +8016,8 @@ i <attr name="contentInsetEnd" /> <attr name="contentInsetLeft" /> <attr name="contentInsetRight" /> + <attr name="contentInsetStartWithNavigation" /> + <attr name="contentInsetEndWithActions" /> <attr name="maxButtonHeight" format="dimension" /> <attr name="navigationButtonStyle" format="reference" /> <attr name="buttonGravity"> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 6ecaa1faac55..892b3d58b388 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2497,4 +2497,8 @@ <!-- True if the device supports at least one form of multi-window. E.g. freeform, split-screen, picture-in-picture. --> <bool name="config_supportsMultiWindow">true</bool> + + <!-- True if the device requires AppWidgetService even if it does not have + the PackageManager.FEATURE_APP_WIDGETS feature --> + <bool name="config_enableAppWidgetService">false</bool> </resources> diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml index 2fe4f6652a87..ad2b335de5bb 100644 --- a/core/res/res/values/dimens_material.xml +++ b/core/res/res/values/dimens_material.xml @@ -41,6 +41,8 @@ <dimen name="action_bar_default_padding_end_material">0dp</dimen> <!-- Default content inset of an action bar. --> <dimen name="action_bar_content_inset_material">16dp</dimen> + <!-- Default content inset of an action bar when a navigation button is present. --> + <dimen name="action_bar_content_inset_with_nav">72dp</dimen> <!-- Vertical padding around action bar icons. --> <dimen name="action_bar_icon_vertical_padding_material">16dp</dimen> <!-- Top margin for action bar subtitles --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index ac29f92fe5d3..0839187c48d1 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2711,6 +2711,8 @@ <public type="attr" name="popupExitTransition" /> <public type="attr" name="minimalHeight" /> <public type="attr" name="forceHasOverlappingRendering" /> + <public type="attr" name="contentInsetStartWithNavigation" /> + <public type="attr" name="contentInsetEndWithActions" /> <public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" /> <public type="style" name="Widget.Material.SeekBar.Discrete" /> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 86b9f1d1b7d5..790dcfa3f127 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1234,6 +1234,7 @@ please see styles_device_defaults.xml. <item name="collapseIcon">?attr/homeAsUpIndicator</item> <item name="collapseContentDescription">@string/toolbar_collapse_description</item> <item name="contentInsetStart">16dp</item> + <item name="contentInsetStartWithNavigation">@dimen/action_bar_content_inset_with_nav</item> <item name="touchscreenBlocksFocus">true</item> </style> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index e636bc09d2bd..2420c1a96a6c 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -311,7 +311,7 @@ please see styles_device_defaults.xml. <style name="TextAppearance.Material.Widget.PopupMenu.Header"> <item name="fontFamily">@string/font_family_title_material</item> <item name="textSize">@dimen/text_size_menu_header_material</item> - <item name="textColor">?attr/textColorSecondary</item> + <item name="textColor">?attr/colorAccent</item> </style> <style name="TextAppearance.Material.Widget.DropDownHint" parent="TextAppearance.Material.Menu" /> @@ -944,6 +944,7 @@ please see styles_device_defaults.xml. <item name="homeLayout">@layout/action_bar_home_material</item> <item name="gravity">center_vertical</item> <item name="contentInsetStart">@dimen/action_bar_content_inset_material</item> + <item name="contentInsetStartWithNavigation">@dimen/action_bar_content_inset_with_nav</item> <item name="contentInsetEnd">@dimen/action_bar_content_inset_material</item> <item name="elevation">@dimen/action_bar_elevation_material</item> <item name="popupTheme">?attr/actionBarPopupTheme</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 6526571e3a11..694e9342000d 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -307,6 +307,7 @@ <java-symbol type="bool" name="config_supportsMultiWindow" /> <java-symbol type="bool" name="config_guestUserEphemeral" /> <java-symbol type="bool" name="config_localDisplaysMirrorContent" /> + <java-symbol type="bool" name="config_enableAppWidgetService" /> <java-symbol type="string" name="config_defaultPictureInPictureBounds" /> <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_threshold" /> <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_factor" /> diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 8f7c6a62a4a3..7871aa81dded 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -179,10 +179,10 @@ public class FontListParser { int tag = 0; String tagStr = parser.getAttributeValue(null, "tag"); if (tagStr != null && TAG_PATTERN.matcher(tagStr).matches()) { - tag = tagStr.charAt(0) << 24 + - tagStr.charAt(1) << 16 + - tagStr.charAt(2) << 8 + - tagStr.charAt(3); + tag = (tagStr.charAt(0) << 24) + + (tagStr.charAt(1) << 16) + + (tagStr.charAt(2) << 8) + + (tagStr.charAt(3) ); } else { throw new XmlPullParserException("Invalid tag attribute value.", parser, null); } diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 01f9cde823c7..1f1785139257 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -322,6 +322,7 @@ LOCAL_SRC_FILES += \ $(hwui_test_common_src_files) \ tests/microbench/main.cpp \ tests/microbench/DisplayListCanvasBench.cpp \ + tests/microbench/FontBench.cpp \ tests/microbench/LinearAllocatorBench.cpp \ tests/microbench/PathParserBench.cpp \ tests/microbench/ShadowBench.cpp \ diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp index 859036543b4a..b70d5868c718 100644 --- a/libs/hwui/BakedOpState.cpp +++ b/libs/hwui/BakedOpState.cpp @@ -108,5 +108,63 @@ ResolvedRenderState::ResolvedRenderState(const ClipRect* clipRect, const Rect& d clippedBounds.doIntersect(clipRect->rect); } +BakedOpState* BakedOpState::tryConstruct(LinearAllocator& allocator, + Snapshot& snapshot, const RecordedOp& recordedOp) { + if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; + BakedOpState* bakedState = allocator.create_trivial<BakedOpState>( + allocator, snapshot, recordedOp, false); + if (bakedState->computedState.clippedBounds.isEmpty()) { + // bounds are empty, so op is rejected + allocator.rewindIfLastAlloc(bakedState); + return nullptr; + } + return bakedState; +} + +BakedOpState* BakedOpState::tryConstructUnbounded(LinearAllocator& allocator, + Snapshot& snapshot, const RecordedOp& recordedOp) { + if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; + return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp); +} + +BakedOpState* BakedOpState::tryStrokeableOpConstruct(LinearAllocator& allocator, + Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) { + if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; + bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined) + ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style) + : true; + + BakedOpState* bakedState = allocator.create_trivial<BakedOpState>( + allocator, snapshot, recordedOp, expandForStroke); + if (bakedState->computedState.clippedBounds.isEmpty()) { + // bounds are empty, so op is rejected + // NOTE: this won't succeed if a clip was allocated + allocator.rewindIfLastAlloc(bakedState); + return nullptr; + } + return bakedState; +} + +BakedOpState* BakedOpState::tryShadowOpConstruct(LinearAllocator& allocator, + Snapshot& snapshot, const ShadowOp* shadowOpPtr) { + if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; + + // clip isn't empty, so construct the op + return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr); +} + +BakedOpState* BakedOpState::directConstruct(LinearAllocator& allocator, + const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) { + return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp); +} + +void BakedOpState::setupOpacity(const SkPaint* paint) { + computedState.opaqueOverClippedBounds = computedState.transform.isSimple() + && computedState.clipState->mode == ClipMode::Rectangle + && MathUtils::areEqual(alpha, 1.0f) + && !roundRectClipState + && PaintUtils::isOpaquePaint(paint); +} + } // namespace uirenderer } // namespace android diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h index 4e3cb8a15e24..e1441fca5ee2 100644 --- a/libs/hwui/BakedOpState.h +++ b/libs/hwui/BakedOpState.h @@ -93,6 +93,7 @@ public: Rect clippedBounds; int clipSideFlags = 0; const SkPath* localProjectionPathMask = nullptr; + bool opaqueOverClippedBounds = false; }; /** @@ -103,23 +104,10 @@ public: class BakedOpState { public: static BakedOpState* tryConstruct(LinearAllocator& allocator, - Snapshot& snapshot, const RecordedOp& recordedOp) { - if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; - BakedOpState* bakedState = allocator.create_trivial<BakedOpState>( - allocator, snapshot, recordedOp, false); - if (bakedState->computedState.clippedBounds.isEmpty()) { - // bounds are empty, so op is rejected - allocator.rewindIfLastAlloc(bakedState); - return nullptr; - } - return bakedState; - } + Snapshot& snapshot, const RecordedOp& recordedOp); static BakedOpState* tryConstructUnbounded(LinearAllocator& allocator, - Snapshot& snapshot, const RecordedOp& recordedOp) { - if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; - return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp); - } + Snapshot& snapshot, const RecordedOp& recordedOp); enum class StrokeBehavior { // stroking is forced, regardless of style on paint (such as for lines) @@ -129,35 +117,16 @@ public: }; static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator, - Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) { - if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; - bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined) - ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style) - : true; - - BakedOpState* bakedState = allocator.create_trivial<BakedOpState>( - allocator, snapshot, recordedOp, expandForStroke); - if (bakedState->computedState.clippedBounds.isEmpty()) { - // bounds are empty, so op is rejected - // NOTE: this won't succeed if a clip was allocated - allocator.rewindIfLastAlloc(bakedState); - return nullptr; - } - return bakedState; - } + Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior); static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator, - Snapshot& snapshot, const ShadowOp* shadowOpPtr) { - if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; - - // clip isn't empty, so construct the op - return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr); - } + Snapshot& snapshot, const ShadowOp* shadowOpPtr); static BakedOpState* directConstruct(LinearAllocator& allocator, - const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) { - return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp); - } + const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp); + + // Set opaqueOverClippedBounds. If this method isn't called, the op is assumed translucent. + void setupOpacity(const SkPaint* paint); // computed state: ResolvedRenderState computedState; diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp index b1314feebf34..b18836f175ea 100644 --- a/libs/hwui/FrameBuilder.cpp +++ b/libs/hwui/FrameBuilder.cpp @@ -481,12 +481,17 @@ void FrameBuilder::deferRenderNodeOp(const RenderNodeOp& op) { * Defers an unmergeable, strokeable op, accounting correctly * for paint's style on the bounds being computed. */ -const BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId, +BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId, BakedOpState::StrokeBehavior strokeBehavior) { // Note: here we account for stroke when baking the op BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct( mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior); if (!bakedState) return nullptr; // quick rejected + + if (op.opId == RecordedOpId::RectOp && op.paint->getStyle() != SkPaint::kStroke_Style) { + bakedState->setupOpacity(op.paint); + } + currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId); return bakedState; } @@ -516,6 +521,7 @@ static bool hasMergeableClip(const BakedOpState& state) { void FrameBuilder::deferBitmapOp(const BitmapOp& op) { BakedOpState* bakedState = tryBakeOpState(op); if (!bakedState) return; // quick rejected + bakedState->setupOpacity(op.paint); // Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h index 0b7a6062456a..02c05cb1bbbe 100644 --- a/libs/hwui/FrameBuilder.h +++ b/libs/hwui/FrameBuilder.h @@ -201,7 +201,7 @@ private: return mAllocator.create<SkPath>(); } - const BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId, + BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId, BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined); /** diff --git a/libs/hwui/LayerBuilder.cpp b/libs/hwui/LayerBuilder.cpp index e6a95ff177a4..eea11bff7d8a 100644 --- a/libs/hwui/LayerBuilder.cpp +++ b/libs/hwui/LayerBuilder.cpp @@ -236,6 +236,21 @@ void LayerBuilder::deferLayerClear(const Rect& rect) { mClearRects.push_back(rect); } +void LayerBuilder::onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState) { + if (bakedState->op->opId != RecordedOpId::CopyToLayerOp) { + // First non-CopyToLayer, so stop stashing up layer clears for unclipped save layers, + // and issue them together in one draw. + flushLayerClears(allocator); + + if (CC_UNLIKELY(activeUnclippedSaveLayers.empty() + && bakedState->computedState.opaqueOverClippedBounds + && bakedState->computedState.clippedBounds.contains(repaintRect))) { + // discard all deferred drawing ops, since new one will occlude them + clear(); + } + } +} + void LayerBuilder::flushLayerClears(LinearAllocator& allocator) { if (CC_UNLIKELY(!mClearRects.empty())) { const int vertCount = mClearRects.size() * 4; @@ -270,11 +285,7 @@ void LayerBuilder::flushLayerClears(LinearAllocator& allocator) { void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId) { - if (batchId != OpBatchType::CopyToLayer) { - // if first op after one or more unclipped saveLayers, flush the layer clears - flushLayerClears(allocator); - } - + onDeferOp(allocator, op); OpBatch* targetBatch = mBatchLookup[batchId]; size_t insertBatchIndex = mBatches.size(); @@ -295,10 +306,7 @@ void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator, void LayerBuilder::deferMergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId, mergeid_t mergeId) { - if (batchId != OpBatchType::CopyToLayer) { - // if first op after one or more unclipped saveLayers, flush the layer clears - flushLayerClears(allocator); - } + onDeferOp(allocator, op); MergingOpBatch* targetBatch = nullptr; // Try to merge with any existing batch with same mergeId @@ -348,6 +356,14 @@ void LayerBuilder::replayBakedOpsImpl(void* arg, } } +void LayerBuilder::clear() { + mBatches.clear(); + for (int i = 0; i < OpBatchType::Count; i++) { + mBatchLookup[i] = nullptr; + mMergingBatchLookup[i].clear(); + } +} + void LayerBuilder::dump() const { ALOGD("LayerBuilder %p, %ux%u buffer %p, blo %p, rn %p (%s)", this, width, height, offscreenBuffer, beginLayerOp, diff --git a/libs/hwui/LayerBuilder.h b/libs/hwui/LayerBuilder.h index 4a7ca2de9b1b..4de432c5e7be 100644 --- a/libs/hwui/LayerBuilder.h +++ b/libs/hwui/LayerBuilder.h @@ -100,9 +100,7 @@ public: return mBatches.empty(); } - void clear() { - mBatches.clear(); - } + void clear(); void dump() const; @@ -117,6 +115,7 @@ public: // list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps std::vector<BakedOpState*> activeUnclippedSaveLayers; private: + void onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState); void flushLayerClears(LinearAllocator& allocator); std::vector<BatchBase*> mBatches; diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp index 9a825fdec601..8e04c8715f62 100644 --- a/libs/hwui/font/Font.cpp +++ b/libs/hwui/font/Font.cpp @@ -356,8 +356,6 @@ void Font::measure(const SkPaint* paint, const glyph_t* glyphs, } void Font::precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs) { - ATRACE_NAME("Precache Glyphs"); - if (numGlyphs == 0 || glyphs == nullptr) { return; } diff --git a/libs/hwui/tests/microbench/FontBench.cpp b/libs/hwui/tests/microbench/FontBench.cpp new file mode 100644 index 000000000000..df3d041e722d --- /dev/null +++ b/libs/hwui/tests/microbench/FontBench.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <benchmark/benchmark.h> + +#include "GammaFontRenderer.h" +#include "tests/common/TestUtils.h" + +#include <SkPaint.h> + +using namespace android; +using namespace android::uirenderer; + +void BM_FontRenderer_precache_cachehits(benchmark::State& state) { + TestUtils::runOnRenderThread([&state](renderthread::RenderThread& thread) { + SkPaint paint; + paint.setTextSize(20); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + GammaFontRenderer gammaFontRenderer; + FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer(); + fontRenderer.setFont(&paint, SkMatrix::I()); + + std::vector<glyph_t> glyphs; + std::vector<float> positions; + float totalAdvance; + uirenderer::Rect bounds; + TestUtils::layoutTextUnscaled(paint, "This is a test", + &glyphs, &positions, &totalAdvance, &bounds); + + fontRenderer.precache(&paint, glyphs.data(), glyphs.size(), SkMatrix::I()); + + while (state.KeepRunning()) { + fontRenderer.precache(&paint, glyphs.data(), glyphs.size(), SkMatrix::I()); + } + }); +} +BENCHMARK(BM_FontRenderer_precache_cachehits); diff --git a/libs/hwui/tests/microbench/FrameBuilderBench.cpp b/libs/hwui/tests/microbench/FrameBuilderBench.cpp index 9daf6334b507..0aef620c7429 100644 --- a/libs/hwui/tests/microbench/FrameBuilderBench.cpp +++ b/libs/hwui/tests/microbench/FrameBuilderBench.cpp @@ -113,15 +113,17 @@ static auto SCENES = { }; void BM_FrameBuilder_defer_scene(benchmark::State& state) { - const char* sceneName = *(SCENES.begin() + state.range_x()); - state.SetLabel(sceneName); - auto nodes = getSyncedSceneNodes(sceneName); - while (state.KeepRunning()) { - FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, - SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h, - nodes, sLightGeometry, Caches::getInstance()); - benchmark::DoNotOptimize(&frameBuilder); - } + TestUtils::runOnRenderThread([&state](RenderThread& thread) { + const char* sceneName = *(SCENES.begin() + state.range_x()); + state.SetLabel(sceneName); + auto nodes = getSyncedSceneNodes(sceneName); + while (state.KeepRunning()) { + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, + SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h, + nodes, sLightGeometry, Caches::getInstance()); + benchmark::DoNotOptimize(&frameBuilder); + } + }); } BENCHMARK(BM_FrameBuilder_defer_scene)->DenseRange(0, SCENES.size() - 1); diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp index e97aaa6ac688..ba22f91cde9d 100644 --- a/libs/hwui/tests/unit/FrameBuilderTests.cpp +++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp @@ -216,6 +216,80 @@ RENDERTHREAD_TEST(FrameBuilder, simpleBatching) { << "Expect number of ops = 2 * loop count"; } +RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_rects) { + class AvoidOverdrawRectsTestRenderer : public TestRendererBase { + public: + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(mIndex++, 0) << "Should be one rect"; + EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds) + << "Last rect should occlude others."; + } + }; + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.drawRect(0, 0, 200, 200, SkPaint()); + canvas.drawRect(0, 0, 200, 200, SkPaint()); + canvas.drawRect(10, 10, 190, 190, SkPaint()); + }); + + // Damage (and therefore clip) is same as last draw, subset of renderable area. + // This means last op occludes other contents, and they'll be rejected to avoid overdraw. + SkRect damageRect = SkRect::MakeLTRB(10, 10, 190, 190); + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, damageRect, 200, 200, + TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); + + EXPECT_EQ(3u, node->getDisplayList()->getOps().size()) + << "Recording must not have rejected ops, in order for this test to be valid"; + + AvoidOverdrawRectsTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()) << "Expect exactly one op"; +} + +RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_bitmaps) { + static SkBitmap opaqueBitmap = TestUtils::createSkBitmap(50, 50, + SkColorType::kRGB_565_SkColorType); + static SkBitmap transpBitmap = TestUtils::createSkBitmap(50, 50, + SkColorType::kAlpha_8_SkColorType); + class AvoidOverdrawBitmapsTestRenderer : public TestRendererBase { + public: + void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { + EXPECT_LT(mIndex++, 2) << "Should be two bitmaps"; + switch(mIndex++) { + case 0: + EXPECT_EQ(opaqueBitmap.pixelRef(), op.bitmap->pixelRef()); + break; + case 1: + EXPECT_EQ(transpBitmap.pixelRef(), op.bitmap->pixelRef()); + break; + default: + ADD_FAILURE() << "Only two ops expected."; + } + } + }; + + auto node = TestUtils::createNode(0, 0, 50, 50, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.drawRect(0, 0, 50, 50, SkPaint()); + canvas.drawRect(0, 0, 50, 50, SkPaint()); + canvas.drawBitmap(transpBitmap, 0, 0, nullptr); + + // only the below draws should remain, since they're + canvas.drawBitmap(opaqueBitmap, 0, 0, nullptr); + canvas.drawBitmap(transpBitmap, 0, 0, nullptr); + }); + + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(50, 50), 50, 50, + TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance()); + + EXPECT_EQ(5u, node->getDisplayList()->getOps().size()) + << "Recording must not have rejected ops, in order for this test to be valid"; + + AvoidOverdrawBitmapsTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2, renderer.getIndex()) << "Expect exactly one op"; +} + RENDERTHREAD_TEST(FrameBuilder, clippedMerging) { class ClippedMergingTestRenderer : public TestRendererBase { public: diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h index db537130e12e..4faab9a5f648 100644 --- a/libs/hwui/utils/PaintUtils.h +++ b/libs/hwui/utils/PaintUtils.h @@ -67,6 +67,21 @@ public: && getXfermode(paint.getXfermode()) == SkXfermode::kSrcOver_Mode; } + static bool isOpaquePaint(const SkPaint* paint) { + if (!paint) return true; // default (paintless) behavior is SrcOver, black + + if (paint->getAlpha() != 0xFF + || PaintUtils::isBlendedShader(paint->getShader()) + || PaintUtils::isBlendedColorFilter(paint->getColorFilter())) { + return false; + } + + // Only let simple srcOver / src blending modes declare opaque, since behavior is clear. + SkXfermode::Mode mode = getXfermode(paint->getXfermode()); + return mode == SkXfermode::Mode::kSrcOver_Mode + || mode == SkXfermode::Mode::kSrc_Mode; + } + static bool isBlendedShader(const SkShader* shader) { if (shader == nullptr) { return false; diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java index 9ed2abf9c9b8..f10af4378c21 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java +++ b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java @@ -45,6 +45,8 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; +import android.system.ErrnoException; +import android.system.Os; import android.text.format.DateUtils; import android.util.Log; import android.webkit.MimeTypeMap; @@ -451,7 +453,7 @@ class CopyJob extends Job { ParcelFileDescriptor srcFile = null; ParcelFileDescriptor dstFile = null; InputStream in = null; - OutputStream out = null; + ParcelFileDescriptor.AutoCloseOutputStream out = null; boolean success = false; try { @@ -502,6 +504,8 @@ class CopyJob extends Job { makeCopyProgress(len); } + // Need to invoke IoUtils.close explicitly to avoid from ignoring errors at flush. + IoUtils.close(dstFile.getFileDescriptor()); srcFile.checkError(); } catch (IOException e) { throw new ResourceException( diff --git a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp index 1d4ed1dc5c5b..eb9601537996 100644 --- a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp +++ b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp @@ -52,6 +52,8 @@ static jclass app_fuse_class; static jmethodID app_fuse_get_file_size; static jmethodID app_fuse_read_object_bytes; static jmethodID app_fuse_write_object_bytes; +static jmethodID app_fuse_flush_file_handle; +static jmethodID app_fuse_close_file_handle; static jfieldID app_fuse_buffer; // NOTE: @@ -307,7 +309,8 @@ private: const uint32_t size = in->size; const void* const buffer = reinterpret_cast<const uint8_t*>(in) + sizeof(fuse_write_in); uint32_t written_size; - const int result = write_object_bytes(it->second, offset, size, buffer, &written_size); + const int result = write_object_bytes( + in->fh, it->second, offset, size, buffer, &written_size); if (result < 0) { return result; } @@ -320,13 +323,13 @@ private: const fuse_release_in* in, FuseResponse<void>* /* out */) { handles_.erase(in->fh); - return 0; + return env_->CallIntMethod(self_, app_fuse_close_file_handle, file_handle_to_jlong(in->fh)); } int handle_fuse_flush(const fuse_in_header& /* header */, - const void* /* in */, + const fuse_flush_in* in, FuseResponse<void>* /* out */) { - return 0; + return env_->CallIntMethod(self_, app_fuse_flush_file_handle, file_handle_to_jlong(in->fh)); } template <typename T, typename S> @@ -382,8 +385,10 @@ private: return read_size; } - int write_object_bytes(int inode, uint64_t offset, uint32_t size, const void* buffer, - uint32_t* written_size) { + int write_object_bytes(uint64_t handle, int inode, uint64_t offset, uint32_t size, + const void* buffer, uint32_t* written_size) { + static_assert(sizeof(uint64_t) <= sizeof(jlong), + "jlong must be able to express any uint64_t values"); ScopedLocalRef<jbyteArray> array( env_, static_cast<jbyteArray>(env_->GetObjectField(self_, app_fuse_buffer))); @@ -394,15 +399,28 @@ private: } memcpy(bytes.get(), buffer, size); } - *written_size = env_->CallIntMethod( - self_, app_fuse_write_object_bytes, inode, offset, size, array.get()); - if (env_->ExceptionCheck()) { - env_->ExceptionClear(); - return -EIO; + const int result = env_->CallIntMethod( + self_, + app_fuse_write_object_bytes, + file_handle_to_jlong(handle), + inode, + offset, + size, + array.get()); + if (result < 0) { + return result; } + *written_size = result; return 0; } + static jlong file_handle_to_jlong(uint64_t handle) { + static_assert( + sizeof(uint64_t) <= sizeof(jlong), + "jlong must be able to express any uint64_t values"); + return static_cast<jlong>(handle); + } + static void fuse_reply(int fd, int unique, int reply_code, void* reply_data, size_t reply_size) { // Don't send any data for error case. @@ -511,15 +529,21 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { return -1; } - app_fuse_buffer = env->GetFieldID(app_fuse_class, "mBuffer", "[B"); - if (app_fuse_buffer == nullptr) { - ALOGE("Can't find mBuffer"); + app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(JIJI[B)I"); + if (app_fuse_write_object_bytes == nullptr) { + ALOGE("Can't find writeObjectBytes"); return -1; } - app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(IJI[B)I"); - if (app_fuse_write_object_bytes == nullptr) { - ALOGE("Can't find getWriteObjectBytes"); + app_fuse_flush_file_handle = env->GetMethodID(app_fuse_class, "flushFileHandle", "(J)I"); + if (app_fuse_flush_file_handle == nullptr) { + ALOGE("Can't find flushFileHandle"); + return -1; + } + + app_fuse_close_file_handle = env->GetMethodID(app_fuse_class, "closeFileHandle", "(J)I"); + if (app_fuse_close_file_handle == nullptr) { + ALOGE("Can't find closeFileHandle"); return -1; } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java index 777dc606f6c3..88858a8b5322 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java @@ -20,6 +20,7 @@ import android.annotation.WorkerThread; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.storage.StorageManager; +import android.system.ErrnoException; import android.system.OsConstants; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -34,6 +35,8 @@ public class AppFuse { System.loadLibrary("appfuse_jni"); } + private static final boolean DEBUG = false; + /** * Max read amount specified at the FUSE kernel implementation. * The value is copied from sdcard.c. @@ -94,7 +97,8 @@ public class AppFuse { public ParcelFileDescriptor openFile(int i, int mode) throws FileNotFoundException { Preconditions.checkArgument( mode == ParcelFileDescriptor.MODE_READ_ONLY || - mode == ParcelFileDescriptor.MODE_WRITE_ONLY); + mode == (ParcelFileDescriptor.MODE_WRITE_ONLY | + ParcelFileDescriptor.MODE_TRUNCATE)); return ParcelFileDescriptor.open(new File( getMountPoint(), Integer.toString(i)), @@ -127,6 +131,7 @@ public class AppFuse { /** * Handles writing bytes for the give inode. + * @param fileHandle * @param inode * @param offset Offset for file bytes. * @param size Size for file bytes. @@ -134,7 +139,23 @@ public class AppFuse { * @return Number of read bytes. Must not be negative. * @throws IOException */ - int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException; + int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes) + throws IOException, ErrnoException; + + /** + * Flushes bytes for file handle. + * @param fileHandle + * @throws IOException + * @throws ErrnoException + */ + void flushFileHandle(long fileHandle) throws IOException, ErrnoException; + + /** + * Closes file handle. + * @param fileHandle + * @throws IOException + */ + void closeFileHandle(long fileHandle) throws IOException, ErrnoException; } @UsedByNative("com_android_mtp_AppFuse.cpp") @@ -142,10 +163,8 @@ public class AppFuse { private long getFileSize(int inode) { try { return mCallback.getFileSize(inode); - } catch (FileNotFoundException e) { - return -OsConstants.ENOENT; - } catch (UnsupportedOperationException e) { - return -OsConstants.ENOTSUP; + } catch (Exception error) { + return -getErrnoFromException(error); } } @@ -159,20 +178,62 @@ public class AppFuse { // It's OK to share the same mBuffer among requests because the requests are processed // by AppFuseMessageThread sequentially. return mCallback.readObjectBytes(inode, offset, size, mBuffer); - } catch (IOException e) { - return -OsConstants.EIO; - } catch (UnsupportedOperationException e) { - return -OsConstants.ENOTSUP; + } catch (Exception error) { + return -getErrnoFromException(error); } } @UsedByNative("com_android_mtp_AppFuse.cpp") @WorkerThread - private /* unsgined */ int writeObjectBytes(int inode, + private /* unsgined */ int writeObjectBytes(long fileHandler, + int inode, /* unsigned */ long offset, /* unsigned */ int size, - byte[] bytes) throws IOException { - return mCallback.writeObjectBytes(inode, offset, size, bytes); + byte[] bytes) { + try { + return mCallback.writeObjectBytes(fileHandler, inode, offset, size, bytes); + } catch (Exception error) { + return -getErrnoFromException(error); + } + } + + @UsedByNative("com_android_mtp_AppFuse.cpp") + @WorkerThread + private int flushFileHandle(long fileHandle) { + try { + mCallback.flushFileHandle(fileHandle); + return 0; + } catch (Exception error) { + return -getErrnoFromException(error); + } + } + + @UsedByNative("com_android_mtp_AppFuse.cpp") + @WorkerThread + private int closeFileHandle(long fileHandle) { + try { + mCallback.closeFileHandle(fileHandle); + return 0; + } catch (Exception error) { + return -getErrnoFromException(error); + } + } + + private static int getErrnoFromException(Exception error) { + if (DEBUG) { + Log.e(MtpDocumentsProvider.TAG, "AppFuse callbacks", error); + } + if (error instanceof FileNotFoundException) { + return OsConstants.ENOENT; + } else if (error instanceof IOException) { + return OsConstants.EIO; + } else if (error instanceof UnsupportedOperationException) { + return OsConstants.ENOTSUP; + } else if (error instanceof IllegalArgumentException) { + return OsConstants.EINVAL; + } else { + return OsConstants.EIO; + } } private native boolean native_start_app_fuse_loop(int fd); diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java index 9f64046ce64a..50781bf2c9ca 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java @@ -17,6 +17,7 @@ package com.android.mtp; import android.content.ContentResolver; +import android.content.Context; import android.content.UriPermission; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; @@ -38,11 +39,16 @@ import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract; import android.provider.DocumentsProvider; import android.provider.Settings; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.io.File; +import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; @@ -82,6 +88,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { private MtpDatabase mDatabase; private AppFuse mAppFuse; private ServiceIntentSender mIntentSender; + private Context mContext; /** * Provides singleton instance to MtpDocumentsService. @@ -93,6 +100,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { @Override public boolean onCreate() { sSingleton = this; + mContext = getContext(); mResources = getContext().getResources(); mMtpManager = new MtpManager(getContext()); mResolver = getContext().getContentResolver(); @@ -137,12 +145,14 @@ public class MtpDocumentsProvider extends DocumentsProvider { @VisibleForTesting boolean onCreateForTesting( + Context context, Resources resources, MtpManager mtpManager, ContentResolver resolver, MtpDatabase database, StorageManager storageManager, ServiceIntentSender intentSender) { + mContext = context; mResources = resources; mMtpManager = mtpManager; mResolver = resolver; @@ -232,43 +242,43 @@ public class MtpDocumentsProvider extends DocumentsProvider { try { openDevice(identifier.mDeviceId); final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord; - switch (mode) { - case "r": - long fileSize; - try { - fileSize = getFileSize(documentId); - } catch (UnsupportedOperationException exception) { - fileSize = -1; - } - // MTP getPartialObject operation does not support files that are larger than - // 4GB. Fallback to non-seekable file descriptor. - if (MtpDeviceRecord.isPartialReadSupported( - device.operationsSupported, fileSize)) { - return mAppFuse.openFile( - Integer.parseInt(documentId), ParcelFileDescriptor.MODE_READ_ONLY); - } else { - return getPipeManager(identifier).readDocument(mMtpManager, identifier); - } - case "w": - // TODO: Clear the parent document loader task (if exists) and call notify - // when writing is completed. - if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) { - return getPipeManager(identifier).writeDocument( - getContext(), mMtpManager, identifier, device.operationsSupported); - } else { - throw new UnsupportedOperationException( - "The device does not support writing operation."); - } - case "rw": - // TODO: Add support for "rw" mode. + // Turn off MODE_CREATE because openDocument does not allow to create new files. + final int modeFlag = + ParcelFileDescriptor.parseMode(mode) & ~ParcelFileDescriptor.MODE_CREATE; + if ((modeFlag & ParcelFileDescriptor.MODE_READ_ONLY) != 0) { + long fileSize; + try { + fileSize = getFileSize(documentId); + } catch (UnsupportedOperationException exception) { + fileSize = -1; + } + if (MtpDeviceRecord.isPartialReadSupported( + device.operationsSupported, fileSize)) { + return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag); + } else { + // If getPartialObject{|64} are not supported for the device, returns + // non-seekable pipe FD instead. + return getPipeManager(identifier).readDocument(mMtpManager, identifier); + } + } else if ((modeFlag & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) { + // TODO: Clear the parent document loader task (if exists) and call notify + // when writing is completed. + if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) { + return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag); + } else { throw new UnsupportedOperationException( - "The provider does not support 'rw' mode."); - default: - throw new IllegalArgumentException("Unknown mode for openDocument: " + mode); + "The device does not support writing operation."); + } + } else { + // TODO: Add support for "rw" mode. + throw new UnsupportedOperationException("The provider does not support 'rw' mode."); } + } catch (FileNotFoundException | RuntimeException error) { + Log.e(MtpDocumentsProvider.TAG, "openDocument", error); + throw error; } catch (IOException error) { Log.e(MtpDocumentsProvider.TAG, "openDocument", error); - throw new FileNotFoundException(error.getMessage()); + throw new IllegalStateException(error); } } @@ -595,6 +605,13 @@ public class MtpDocumentsProvider extends DocumentsProvider { } private class AppFuseCallback implements AppFuse.Callback { + private final Map<Long, MtpFileWriter> mWriters = new HashMap<>(); + + @Override + public long getFileSize(int inode) throws FileNotFoundException { + return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); + } + @Override public long readObjectBytes( int inode, long offset, long size, byte[] buffer) throws IOException { @@ -617,15 +634,43 @@ public class MtpDocumentsProvider extends DocumentsProvider { } @Override - public long getFileSize(int inode) throws FileNotFoundException { - return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); + public int writeObjectBytes( + long fileHandle, int inode, long offset, int size, byte[] bytes) + throws IOException, ErrnoException { + final MtpFileWriter writer; + if (mWriters.containsKey(fileHandle)) { + writer = mWriters.get(fileHandle); + } else { + writer = new MtpFileWriter(mContext, String.valueOf(inode)); + mWriters.put(fileHandle, writer); + } + return writer.write(offset, size, bytes); } @Override - public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) - throws IOException { - // TODO: Implement it. - throw new IOException(); + public void flushFileHandle(long fileHandle) throws IOException, ErrnoException { + final MtpFileWriter writer = mWriters.get(fileHandle); + if (writer == null) { + // File handle for reading. + return; + } + final MtpDeviceRecord device = getDeviceToolkit( + mDatabase.createIdentifier(writer.getDocumentId()).mDeviceId).mDeviceRecord; + writer.flush(mMtpManager, mDatabase, device.operationsSupported); + } + + @Override + public void closeFileHandle(long fileHandle) throws IOException, ErrnoException { + final MtpFileWriter writer = mWriters.get(fileHandle); + if (writer == null) { + // File handle for reading. + return; + } + try { + writer.close(); + } finally { + mWriters.remove(fileHandle); + } } } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpFileWriter.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpFileWriter.java new file mode 100644 index 000000000000..3e1bedce49ea --- /dev/null +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpFileWriter.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mtp; + +import android.content.Context; +import android.mtp.MtpObjectInfo; +import android.os.ParcelFileDescriptor; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; + +import com.android.internal.util.Preconditions; + +import java.io.File; +import java.io.IOException; + +class MtpFileWriter implements AutoCloseable { + final ParcelFileDescriptor mCacheFd; + final String mDocumentId; + boolean mDirty; + + MtpFileWriter(Context context, String documentId) throws IOException { + mDocumentId = documentId; + mDirty = false; + final File tempFile = File.createTempFile("mtp", "tmp", context.getCacheDir()); + mCacheFd = ParcelFileDescriptor.open( + tempFile, + ParcelFileDescriptor.MODE_READ_WRITE | + ParcelFileDescriptor.MODE_TRUNCATE | + ParcelFileDescriptor.MODE_CREATE); + tempFile.delete(); + } + + String getDocumentId() { + return mDocumentId; + } + + int write(long offset, int size, byte[] bytes) throws IOException, ErrnoException { + Preconditions.checkArgumentNonnegative(offset, "offset"); + Preconditions.checkArgumentNonnegative(size, "size"); + Preconditions.checkArgument(size <= bytes.length); + if (size == 0) { + return 0; + } + mDirty = true; + Os.lseek(mCacheFd.getFileDescriptor(), offset, OsConstants.SEEK_SET); + return Os.write(mCacheFd.getFileDescriptor(), bytes, 0, size); + } + + void flush(MtpManager manager, MtpDatabase database, int[] operationsSupported) + throws IOException, ErrnoException { + // Skip unnecessary flush. + if (!mDirty) { + return; + } + + // Get the placeholder object info. + final Identifier identifier = database.createIdentifier(mDocumentId); + final MtpObjectInfo placeholderObjectInfo = + manager.getObjectInfo(identifier.mDeviceId, identifier.mObjectHandle); + + // Delete the target object info if it already exists (as a placeholder). + manager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); + + // Create the target object info with a correct file size and upload the file. + final long size = Os.lseek(mCacheFd.getFileDescriptor(), 0, OsConstants.SEEK_END); + final MtpObjectInfo targetObjectInfo = new MtpObjectInfo.Builder(placeholderObjectInfo) + .setCompressedSize(size) + .build(); + + Os.lseek(mCacheFd.getFileDescriptor(), 0, OsConstants.SEEK_SET); + final int newObjectHandle = manager.createDocument( + identifier.mDeviceId, targetObjectInfo, mCacheFd); + + final MtpObjectInfo newObjectInfo = manager.getObjectInfo( + identifier.mDeviceId, newObjectHandle); + final Identifier parentIdentifier = + database.getParentIdentifier(identifier.mDocumentId); + database.updateObject( + identifier.mDocumentId, + identifier.mDeviceId, + parentIdentifier.mDocumentId, + operationsSupported, + newObjectInfo, + size); + + mDirty = false; + } + + @Override + public void close() throws IOException { + mCacheFd.close(); + } +} diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java index 1520f3b83c2e..795bbc190336 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java @@ -16,13 +16,9 @@ package com.android.mtp; -import android.content.Context; -import android.mtp.MtpObjectInfo; import android.os.ParcelFileDescriptor; import android.util.Log; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -52,15 +48,6 @@ class PipeManager { return task.getReadingFileDescriptor(); } - ParcelFileDescriptor writeDocument(Context context, MtpManager model, Identifier identifier, - int[] operationsSupported) - throws IOException { - final Task task = new WriteDocumentTask( - context, model, identifier, operationsSupported, mDatabase); - mExecutor.execute(task); - return task.getWritingFileDescriptor(); - } - ParcelFileDescriptor readThumbnail(MtpManager model, Identifier identifier) throws IOException { final Task task = new GetThumbnailTask(model, identifier); mExecutor.execute(task); @@ -81,10 +68,6 @@ class PipeManager { ParcelFileDescriptor getReadingFileDescriptor() { return mDescriptors[0]; } - - ParcelFileDescriptor getWritingFileDescriptor() { - return mDescriptors[1]; - } } private static class ImportFileTask extends Task { @@ -108,85 +91,6 @@ class PipeManager { } } - private static class WriteDocumentTask extends Task { - private final Context mContext; - private final MtpDatabase mDatabase; - private final int[] mOperationsSupported; - - WriteDocumentTask(Context context, - MtpManager model, - Identifier identifier, - int[] supportedOperations, - MtpDatabase database) - throws IOException { - super(model, identifier); - mContext = context; - mDatabase = database; - mOperationsSupported = supportedOperations; - } - - @Override - public void run() { - File tempFile = null; - try { - // Obtain a temporary file and copy the data to it. - tempFile = File.createTempFile("mtp", "tmp", mContext.getCacheDir()); - try ( - final FileOutputStream tempOutputStream = - new ParcelFileDescriptor.AutoCloseOutputStream( - ParcelFileDescriptor.open( - tempFile, ParcelFileDescriptor.MODE_WRITE_ONLY)); - final ParcelFileDescriptor.AutoCloseInputStream inputStream = - new ParcelFileDescriptor.AutoCloseInputStream(mDescriptors[0]) - ) { - final byte[] buffer = new byte[32 * 1024]; - int bytes; - while ((bytes = inputStream.read(buffer)) != -1) { - mDescriptors[0].checkError(); - tempOutputStream.write(buffer, 0, bytes); - } - tempOutputStream.flush(); - } - - // Get the placeholder object info. - final MtpObjectInfo placeholderObjectInfo = - mManager.getObjectInfo(mIdentifier.mDeviceId, mIdentifier.mObjectHandle); - - // Delete the target object info if it already exists (as a placeholder). - mManager.deleteDocument(mIdentifier.mDeviceId, mIdentifier.mObjectHandle); - - // Create the target object info with a correct file size and upload the file. - final MtpObjectInfo targetObjectInfo = - new MtpObjectInfo.Builder(placeholderObjectInfo) - .setCompressedSize(tempFile.length()) - .build(); - final ParcelFileDescriptor tempInputDescriptor = ParcelFileDescriptor.open( - tempFile, ParcelFileDescriptor.MODE_READ_ONLY); - final int newObjectHandle = mManager.createDocument( - mIdentifier.mDeviceId, targetObjectInfo, tempInputDescriptor); - - final MtpObjectInfo newObjectInfo = mManager.getObjectInfo( - mIdentifier.mDeviceId, newObjectHandle); - final Identifier parentIdentifier = - mDatabase.getParentIdentifier(mIdentifier.mDocumentId); - mDatabase.updateObject( - mIdentifier.mDocumentId, - mIdentifier.mDeviceId, - parentIdentifier.mDocumentId, - mOperationsSupported, - newObjectInfo, - tempFile.length()); - } catch (IOException error) { - Log.w(MtpDocumentsProvider.TAG, - "Failed to send a file because of: " + error.getMessage()); - } finally { - if (tempFile != null) { - tempFile.delete(); - } - } - } - } - private static class GetThumbnailTask extends Task { GetThumbnailTask(MtpManager model, Identifier identifier) throws IOException { super(model, identifier); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java index 3b925068c50c..e421de7113de 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java @@ -23,6 +23,8 @@ import android.system.Os; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.MediumTest; +import libcore.io.IoUtils; + import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -143,7 +145,8 @@ public class AppFuseTest extends AndroidTestCase { } @Override - public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) { + public int writeObjectBytes( + long fileHandle, int inode, long offset, int size, byte[] bytes) { for (int i = 0; i < size; i++) { resultBytes[(int)(offset + i)] = bytes[i]; } @@ -152,7 +155,7 @@ public class AppFuseTest extends AndroidTestCase { }); appFuse.mount(storageManager); final ParcelFileDescriptor fd = appFuse.openFile( - INODE, ParcelFileDescriptor.MODE_WRITE_ONLY); + INODE, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE); try (final ParcelFileDescriptor.AutoCloseOutputStream stream = new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { stream.write('a'); @@ -182,7 +185,7 @@ public class AppFuseTest extends AndroidTestCase { }); appFuse.mount(storageManager); final ParcelFileDescriptor fd = appFuse.openFile( - INODE, ParcelFileDescriptor.MODE_WRITE_ONLY); + INODE, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE); try (final ParcelFileDescriptor.AutoCloseOutputStream stream = new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { stream.write('a'); @@ -192,6 +195,46 @@ public class AppFuseTest extends AndroidTestCase { appFuse.close(); } + public void testWriteFile_flushError() throws IOException { + final StorageManager storageManager = getContext().getSystemService(StorageManager.class); + final int INODE = 10; + final AppFuse appFuse = new AppFuse( + "test", + new TestCallback() { + @Override + public long getFileSize(int inode) throws FileNotFoundException { + if (inode != INODE) { + throw new FileNotFoundException(); + } + return 5; + } + + @Override + public int writeObjectBytes( + long fileHandle, int inode, long offset, int size, byte[] bytes) { + return size; + } + + @Override + public void flushFileHandle(long fileHandle) throws IOException { + throw new IOException(); + } + }); + appFuse.mount(storageManager); + final ParcelFileDescriptor fd = appFuse.openFile( + INODE, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE); + try (final ParcelFileDescriptor.AutoCloseOutputStream stream = + new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { + stream.write('a'); + try { + IoUtils.close(fd.getFileDescriptor()); + fail(); + } catch (IOException e) { + } + } + appFuse.close(); + } + private static class TestCallback implements AppFuse.Callback { @Override public long getFileSize(int inode) throws FileNotFoundException { @@ -205,9 +248,15 @@ public class AppFuseTest extends AndroidTestCase { } @Override - public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) + public int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes) throws IOException { throw new IOException(); } + + @Override + public void flushFileHandle(long fileHandle) throws IOException {} + + @Override + public void closeFileHandle(long fileHandle) {} } } diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index 0de761cb57c4..9ed15c82f336 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -21,6 +21,7 @@ import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; import android.net.Uri; import android.os.ParcelFileDescriptor; +import android.os.ParcelFileDescriptor.AutoCloseOutputStream; import android.os.storage.StorageManager; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; @@ -533,6 +534,30 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { } } + public void testOpenDocument_writing() throws Exception { + setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); + setupRoots(0, new MtpRoot[] { + new MtpRoot(0, 0, "Storage", 0, 0, "") + }); + final String documentId = mProvider.createDocument("2", "text/plain", "test.txt"); + { + final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "w", null); + try (ParcelFileDescriptor.AutoCloseOutputStream stream = + new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { + stream.write("Hello".getBytes()); + } + } + { + final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "r", null); + try (ParcelFileDescriptor.AutoCloseInputStream stream = + new ParcelFileDescriptor.AutoCloseInputStream(fd)) { + final byte[] bytes = new byte[5]; + stream.read(bytes); + assertTrue(Arrays.equals("Hello".getBytes(), bytes)); + } + } + } + public void testBusyDevice() throws Exception { mMtpManager = new TestMtpManager(getContext()) { @Override @@ -740,6 +765,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { mProvider = new MtpDocumentsProvider(); final StorageManager storageManager = getContext().getSystemService(StorageManager.class); assertTrue(mProvider.onCreateForTesting( + getContext(), mResources, mMtpManager, mResolver, diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java index 86117971de92..53dc3dbcf1ea 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java @@ -16,10 +16,7 @@ package com.android.mtp; -import android.database.Cursor; -import android.mtp.MtpObjectInfo; import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract.Document; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.MediumTest; @@ -66,64 +63,6 @@ public class PipeManagerTest extends AndroidTestCase { assertDescriptorError(descriptor); } - public void testWriteDocument_basic() throws Exception { - TestUtil.addTestDevice(mDatabase); - TestUtil.addTestStorage(mDatabase, "1"); - - final MtpObjectInfo info = - new MtpObjectInfo.Builder().setObjectHandle(1).setName("note.txt").build(); - mDatabase.getMapper().startAddingDocuments("2"); - mDatabase.getMapper().putChildDocuments( - 0, "2", TestUtil.OPERATIONS_SUPPORTED, - new MtpObjectInfo[] { info }, - new long[] { 0L }); - mDatabase.getMapper().stopAddingDocuments("2"); - // Create a placeholder file which should be replaced by a real file later. - mtpManager.setObjectInfo(0, info); - - // Upload testing bytes. - final ParcelFileDescriptor descriptor = mPipeManager.writeDocument( - getContext(), - mtpManager, - new Identifier(0, 0, 1, "2", MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT), - TestUtil.OPERATIONS_SUPPORTED); - final ParcelFileDescriptor.AutoCloseOutputStream outputStream = - new ParcelFileDescriptor.AutoCloseOutputStream(descriptor); - outputStream.write(HELLO_BYTES, 0, HELLO_BYTES.length); - outputStream.close(); - mExecutor.shutdown(); - assertTrue(mExecutor.awaitTermination(1000, TimeUnit.MILLISECONDS)); - - // Check if the placeholder file is removed. - try { - mtpManager.getObjectInfo(0, 1); - fail(); // The placeholder file has not been deleted. - } catch (IOException e) { - // Expected error, as the file is gone. - } - - // Confirm that the target file is created. - final MtpObjectInfo targetDocument = mtpManager.getObjectInfo( - 0, TestMtpManager.CREATED_DOCUMENT_HANDLE); - assertTrue(targetDocument != null); - - // Confirm the object handle is updated. - try (final Cursor cursor = mDatabase.queryDocument( - "2", new String[] { MtpDatabaseConstants.COLUMN_OBJECT_HANDLE })) { - assertEquals(1, cursor.getCount()); - cursor.moveToNext(); - assertEquals(TestMtpManager.CREATED_DOCUMENT_HANDLE, cursor.getInt(0)); - } - - // Verify uploaded bytes. - final byte[] uploadedBytes = mtpManager.getImportFileBytes( - 0, TestMtpManager.CREATED_DOCUMENT_HANDLE); - assertEquals(HELLO_BYTES.length, uploadedBytes.length); - for (int i = 0; i < HELLO_BYTES.length; i++) { - assertEquals(HELLO_BYTES[i], uploadedBytes[i]); - } - } - public void testReadThumbnail_basic() throws Exception { mtpManager.setThumbnail(0, 1, HELLO_BYTES); final ParcelFileDescriptor descriptor = mPipeManager.readThumbnail( diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java index 64910fdf8695..c3a50890456a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java @@ -155,8 +155,10 @@ public class SettingsDrawerActivity extends Activity { mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); updateDrawer(); } else { - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); - mDrawerLayout = null; + if (mDrawerLayout != null) { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + mDrawerLayout = null; + } } } diff --git a/packages/SystemUI/res/layout/recents_stack_action_button.xml b/packages/SystemUI/res/layout/recents_stack_action_button.xml index 87832616f710..2b2ce4e53483 100644 --- a/packages/SystemUI/res/layout/recents_stack_action_button.xml +++ b/packages/SystemUI/res/layout/recents_stack_action_button.xml @@ -27,8 +27,6 @@ android:textSize="14sp" android:textColor="#FFFFFF" android:textAllCaps="true" - android:drawableStart="@drawable/ic_history" - android:drawablePadding="6dp" android:shadowColor="#99000000" android:shadowDx="0" android:shadowDy="2" diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 622ae717058c..fd051b1b371f 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -153,6 +153,9 @@ <!-- The animation duration for animating the removal of a task view. --> <integer name="recents_animate_task_view_remove_duration">175</integer> + <!-- The base animation duration for animating the removal of all task views. --> + <integer name="recents_animate_task_views_remove_all_duration">300</integer> + <!-- The animation duration for scrolling the stack to a particular item. --> <integer name="recents_animate_task_stack_scroll_duration">200</integer> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index d1faa4a55d96..c094da9db43a 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -574,10 +574,12 @@ <dimen name="recents_layout_bottom_margin">16dp</dimen> <dimen name="recents_layout_side_margin_phone">16dp</dimen> <dimen name="recents_layout_side_margin_tablet">48dp</dimen> + <dimen name="recents_layout_side_margin_tablet_docked">16dp</dimen> <dimen name="recents_layout_side_margin_tablet_xlarge">64dp</dimen> + <dimen name="recents_layout_side_margin_tablet_xlarge_docked">16dp</dimen> <!-- The height between the top margin and the top of the focused task. --> - <dimen name="recents_layout_top_peek_size">56dp</dimen> + <dimen name="recents_layout_top_peek_size">48dp</dimen> <!-- The height between the bottom margin and the top of task in front of the focused task. --> <dimen name="recents_layout_bottom_peek_size">56dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java index 5e33a9f84cac..17f4dab93bf4 100644 --- a/packages/SystemUI/src/com/android/systemui/Interpolators.java +++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java @@ -17,6 +17,7 @@ package com.android.systemui; import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; @@ -32,6 +33,7 @@ public class Interpolators { public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f); public static final Interpolator LINEAR = new LinearInterpolator(); + public static final Interpolator ACCELERATE = new AccelerateInterpolator(); public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator(); public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java index 165988805f51..5c084f96585f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java @@ -24,6 +24,8 @@ import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.UserHandle; +import android.os.UserManager; import android.util.Log; import android.util.SparseArray; import android.view.View; @@ -81,6 +83,8 @@ public abstract class QSTile<TState extends State> implements Listenable { abstract protected void handleClick(); abstract protected void handleUpdateState(TState state, Object arg); + private UserManager mUserManager; + /** * Declare the category of this tile. * @@ -93,6 +97,7 @@ public abstract class QSTile<TState extends State> implements Listenable { mHost = host; mContext = host.getContext(); mHandler = new H(host.getLooper()); + mUserManager = UserManager.get(mContext); } public String getTileSpec() { @@ -282,12 +287,11 @@ public abstract class QSTile<TState extends State> implements Listenable { } protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) { - EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(mContext, - userRestriction, ActivityManager.getCurrentUser()); - if (admin != null && !RestrictedLockUtils.hasBaseUserRestriction(mContext, - userRestriction, ActivityManager.getCurrentUser())) { + UserHandle user = UserHandle.of(ActivityManager.getCurrentUser()); + if (mUserManager.hasUserRestriction(userRestriction, user) + && !mUserManager.hasBaseUserRestriction(userRestriction, user)) { state.disabledByPolicy = true; - state.enforcedAdmin = admin; + state.enforcedAdmin = EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; } else { state.disabledByPolicy = false; state.enforcedAdmin = null; diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index b89c2f67a220..d7777d569337 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -90,6 +90,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD private RecentsPackageMonitor mPackageMonitor; private long mLastTabKeyEventTime; + private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED; private boolean mFinishedOnStartup; private boolean mIgnoreAltTabRelease; private boolean mIsVisible; @@ -266,6 +267,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD getWindow().getAttributes().privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; + mLastOrientation = getResources().getConfiguration().orientation; mFocusTimerDuration = getResources().getInteger(R.integer.recents_auto_advance_duration); mIterateTrigger = new DozeTrigger(mFocusTimerDuration, new Runnable() { @Override @@ -274,6 +276,9 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD } }); + // Set the window background + getWindow().setBackgroundDrawable(mRecentsView.getBackgroundScrim()); + // Create the home intent runnable mHomeIntent = new Intent(Intent.ACTION_MAIN, null); mHomeIntent.addCategory(Intent.CATEGORY_HOME); @@ -415,13 +420,18 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD // Update the nav bar for the current orientation updateNavBarScrim(false /* animateNavBarScrim */, AnimationProps.IMMEDIATE); - EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */)); + // Notify of the config change + int newOrientation = getResources().getConfiguration().orientation; + EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */, + (mLastOrientation != newOrientation))); + mLastOrientation = newOrientation; } @Override public void onMultiWindowChanged(boolean inMultiWindow) { super.onMultiWindowChanged(inMultiWindow); - EventBus.getDefault().send(new ConfigurationChangedEvent(true /* fromMultiWindow */)); + EventBus.getDefault().send(new ConfigurationChangedEvent(true /* fromMultiWindow */, + false /* fromOrientationChange */)); if (mRecentsView != null) { // Reload the task stack completely diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java index 043510ed72ca..171535691bbc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java @@ -36,7 +36,7 @@ public class RecentsDebugFlags implements TunerService.Tunable { // Enables the task affiliations public static final boolean EnableAffiliatedTaskGroups = false; // TODO: To be repurposed - public static final boolean EnableStackActionButton = false; + public static final boolean EnableStackActionButton = true; // Overrides the Tuner flags and enables the timeout private static final boolean EnableFastToggleTimeout = false; // Overrides the Tuner flags and enables the paging via the Recents button diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index ffc037dba5e5..43d627d9e13a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -663,13 +663,18 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener @Override public void run() { final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform); - mHandler.post(new Runnable() { - @Override - public void run() { - mThumbnailTransitionBitmapCache = transitionBitmap; - mThumbnailTransitionBitmapCacheKey = toTask; - } - }); + if (transitionBitmap != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + mThumbnailTransitionBitmapCache = transitionBitmap; + mThumbnailTransitionBitmapCacheKey = toTask; + } + }); + } else { + Log.e(TAG, "Could not load thumbnail for task: " + toTask + " at transform: " + + toTransform); + } } }); } @@ -774,7 +779,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // Get the transform for the running task stackView.updateLayoutAlgorithm(true /* boundScroll */); stackView.updateToInitialState(true /* scrollToInitialState */); - mTmpTransform = stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask, + stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask, stackView.getScroller().getStackScroll(), mTmpTransform, null); return mTmpTransform; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java index c234c348ad66..8be9ca7b5f2b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java @@ -24,8 +24,10 @@ import com.android.systemui.recents.events.EventBus; public class ConfigurationChangedEvent extends EventBus.AnimatedEvent { public final boolean fromMultiWindow; + public final boolean fromOrientationChange; - public ConfigurationChangedEvent(boolean fromMultiWindow) { + public ConfigurationChangedEvent(boolean fromMultiWindow, boolean fromOrientationChange) { this.fromMultiWindow = fromMultiWindow; + this.fromOrientationChange = fromOrientationChange; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/ResetBackgroundScrimEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissAllTaskViewsEvent.java index 863f40b39537..f8b59c7c62f7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/ResetBackgroundScrimEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissAllTaskViewsEvent.java @@ -17,10 +17,11 @@ package com.android.systemui.recents.events.ui; import com.android.systemui.recents.events.EventBus; +import com.android.systemui.recents.views.TaskView; /** - * This is sent to reset the background scrim back to the initial state. + * This event is sent to request that all the {@link TaskView}s are dismissed. */ -public class ResetBackgroundScrimEvent extends EventBus.Event { +public class DismissAllTaskViewsEvent extends EventBus.AnimatedEvent { // Simple event } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java index 1165f4e76861..1f8c6443502f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java @@ -17,7 +17,6 @@ package com.android.systemui.recents.events.ui; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.Task; import com.android.systemui.recents.views.TaskView; /** @@ -26,10 +25,8 @@ import com.android.systemui.recents.views.TaskView; public class DismissTaskViewEvent extends EventBus.AnimatedEvent { public final TaskView taskView; - public final Task task; - public DismissTaskViewEvent(TaskView taskView, Task task) { + public DismissTaskViewEvent(TaskView taskView) { this.taskView = taskView; - this.task = task; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateBackgroundScrimEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateBackgroundScrimEvent.java deleted file mode 100644 index fdd4c673540d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateBackgroundScrimEvent.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.events.ui; - -import com.android.systemui.recents.events.EventBus; - -/** - * This is sent to request an update to the background scrim. - */ -public class UpdateBackgroundScrimEvent extends EventBus.Event { - - public final float alpha; - - public UpdateBackgroundScrimEvent(float alpha) { - this.alpha = alpha; - } -} 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 5a2507debd7e..df3f56c7c3ac 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -222,6 +222,11 @@ public class TaskStack { Task newFrontMostTask, AnimationProps animation, boolean fromDockGesture); /** + * Notifies when all tasks have been removed from the stack. + */ + void onStackTasksRemoved(TaskStack stack); + + /** * Notifies when tasks in the stack have been updated. */ void onStackTasksUpdated(TaskStack stack); @@ -510,6 +515,22 @@ public class TaskStack { } /** + * Removes all tasks from the stack. + */ + public void removeAllTasks() { + ArrayList<Task> tasks = mStackTaskList.getTasks(); + for (int i = tasks.size() - 1; i >= 0; i--) { + Task t = tasks.get(i); + removeTaskImpl(mStackTaskList, t); + mRawTaskList.remove(t); + } + if (mCb != null) { + // Notify that all tasks have been removed + mCb.onStackTasksRemoved(this); + } + } + + /** * Sets a few tasks in one go, without calling any callbacks. * * @param tasks the new set of tasks to replace the current set. diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java index 3d0e75a8ef70..5eb9fda98d93 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java @@ -158,6 +158,11 @@ public class TaskStackHorizontalGridView extends HorizontalGridView implements T } @Override + public void onStackTasksRemoved(TaskStack stack) { + // Do nothing + } + + @Override public void onStackTasksUpdated(TaskStack stack) { // Do nothing } 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 9dc3fb14936a..9eec2ce030c2 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java @@ -28,11 +28,13 @@ import android.app.ActivityOptions.OnAnimationStartedListener; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.IRemoteCallback; import android.os.RemoteException; +import android.util.Log; import android.view.AppTransitionAnimationSpec; import android.view.IAppTransitionAnimationSpecsFuture; @@ -252,12 +254,13 @@ public class RecentsTransitionHelper { /** * Composes the transition spec when docking a task, which includes a full task bitmap. */ - public List<AppTransitionAnimationSpec> composeDockAnimationSpec( - TaskView taskView, Rect transform) { - TaskViewTransform viewTransform = new TaskViewTransform(); - viewTransform.fillIn(taskView); - return Collections.singletonList(new AppTransitionAnimationSpec(taskView.getTask().key.id, - RecentsTransitionHelper.composeTaskBitmap(taskView, viewTransform), transform)); + public List<AppTransitionAnimationSpec> composeDockAnimationSpec(TaskView taskView, + Rect bounds) { + mTmpTransform.fillIn(taskView); + Task task = taskView.getTask(); + Bitmap thumbnail = RecentsTransitionHelper.composeTaskBitmap(taskView, mTmpTransform); + return Collections.singletonList(new AppTransitionAnimationSpec(task.key.id, thumbnail, + bounds)); } /** @@ -336,18 +339,27 @@ public class RecentsTransitionHelper { float scale = transform.scale; int fromWidth = (int) (transform.rect.width() * scale); int fromHeight = (int) (transform.rect.height() * scale); - Bitmap b = Bitmap.createBitmap(fromWidth, fromHeight, - Bitmap.Config.ARGB_8888); + if (fromWidth == 0 || fromHeight == 0) { + Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() + + " at transform: " + transform); - if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) { - b.eraseColor(0xFFff0000); + Bitmap b = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + b.eraseColor(Color.TRANSPARENT); + return b; } else { - Canvas c = new Canvas(b); - c.scale(scale, scale); - taskView.draw(c); - c.setBitmap(null); + Bitmap b = Bitmap.createBitmap(fromWidth, fromHeight, + Bitmap.Config.ARGB_8888); + + if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) { + b.eraseColor(0xFFff0000); + } else { + Canvas c = new Canvas(b); + c.scale(scale, scale); + taskView.draw(c); + c.setBitmap(null); + } + return b.createAshmemBitmap(); } - return b.createAshmemBitmap(); } private static Bitmap composeHeaderBitmap(TaskView taskView, 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 b23b01f93320..da2f7212cae0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -58,10 +58,11 @@ import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationC import com.android.systemui.recents.events.activity.HideStackActionButtonEvent; import com.android.systemui.recents.events.activity.LaunchTaskEvent; import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent; +import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; +import com.android.systemui.recents.events.ui.DeleteTaskDataEvent; +import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent; import com.android.systemui.recents.events.ui.DraggingInRecentsEvent; -import com.android.systemui.recents.events.ui.ResetBackgroundScrimEvent; -import com.android.systemui.recents.events.ui.UpdateBackgroundScrimEvent; import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; @@ -87,6 +88,9 @@ public class RecentsView extends FrameLayout { private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200; private static final float DEFAULT_SCRIM_ALPHA = 0.33f; + private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 150; + private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100; + private TaskStack mStack; private TaskStackView mTaskStackView; private TextView mStackActionButton; @@ -99,7 +103,8 @@ public class RecentsView extends FrameLayout { private Rect mSystemInsets = new Rect(); private int mDividerSize; - private ColorDrawable mBackgroundScrim = new ColorDrawable(Color.BLACK); + private Drawable mBackgroundScrim = new ColorDrawable( + Color.argb((int) (DEFAULT_SCRIM_ALPHA * 255), 0, 0, 0)).mutate(); private Animator mBackgroundScrimAnimator; private RecentsTransitionHelper mTransitionHelper; @@ -135,10 +140,11 @@ public class RecentsView extends FrameLayout { R.dimen.recents_task_view_rounded_corners_radius); mStackActionButton = (TextView) inflater.inflate(R.layout.recents_stack_action_button, this, false); + mStackActionButton.forceHasOverlappingRendering(false); mStackActionButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - // TODO: To be implemented + EventBus.getDefault().send(new DismissAllTaskViewsEvent()); } }); addView(mStackActionButton); @@ -152,8 +158,6 @@ public class RecentsView extends FrameLayout { } mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false); addView(mEmptyView); - - setBackground(mBackgroundScrim); } /** @@ -179,14 +183,14 @@ public class RecentsView extends FrameLayout { if (isResumingFromVisible) { // If we are already visible, then restore the background scrim - animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, DEFAULT_UPDATE_SCRIM_DURATION); + animateBackgroundScrim(1f, DEFAULT_UPDATE_SCRIM_DURATION); } else { // If we are already occluded by the app, then set the final background scrim alpha now. // Otherwise, defer until the enter animation completes to animate the scrim alpha with // the tasks for the home animation. if (launchState.launchedViaDockGesture || launchState.launchedFromApp || isTaskStackEmpty) { - mBackgroundScrim.setAlpha((int) (DEFAULT_SCRIM_ALPHA * 255)); + mBackgroundScrim.setAlpha(255); } else { mBackgroundScrim.setAlpha(0); } @@ -215,6 +219,13 @@ public class RecentsView extends FrameLayout { return mStack; } + /* + * Returns the window background scrim. + */ + public Drawable getBackgroundScrim() { + return mBackgroundScrim; + } + /** * Returns whether the last task launched was in the freeform stack or not. */ @@ -566,17 +577,21 @@ public class RecentsView extends FrameLayout { RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp && mStack.getTaskCount() > 0) { - animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, + animateBackgroundScrim(1f, TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION); } } - public final void onBusEvent(UpdateBackgroundScrimEvent event) { - animateBackgroundScrim(event.alpha, DEFAULT_UPDATE_SCRIM_DURATION); + public final void onBusEvent(AllTaskViewsDismissedEvent event) { + hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */); } - public final void onBusEvent(ResetBackgroundScrimEvent event) { - animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, DEFAULT_UPDATE_SCRIM_DURATION); + public final void onBusEvent(DismissAllTaskViewsEvent event) { + SystemServicesProxy ssp = Recents.getSystemServices(); + if (!ssp.hasDockedTask()) { + // Animate the background away only if we are dismissing Recents to home + animateBackgroundScrim(0f, DEFAULT_UPDATE_SCRIM_DURATION); + } } public final void onBusEvent(ShowStackActionButtonEvent event) { @@ -584,7 +599,7 @@ public class RecentsView extends FrameLayout { return; } - showStackActionButton(150, event.translate); + showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate); } public final void onBusEvent(HideStackActionButtonEvent event) { @@ -592,7 +607,7 @@ public class RecentsView extends FrameLayout { return; } - hideStackActionButton(100, true /* translate */); + hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */); } /** @@ -623,7 +638,6 @@ public class RecentsView extends FrameLayout { .alpha(1f) .setDuration(duration) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .withLayer() .start(); } }); @@ -669,7 +683,6 @@ public class RecentsView extends FrameLayout { postAnimationTrigger.decrement(); } }) - .withLayer() .start(); postAnimationTrigger.increment(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java index 9c8189a216bd..13ad9a5ee90d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java @@ -24,6 +24,7 @@ import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent; +import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; /** Manages the scrims for the various system bars. */ public class SystemBarScrimViews { @@ -107,4 +108,14 @@ public class SystemBarScrimViews { animateNavBarScrimVisibility(false, animation); } } + + public final void onBusEvent(DismissAllTaskViewsEvent event) { + if (mHasNavBarScrim) { + AnimationProps animation = new AnimationProps() + .setDuration(AnimationProps.BOUNDS, + TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION) + .setInterpolator(AnimationProps.BOUNDS, Interpolators.FAST_OUT_SLOW_IN); + animateNavBarScrimVisibility(false, animation); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java index 8db81f73f700..1c433d8eb31c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java @@ -31,6 +31,7 @@ import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivityLaunchState; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.ReferenceCountedTrigger; +import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; @@ -277,7 +278,6 @@ public class TaskStackAnimationHelper { public void startExitToHomeAnimation(boolean animated, ReferenceCountedTrigger postAnimationTrigger) { TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); - TaskStackViewScroller stackScroller = mStackView.getScroller(); TaskStack stack = mStackView.getStack(); // Break early if there are no tasks @@ -313,8 +313,7 @@ public class TaskStackAnimationHelper { taskAnimation = AnimationProps.IMMEDIATE; } - stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, - null); + mTmpTransform.fillIn(tv); mTmpTransform.alpha = 0f; mTmpTransform.rect.offset(0, offscreenYOffset); mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); @@ -328,8 +327,6 @@ public class TaskStackAnimationHelper { public void startLaunchTaskAnimation(TaskView launchingTaskView, boolean screenPinningRequested, final ReferenceCountedTrigger postAnimationTrigger) { Resources res = mStackView.getResources(); - TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); - TaskStackViewScroller stackScroller = mStackView.getScroller(); int taskViewExitToAppDuration = res.getInteger( R.integer.recents_task_exit_to_app_duration); @@ -362,8 +359,7 @@ public class TaskStackAnimationHelper { postAnimationTrigger.decrementOnAnimationEnd()); postAnimationTrigger.increment(); - stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, - null); + mTmpTransform.fillIn(tv); mTmpTransform.alpha = 0f; mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset); mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); @@ -374,16 +370,14 @@ public class TaskStackAnimationHelper { /** * Starts the delete animation for the specified {@link TaskView}. */ - public void startDeleteTaskAnimation(Task deleteTask, final TaskView deleteTaskView, + public void startDeleteTaskAnimation(final TaskView deleteTaskView, final ReferenceCountedTrigger postAnimationTrigger) { Resources res = mStackView.getResources(); TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); - TaskStackViewScroller stackScroller = mStackView.getScroller(); int taskViewRemoveAnimDuration = res.getInteger( R.integer.recents_animate_task_view_remove_duration); - int taskViewRemoveAnimTranslationXPx = res.getDimensionPixelSize( - R.dimen.recents_task_view_remove_anim_translation_x); + int offscreenXOffset = mStackView.getMeasuredWidth() - stackLayout.mTaskRect.left; // Disabling clipping with the stack while the view is animating away, this will get // restored when the task is next picked up from the view pool @@ -399,14 +393,58 @@ public class TaskStackAnimationHelper { }); postAnimationTrigger.increment(); - stackLayout.getStackTransform(deleteTask, stackScroller.getStackScroll(), mTmpTransform, - null); + mTmpTransform.fillIn(deleteTaskView); mTmpTransform.alpha = 0f; - mTmpTransform.rect.offset(taskViewRemoveAnimTranslationXPx, 0); + mTmpTransform.rect.offset(offscreenXOffset, 0); mStackView.updateTaskViewToTransform(deleteTaskView, mTmpTransform, taskAnimation); } /** + * Starts the delete animation for all the {@link TaskView}s. + */ + public void startDeleteAllTasksAnimation(final List<TaskView> taskViews, + final ReferenceCountedTrigger postAnimationTrigger) { + Resources res = mStackView.getResources(); + TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); + + int taskViewRemoveAnimDuration = res.getInteger( + R.integer.recents_animate_task_views_remove_all_duration); + int offscreenXOffset = mStackView.getMeasuredWidth() - stackLayout.mTaskRect.left; + + int taskViewCount = taskViews.size(); + int startDelayMax = 125; + + for (int i = taskViewCount - 1; i >= 0; i--) { + TaskView tv = taskViews.get(i); + int indexFromFront = taskViewCount - i - 1; + float x = Interpolators.ACCELERATE.getInterpolation((float) indexFromFront / + taskViewCount); + int startDelay = (int) Utilities.mapRange(x, 0, startDelayMax); + + // Disabling clipping with the stack while the view is animating away + tv.setClipViewInStack(false); + + // Compose the new animation and transform and star the animation + AnimationProps taskAnimation = new AnimationProps(startDelay, + taskViewRemoveAnimDuration, Interpolators.FAST_OUT_LINEAR_IN, + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + postAnimationTrigger.decrement(); + + // Re-enable clipping with the stack (we will reuse this view) + tv.setClipViewInStack(true); + } + }); + postAnimationTrigger.increment(); + + mTmpTransform.fillIn(tv); + mTmpTransform.rect.offset(offscreenXOffset, 0); + mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); + } + } + + /** * Starts the animation to focus the next {@link TaskView} when paging through recents. * * @return whether or not this will trigger a scroll in the stack 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 4b1faf3ef395..25083040df1d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -557,15 +557,17 @@ public class TaskStackLayoutAlgorithm { mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP); mInitialNormX = null; } else { - float normX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP); - mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, (mNumStackTasks - 2)) - - Math.max(0, mUnfocusedRange.getAbsoluteX(normX))); + // We are overriding the initial two task positions, so set the initial scroll + // position to match the second task (aka focused task) position + float initialTopNormX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP); + mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, (mNumStackTasks - 2)) + - Math.max(0, mUnfocusedRange.getAbsoluteX(initialTopNormX))); // Set the initial scroll to the predefined state (which differs from the stack) mInitialNormX = new float[] { getNormalizedXFromUnfocusedY(mSystemInsets.bottom + mInitialBottomOffset, FROM_BOTTOM), - normX + initialTopNormX }; } } 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 04f153fb7996..5416a4897666 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -72,6 +72,7 @@ import com.android.systemui.recents.events.activity.PackagesChangedEvent; import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent; import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; import com.android.systemui.recents.events.ui.DeleteTaskDataEvent; +import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; import com.android.systemui.recents.events.ui.DismissTaskViewEvent; import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent; @@ -1322,7 +1323,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Update the stack action button visibility - if (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) { + if (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && + mStack.getTaskCount() > 0) { EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */)); } else { EventBus.getDefault().send(new HideStackActionButtonEvent()); @@ -1447,6 +1449,26 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } @Override + public void onStackTasksRemoved(TaskStack stack) { + // Reset the focused task + resetFocusedTask(getFocusedTask()); + + // Return all the views to the pool + List<TaskView> taskViews = new ArrayList<>(); + taskViews.addAll(getTaskViews()); + for (int i = taskViews.size() - 1; i >= 0; i--) { + mViewPool.returnViewToPool(taskViews.get(i)); + } + + // Remove all the ignore tasks + mIgnoreTasks.clear(); + + // If there are no remaining tasks, then just close recents + EventBus.getDefault().send(new AllTaskViewsDismissedEvent( + R.string.recents_empty_message_dismissed_all)); + } + + @Override public void onStackTasksUpdated(TaskStack stack) { // Update the layout and immediately layout updateLayoutAlgorithm(false /* boundScroll */); @@ -1597,7 +1619,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (mEnterAnimationComplete) { if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && - curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) { + curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && + mStack.getTaskCount() > 0) { EventBus.getDefault().send(new ShowStackActionButtonEvent(true /* translate */)); } else if (prevScroll < HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && curScroll >= HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) { @@ -1705,14 +1728,42 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } - public final void onBusEvent(final DismissTaskViewEvent event) { + public final void onBusEvent(DismissTaskViewEvent event) { // For visible children, defer removing the task until after the animation - mAnimationHelper.startDeleteTaskAnimation(event.task, event.taskView, - event.getAnimationTrigger()); + mAnimationHelper.startDeleteTaskAnimation(event.taskView, event.getAnimationTrigger()); + } + + public final void onBusEvent(final DismissAllTaskViewsEvent event) { + // Keep track of the tasks which will have their data removed + ArrayList<Task> tasks = new ArrayList<>(mStack.getStackTasks()); + mAnimationHelper.startDeleteAllTasksAnimation(getTaskViews(), event.getAnimationTrigger()); + event.addPostAnimationCallback(new Runnable() { + @Override + public void run() { + // Announce for accessibility + announceForAccessibility(getContext().getString( + R.string.accessibility_recents_all_items_dismissed)); + + // Remove all tasks and delete the task data for all tasks + mStack.removeAllTasks(); + for (int i = tasks.size() - 1; i >= 0; i--) { + EventBus.getDefault().send(new DeleteTaskDataEvent(tasks.get(i))); + } + + MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS_ALL); + } + }); + } public final void onBusEvent(TaskViewDismissedEvent event) { - removeTaskViewFromStack(event.taskView, event.task); + // Announce for accessibility + announceForAccessibility(getContext().getString( + R.string.accessibility_recents_item_dismissed, event.task.title)); + + // Remove the task from the stack + mStack.removeTask(event.task, new AnimationProps(DEFAULT_SYNC_STACK_DURATION, + Interpolators.FAST_OUT_SLOW_IN), false /* fromDockGesture */); EventBus.getDefault().send(new DeleteTaskDataEvent(event.task)); MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS, @@ -1948,28 +1999,16 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } - // Trigger a new layout and scroll to the initial state - mInitialState = event.fromMultiWindow - ? INITIAL_STATE_UPDATE_ALL - : INITIAL_STATE_UPDATE_LAYOUT_ONLY; + // Trigger a new layout and update to the initial state if necessary + if (event.fromMultiWindow) { + mInitialState = INITIAL_STATE_UPDATE_ALL; + } else if (event.fromOrientationChange) { + mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY; + } requestLayout(); } /** - * Removes the task from the stack, and updates the focus to the next task in the stack if the - * removed TaskView was focused. - */ - private void removeTaskViewFromStack(TaskView tv, Task task) { - // Announce for accessibility - tv.announceForAccessibility(getContext().getString( - R.string.accessibility_recents_item_dismissed, task.title)); - - // Remove the task from the stack - mStack.removeTask(task, new AnimationProps(DEFAULT_SYNC_STACK_DURATION, - Interpolators.FAST_OUT_SLOW_IN), false /* fromDockGesture */); - } - - /** * Starts an alpha animation on the freeform workspace background. */ private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha, 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 aed19c3e0b9a..ee0de1ad05df 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -356,6 +356,11 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { return; } + // Disallow tapping above and below the stack to dismiss recents + if (x > mSv.mLayoutAlgorithm.mStackRect.left && x < mSv.mLayoutAlgorithm.mStackRect.right) { + return; + } + // If tapping on the freeform workspace background, just launch the first freeform task SystemServicesProxy ssp = Recents.getSystemServices(); if (ssp.hasFreeformWorkspaceSupport()) { @@ -507,13 +512,13 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { tv.setClipViewInStack(true); // Re-enable touch events from this task view tv.setTouchEnabled(true); + // Remove the task view from the stack + EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv)); // Update the scroll to the final scroll position from onBeginDrag() mSv.getScroller().setStackScroll(mTargetStackScroll, null); // Update the focus state to the final focus state mSv.getStackAlgorithm().setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED); mSv.getStackAlgorithm().clearUnfocusedTaskOverrides(); - // Remove the task view from the stack - EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv)); // Stop tracking this deletion animation mSwipeHelperAnimations.remove(v); // Keep track of deletions by keyboard 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 c085d8092a1f..f8ed7008d912 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -384,7 +384,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks void dismissTask() { // Animate out the view and call the callback final TaskView tv = this; - DismissTaskViewEvent dismissEvent = new DismissTaskViewEvent(tv, mTask); + DismissTaskViewEvent dismissEvent = new DismissTaskViewEvent(tv); dismissEvent.addPostAnimationCallback(new Runnable() { @Override public void run() { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java index dc76e61c0d1e..b512393304c3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java @@ -226,4 +226,9 @@ public class TaskViewTransform { v.getViewBounds().setClipBottom(0); v.setLeftTopRightBottom(0, 0, 0, 0); } + + @Override + public String toString() { + return "R: " + rect + " V: " + visible; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index ab44b6a277ee..84e785d066ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -654,12 +654,11 @@ public class UserSwitcherController { } private void checkIfAddUserDisallowedByAdminOnly(UserRecord record) { - EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(mContext, - UserManager.DISALLOW_ADD_USER, ActivityManager.getCurrentUser()); - if (admin != null && !RestrictedLockUtils.hasBaseUserRestriction(mContext, - UserManager.DISALLOW_ADD_USER, ActivityManager.getCurrentUser())) { + UserHandle user = UserHandle.of(ActivityManager.getCurrentUser()); + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, user) + && !mUserManager.hasBaseUserRestriction(UserManager.DISALLOW_ADD_USER, user)) { record.isDisabledByAdmin = true; - record.enforcedAdmin = admin; + record.enforcedAdmin = EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; } else { record.isDisabledByAdmin = false; record.enforcedAdmin = null; diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java index 6976c0b1d312..e3ed92cb5e88 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -86,7 +86,7 @@ public class ZenModePanel extends LinearLayout { = new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS); private final Context mContext; - private final LayoutInflater mInflater; + protected final LayoutInflater mInflater; private final H mHandler = new H(); private final ZenPrefs mPrefs; private final TransitionHelper mTransitionHelper = new TransitionHelper(); @@ -95,12 +95,12 @@ public class ZenModePanel extends LinearLayout { private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this)); - private SegmentedButtons mZenButtons; + protected SegmentedButtons mZenButtons; private View mZenIntroduction; private TextView mZenIntroductionMessage; private View mZenIntroductionConfirm; private TextView mZenIntroductionCustomize; - private LinearLayout mZenConditions; + protected LinearLayout mZenConditions; private TextView mZenAlarmWarning; private Callback mCallback; @@ -148,10 +148,7 @@ public class ZenModePanel extends LinearLayout { mTransitionHelper.dump(fd, pw, args); } - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - + protected void createZenButtons() { mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons); mZenButtons.addButton(R.string.interruption_level_none_twoline, R.string.interruption_level_none_with_warning, @@ -163,7 +160,12 @@ public class ZenModePanel extends LinearLayout { R.string.interruption_level_priority, Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); mZenButtons.setCallback(mZenButtonsCallback); + } + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + createZenButtons(); mZenIntroduction = findViewById(R.id.zen_introduction); mZenIntroductionMessage = (TextView) findViewById(R.id.zen_introduction_message); mSpTexts.add(mZenIntroductionMessage); @@ -302,14 +304,18 @@ public class ZenModePanel extends LinearLayout { } } + protected void addZenConditions(int count) { + for (int i = 0; i < count; i++) { + mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false)); + } + } + public void init(ZenModeController controller) { mController = controller; mCountdownConditionSupported = mController.isCountdownConditionSupported(); final int countdownDelta = mCountdownConditionSupported ? COUNTDOWN_CONDITION_COUNT : 0; final int minConditions = 1 /*forever*/ + countdownDelta; - for (int i = 0; i < minConditions; i++) { - mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false)); - } + addZenConditions(minConditions); mSessionZen = getSelectedZen(-1); handleUpdateManualRule(mController.getManualRule()); if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition); @@ -917,7 +923,7 @@ public class ZenModePanel extends LinearLayout { } } - private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() { + protected final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() { @Override public void onSelected(final Object value, boolean fromClick) { if (value != null && mZenButtons.isShown() && isAttachedToWindow()) { diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index d6f1499ef684..a2cfae9f3f11 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -2017,6 +2017,9 @@ message MetricsEvent { // action pass package name of calling package. ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_PACKAGE = 356; + // Logged when a user dismisses all task in overview + OVERVIEW_DISMISS_ALL = 357; + // Add new aosp constants above this line. // END OF AOSP CONSTANTS } diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java index f2ef06520b92..81f63aeab0a5 100644 --- a/rs/java/android/renderscript/Allocation.java +++ b/rs/java/android/renderscript/Allocation.java @@ -296,8 +296,13 @@ public class Allocation extends BaseObj { } /** - * Enable/Disable AutoPadding for Vec3 elements. - * By default: Diabled. + * Enable/Disable AutoPadding for Vec3 Elements. + * + * <p> Vec3 Elements, such as {@link Element#U8_3} are treated as Vec4 Elements + * with the fourth vector element used as padding. Enabling the AutoPadding feature + * will automatically add/remove the padding when you copy to/from an Allocation + * with a Vec3 Element. + * <p> By default: Disabled. * * @param useAutoPadding True: enable AutoPadding; False: disable AutoPadding * diff --git a/rs/java/android/renderscript/Script.java b/rs/java/android/renderscript/Script.java index 2b0678046e32..f6f93cb7b964 100644 --- a/rs/java/android/renderscript/Script.java +++ b/rs/java/android/renderscript/Script.java @@ -541,21 +541,22 @@ public class Script extends BaseObj { /** * Class for specifying the specifics about how a kernel will be - * launched + * launched. * * This class can specify a potential range of cells on which to * run a kernel. If no set is called for a dimension then this * class will have no impact on that dimension when the kernel * is executed. * - * The forEach launch will operate over the intersection of the - * dimensions. + * The forEach kernel launch will operate over the intersection of + * the dimensions. * * Example: * LaunchOptions with setX(5, 15) * Allocation with dimension X=10, Y=10 - * The resulting forEach run would execute over x = 5 to 10 and - * y = 0 to 10. + * The resulting forEach run would execute over: + * x = 5 to 9 (inclusive) and + * y = 0 to 9 (inclusive). * * */ @@ -569,11 +570,11 @@ public class Script extends BaseObj { private int strategy; /** - * Set the X range. If the end value is set to 0 the X dimension is not - * clipped. + * Set the X range. xstartArg is the lowest coordinate of the range, + * and xendArg-1 is the highest coordinate of the range. * * @param xstartArg Must be >= 0 - * @param xendArg Must be >= xstartArg + * @param xendArg Must be > xstartArg * * @return LaunchOptions */ @@ -587,11 +588,11 @@ public class Script extends BaseObj { } /** - * Set the Y range. If the end value is set to 0 the Y dimension is not - * clipped. + * Set the Y range. ystartArg is the lowest coordinate of the range, + * and yendArg-1 is the highest coordinate of the range. * * @param ystartArg Must be >= 0 - * @param yendArg Must be >= ystartArg + * @param yendArg Must be > ystartArg * * @return LaunchOptions */ @@ -605,11 +606,11 @@ public class Script extends BaseObj { } /** - * Set the Z range. If the end value is set to 0 the Z dimension is not - * clipped. + * Set the Z range. zstartArg is the lowest coordinate of the range, + * and zendArg-1 is the highest coordinate of the range. * * @param zstartArg Must be >= 0 - * @param zendArg Must be >= zstartArg + * @param zendArg Must be > zstartArg * * @return LaunchOptions */ diff --git a/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java b/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java index 76da781004d8..339e0e9dc975 100644 --- a/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java +++ b/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java @@ -32,10 +32,9 @@ public final class ScriptIntrinsicConvolve3x3 extends ScriptIntrinsic { * Supported elements types are {@link Element#U8}, {@link * Element#U8_2}, {@link Element#U8_3}, {@link Element#U8_4}, * {@link Element#F32}, {@link Element#F32_2}, {@link - * Element#F32_3}, and {@link Element#F32_4} - * - * The default coefficients are. + * Element#F32_3}, and {@link Element#F32_4}. * + * <p> The default coefficients are: * <code> * <p> [ 0, 0, 0 ] * <p> [ 0, 1, 0 ] @@ -67,7 +66,7 @@ public final class ScriptIntrinsicConvolve3x3 extends ScriptIntrinsic { } /** - * Set the input of the blur. + * Set the input of the 3x3 convolve. * Must match the element type supplied during create. * * @param ain The input allocation. @@ -80,7 +79,7 @@ public final class ScriptIntrinsicConvolve3x3 extends ScriptIntrinsic { /** * Set the coefficients for the convolve. * - * The convolve layout is + * <p> The convolve layout is: * <code> * <p> [ 0, 1, 2 ] * <p> [ 3, 4, 5 ] diff --git a/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java b/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java index 2d37600dc492..a288cee407f0 100644 --- a/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java +++ b/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java @@ -32,9 +32,9 @@ public final class ScriptIntrinsicConvolve5x5 extends ScriptIntrinsic { * Supported elements types are {@link Element#U8}, {@link * Element#U8_2}, {@link Element#U8_3}, {@link Element#U8_4}, * {@link Element#F32}, {@link Element#F32_2}, {@link - * Element#F32_3}, and {@link Element#F32_4} + * Element#F32_3}, and {@link Element#F32_4}. * - * The default coefficients are. + * <p> The default coefficients are: * <code> * <p> [ 0, 0, 0, 0, 0 ] * <p> [ 0, 0, 0, 0, 0 ] @@ -66,7 +66,7 @@ public final class ScriptIntrinsicConvolve5x5 extends ScriptIntrinsic { } /** - * Set the input of the blur. + * Set the input of the 5x5 convolve. * Must match the element type supplied during create. * * @param ain The input allocation. @@ -79,7 +79,7 @@ public final class ScriptIntrinsicConvolve5x5 extends ScriptIntrinsic { /** * Set the coefficients for the convolve. * - * The convolve layout is + * <p> The convolve layout is: * <code> * <p> [ 0, 1, 2, 3, 4 ] * <p> [ 5, 6, 7, 8, 9 ] diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 4749417e1c6a..6023d7f0c479 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -108,7 +108,7 @@ public class DeviceIdleController extends SystemService private static final boolean COMPRESS_TIME = false; - private static final int EVENT_BUFFER_SIZE = 40; + private static final int EVENT_BUFFER_SIZE = 100; private AlarmManager mAlarmManager; private IBatteryStats mBatteryStats; @@ -192,6 +192,7 @@ public class DeviceIdleController extends SystemService private long mNextAlarmTime; private long mNextIdlePendingDelay; private long mNextIdleDelay; + private long mNextLightIdleDelay; private long mNextLightAlarmTime; private long mCurIdleBudget; private long mMaintenanceStartTime; @@ -353,6 +354,8 @@ public class DeviceIdleController extends SystemService } }; + private boolean mMaintenanceMinCheckScheduled; + private final BroadcastReceiver mIdleStartedDoneReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { decActiveIdleOps(); @@ -477,7 +480,11 @@ public class DeviceIdleController extends SystemService */ private final class Constants extends ContentObserver { // Key names stored in the settings value. + private static final String KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT + = "light_after_inactive_to"; private static final String KEY_LIGHT_IDLE_TIMEOUT = "light_idle_to"; + private static final String KEY_LIGHT_IDLE_FACTOR = "light_idle_factor"; + private static final String KEY_LIGHT_MAX_IDLE_TIMEOUT = "light_max_idle_to"; private static final String KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = "light_idle_maintenance_min_budget"; private static final String KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET @@ -505,14 +512,35 @@ public class DeviceIdleController extends SystemService "sms_temp_app_whitelist_duration"; /** - * This is the time, after becoming inactive, that we will start going - * in to light-weight idle mode. + * This is the time, after becoming inactive, that we go in to the first + * light-weight idle mode. + * @see Settings.Global#DEVICE_IDLE_CONSTANTS + * @see #KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT + */ + public long LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT; + + /** + * This is the initial time that we will run in idle maintenance mode. * @see Settings.Global#DEVICE_IDLE_CONSTANTS * @see #KEY_LIGHT_IDLE_TIMEOUT */ public long LIGHT_IDLE_TIMEOUT; /** + * Scaling factor to apply to the light idle mode time each time we complete a cycle. + * @see Settings.Global#DEVICE_IDLE_CONSTANTS + * @see #KEY_LIGHT_IDLE_FACTOR + */ + public float LIGHT_IDLE_FACTOR; + + /** + * This is the maximum time we will run in idle maintenence mode. + * @see Settings.Global#DEVICE_IDLE_CONSTANTS + * @see #KEY_LIGHT_MAX_IDLE_TIMEOUT + */ + public long LIGHT_MAX_IDLE_TIMEOUT; + + /** * This is the minimum amount of time we want to make available for maintenance mode * when lightly idling. That is, we will always have at least this amount of time * available maintenance before timing out and cutting off maintenance mode. @@ -716,7 +744,14 @@ public class DeviceIdleController extends SystemService Slog.e(TAG, "Bad device idle settings", e); } + LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getLong( + KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT, + !COMPRESS_TIME ? 5 * 60 * 1000L : 15 * 1000L); LIGHT_IDLE_TIMEOUT = mParser.getLong(KEY_LIGHT_IDLE_TIMEOUT, + !COMPRESS_TIME ? 5 * 60 * 1000L : 15 * 1000L); + LIGHT_IDLE_FACTOR = mParser.getFloat(KEY_LIGHT_IDLE_FACTOR, + 2f); + LIGHT_MAX_IDLE_TIMEOUT = mParser.getLong(KEY_LIGHT_MAX_IDLE_TIMEOUT, !COMPRESS_TIME ? 15 * 60 * 1000L : 60 * 1000L); LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = mParser.getLong( KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET, @@ -770,10 +805,22 @@ public class DeviceIdleController extends SystemService void dump(PrintWriter pw) { pw.println(" Settings:"); + pw.print(" "); pw.print(KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT); pw.print("="); + TimeUtils.formatDuration(LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT, pw); + pw.println(); + pw.print(" "); pw.print(KEY_LIGHT_IDLE_TIMEOUT); pw.print("="); TimeUtils.formatDuration(LIGHT_IDLE_TIMEOUT, pw); pw.println(); + pw.print(" "); pw.print(KEY_LIGHT_IDLE_FACTOR); pw.print("="); + pw.print(LIGHT_IDLE_FACTOR); + pw.println(); + + pw.print(" "); pw.print(KEY_LIGHT_MAX_IDLE_TIMEOUT); pw.print("="); + TimeUtils.formatDuration(LIGHT_MAX_IDLE_TIMEOUT, pw); + pw.println(); + pw.print(" "); pw.print(KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET); pw.print("="); TimeUtils.formatDuration(LIGHT_IDLE_MAINTENANCE_MIN_BUDGET, pw); pw.println(); @@ -1640,7 +1687,10 @@ public class DeviceIdleController extends SystemService mInactiveTimeout = mConstants.INACTIVE_TIMEOUT; mCurIdleBudget = 0; mMaintenanceStartTime = 0; - mAlarmManager.cancel(mMaintenanceMinCheckListener); + if (mMaintenanceMinCheckScheduled) { + mAlarmManager.cancel(mMaintenanceMinCheckListener); + mMaintenanceMinCheckScheduled = false; + } resetIdleManagementLocked(); resetLightIdleManagementLocked(); addEvent(EVENT_NORMAL); @@ -1663,7 +1713,7 @@ public class DeviceIdleController extends SystemService mLightState = LIGHT_STATE_INACTIVE; if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE"); resetLightIdleManagementLocked(); - scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT); + scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT); EventLogTags.writeDeviceIdleLight(mLightState, "no activity"); } } @@ -1672,6 +1722,7 @@ public class DeviceIdleController extends SystemService void resetIdleManagementLocked() { mNextIdlePendingDelay = 0; mNextIdleDelay = 0; + mNextLightIdleDelay = 0; cancelAlarmLocked(); cancelLocatingLocked(); stopMonitoringMotionLocked(); @@ -1704,6 +1755,8 @@ public class DeviceIdleController extends SystemService switch (mLightState) { case LIGHT_STATE_INACTIVE: mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET; + // Reset the upcoming idle delays. + mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT; mMaintenanceStartTime = 0; case LIGHT_STATE_IDLE_MAINTENANCE: if (mMaintenanceStartTime != 0) { @@ -1717,13 +1770,21 @@ public class DeviceIdleController extends SystemService } } mMaintenanceStartTime = 0; - scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT); + scheduleLightAlarmLocked(mNextLightIdleDelay); + mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT, + (long)(mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR)); + if (mNextLightIdleDelay < mConstants.LIGHT_IDLE_TIMEOUT) { + mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT; + } if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE."); mLightState = LIGHT_STATE_IDLE; EventLogTags.writeDeviceIdleLight(mLightState, reason); addEvent(EVENT_LIGHT_IDLE); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT); - mAlarmManager.cancel(mMaintenanceMinCheckListener); + if (mMaintenanceMinCheckScheduled) { + mAlarmManager.cancel(mMaintenanceMinCheckListener); + mMaintenanceMinCheckScheduled = false; + } break; case LIGHT_STATE_IDLE: // We have been idling long enough, now it is time to do some work. @@ -1744,6 +1805,7 @@ public class DeviceIdleController extends SystemService mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, mMaintenanceStartTime + mConstants.MIN_LIGHT_MAINTENANCE_TIME, "DeviceIdleController.maint-check", mMaintenanceMinCheckListener, mHandler); + mMaintenanceMinCheckScheduled = true; break; } } @@ -1820,6 +1882,7 @@ public class DeviceIdleController extends SystemService cancelAlarmLocked(); cancelLocatingLocked(); mAnyMotionDetector.stop(); + case STATE_IDLE_MAINTENANCE: scheduleAlarmLocked(mNextIdleDelay, true); if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay + @@ -1827,6 +1890,9 @@ public class DeviceIdleController extends SystemService mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR); if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay); mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT); + if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) { + mNextIdleDelay = mConstants.IDLE_TIMEOUT; + } mState = STATE_IDLE; if (mLightState != LIGHT_STATE_OVERRIDE) { mLightState = LIGHT_STATE_OVERRIDE; @@ -1835,7 +1901,10 @@ public class DeviceIdleController extends SystemService EventLogTags.writeDeviceIdle(mState, reason); addEvent(EVENT_DEEP_IDLE); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON); - mAlarmManager.cancel(mMaintenanceMinCheckListener); + if (mMaintenanceMinCheckScheduled) { + mAlarmManager.cancel(mMaintenanceMinCheckListener); + mMaintenanceMinCheckScheduled = false; + } break; case STATE_IDLE: // We have been idling long enough, now it is time to do some work. @@ -1846,6 +1915,9 @@ public class DeviceIdleController extends SystemService mMaintenanceStartTime = SystemClock.elapsedRealtime(); mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT, (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR)); + if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) { + mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT; + } mState = STATE_IDLE_MAINTENANCE; EventLogTags.writeDeviceIdle(mState, reason); addEvent(EVENT_DEEP_MAINTENANCE); @@ -1853,6 +1925,7 @@ public class DeviceIdleController extends SystemService mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, mMaintenanceStartTime + mConstants.MIN_DEEP_MAINTENANCE_TIME, "DeviceIdleController.maint-check", mMaintenanceMinCheckListener, mHandler); + mMaintenanceMinCheckScheduled = true; break; } } @@ -2735,6 +2808,11 @@ public class DeviceIdleController extends SystemService TimeUtils.formatDuration(mNextIdleDelay, pw); pw.println(); } + if (mNextLightIdleDelay != 0) { + pw.print(" mNextIdleDelay="); + TimeUtils.formatDuration(mNextLightIdleDelay, pw); + pw.println(); + } if (mNextLightAlarmTime != 0) { pw.print(" mNextLightAlarmTime="); TimeUtils.formatDuration(mNextLightAlarmTime, SystemClock.elapsedRealtime(), pw); @@ -2750,6 +2828,10 @@ public class DeviceIdleController extends SystemService TimeUtils.formatDuration(mMaintenanceStartTime, SystemClock.elapsedRealtime(), pw); pw.println(); } + if (mMaintenanceMinCheckScheduled) { + pw.print(" mMaintenanceMinCheckScheduled="); + pw.println(mMaintenanceMinCheckScheduled); + } if (mJobsActive) { pw.print(" mJobsActive="); pw.println(mJobsActive); } diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index 45008dcf72a1..9e2f11670017 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -165,6 +165,7 @@ class MountService extends IMountService.Stub public void onStart() { mMountService = new MountService(getContext()); publishBinderService("mount", mMountService); + mMountService.start(); } @Override @@ -430,9 +431,13 @@ class MountService extends IMountService.Stub = { "password", "default", "pattern", "pin" }; private final Context mContext; + private final NativeDaemonConnector mConnector; private final NativeDaemonConnector mCryptConnector; + private final Thread mConnectorThread; + private final Thread mCryptConnectorThread; + private volatile boolean mSystemReady = false; private volatile boolean mBootCompleted = false; private volatile boolean mDaemonConnected = false; @@ -1494,17 +1499,13 @@ class MountService extends IMountService.Stub null); mConnector.setDebug(true); mConnector.setWarnIfHeld(mLock); - - Thread thread = new Thread(mConnector, VOLD_TAG); - thread.start(); + mConnectorThread = new Thread(mConnector, VOLD_TAG); // Reuse parameters from first connector since they are tested and safe mCryptConnector = new NativeDaemonConnector(this, "cryptd", MAX_CONTAINERS * 2, CRYPTD_TAG, 25, null); mCryptConnector.setDebug(true); - - Thread crypt_thread = new Thread(mCryptConnector, CRYPTD_TAG); - crypt_thread.start(); + mCryptConnectorThread = new Thread(mCryptConnector, CRYPTD_TAG); final IntentFilter userFilter = new IntentFilter(); userFilter.addAction(Intent.ACTION_USER_ADDED); @@ -1521,6 +1522,11 @@ class MountService extends IMountService.Stub } } + private void start() { + mConnectorThread.start(); + mCryptConnectorThread.start(); + } + private void systemReady() { mSystemReady = true; mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget(); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 467fc49c1ca1..9bd4117753ed 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1468,6 +1468,9 @@ public final class ActivityManagerService extends ActivityManagerNative static final int FIRST_COMPAT_MODE_MSG = 300; static final int FIRST_SUPERVISOR_STACK_MSG = 100; + static ServiceThread sKillThread = null; + static KillHandler sKillHandler = null; + CompatModeDialog mCompatModeDialog; long mLastMemUsageReportTime = 0; @@ -1488,10 +1491,33 @@ public final class ActivityManagerService extends ActivityManagerNative final ServiceThread mHandlerThread; final MainHandler mHandler; final UiHandler mUiHandler; - final ProcessStartLogger mProcessStartLogger; PackageManagerInternal mPackageManagerInt; + final class KillHandler extends Handler { + static final int KILL_PROCESS_GROUP_MSG = 4000; + + public KillHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case KILL_PROCESS_GROUP_MSG: + { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "killProcessGroup"); + Process.killProcessGroup(msg.arg1 /* uid */, msg.arg2 /* pid */); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + break; + + default: + super.handleMessage(msg); + } + } + } + final class UiHandler extends Handler { public UiHandler() { super(com.android.server.UiThread.get().getLooper(), null, true); @@ -2460,7 +2486,13 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler = new MainHandler(mHandlerThread.getLooper()); mUiHandler = new UiHandler(); - mProcessStartLogger = new ProcessStartLogger(); + /* static; one-time init here */ + if (sKillHandler == null) { + sKillThread = new ServiceThread(TAG + ":kill", + android.os.Process.THREAD_PRIORITY_BACKGROUND, true /* allowIo */); + sKillThread.start(); + sKillHandler = new KillHandler(sKillThread.getLooper()); + } mFgBroadcastQueue = new BroadcastQueue(this, mHandler, "foreground", BROADCAST_FG_TIMEOUT, false); @@ -3010,9 +3042,13 @@ public final class ActivityManagerService extends ActivityManagerNative } static void killProcessGroup(int uid, int pid) { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "killProcessGroup"); - Process.killProcessGroup(uid, pid); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + if (sKillHandler != null) { + sKillHandler.sendMessage( + sKillHandler.obtainMessage(KillHandler.KILL_PROCESS_GROUP_MSG, uid, pid)); + } else { + Slog.w(TAG, "Asked to kill process group before system bringup!"); + Process.killProcessGroup(uid, pid); + } } final void removeLruProcessLocked(ProcessRecord app) { @@ -3594,7 +3630,12 @@ public final class ActivityManagerService extends ActivityManagerNative app.processName, hostingType, hostingNameStr != null ? hostingNameStr : ""); - mProcessStartLogger.logIfNeededLocked(app, startResult); + try { + AppGlobals.getPackageManager().logAppProcessStartIfNeeded(app.processName, app.uid, + app.info.seinfo, app.info.sourceDir, startResult.pid); + } catch (RemoteException ex) { + // Ignore + } if (app.persistent) { Watchdog.getInstance().processStarted(app.processName, startResult.pid); @@ -6537,8 +6578,6 @@ public final class ActivityManagerService extends ActivityManagerNative } }, dumpheapFilter); - mProcessStartLogger.registerListener(mContext); - // Let system services know. mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 0993ce65eb6d..93d40604dbda 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -560,7 +560,7 @@ final class ProcessRecord { } EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason); Process.killProcessQuiet(pid); - Process.killProcessGroup(uid, pid); + ActivityManagerService.killProcessGroup(uid, pid); if (!persistent) { killed = true; killedByAm = true; diff --git a/services/core/java/com/android/server/am/ProcessStartLogger.java b/services/core/java/com/android/server/am/ProcessStartLogger.java deleted file mode 100644 index 39fbeb5e1614..000000000000 --- a/services/core/java/com/android/server/am/ProcessStartLogger.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.android.server.am; - -import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; -import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; - -import android.app.AppGlobals; -import android.app.admin.SecurityLog; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Process; -import android.os.RemoteException; -import android.os.Process.ProcessStartResult; -import android.util.Slog; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.HashMap; - -/** - * A class that logs process start information (including APK hash) to the security log. - */ -class ProcessStartLogger { - private static final String CLASS_NAME = "ProcessStartLogger"; - private static final String TAG = TAG_WITH_CLASS_NAME ? CLASS_NAME : TAG_AM; - - final HandlerThread mHandlerProcessLoggingThread; - Handler mHandlerProcessLogging; - // Should only access in mHandlerProcessLoggingThread - final HashMap<String, String> mProcessLoggingApkHashes; - - ProcessStartLogger() { - mHandlerProcessLoggingThread = new HandlerThread(CLASS_NAME, - Process.THREAD_PRIORITY_BACKGROUND); - mProcessLoggingApkHashes = new HashMap(); - } - - void logIfNeededLocked(ProcessRecord app, ProcessStartResult startResult) { - if (!SecurityLog.isLoggingEnabled()) { - return; - } - if (!mHandlerProcessLoggingThread.isAlive()) { - mHandlerProcessLoggingThread.start(); - mHandlerProcessLogging = new Handler(mHandlerProcessLoggingThread.getLooper()); - } - mHandlerProcessLogging.post(new ProcessLoggingRunnable(app, startResult, - System.currentTimeMillis())); - } - - void registerListener(Context context) { - IntentFilter packageChangedFilter = new IntentFilter(); - packageChangedFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - packageChangedFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); - context.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) - || Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) { - int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, - getSendingUserId()); - String packageName = intent.getData().getSchemeSpecificPart(); - try { - ApplicationInfo info = AppGlobals.getPackageManager().getApplicationInfo( - packageName, 0, userHandle); - invaildateCache(info.sourceDir); - } catch (RemoteException e) { - } - } - } - }, packageChangedFilter); - } - - private void invaildateCache(final String apkPath) { - if (mHandlerProcessLogging != null) { - mHandlerProcessLogging.post(new Runnable() { - @Override - public void run() { - mProcessLoggingApkHashes.remove(apkPath); - } - }); - } - } - - private class ProcessLoggingRunnable implements Runnable { - - private final ProcessRecord app; - private final Process.ProcessStartResult startResult; - private final long startTimestamp; - - public ProcessLoggingRunnable(ProcessRecord app, Process.ProcessStartResult startResult, - long startTimestamp){ - this.app = app; - this.startResult = startResult; - this.startTimestamp = startTimestamp; - } - - @Override - public void run() { - String apkHash = computeStringHashOfApk(app); - SecurityLog.writeEvent(SecurityLog.TAG_APP_PROCESS_START, - app.processName, - startTimestamp, - app.uid, - startResult.pid, - app.info.seinfo, - apkHash); - } - - private String computeStringHashOfApk(ProcessRecord app){ - final String apkFile = app.info.sourceDir; - if(apkFile == null) { - return "No APK"; - } - String apkHash = mProcessLoggingApkHashes.get(apkFile); - if (apkHash == null) { - try { - byte[] hash = computeHashOfApkFile(apkFile); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < hash.length; i++) { - sb.append(String.format("%02x", hash[i])); - } - apkHash = sb.toString(); - mProcessLoggingApkHashes.put(apkFile, apkHash); - } catch (IOException | NoSuchAlgorithmException e) { - Slog.w(TAG, "computeStringHashOfApk() failed", e); - } - } - return apkHash != null ? apkHash : "Failed to count APK hash"; - } - - private byte[] computeHashOfApkFile(String packageArchiveLocation) - throws IOException, NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - FileInputStream input = new FileInputStream(new File(packageArchiveLocation)); - byte[] buffer = new byte[65536]; - int size; - while((size = input.read(buffer)) > 0) { - md.update(buffer, 0, size); - } - input.close(); - return md.digest(); - } - } -}
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index d2fd762c7f3f..442643a976e2 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -106,6 +106,7 @@ import android.app.ActivityManagerNative; import android.app.IActivityManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.IDevicePolicyManager; +import android.app.admin.SecurityLog; import android.app.backup.IBackupManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -459,6 +460,8 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageHandler mHandler; + private final ProcessLoggingHandler mProcessLoggingHandler; + /** * Messages for {@link #mHandler} that need to wait for system ready before * being dispatched. @@ -1712,6 +1715,8 @@ public class PackageManagerService extends IPackageManager.Stub { // Send installed broadcasts if the install/update is not ephemeral if (!isEphemeral(res.pkg)) { + mProcessLoggingHandler.invalidateProcessLoggingBaseApkHash(res.pkg.baseCodePath); + // Send added for users that see the package for the first time sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, extras, 0 /*flags*/, null /*targetPackage*/, @@ -2096,6 +2101,7 @@ public class PackageManagerService extends IPackageManager.Stub { Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); mHandlerThread.start(); mHandler = new PackageHandler(mHandlerThread.getLooper()); + mProcessLoggingHandler = new ProcessLoggingHandler(); Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT); File dataDir = Environment.getDataDirectory(); @@ -19458,4 +19464,26 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); return new ArrayList<>(mPackages.values()); } } + + /** + * Logs process start information (including base APK hash) to the security log. + * @hide + */ + public void logAppProcessStartIfNeeded(String processName, int uid, String seinfo, + String apkFile, int pid) { + if (!SecurityLog.isLoggingEnabled()) { + return; + } + Bundle data = new Bundle(); + data.putLong("startTimestamp", System.currentTimeMillis()); + data.putString("processName", processName); + data.putInt("uid", uid); + data.putString("seinfo", seinfo); + data.putString("apkFile", apkFile); + data.putInt("pid", pid); + Message msg = mProcessLoggingHandler.obtainMessage( + ProcessLoggingHandler.LOG_APP_PROCESS_START_MSG); + msg.setData(data); + mProcessLoggingHandler.sendMessage(msg); + } } diff --git a/services/core/java/com/android/server/pm/ProcessLoggingHandler.java b/services/core/java/com/android/server/pm/ProcessLoggingHandler.java new file mode 100644 index 000000000000..c47dda4681f9 --- /dev/null +++ b/services/core/java/com/android/server/pm/ProcessLoggingHandler.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.app.admin.SecurityLog; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; + +import com.android.internal.os.BackgroundThread; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import android.util.Slog; + +public final class ProcessLoggingHandler extends Handler { + + private static final String TAG = "ProcessLoggingHandler"; + static final int LOG_APP_PROCESS_START_MSG = 1; + static final int INVALIDATE_BASE_APK_HASH_MSG = 2; + + private final HashMap<String, String> mProcessLoggingBaseApkHashes = new HashMap(); + + ProcessLoggingHandler() { + super(BackgroundThread.getHandler().getLooper()); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case LOG_APP_PROCESS_START_MSG: { + Bundle bundle = msg.getData(); + String processName = bundle.getString("processName"); + int uid = bundle.getInt("uid"); + String seinfo = bundle.getString("seinfo"); + String apkFile = bundle.getString("apkFile"); + int pid = bundle.getInt("pid"); + long startTimestamp = bundle.getLong("startTimestamp"); + String apkHash = computeStringHashOfApk(apkFile); + SecurityLog.writeEvent(SecurityLog.TAG_APP_PROCESS_START, processName, + startTimestamp, uid, pid, seinfo, apkHash); + break; + } + case INVALIDATE_BASE_APK_HASH_MSG: { + Bundle bundle = msg.getData(); + mProcessLoggingBaseApkHashes.remove(bundle.getString("apkFile")); + break; + } + } + } + + void invalidateProcessLoggingBaseApkHash(String apkPath) { + Bundle data = new Bundle(); + data.putString("apkFile", apkPath); + Message msg = obtainMessage(INVALIDATE_BASE_APK_HASH_MSG); + msg.setData(data); + sendMessage(msg); + } + + private String computeStringHashOfApk(String apkFile) { + if (apkFile == null) { + return "No APK"; + } + String apkHash = mProcessLoggingBaseApkHashes.get(apkFile); + if (apkHash == null) { + try { + byte[] hash = computeHashOfApkFile(apkFile); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < hash.length; i++) { + sb.append(String.format("%02x", hash[i])); + } + apkHash = sb.toString(); + mProcessLoggingBaseApkHashes.put(apkFile, apkHash); + } catch (IOException | NoSuchAlgorithmException e) { + Slog.w(TAG, "computeStringHashOfApk() failed", e); + } + } + return apkHash != null ? apkHash : "Failed to count APK hash"; + } + + private byte[] computeHashOfApkFile(String packageArchiveLocation) + throws IOException, NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + FileInputStream input = new FileInputStream(new File(packageArchiveLocation)); + byte[] buffer = new byte[65536]; + int size; + while ((size = input.read(buffer)) > 0) { + md.update(buffer, 0, size); + } + input.close(); + return md.digest(); + } +} diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java index b759e1682940..7699f305fb17 100644 --- a/services/core/java/com/android/server/pm/ShortcutLauncher.java +++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java @@ -32,7 +32,7 @@ import java.util.List; /** * Launcher information used by {@link ShortcutService}. */ -class ShortcutLauncher implements ShortcutPackageItem { +class ShortcutLauncher extends ShortcutPackageItem { private static final String TAG = ShortcutService.TAG; static final String TAG_ROOT = "launcher-pins"; @@ -44,44 +44,34 @@ class ShortcutLauncher implements ShortcutPackageItem { private static final String ATTR_VALUE = "value"; private static final String ATTR_PACKAGE_NAME = "package-name"; - @UserIdInt - private final int mUserId; - - @NonNull - private final String mPackageName; - - @UserIdInt - private final int mLauncherUserId; + private final int mOwnerUserId; /** * Package name -> IDs. */ final private ArrayMap<String, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>(); - ShortcutLauncher(@UserIdInt int userId, @NonNull String packageName, - @UserIdInt int launcherUserId) { - mUserId = userId; - mPackageName = packageName; - mLauncherUserId = launcherUserId; + public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName, + @UserIdInt int launcherUserId, ShortcutPackageInfo spi) { + super(launcherUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty()); + mOwnerUserId = ownerUserId; } - @UserIdInt - public int getUserId() { - return mUserId; + public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName, + @UserIdInt int launcherUserId) { + this(launcherUserId, packageName, launcherUserId, null); } - @UserIdInt - public int getLauncherUserId() { - return mLauncherUserId; + @Override + public int getOwnerUserId() { + return mOwnerUserId; } - @NonNull - public String getPackageName() { - return mPackageName; - } + public void pinShortcuts(@NonNull ShortcutService s, @UserIdInt int packageUserId, + @NonNull String packageName, @NonNull List<String> ids) { + final ShortcutPackage packageShortcuts = + s.getPackageShortcutsLocked(packageName, packageUserId); - public void pinShortcuts(@NonNull ShortcutService s, @NonNull String packageName, - @NonNull List<String> ids) { final int idSize = ids.size(); if (idSize == 0) { mPinnedShortcuts.remove(packageName); @@ -91,8 +81,6 @@ class ShortcutLauncher implements ShortcutPackageItem { // Pin shortcuts. Make sure only pin the ones that were visible to the caller. // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here. - final ShortcutPackage packageShortcuts = - s.getPackageShortcutsLocked(packageName, mUserId); final ArraySet<String> newSet = new ArraySet<>(); for (int i = 0; i < idSize; i++) { @@ -107,7 +95,7 @@ class ShortcutLauncher implements ShortcutPackageItem { } mPinnedShortcuts.put(packageName, newSet); } - s.getPackageShortcutsLocked(packageName, mUserId).refreshPinnedFlags(s); + packageShortcuts.refreshPinnedFlags(s); } /** @@ -124,15 +112,18 @@ class ShortcutLauncher implements ShortcutPackageItem { /** * Persist. */ - public void saveToXml(XmlSerializer out, boolean forBackup) throws IOException { + @Override + public void saveToXml(XmlSerializer out, boolean forBackup) + throws IOException { final int size = mPinnedShortcuts.size(); if (size == 0) { return; // Nothing to write. } out.startTag(null, TAG_ROOT); - ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, mPackageName); - ShortcutService.writeAttr(out, ATTR_LAUNCHER_USER_ID, mLauncherUserId); + ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, getPackageName()); + ShortcutService.writeAttr(out, ATTR_LAUNCHER_USER_ID, getPackageUserId()); + getPackageInfo().saveToXml(out); for (int i = 0; i < size; i++) { out.startTag(null, TAG_PACKAGE); @@ -153,16 +144,21 @@ class ShortcutLauncher implements ShortcutPackageItem { /** * Load. */ - public static ShortcutLauncher loadFromXml(XmlPullParser parser, int ownerUserId) - throws IOException, XmlPullParserException { + public static ShortcutLauncher loadFromXml(XmlPullParser parser, int ownerUserId, + boolean fromBackup) throws IOException, XmlPullParserException { final String launcherPackageName = ShortcutService.parseStringAttribute(parser, ATTR_PACKAGE_NAME); - final int launcherUserId = ShortcutService.parseIntAttribute(parser, - ATTR_LAUNCHER_USER_ID, ownerUserId); + + // If restoring, just use the real user ID. + final int launcherUserId = + fromBackup ? ownerUserId + : ShortcutService.parseIntAttribute(parser, ATTR_LAUNCHER_USER_ID, ownerUserId); final ShortcutLauncher ret = new ShortcutLauncher(launcherUserId, launcherPackageName, launcherUserId); + ShortcutPackageInfo spi = null; + ArraySet<String> ids = null; final int outerDepth = parser.getDepth(); int type; @@ -173,21 +169,33 @@ class ShortcutLauncher implements ShortcutPackageItem { } final int depth = parser.getDepth(); final String tag = parser.getName(); - switch (tag) { - case TAG_PACKAGE: { - final String packageName = ShortcutService.parseStringAttribute(parser, - ATTR_PACKAGE_NAME); - ids = new ArraySet<>(); - ret.mPinnedShortcuts.put(packageName, ids); - continue; + if (depth == outerDepth + 1) { + switch (tag) { + case ShortcutPackageInfo.TAG_ROOT: + spi = ShortcutPackageInfo.loadFromXml(parser); + continue; + case TAG_PACKAGE: { + final String packageName = ShortcutService.parseStringAttribute(parser, + ATTR_PACKAGE_NAME); + ids = new ArraySet<>(); + ret.mPinnedShortcuts.put(packageName, ids); + continue; + } } - case TAG_PIN: { - ids.add(ShortcutService.parseStringAttribute(parser, - ATTR_VALUE)); - continue; + } + if (depth == outerDepth + 2) { + switch (tag) { + case TAG_PIN: { + ids.add(ShortcutService.parseStringAttribute(parser, + ATTR_VALUE)); + continue; + } } } - throw ShortcutService.throwForInvalidTag(depth, tag); + ShortcutService.warnForInvalidTag(depth, tag); + } + if (spi != null) { + ret.replacePackageInfo(spi); } return ret; } @@ -197,9 +205,12 @@ class ShortcutLauncher implements ShortcutPackageItem { pw.print(prefix); pw.print("Launcher: "); - pw.print(mPackageName); - pw.print(" UserId: "); - pw.print(mLauncherUserId); + pw.print(getPackageName()); + pw.print(" Package user: "); + pw.print(getPackageUserId()); + pw.println(); + + getPackageInfo().dump(s, pw, prefix + " "); pw.println(); final int size = mPinnedShortcuts.size(); diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index e4d578744159..f9414328885c 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -17,7 +17,6 @@ package com.android.server.pm; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ShortcutInfo; @@ -41,7 +40,7 @@ import java.util.function.Predicate; /** * Package information used by {@link ShortcutService}. */ -class ShortcutPackage implements ShortcutPackageItem { +class ShortcutPackage extends ShortcutPackageItem { private static final String TAG = ShortcutService.TAG; static final String TAG_ROOT = "package"; @@ -63,12 +62,6 @@ class ShortcutPackage implements ShortcutPackageItem { private static final String ATTR_ICON_RES = "icon-res"; private static final String ATTR_BITMAP_PATH = "bitmap-path"; - @UserIdInt - private final int mUserId; - - @NonNull - private final String mPackageName; - /** * All the shortcuts from the package, keyed on IDs. */ @@ -89,19 +82,18 @@ class ShortcutPackage implements ShortcutPackageItem { */ private long mLastResetTime; - ShortcutPackage(int userId, String packageName) { - mUserId = userId; - mPackageName = packageName; + public ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) { + super(packageUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty()); } - @UserIdInt - public int getUserId() { - return mUserId; + public ShortcutPackage(int packageUserId, String packageName) { + this(packageUserId, packageName, null); } - @NonNull - public String getPackageName() { - return mPackageName; + @Override + public int getOwnerUserId() { + // For packages, always owner user == package user. + return getPackageUserId(); } /** @@ -116,7 +108,7 @@ class ShortcutPackage implements ShortcutPackageItem { @NonNull String id) { final ShortcutInfo shortcut = mShortcuts.remove(id); if (shortcut != null) { - s.removeIcon(mUserId, shortcut); + s.removeIcon(getPackageUserId(), shortcut); shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED); } return shortcut; @@ -124,7 +116,7 @@ class ShortcutPackage implements ShortcutPackageItem { void addShortcut(@NonNull ShortcutService s, @NonNull ShortcutInfo newShortcut) { deleteShortcut(s, newShortcut.getId()); - s.saveIconAndFixUpShortcut(mUserId, newShortcut); + s.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut); mShortcuts.put(newShortcut.getId(), newShortcut); } @@ -233,11 +225,12 @@ class ShortcutPackage implements ShortcutPackageItem { // Then, for the pinned set for each launcher, set the pin flag one by one. final ArrayMap<ShortcutUser.PackageWithUser, ShortcutLauncher> launchers = - s.getUserShortcutsLocked(mUserId).getAllLaunchers(); + s.getUserShortcutsLocked(getPackageUserId()).getAllLaunchers(); for (int l = launchers.size() - 1; l >= 0; l--) { final ShortcutLauncher launcherShortcuts = launchers.valueAt(l); - final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(mPackageName); + final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds( + getPackageName()); if (pinned == null || pinned.size() == 0) { continue; @@ -321,8 +314,8 @@ class ShortcutPackage implements ShortcutPackageItem { // Set of pinned shortcuts by the calling launcher. final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null - : s.getLauncherShortcuts(callingLauncher, mUserId, launcherUserId) - .getPinnedShortcutIds(mPackageName); + : s.getLauncherShortcuts(callingLauncher, getPackageUserId(), launcherUserId) + .getPinnedShortcutIds(getPackageName()); for (int i = 0; i < mShortcuts.size(); i++) { final ShortcutInfo si = mShortcuts.valueAt(i); @@ -362,7 +355,7 @@ class ShortcutPackage implements ShortcutPackageItem { pw.print(prefix); pw.print("Package: "); - pw.print(mPackageName); + pw.print(getPackageName()); pw.println(); pw.print(prefix); @@ -380,6 +373,9 @@ class ShortcutPackage implements ShortcutPackageItem { pw.print(s.formatTime(mLastResetTime)); pw.println(); + getPackageInfo().dump(s, pw, prefix + " "); + pw.println(); + pw.println(" Shortcuts:"); long totalBitmapSize = 0; final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts; @@ -406,6 +402,7 @@ class ShortcutPackage implements ShortcutPackageItem { pw.println(")"); } + @Override public void saveToXml(@NonNull XmlSerializer out, boolean forBackup) throws IOException, XmlPullParserException { final int size = mShortcuts.size(); @@ -416,10 +413,11 @@ class ShortcutPackage implements ShortcutPackageItem { out.startTag(null, TAG_ROOT); - ShortcutService.writeAttr(out, ATTR_NAME, mPackageName); + ShortcutService.writeAttr(out, ATTR_NAME, getPackageName()); ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount); ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount); ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime); + getPackageInfo().saveToXml(out); for (int j = 0; j < size; j++) { saveShortcut(out, mShortcuts.valueAt(j), forBackup); @@ -464,13 +462,14 @@ class ShortcutPackage implements ShortcutPackageItem { out.endTag(null, TAG_SHORTCUT); } - public static ShortcutPackage loadFromXml(XmlPullParser parser, int userId) + public static ShortcutPackage loadFromXml(ShortcutService s, XmlPullParser parser, + int ownerUserId, boolean fromBackup) throws IOException, XmlPullParserException { final String packageName = ShortcutService.parseStringAttribute(parser, ATTR_NAME); - final ShortcutPackage ret = new ShortcutPackage(userId, packageName); + final ShortcutPackage ret = new ShortcutPackage(ownerUserId, packageName); ret.mDynamicShortcutCount = ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT); @@ -478,6 +477,7 @@ class ShortcutPackage implements ShortcutPackageItem { ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT); ret.mLastResetTime = ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET); + ShortcutPackageInfo spi = null; final int outerDepth = parser.getDepth(); int type; @@ -488,15 +488,23 @@ class ShortcutPackage implements ShortcutPackageItem { } final int depth = parser.getDepth(); final String tag = parser.getName(); - switch (tag) { - case TAG_SHORTCUT: - final ShortcutInfo si = parseShortcut(parser, packageName); - - // Don't use addShortcut(), we don't need to save the icon. - ret.mShortcuts.put(si.getId(), si); - continue; + if (depth == outerDepth + 1) { + switch (tag) { + case ShortcutPackageInfo.TAG_ROOT: + spi = ShortcutPackageInfo.loadFromXml(parser); + continue; + case TAG_SHORTCUT: + final ShortcutInfo si = parseShortcut(parser, packageName); + + // Don't use addShortcut(), we don't need to save the icon. + ret.mShortcuts.put(si.getId(), si); + continue; + } } - throw ShortcutService.throwForInvalidTag(depth, tag); + ShortcutService.warnForInvalidTag(depth, tag); + } + if (spi != null) { + ret.replacePackageInfo(spi); } return ret; } @@ -522,8 +530,7 @@ class ShortcutPackage implements ShortcutPackageItem { title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE); intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT); weight = (int) ShortcutService.parseLongAttribute(parser, ATTR_WEIGHT); - lastChangedTimestamp = (int) ShortcutService.parseLongAttribute(parser, - ATTR_TIMESTAMP); + lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP); flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS); iconRes = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES); bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH); diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java index ab456893f244..5f706b83271e 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java +++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java @@ -15,14 +15,11 @@ */ package com.android.server.pm; -import android.annotation.NonNull; import android.annotation.UserIdInt; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.Signature; import android.util.Slog; -import com.android.internal.util.Preconditions; +import com.android.server.backup.BackupUtils; import libcore.io.Base64; import libcore.util.HexEncoding; @@ -33,32 +30,21 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.PrintWriter; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; -import java.util.Arrays; /** * Package information used by {@link android.content.pm.ShortcutManager} for backup / restore. - * - * TODO: The methods about signature hashes are copied from BackupManagerService, which is not - * visible here. Unify the code. */ -class ShortcutPackageInfo implements ShortcutPackageItem { +class ShortcutPackageInfo { private static final String TAG = ShortcutService.TAG; static final String TAG_ROOT = "package-info"; - private static final String ATTR_USER_ID = "user"; - private static final String ATTR_NAME = "name"; private static final String ATTR_VERSION = "version"; private static final String ATTR_SHADOW = "shadow"; private static final String TAG_SIGNATURE = "signature"; private static final String ATTR_SIGNATURE_HASH = "hash"; - private final String mPackageName; - private final int mUserId; - /** * When true, this package information was restored from the previous device, and the app hasn't * been installed yet. @@ -67,22 +53,14 @@ class ShortcutPackageInfo implements ShortcutPackageItem { private int mVersionCode; private ArrayList<byte[]> mSigHashes; - private ShortcutPackageInfo(String packageName, int userId, - int versionCode, ArrayList<byte[]> sigHashes, boolean isShadow) { - mPackageName = Preconditions.checkNotNull(packageName); - mUserId = userId; + private ShortcutPackageInfo(int versionCode, ArrayList<byte[]> sigHashes, boolean isShadow) { mVersionCode = versionCode; mIsShadow = isShadow; mSigHashes = sigHashes; } - @NonNull - public String getPackageName() { - return mPackageName; - } - - public int getUserId() { - return mUserId; + public static ShortcutPackageInfo newEmpty() { + return new ShortcutPackageInfo(0, new ArrayList<>(0), /* isShadow */ false); } public boolean isShadow() { @@ -101,92 +79,13 @@ class ShortcutPackageInfo implements ShortcutPackageItem { return mVersionCode; } - private static byte[] hashSignature(Signature sig) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - digest.update(sig.toByteArray()); - return digest.digest(); - } catch (NoSuchAlgorithmException e) { - Slog.w(TAG, "No SHA-256 algorithm found!"); - } - return null; - } - - private static ArrayList<byte[]> hashSignatureArray(Signature[] sigs) { - if (sigs == null) { - return null; - } - - ArrayList<byte[]> hashes = new ArrayList<byte[]>(sigs.length); - for (Signature s : sigs) { - hashes.add(hashSignature(s)); - } - return hashes; - } - - private static boolean signaturesMatch(ArrayList<byte[]> storedSigHashes, PackageInfo target) { - if (target == null) { - return false; - } - - // If the target resides on the system partition, we allow it to restore - // data from the like-named package in a restore set even if the signatures - // do not match. (Unlike general applications, those flashed to the system - // partition will be signed with the device's platform certificate, so on - // different phones the same system app will have different signatures.) - if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - return true; - } - - // Allow unsigned apps, but not signed on one device and unsigned on the other - // !!! TODO: is this the right policy? - Signature[] deviceSigs = target.signatures; - if ((storedSigHashes == null || storedSigHashes.size() == 0) - && (deviceSigs == null || deviceSigs.length == 0)) { - return true; - } - if (storedSigHashes == null || deviceSigs == null) { - return false; - } - - // !!! TODO: this demands that every stored signature match one - // that is present on device, and does not demand the converse. - // Is this this right policy? - final int nStored = storedSigHashes.size(); - final int nDevice = deviceSigs.length; - - // hash each on-device signature - ArrayList<byte[]> deviceHashes = new ArrayList<byte[]>(nDevice); - for (int i = 0; i < nDevice; i++) { - deviceHashes.add(hashSignature(deviceSigs[i])); - } - - // now ensure that each stored sig (hash) matches an on-device sig (hash) - for (int n = 0; n < nStored; n++) { - boolean match = false; - final byte[] storedHash = storedSigHashes.get(n); - for (int i = 0; i < nDevice; i++) { - if (Arrays.equals(storedHash, deviceHashes.get(i))) { - match = true; - break; - } - } - // match is false when no on-device sig matched one of the stored ones - if (!match) { - return false; - } - } - - return true; - } - public boolean canRestoreTo(PackageInfo target) { if (target.versionCode < mVersionCode) { Slog.w(TAG, String.format("Package current version %d < backed up version %d", target.versionCode, mVersionCode)); return false; } - if (!signaturesMatch(mSigHashes, target)) { + if (!BackupUtils.signaturesMatch(mSigHashes, target)) { Slog.w(TAG, "Package signature mismtach"); return false; } @@ -194,37 +93,34 @@ class ShortcutPackageInfo implements ShortcutPackageItem { } public static ShortcutPackageInfo generateForInstalledPackage( - ShortcutService s, String packageName, @UserIdInt int userId) { - final PackageInfo pi = s.getPackageInfoWithSignatures(packageName, userId); + ShortcutService s, String packageName, @UserIdInt int packageUserId) { + final PackageInfo pi = s.getPackageInfoWithSignatures(packageName, packageUserId); if (pi.signatures == null || pi.signatures.length == 0) { Slog.e(TAG, "Can't get signatures: package=" + packageName); return null; } - final ShortcutPackageInfo ret = new ShortcutPackageInfo(packageName, userId, pi.versionCode, - hashSignatureArray(pi.signatures), /* shadow=*/ false); + final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode, + BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false); return ret; } - public void refreshAndSave(ShortcutService s, @UserIdInt int userId) { - final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, userId); + public void refresh(ShortcutService s, ShortcutPackageItem pkg) { + // Note use mUserId here, rather than userId. + final PackageInfo pi = s.getPackageInfoWithSignatures( + pkg.getPackageName(), pkg.getPackageUserId()); if (pi == null) { - Slog.w(TAG, "Package not found: " + mPackageName); + Slog.w(TAG, "Package not found: " + pkg.getPackageName()); return; } mVersionCode = pi.versionCode; - mSigHashes = hashSignatureArray(pi.signatures); - - s.scheduleSaveUser(userId); + mSigHashes = BackupUtils.hashSignatureArray(pi.signatures); } - public void saveToXml(XmlSerializer out, boolean forBackup) - throws IOException, XmlPullParserException { + public void saveToXml(XmlSerializer out) throws IOException { out.startTag(null, TAG_ROOT); - ShortcutService.writeAttr(out, ATTR_NAME, mPackageName); - ShortcutService.writeAttr(out, ATTR_USER_ID, mUserId); ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode); ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow); @@ -236,11 +132,9 @@ class ShortcutPackageInfo implements ShortcutPackageItem { out.endTag(null, TAG_ROOT); } - public static ShortcutPackageInfo loadFromXml(XmlPullParser parser, int ownerUserId) + public static ShortcutPackageInfo loadFromXml(XmlPullParser parser) throws IOException, XmlPullParserException { - final String packageName = ShortcutService.parseStringAttribute(parser, ATTR_NAME); - final int userId = ShortcutService.parseIntAttribute(parser, ATTR_USER_ID, ownerUserId); final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION); final boolean shadow = ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW); @@ -256,31 +150,27 @@ class ShortcutPackageInfo implements ShortcutPackageItem { } final int depth = parser.getDepth(); final String tag = parser.getName(); - switch (tag) { - case TAG_SIGNATURE: { - final String hash = ShortcutService.parseStringAttribute( - parser, ATTR_SIGNATURE_HASH); - hashes.add(Base64.decode(hash.getBytes())); - continue; + + if (depth == outerDepth + 1) { + switch (tag) { + case TAG_SIGNATURE: { + final String hash = ShortcutService.parseStringAttribute( + parser, ATTR_SIGNATURE_HASH); + hashes.add(Base64.decode(hash.getBytes())); + continue; + } } } - throw ShortcutService.throwForInvalidTag(depth, tag); + ShortcutService.warnForInvalidTag(depth, tag); } - return new ShortcutPackageInfo(packageName, userId, versionCode, hashes, shadow); + return new ShortcutPackageInfo(versionCode, hashes, shadow); } public void dump(ShortcutService s, PrintWriter pw, String prefix) { pw.println(); pw.print(prefix); - pw.print("PackageInfo: "); - pw.print(mPackageName); - pw.println(); - - pw.print(prefix); - pw.print(" User: "); - pw.print(mUserId); - pw.println(); + pw.println("PackageInfo:"); pw.print(prefix); pw.print(" IsShadow: "); diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java index 526c84db6963..de2709d3d7ac 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java +++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java @@ -17,15 +17,69 @@ package com.android.server.pm; import android.annotation.NonNull; +import com.android.internal.util.Preconditions; + import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.IOException; -public interface ShortcutPackageItem { +abstract class ShortcutPackageItem { + private final int mPackageUserId; + private final String mPackageName; + + private ShortcutPackageInfo mPackageInfo; + + protected ShortcutPackageItem(int packageUserId, @NonNull String packageName, + @NonNull ShortcutPackageInfo packageInfo) { + mPackageUserId = packageUserId; + mPackageName = Preconditions.checkStringNotEmpty(packageName); + mPackageInfo = Preconditions.checkNotNull(packageInfo); + } + + /** + * ID of the user who actually has this package running on. For {@link ShortcutPackage}, + * this is the same thing as {@link #getOwnerUserId}, but if it's a {@link ShortcutLauncher} and + * {@link #getOwnerUserId} is of a work profile, then this ID could be the user who owns the + * profile. + */ + public int getPackageUserId() { + return mPackageUserId; + } + + /** + * ID of the user who sees the shortcuts from this instance. + */ + public abstract int getOwnerUserId(); + @NonNull - String getPackageName(); + public String getPackageName() { + return mPackageName; + } + + public ShortcutPackageInfo getPackageInfo() { + return mPackageInfo; + } + + /** + * Should be only used when loading from a file.o + */ + protected void replacePackageInfo(@NonNull ShortcutPackageInfo packageInfo) { + mPackageInfo = Preconditions.checkNotNull(packageInfo); + } + + public void refreshPackageInfoAndSave(ShortcutService s) { + mPackageInfo.refresh(s, this); + s.scheduleSaveUser(getOwnerUserId()); + } + + public void ensureNotShadowAndSave(ShortcutService s) { + if (mPackageInfo.isShadow()) { + mPackageInfo.setShadow(false); + s.scheduleSaveUser(getOwnerUserId()); + } + } - void saveToXml(@NonNull XmlSerializer out, boolean forBackup) + public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup) throws IOException, XmlPullParserException; } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index cf1102595d84..76a2dfadc264 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -72,7 +72,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; @@ -85,6 +84,10 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -92,6 +95,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintWriter; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; @@ -104,8 +108,6 @@ import java.util.function.Predicate; * * - Default launcher check does take a few ms. Worth caching. * - * - Don't backup launcher from different profile. - * * - Clear data -> remove all dynamic? but not the pinned? * * - Scan and remove orphan bitmaps (just in case). @@ -633,31 +635,45 @@ public class ShortcutService extends IShortcutService.Stub { } path.mkdirs(); final AtomicFile file = new AtomicFile(path); - FileOutputStream outs = null; + FileOutputStream os = null; try { - outs = file.startWrite(); - - // Write to XML - XmlSerializer out = new FastXmlSerializer(); - out.setOutput(outs, StandardCharsets.UTF_8.name()); - out.startDocument(null, true); + os = file.startWrite(); - getUserShortcutsLocked(userId).saveToXml(this, out, /* forBackup= */ false); + saveUserInternalLocked(userId, os, /* forBackup= */ false); - out.endDocument(); - - // Close. - file.finishWrite(outs); - } catch (IOException|XmlPullParserException e) { + file.finishWrite(os); + } catch (XmlPullParserException|IOException e) { Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); - file.failWrite(outs); + file.failWrite(os); } } + private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os, + boolean forBackup) throws IOException, XmlPullParserException { + + final BufferedOutputStream bos = new BufferedOutputStream(os); + + // Write to XML + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(bos, StandardCharsets.UTF_8.name()); + out.startDocument(null, true); + + getUserShortcutsLocked(userId).saveToXml(this, out, forBackup); + + out.endDocument(); + + bos.flush(); + os.flush(); + } + static IOException throwForInvalidTag(int depth, String tag) throws IOException { throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth)); } + static void warnForInvalidTag(int depth, String tag) throws IOException { + Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth)); + } + @Nullable private ShortcutUser loadUserLocked(@UserIdInt int userId) { final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); @@ -675,30 +691,8 @@ public class ShortcutService extends IShortcutService.Stub { } return null; } - ShortcutUser ret = null; try { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(in, StandardCharsets.UTF_8.name()); - - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { - if (type != XmlPullParser.START_TAG) { - continue; - } - final int depth = parser.getDepth(); - - final String tag = parser.getName(); - if (DEBUG_LOAD) { - Slog.d(TAG, String.format("depth=%d type=%d name=%s", - depth, type, tag)); - } - if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) { - ret = ShortcutUser.loadFromXml(parser, userId); - continue; - } - throwForInvalidTag(depth, tag); - } - return ret; + return loadUserInternal(userId, in, /* forBackup= */ false); } catch (IOException|XmlPullParserException e) { Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); return null; @@ -707,6 +701,36 @@ public class ShortcutService extends IShortcutService.Stub { } } + private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, + boolean fromBackup) throws XmlPullParserException, IOException { + + final BufferedInputStream bis = new BufferedInputStream(is); + + ShortcutUser ret = null; + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(bis, StandardCharsets.UTF_8.name()); + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + + final String tag = parser.getName(); + if (DEBUG_LOAD) { + Slog.d(TAG, String.format("depth=%d type=%d name=%s", + depth, type, tag)); + } + if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) { + ret = ShortcutUser.loadFromXml(this, parser, userId, fromBackup); + continue; + } + throwForInvalidTag(depth, tag); + } + return ret; + } + private void scheduleSaveBaseState() { scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state. } @@ -1042,6 +1066,10 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkState(isCallerShell(), "Caller must be shell"); } + private void enforceSystem() { + Preconditions.checkState(isCallerSystem(), "Caller must be system"); + } + private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) { Preconditions.checkStringNotEmpty(packageName, "packageName"); @@ -1182,10 +1210,10 @@ public class ShortcutService extends IShortcutService.Stub { final int size = newShortcuts.size(); synchronized (mLock) { - getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId); - final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); + ps.ensureNotShadowAndSave(this); + // Throttling. if (!ps.tryApiCall(this)) { return false; @@ -1219,10 +1247,10 @@ public class ShortcutService extends IShortcutService.Stub { final int size = newShortcuts.size(); synchronized (mLock) { - getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId); - final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); + ps.ensureNotShadowAndSave(this); + // Throttling. if (!ps.tryApiCall(this)) { return false; @@ -1258,10 +1286,10 @@ public class ShortcutService extends IShortcutService.Stub { verifyCaller(packageName, userId); synchronized (mLock) { - getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId); - final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); + ps.ensureNotShadowAndSave(this); + // Throttling. if (!ps.tryApiCall(this)) { return false; @@ -1466,6 +1494,9 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) { + + // TODO Don't remove shadow packages' information. + final boolean wasUserLoaded = isUserLoadedLocked(owningUserId); final ShortcutUser mUser = getUserShortcutsLocked(owningUserId); @@ -1492,9 +1523,6 @@ public class ShortcutService extends IShortcutService.Stub { mUser.getPackages().valueAt(i).refreshPinnedFlags(this); } - // Remove the package info too. - mUser.removePackageInfo(packageUserId, packageName); - scheduleSaveUser(owningUserId); if (doNotify) { @@ -1617,11 +1645,13 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkNotNull(shortcutIds, "shortcutIds"); synchronized (mLock) { - getUserShortcutsLocked(userId).ensurePackageInfo( - ShortcutService.this, callingPackage, launcherUserId); + final ShortcutLauncher launcher = + getLauncherShortcuts(callingPackage, userId, launcherUserId); - getLauncherShortcuts(callingPackage, userId, launcherUserId).pinShortcuts( - ShortcutService.this, packageName, shortcutIds); + launcher.ensureNotShadowAndSave(ShortcutService.this); + + launcher.pinShortcuts( + ShortcutService.this, userId, packageName, shortcutIds); } userPackageChanged(packageName, userId); } @@ -1731,23 +1761,21 @@ public class ShortcutService extends IShortcutService.Stub { if (DEBUG) { Slog.d(TAG, "cleanupGonePackages() userId=" + userId); } - ArrayList<PackageWithUser> gonePackages = null; + final ArrayList<PackageWithUser> gonePackages = new ArrayList<>(); synchronized (mLock) { final ShortcutUser user = getUserShortcutsLocked(userId); - final ArrayMap<PackageWithUser, ShortcutPackageInfo> infos = user.getAllPackageInfos(); - for (int i = infos.size() -1; i >= 0; i--) { - final ShortcutPackageInfo info = infos.valueAt(i); - if (info.isShadow()) { - continue; + + user.forAllPackageItems(spi -> { + if (spi.getPackageInfo().isShadow()) { + return; // Don't delete shadow information. } - if (isPackageInstalled(info.getPackageName(), info.getUserId())) { - continue; + if (isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) { + return; } - gonePackages = ArrayUtils.add(gonePackages, - PackageWithUser.of(info.getUserId(), info.getPackageName())); - } - if (gonePackages != null) { + gonePackages.add(PackageWithUser.of(spi)); + }); + if (gonePackages.size() > 0) { for (int i = gonePackages.size() - 1; i >= 0; i--) { final PackageWithUser pu = gonePackages.get(i); cleanUpPackageLocked(pu.packageName, userId, pu.userId); @@ -1761,25 +1789,18 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId)); } synchronized (mLock) { - final ShortcutPackageInfo existing = getUserShortcutsLocked(userId) - .getPackageInfo(userId, packageName); - - if (existing != null && existing.isShadow()) { - Slog.w(TAG, "handlePackageAdded: TODO Restore not implemented"); - } + getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId); } } private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) { if (DEBUG) { - Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d", packageName, userId)); + Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d", + packageName, userId)); } + synchronized (mLock) { - final ShortcutPackageInfo spi = - getUserShortcutsLocked(userId).getPackageInfo(userId, packageName); - if (spi != null) { - spi.refreshAndSave(this, userId); - } + getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId); } } @@ -1792,13 +1813,14 @@ public class ShortcutService extends IShortcutService.Stub { } } - // === Backup & restore === + // === PackageManager interaction === PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) { return injectPackageInfo(packageName, userId, true); } int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) { + final long token = injectClearCallingIdentity(); try { return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS , userId); @@ -1806,12 +1828,15 @@ public class ShortcutService extends IShortcutService.Stub { // Shouldn't happen. Slog.wtf(TAG, "RemoteException", e); return -1; + } finally { + injectRestoreCallingIdentity(token); } } @VisibleForTesting PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId, boolean getSignatures) { + final long token = injectClearCallingIdentity(); try { return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS | (getSignatures ? PackageManager.GET_SIGNATURES : 0) @@ -1820,17 +1845,22 @@ public class ShortcutService extends IShortcutService.Stub { // Shouldn't happen. Slog.wtf(TAG, "RemoteException", e); return null; + } finally { + injectRestoreCallingIdentity(token); } } @VisibleForTesting ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) { + final long token = injectClearCallingIdentity(); try { return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId); } catch (RemoteException e) { // Shouldn't happen. Slog.wtf(TAG, "RemoteException", e); return null; + } finally { + injectRestoreCallingIdentity(token); } } @@ -1839,12 +1869,61 @@ public class ShortcutService extends IShortcutService.Stub { return (ai != null) && ((ai.flags & flags) == flags); } + private boolean isPackageInstalled(String packageName, int userId) { + return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED); + } + + // === Backup & restore === + boolean shouldBackupApp(String packageName, int userId) { return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP); } - private boolean isPackageInstalled(String packageName, int userId) { - return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED); + @Override + public byte[] getBackupPayload(@UserIdInt int userId) throws RemoteException { + enforceSystem(); + if (DEBUG) { + Slog.d(TAG, "Backing up user " + userId); + } + synchronized (mLock) { + final ShortcutUser user = getUserShortcutsLocked(userId); + if (user == null) { + Slog.w(TAG, "Can't backup: user not found: id=" + userId); + return null; + } + + user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave(this)); + + // Then save. + final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024); + try { + saveUserInternalLocked(userId, os, /* forBackup */ true); + } catch (XmlPullParserException|IOException e) { + // Shouldn't happen. + Slog.w(TAG, "Backup failed.", e); + return null; + } + return os.toByteArray(); + } + } + + @Override + public void applyRestore(byte[] payload, @UserIdInt int userId) throws RemoteException { + enforceSystem(); + if (DEBUG) { + Slog.d(TAG, "Restoring user " + userId); + } + final ShortcutUser user; + final ByteArrayInputStream is = new ByteArrayInputStream(payload); + try { + user = loadUserInternal(userId, is, /* fromBackup */ true); + } catch (XmlPullParserException|IOException e) { + Slog.w(TAG, "Restoration failed.", e); + return; + } + synchronized (mLock) { + mUsers.put(userId, user); + } } // === Dump === @@ -2202,19 +2281,4 @@ public class ShortcutService extends IShortcutService.Stub { return pkg.findShortcutById(shortcutId); } } - - @VisibleForTesting - ShortcutPackageInfo getPackageInfoForTest(String packageName, int userId) { - return getPackageInfoForTest(packageName, userId, userId); - } - - @VisibleForTesting - ShortcutPackageInfo getPackageInfoForTest(String packageName, int userId, int packageUserId) { - synchronized (mLock) { - final ShortcutUser user = mUsers.get(userId); - if (user == null) return null; - - return user.getPackageInfo(packageUserId, packageName); - } - } } diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index 19feb2aee943..487558f81e45 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -31,6 +31,7 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.PrintWriter; +import java.util.function.Consumer; /** * User information used by {@link ShortcutService}. @@ -56,6 +57,10 @@ class ShortcutUser { return new PackageWithUser(launcherUserId, packageName); } + public static PackageWithUser of(ShortcutPackageItem spi) { + return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName()); + } + @Override public int hashCode() { return packageName.hashCode() ^ userId; @@ -84,8 +89,6 @@ class ShortcutUser { private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>(); - private final ArrayMap<PackageWithUser, ShortcutPackageInfo> mPackageInfos = new ArrayMap<>(); - private ComponentName mLauncherComponent; public ShortcutUser(int userId) { @@ -100,35 +103,14 @@ class ShortcutUser { return mLaunchers; } - public ShortcutLauncher getLauncher(@UserIdInt int userId, @NonNull String packageName) { - return mLaunchers.get(PackageWithUser.of(userId, packageName)); - } - public void addLauncher(ShortcutLauncher launcher) { - mLaunchers.put(PackageWithUser.of(launcher.getUserId(), launcher.getPackageName()), - launcher); + mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(), + launcher.getPackageName()), launcher); } public ShortcutLauncher removeLauncher( - @UserIdInt int userId, @NonNull String packageName) { - return mLaunchers.remove(PackageWithUser.of(userId, packageName)); - } - - public ArrayMap<PackageWithUser, ShortcutPackageInfo> getAllPackageInfos() { - return mPackageInfos; - } - - public ShortcutPackageInfo getPackageInfo(@UserIdInt int userId, @NonNull String packageName) { - return mPackageInfos.get(PackageWithUser.of(userId, packageName)); - } - - public void addPackageInfo(ShortcutPackageInfo spi) { - mPackageInfos.put(PackageWithUser.of(spi.getUserId(), spi.getPackageName()), spi); - } - - public ShortcutPackageInfo removePackageInfo( - @UserIdInt int userId, @NonNull String packageName) { - return mPackageInfos.remove(PackageWithUser.of(userId, packageName)); + @UserIdInt int packageUserId, @NonNull String packageName) { + return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName)); } public ShortcutPackage getPackageShortcuts(@NonNull String packageName) { @@ -151,20 +133,37 @@ class ShortcutUser { return ret; } - public void ensurePackageInfo(ShortcutService s, String packageName, @UserIdInt int userId) { - final PackageWithUser key = PackageWithUser.of(userId, packageName); - final ShortcutPackageInfo existing = mPackageInfos.get(key); - - if (existing != null) { - return; + public void forAllPackageItems(Consumer<ShortcutPackageItem> callback) { + { + final int size = mLaunchers.size(); + for (int i = 0; i < size; i++) { + callback.accept(mLaunchers.valueAt(i)); + } } - if (ShortcutService.DEBUG) { - Slog.d(TAG, String.format("Fetching package info: %s user=%d", packageName, userId)); + { + final int size = mPackages.size(); + for (int i = 0; i < size; i++) { + callback.accept(mPackages.valueAt(i)); + } } - final ShortcutPackageInfo newSpi = ShortcutPackageInfo.generateForInstalledPackage( - s, packageName, userId); - mPackageInfos.put(key, newSpi); - s.scheduleSaveUser(mUserId); + } + + public void unshadowPackage(ShortcutService s, @NonNull String packageName, + @UserIdInt int packageUserId) { + forPackageItem(packageName, packageUserId, spi -> { + Slog.i(TAG, String.format("Restoring for %s, user=%d", packageName, packageUserId)); + spi.ensureNotShadowAndSave(s); + }); + } + + public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId, + Consumer<ShortcutPackageItem> callback) { + forAllPackageItems(spi -> { + if ((spi.getPackageUserId() == packageUserId) + && spi.getPackageName().equals(packageName)) { + callback.accept(spi); + } + }); } public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup) @@ -174,12 +173,7 @@ class ShortcutUser { ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLauncherComponent); - { - final int size = mPackageInfos.size(); - for (int i = 0; i < size; i++) { - saveShortcutPackageItem(s, out, mPackageInfos.valueAt(i), forBackup); - } - } + // Can't use forEachPackageItem due to the checked exceptions. { final int size = mLaunchers.size(); for (int i = 0; i < size; i++) { @@ -198,14 +192,19 @@ class ShortcutUser { private void saveShortcutPackageItem(ShortcutService s, XmlSerializer out, ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException { - if (forBackup && !s.shouldBackupApp(spi.getPackageName(), mUserId)) { - return; // Don't save. + if (forBackup) { + if (!s.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) { + return; // Don't save. + } + if (spi.getPackageUserId() != spi.getOwnerUserId()) { + return; // Don't save cross-user information. + } } spi.saveToXml(out, forBackup); } - public static ShortcutUser loadFromXml(XmlPullParser parser, int userId) - throws IOException, XmlPullParserException { + public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId, + boolean fromBackup) throws IOException, XmlPullParserException { final ShortcutUser ret = new ShortcutUser(userId); final int outerDepth = parser.getDepth(); @@ -217,31 +216,30 @@ class ShortcutUser { } final int depth = parser.getDepth(); final String tag = parser.getName(); - switch (tag) { - case TAG_LAUNCHER: { - ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute( - parser, ATTR_VALUE); - continue; - } - case ShortcutPackage.TAG_ROOT: { - final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(parser, userId); - - // Don't use addShortcut(), we don't need to save the icon. - ret.getPackages().put(shortcuts.getPackageName(), shortcuts); - continue; - } - - case ShortcutLauncher.TAG_ROOT: { - ret.addLauncher(ShortcutLauncher.loadFromXml(parser, userId)); - continue; - } - case ShortcutPackageInfo.TAG_ROOT: { - ret.addPackageInfo(ShortcutPackageInfo.loadFromXml(parser, userId)); - continue; + if (depth == outerDepth + 1) { + switch (tag) { + case TAG_LAUNCHER: { + ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute( + parser, ATTR_VALUE); + continue; + } + case ShortcutPackage.TAG_ROOT: { + final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml( + s, parser, userId, fromBackup); + + // Don't use addShortcut(), we don't need to save the icon. + ret.getPackages().put(shortcuts.getPackageName(), shortcuts); + continue; + } + + case ShortcutLauncher.TAG_ROOT: { + ret.addLauncher(ShortcutLauncher.loadFromXml(parser, userId, fromBackup)); + continue; + } } } - throw ShortcutService.throwForInvalidTag(depth, tag); + ShortcutService.warnForInvalidTag(depth, tag); } return ret; } @@ -283,9 +281,5 @@ class ShortcutUser { for (int i = 0; i < mPackages.size(); i++) { mPackages.valueAt(i).dump(s, pw, prefix + " "); } - - for (int i = 0; i < mPackageInfos.size(); i++) { - mPackageInfos.valueAt(i).dump(s, pw, prefix + " "); - } } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 6df36e4b053f..7f0da1ec669b 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -31,7 +31,6 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; @@ -924,12 +923,12 @@ public class UserManagerService extends IUserManager.Stub { } // Don't call them within the mRestrictionsLock. synchronized (mPackagesLock) { - if (globalChanged) { - writeUserListLP(); - } if (localChanged) { writeUserLP(getUserDataNoChecks(userId)); } + if (globalChanged) { + writeUserListLP(); + } } synchronized (mRestrictionsLock) { @@ -1491,8 +1490,8 @@ public class UserManagerService extends IUserManager.Stub { updateUserIds(); initDefaultGuestRestrictions(); - writeUserListLP(); writeUserLP(userData); + writeUserListLP(); } private String getOwnerName() { @@ -1542,8 +1541,10 @@ public class UserManagerService extends IUserManager.Stub { serializer.attribute(null, ATTR_CREATION_TIME, Long.toString(userInfo.creationTime)); serializer.attribute(null, ATTR_LAST_LOGGED_IN_TIME, Long.toString(userInfo.lastLoggedInTime)); - serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT, - userInfo.lastLoggedInFingerprint); + if (userInfo.lastLoggedInFingerprint != null) { + serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT, + userInfo.lastLoggedInFingerprint); + } if (userInfo.iconPath != null) { serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath); } @@ -1599,7 +1600,7 @@ public class UserManagerService extends IUserManager.Stub { serializer.endDocument(); userFile.finishWrite(fos); } catch (Exception ioe) { - Slog.e(LOG_TAG, "Error writing user info " + userData.info.id + "\n" + ioe); + Slog.e(LOG_TAG, "Error writing user info " + userData.info.id, ioe); userFile.failWrite(fos); } } @@ -1944,6 +1945,7 @@ public class UserManagerService extends IUserManager.Stub { userData.info = userInfo; mUsers.put(userId, userData); } + writeUserLP(userData); writeUserListLP(); if (parent != null) { if (isManagedProfile) { @@ -2217,13 +2219,13 @@ public class UserManagerService extends IUserManager.Stub { mCachedEffectiveUserRestrictions.remove(userHandle); mDevicePolicyLocalUserRestrictions.remove(userHandle); } - // Remove user file - AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX)); - userFile.delete(); // Update the user list synchronized (mPackagesLock) { writeUserListLP(); } + // Remove user file + AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX)); + userFile.delete(); updateUserIds(); File userDir = Environment.getUserSystemDirectory(userHandle); File renamedUserDir = Environment.getUserSystemDirectory(UserHandle.USER_NULL - userHandle); diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 98d44ac3bffe..b501398c7d57 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -509,34 +509,35 @@ final class AccessibilityController { continue; } - Region windowBounds = mTempRegion2; + // Consider the touchable portion of the window Matrix matrix = mTempMatrix; populateTransformationMatrixLocked(windowState, matrix); + Region touchableRegion = mTempRegion3; + windowState.getTouchableRegion(touchableRegion); + Rect touchableFrame = mTempRect1; + touchableRegion.getBounds(touchableFrame); RectF windowFrame = mTempRectF; + windowFrame.set(touchableFrame); + windowFrame.offset(-windowState.mFrame.left, -windowState.mFrame.top); + matrix.mapRect(windowFrame); + Region windowBounds = mTempRegion2; + windowBounds.set((int) windowFrame.left, (int) windowFrame.top, + (int) windowFrame.right, (int) windowFrame.bottom); + // Only update new regions + Region portionOfWindowAlreadyAccountedFor = mTempRegion3; + portionOfWindowAlreadyAccountedFor.set(mMagnifiedBounds); + portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION); + windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE); if (mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) { - windowFrame.set(windowState.mFrame); - windowFrame.offset(-windowFrame.left, -windowFrame.top); - matrix.mapRect(windowFrame); - windowBounds.set((int) windowFrame.left, (int) windowFrame.top, - (int) windowFrame.right, (int) windowFrame.bottom); mMagnifiedBounds.op(windowBounds, Region.Op.UNION); mMagnifiedBounds.op(mAvailableBounds, Region.Op.INTERSECT); } else { - Region touchableRegion = mTempRegion3; - windowState.getTouchableRegion(touchableRegion); - Rect touchableFrame = mTempRect1; - touchableRegion.getBounds(touchableFrame); - windowFrame.set(touchableFrame); - windowFrame.offset(-windowState.mFrame.left, -windowState.mFrame.top); - matrix.mapRect(windowFrame); - windowBounds.set((int) windowFrame.left, (int) windowFrame.top, - (int) windowFrame.right, (int) windowFrame.bottom); nonMagnifiedBounds.op(windowBounds, Region.Op.UNION); - windowBounds.op(mMagnifiedBounds, Region.Op.DIFFERENCE); mAvailableBounds.op(windowBounds, Region.Op.DIFFERENCE); } + // Update accounted bounds Region accountedBounds = mTempRegion2; accountedBounds.set(mMagnifiedBounds); accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION); diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index 6741aba35f3c..68ea4df0c0b0 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -41,6 +41,8 @@ import android.view.animation.Interpolator; import com.android.server.wm.DimLayer.DimLayerUser; +import java.util.ArrayList; + /** * Keeps information about the docked stack divider. */ @@ -87,7 +89,7 @@ public class DockedStackDividerController implements DimLayerUser { private final DimLayer mDimLayer; private boolean mMinimizedDock; - private boolean mAnimating; + private boolean mAnimatingForMinimizedDockedStack; private boolean mAnimationStarted; private long mAnimationStartTime; private float mAnimationStart; @@ -96,7 +98,8 @@ public class DockedStackDividerController implements DimLayerUser { private final Interpolator mMinimizedDockInterpolator; private float mMaximizeMeetFraction; private final Rect mTouchRegion = new Rect(); - private boolean mAdjustingForIme; + private boolean mAnimatingForIme; + private boolean mAdjustedForIme; DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) { mService = service; @@ -174,12 +177,11 @@ public class DockedStackDividerController implements DimLayerUser { return mLastVisibility; } - void setAdjustingForIme(boolean adjusting) { - mAdjustingForIme = adjusting; - } - - boolean isAdjustingForIme() { - return mAdjustingForIme; + void setAdjustedForIme(boolean adjusted, boolean animate) { + if (mAdjustedForIme != adjusted) { + mAnimatingForIme = animate; + mAdjustedForIme = adjusted; + } } void positionDockedStackedDivider(Rect frame) { @@ -342,6 +344,7 @@ public class DockedStackDividerController implements DimLayerUser { } mMinimizedDock = minimizedDock; + mAnimatingForIme = false; if (minimizedDock) { if (animate) { startAdjustAnimation(0f, 1f); @@ -358,7 +361,7 @@ public class DockedStackDividerController implements DimLayerUser { } private void startAdjustAnimation(float from, float to) { - mAnimating = true; + mAnimatingForMinimizedDockedStack = true; mAnimationStarted = false; mAnimationStart = from; mAnimationTarget = to; @@ -380,10 +383,45 @@ public class DockedStackDividerController implements DimLayerUser { } public boolean animate(long now) { - if (!mAnimating) { + if (mAnimatingForMinimizedDockedStack) { + return animateForMinimizedDockedStack(now); + } else if (mAnimatingForIme) { + return animateForIme(); + } else { return false; } + } + + private boolean animateForIme() { + boolean updated = false; + boolean animating = false; + + final ArrayList<TaskStack> stacks = mDisplayContent.getStacks(); + for (int i = stacks.size() - 1; i >= 0; --i) { + final TaskStack stack = stacks.get(i); + if (stack != null && stack.isAdjustedForIme()) { + updated |= stack.updateAdjustForIme(); + animating |= stack.isAnimatingForIme(); + } + } + + if (updated) { + mService.mWindowPlacerLocked.performSurfacePlacement(); + } + + if (!animating) { + mAnimatingForIme = false; + for (int i = stacks.size() - 1; i >= 0; --i) { + final TaskStack stack = stacks.get(i); + if (stack != null) { + stack.clearImeGoingAway(); + } + } + } + return animating; + } + private boolean animateForMinimizedDockedStack(long now) { final TaskStack stack = mDisplayContent.getDockedStackVisibleForUserLocked(); if (!mAnimationStarted) { mAnimationStarted = true; @@ -406,7 +444,7 @@ public class DockedStackDividerController implements DimLayerUser { } } if (t >= 1.0f) { - mAnimating = false; + mAnimatingForMinimizedDockedStack = false; return false; } else { return true; diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index c0c1ed8e2d0c..daeb860e2ef5 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -195,10 +195,8 @@ final class Session extends IWindowSession.Stub @Override public void repositionChild(IWindow window, int left, int top, int right, int bottom, - int requestedWidth, int requestedHeight, long deferTransactionUntilFrame, Rect outFrame) { mService.repositionChild(this, window, left, top, right, bottom, - requestedWidth, requestedHeight, deferTransactionUntilFrame, outFrame); } diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 8d67771e0bff..0bf7102ddeb8 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -55,6 +55,11 @@ public class TaskStack implements DimLayer.DimLayerUser, // If the stack should be resized to fullscreen. private static final boolean FULLSCREEN = true; + // When we have a top-bottom split screen, we shift the bottom stack up to accommodate + // the IME window. The static flag below controls whether to run animation when the + // IME window goes away. + private static final boolean ANIMATE_IME_GOING_AWAY = false; + /** Unique identifier */ final int mStackId; @@ -107,6 +112,7 @@ public class TaskStack implements DimLayer.DimLayerUser, private final Rect mLastContentBounds = new Rect(); private final Rect mTmpAdjustedBounds = new Rect(); private boolean mAdjustedForIme; + private boolean mImeGoingAway; private WindowState mImeWin; private float mMinimizeAmount; private final int mDockedStackMinimizeThickness; @@ -796,19 +802,54 @@ public class TaskStack implements DimLayer.DimLayerUser, void setAdjustedForIme(WindowState imeWin) { mAdjustedForIme = true; mImeWin = imeWin; - if (updateAdjustedBounds()) { - getDisplayContent().mDividerControllerLocked.setAdjustingForIme(true); + mImeGoingAway = false; + } + + boolean isAdjustedForIme() { + return mAdjustedForIme || mImeGoingAway; + } + void clearImeGoingAway() { + mImeGoingAway = false; + } + + boolean isAnimatingForIme() { + return mImeWin != null && mImeWin.isAnimatingLw(); + } + + /** + * Update the stack's bounds (crop or position) according to the IME window's + * current position. When IME window is animated, the bottom stack is animated + * together to track the IME window's current position, and the top stack is + * cropped as necessary. + * + * @return true if a traversal should be performed after the adjustment. + */ + boolean updateAdjustForIme() { + boolean stopped = false; + if (mImeGoingAway && (!ANIMATE_IME_GOING_AWAY || !isAnimatingForIme())) { + mImeWin = null; + mAdjustedForIme = false; + stopped = true; } + // Make sure to run a traversal when the animation stops so that the stack + // is moved to its final position. + return updateAdjustedBounds() || stopped; } /** * Resets the adjustment after it got adjusted for the IME. + * @param adjustBoundsNow if true, reset and update the bounds immediately and forget about + * animations; otherwise, set flag and animates the window away together + * with IME window. */ - void resetAdjustedForIme() { - mAdjustedForIme = false; - mImeWin = null; - if (updateAdjustedBounds()) { - getDisplayContent().mDividerControllerLocked.setAdjustingForIme(true); + void resetAdjustedForIme(boolean adjustBoundsNow) { + if (adjustBoundsNow) { + mImeWin = null; + mAdjustedForIme = false; + mImeGoingAway = false; + updateAdjustedBounds(); + } else { + mImeGoingAway |= mAdjustedForIme; } } @@ -843,6 +884,12 @@ public class TaskStack implements DimLayer.DimLayerUser, getDisplayContent().getContentRect(displayContentRect); contentBounds.set(displayContentRect); int imeTop = Math.max(imeWin.getDisplayFrameLw().top, contentBounds.top); + + // if IME window is animating, get its actual vertical shown position (but no smaller than + // the final target vertical position) + if (imeWin.isAnimatingLw()) { + imeTop = Math.max(imeTop, imeWin.getShownPositionLw().y); + } imeTop += imeWin.getGivenContentInsetsLw().top; if (contentBounds.bottom > imeTop) { contentBounds.bottom = imeTop; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 5d13b3b38c84..1a9e206db986 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2531,7 +2531,6 @@ public class WindowManagerService extends IWindowManager.Stub void repositionChild(Session session, IWindow client, int left, int top, int right, int bottom, - int requestedWidth, int requestedHeight, long deferTransactionUntilFrame, Rect outFrame) { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "repositionChild"); long origId = Binder.clearCallingIdentity(); @@ -2547,7 +2546,6 @@ public class WindowManagerService extends IWindowManager.Stub "repositionChild called but window is not" + "attached to a parent win=" + win); } - win.setRequestedSize(requestedWidth, requestedHeight); win.mAttrs.x = left; win.mAttrs.y = top; @@ -7380,8 +7378,9 @@ public class WindowManagerService extends IWindowManager.Stub final WindowState imeWin = mInputMethodWindow; final TaskStack focusedStack = mCurrentFocus != null ? mCurrentFocus.getStack() : null; + final boolean dockVisible = isStackVisibleLocked(DOCKED_STACK_ID); if (imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw() - && isStackVisibleLocked(DOCKED_STACK_ID) + && dockVisible && focusedStack != null && focusedStack.getDockSide() == DOCKED_BOTTOM){ final ArrayList<TaskStack> stacks = displayContent.getStacks(); @@ -7391,12 +7390,14 @@ public class WindowManagerService extends IWindowManager.Stub stack.setAdjustedForIme(imeWin); } } + displayContent.mDividerControllerLocked.setAdjustedForIme(true, true); } else { final ArrayList<TaskStack> stacks = displayContent.getStacks(); for (int i = stacks.size() - 1; i >= 0; --i) { final TaskStack stack = stacks.get(i); - stack.resetAdjustedForIme(); + stack.resetAdjustedForIme(!dockVisible); } + displayContent.mDividerControllerLocked.setAdjustedForIme(false, dockVisible); } } diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index 3b0081d6376f..3e5ddbce5a7b 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -693,16 +693,14 @@ class WindowSurfacePlacer { // currently animating... let's do something. final int left = w.mFrame.left; final int top = w.mFrame.top; - final boolean adjustedForMinimizedDockedStack = w.getTask() != null && - w.getTask().mStack.isAdjustedForMinimizedDockedStack(); + final boolean adjustedForMinimizedDockOrIme = task != null + && (task.mStack.isAdjustedForMinimizedDockedStack() + || task.mStack.isAdjustedForIme()); if ((w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0 - && !w.isDragResizing() && !adjustedForMinimizedDockedStack + && !w.isDragResizing() && !adjustedForMinimizedDockOrIme && (task == null || !w.getTask().mStack.getFreezeMovementAnimations()) && !w.mWinAnimator.mLastHidden) { winAnimator.setMoveAnimation(left, top); - } else if (w.mAttrs.type == TYPE_DOCK_DIVIDER && - displayContent.getDockedDividerController().isAdjustingForIme()) { - winAnimator.setMoveAnimation(left, top); } //TODO (multidisplay): Accessibility supported only for the default display. @@ -819,8 +817,6 @@ class WindowSurfacePlacer { mService.updateResizingWindows(w); } - displayContent.getDockedDividerController().setAdjustingForIme(false); - mService.mDisplayManagerInternal.setDisplayProperties(displayId, mDisplayHasContent, mPreferredRefreshRate, diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 14efc27ce35d..0a4effb26249 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1025,7 +1025,8 @@ public final class SystemServer { mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS); } - if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) { + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS) + || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) { mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java index 61249ae7eaa1..f034d55d9736 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java @@ -797,7 +797,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { @NonNull private List<ShortcutInfo> assertShortcutIds(@NonNull List<ShortcutInfo> actualShortcuts, String... expectedIds) { - assertEquals(expectedIds.length, actualShortcuts.size()); final HashSet<String> expected = new HashSet<>(list(expectedIds)); final HashSet<String> actual = new HashSet<>(); for (ShortcutInfo s : actualShortcuts) { @@ -973,18 +972,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertTrue(b == null || b.size() == 0); } - private void assertShortcutPackageInfo(String packageName, int userId, int expectedVersion) { - ShortcutPackageInfo spi = mService.getPackageInfoForTest(packageName, userId); - assertNotNull(spi); - assertEquals(expectedVersion, spi.getVersionCode()); - - assertTrue(spi.canRestoreTo(genPackage(packageName, /*uid*/ 0, 9999999, packageName))); - } - - private void assertNoShortcutPackageInfo(String packageName, int userId) { - assertNull(mService.getPackageInfoForTest(packageName, userId)); - } - private ShortcutInfo getPackageShortcut(String packageName, String shortcutId, int userId) { return mService.getPackageShortcutForTest(packageName, shortcutId, userId); } @@ -1229,11 +1216,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { "shortcut1", "shortcut2"); assertEquals(2, mManager.getRemainingCallCount()); - assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1); - assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_0); - assertNoShortcutPackageInfo(CALLING_PACKAGE_1, USER_10); - assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_10); - // TODO: Check fields assertTrue(mManager.setDynamicShortcuts(list(si1))); @@ -1260,11 +1242,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { runWithCaller(CALLING_PACKAGE_2, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1")))); - - assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1); - assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_0); - assertNoShortcutPackageInfo(CALLING_PACKAGE_1, USER_10); - assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_10, 2); }); } @@ -1302,7 +1279,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { runWithCaller(CALLING_PACKAGE_2, USER_10, () -> { assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); - assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_10, 2); }); } @@ -1608,7 +1584,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { Bitmap bmp; setCaller(LAUNCHER_1); - // Check hasIconResource()/hasIconFile(). assertShortcutIds(assertAllHaveIconResId(mLauncherApps.getShortcutInfo( CALLING_PACKAGE_1, list("res32x32"), @@ -1898,12 +1873,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // TODO Check bitmap removal too. runWithCaller(CALLING_PACKAGE_2, USER_11, () -> { - assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_11); - mManager.updateShortcuts(list()); - - // Even an empty update call will populate the package info. - assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_11, 2); }); } @@ -1940,7 +1910,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4))); setCaller(CALLING_PACKAGE_3); - final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s3", 5000); + final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s3", START_TIME + 5000); assertTrue(mManager.setDynamicShortcuts(list(s3_2))); setCaller(LAUNCHER_1); @@ -2100,16 +2070,9 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Pin some. runWithCaller(LAUNCHER_1, USER_0, () -> { - assertNoShortcutPackageInfo(LAUNCHER_1, USER_0); - mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2", "s3"), getCallingUser()); - assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4); - assertNoShortcutPackageInfo(LAUNCHER_2, USER_0); - assertNoShortcutPackageInfo(LAUNCHER_1, USER_10); - assertNoShortcutPackageInfo(LAUNCHER_2, USER_10); - mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4", "s5"), getCallingUser()); @@ -2170,19 +2133,11 @@ public class ShortcutManagerTest extends InstrumentationTestCase { dumpsysOnLogcat(); - assertNoShortcutPackageInfo(LAUNCHER_1, USER_0); - assertNoShortcutPackageInfo(LAUNCHER_2, USER_0); - assertNoShortcutPackageInfo(LAUNCHER_1, USER_10); - assertNoShortcutPackageInfo(LAUNCHER_2, USER_10); - // Pin some. runWithCaller(LAUNCHER_1, USER_0, () -> { mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s4"), getCallingUser()); - assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4); - assertNoShortcutPackageInfo(LAUNCHER_2, USER_0); - mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s1", "s2", "s4"), getCallingUser()); }); @@ -2256,16 +2211,10 @@ public class ShortcutManagerTest extends InstrumentationTestCase { | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())), "s2"); - assertNoShortcutPackageInfo(LAUNCHER_2, USER_0); - assertNoShortcutPackageInfo(LAUNCHER_2, USER_10); - // Now pin some. mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s2"), getCallingUser()); - assertShortcutPackageInfo(LAUNCHER_2, USER_0, 5); - assertNoShortcutPackageInfo(LAUNCHER_2, USER_10); - mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s1", "s2"), getCallingUser()); @@ -2283,12 +2232,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { "s2"); }); - assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1); - assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2); - assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0); - assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4); - assertShortcutPackageInfo(LAUNCHER_2, USER_0, 5); - // Re-initialize and load from the files. mService.saveDirtyInfo(); initService(); @@ -2297,12 +2240,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.handleUnlockUser(USER_0); // Make sure package info is restored too. - assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1); - assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2); - assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0); - assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4); - assertShortcutPackageInfo(LAUNCHER_2, USER_0, 5); - runWithCaller(LAUNCHER_1, USER_0, () -> { assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly( mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1, @@ -3234,10 +3171,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM).setLauncherComponent( mService, new ComponentName("pkg1", "class")); - assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1); - assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2); - assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0); - // Restore. mService.saveDirtyInfo(); initService(); @@ -3248,10 +3181,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // this will pre-load the per-user info. mService.handleUnlockUser(UserHandle.USER_SYSTEM); - assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1); - assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2); - assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0); - // Now it's loaded. assertEquals(1, mService.getShortcutsForTest().size()); @@ -3394,11 +3323,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10); assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); - mService.saveDirtyInfo(); // Nonexistent package. @@ -3430,11 +3354,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10); assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); - mService.saveDirtyInfo(); // Remove a package. @@ -3465,11 +3384,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10); assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); - mService.saveDirtyInfo(); // Remove a launcher. @@ -3524,11 +3438,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10); assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); - mService.saveDirtyInfo(); // Remove the other launcher from user 10 too. @@ -3585,7 +3494,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.saveDirtyInfo(); } - public void testHandleGonePackage_crossProfile() { + + public void testSaveAndLoadUser_forBackup() { // Create some shortcuts. runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { assertTrue(mManager.setDynamicShortcuts(list( @@ -3667,26 +3577,79 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10)); assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - - // These two shouldn't exist - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0, USER_P0)); - - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10)); - // Make sure all the information is persisted. mService.saveDirtyInfo(); initService(); mService.handleUnlockUser(USER_0); mService.handleUnlockUser(USER_P0); mService.handleUnlockUser(USER_10); + } + + public void testHandleGonePackage_crossProfile() { + // Create some shortcuts. + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertTrue(mManager.setDynamicShortcuts(list( + makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.setDynamicShortcuts(list( + makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertTrue(mManager.setDynamicShortcuts(list( + makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + assertTrue(mManager.setDynamicShortcuts(list( + makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); + }); + + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0)); + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0)); + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0)); + + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0)); + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0)); + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0)); + + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0)); + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0)); + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0)); + + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10)); + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10)); + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10)); + + // Pin some. + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, + list("s1"), HANDLE_USER_0); + + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, + list("s2"), UserHandle.of(USER_P0)); + + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, + list("s3"), HANDLE_USER_0); + }); + + runWithCaller(LAUNCHER_1, USER_P0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, + list("s2"), HANDLE_USER_0); + + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, + list("s3"), UserHandle.of(USER_P0)); + + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, + list("s1"), HANDLE_USER_0); + }); + + runWithCaller(LAUNCHER_1, USER_10, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, + list("s3"), HANDLE_USER_10); + }); + + // Check the state. assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0)); assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0)); @@ -3704,20 +3667,28 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10)); assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); + // Make sure all the information is persisted. + mService.saveDirtyInfo(); + initService(); + mService.handleUnlockUser(USER_0); + mService.handleUnlockUser(USER_P0); + mService.handleUnlockUser(USER_10); - // These two shouldn't exist - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0, USER_P0)); + assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0)); + assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0)); + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10)); + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0)); + assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0)); + assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0)); + assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0)); + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0)); + assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0)); + + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10)); + assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10)); + assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10)); // Start uninstalling. uninstallPackage(USER_10, LAUNCHER_1); @@ -3739,16 +3710,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10)); assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10)); - // Uninstall. uninstallPackage(USER_10, CALLING_PACKAGE_1); mService.cleanupGonePackages(USER_10); @@ -3769,17 +3730,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10)); assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_P0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10)); - uninstallPackage(USER_P0, LAUNCHER_1); mService.cleanupGonePackages(USER_0); @@ -3799,17 +3749,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10)); assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_P0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10)); - mService.cleanupGonePackages(USER_P0); assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0)); @@ -3828,17 +3767,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10)); assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_P0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10)); - uninstallPackage(USER_P0, CALLING_PACKAGE_1); mService.saveDirtyInfo(); @@ -3863,17 +3791,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10)); assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_P0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10)); - // Uninstall uninstallPackage(USER_0, LAUNCHER_1); @@ -3899,17 +3816,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10)); assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_P0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10)); - uninstallPackage(USER_0, CALLING_PACKAGE_2); mService.saveDirtyInfo(); @@ -3933,17 +3839,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10)); assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10)); assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10)); - - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_P0)); - assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10)); } // TODO Detailed test for hasShortcutPermissionInner(). @@ -3998,27 +3893,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { checkCanRestoreTo(false, spi2, 11, "x", "sig2x", "sig1", "y"); } - public void testShortcutPackageInfoRefresh() { - addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1"); - - final ShortcutPackageInfo spi1 = ShortcutPackageInfo.generateForInstalledPackage( - mService, CALLING_PACKAGE_1, USER_0); - - checkCanRestoreTo(true, spi1, 10, "sig1"); - - addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 11, "sig1", "sig2"); - - spi1.refreshAndSave(mService, USER_0); - - mService.handleCleanupUser(USER_0); - initService(); - - checkCanRestoreTo(false, spi1, 10, "sig1", "sig2"); - checkCanRestoreTo(false, spi1, 11, "sig", "sig2"); - checkCanRestoreTo(false, spi1, 11, "sig1", "sig"); - checkCanRestoreTo(true, spi1, 11, "sig1", "sig2"); - } - public void testHandlePackageDelete() { setCaller(CALLING_PACKAGE_1, USER_0); assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); @@ -4038,73 +3912,56 @@ public class ShortcutManagerTest extends InstrumentationTestCase { setCaller(CALLING_PACKAGE_3, USER_10); assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10)); mService.mPackageMonitor.onReceive(getTestContext(), genPackageDeleteIntent(CALLING_PACKAGE_1, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10)); + assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10)); mService.mPackageMonitor.onReceive(getTestContext(), genPackageDeleteIntent(CALLING_PACKAGE_2, USER_10)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10)); + assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10)); + assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10)); mInjectedPackages.remove(CALLING_PACKAGE_1); mInjectedPackages.remove(CALLING_PACKAGE_3); mService.handleUnlockUser(USER_0); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10)); + + assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0)); + assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10)); + assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10)); mService.handleUnlockUser(USER_10); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); - assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10)); + + assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0)); + assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0)); + assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0)); + assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10)); + assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10)); + assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10)); } public void testHandlePackageUpdate() { - setCaller(CALLING_PACKAGE_1, USER_0); - assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); - - assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); - assertEquals(1, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0).getVersionCode()); - - addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 123); - - mService.mPackageMonitor.onReceive(getTestContext(), - genPackageUpdateIntent("abc", USER_0)); - assertEquals(1, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0).getVersionCode()); - - mService.mPackageMonitor.onReceive(getTestContext(), - genPackageUpdateIntent("abc", USER_10)); - assertEquals(1, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0).getVersionCode()); - - mService.mPackageMonitor.onReceive(getTestContext(), - genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0)); - assertEquals(123, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0) - .getVersionCode()); + // TODO: Make sure unshadow is called. } } diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable_linear_progress_bar.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable_linear_progress_bar.xml index 96fd70ecf9cf..a6da114b511b 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_drawable_linear_progress_bar.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable_linear_progress_bar.xml @@ -17,7 +17,7 @@ android:height="4dp" android:viewportHeight="4" android:viewportWidth="360" - android:width="360dp" > + android:width="36dp" > <group android:name="linear_indeterminate" diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java index 087e68a841ed..9351f63551fb 100644 --- a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java +++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java @@ -43,33 +43,37 @@ public class AnimatedVectorDrawableTest extends Activity implements View.OnClick @Override protected void onCreate(Bundle savedInstanceState) { + final int[] layerTypes = {View.LAYER_TYPE_SOFTWARE, View.LAYER_TYPE_HARDWARE}; super.onCreate(savedInstanceState); ScrollView scrollView = new ScrollView(this); GridLayout container = new GridLayout(this); scrollView.addView(container); - container.setColumnCount(1); + container.setColumnCount(2); for (int i = 0; i < icon.length; i++) { - Button button = new Button(this); - button.setWidth(400); - button.setHeight(400); - button.setBackgroundResource(icon[i]); - AnimatedVectorDrawable d = (AnimatedVectorDrawable) button.getBackground(); - d.registerAnimationCallback(new Animatable2.AnimationCallback() { - @Override - public void onAnimationStart(Drawable drawable) { - Log.v(LOGCAT, "Animator start"); - } + for (int j = 0; j < layerTypes.length; j++) { + Button button = new Button(this); + button.setWidth(400); + button.setHeight(400); + button.setLayerType(layerTypes[j], null); + button.setBackgroundResource(icon[i]); + AnimatedVectorDrawable d = (AnimatedVectorDrawable) button.getBackground(); + d.registerAnimationCallback(new Animatable2.AnimationCallback() { + @Override + public void onAnimationStart(Drawable drawable) { + Log.v(LOGCAT, "Animator start"); + } - @Override - public void onAnimationEnd(Drawable drawable) { + @Override + public void onAnimationEnd(Drawable drawable) { Log.v(LOGCAT, "Animator end"); - } - }); + } + }); - container.addView(button); - button.setOnClickListener(this); + container.addView(button); + button.setOnClickListener(this); + } } setContentView(scrollView); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java index 53adb41af0cb..5a6a00fc3985 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -97,7 +97,6 @@ public final class BridgeWindowSession implements IWindowSession { @Override public void repositionChild(IWindow window, int left, int top, int right, int bottom, - int requestedWidth, int requestedHeight, long deferTransactionUntilFrame, Rect outFrame) { // pass for now. return; diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 9f8af6e2224c..8d5efba8620e 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -320,7 +320,8 @@ public class WifiInfo implements Parcelable { if (!TextUtils.isEmpty(unicode)) { return "\"" + unicode + "\""; } else { - return mWifiSsid.getHexString(); + String hex = mWifiSsid.getHexString(); + return (hex != null) ? hex : WifiSsid.NONE; } } return WifiSsid.NONE; diff --git a/wifi/java/android/net/wifi/WifiSsid.java b/wifi/java/android/net/wifi/WifiSsid.java index f8ba95dc07de..c53cd3c6454e 100644 --- a/wifi/java/android/net/wifi/WifiSsid.java +++ b/wifi/java/android/net/wifi/WifiSsid.java @@ -205,7 +205,7 @@ public class WifiSsid implements Parcelable { for (int i = 0; i < octets.size(); i++) { out += String.format(Locale.US, "%02x", ssidbytes[i]); } - return out; + return (octets.size() > 0) ? out : null; } /** Implement the Parcelable interface {@hide} */ |