diff options
40 files changed, 1090 insertions, 744 deletions
diff --git a/api/current.txt b/api/current.txt index 693d9f4223a8..b0eb006e5b5a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -9488,8 +9488,6 @@ package android.content.pm { method public abstract int getComponentEnabledSetting(android.content.ComponentName); method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon(); method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo); - method public abstract byte[] getEphemeralCookie(); - method public abstract int getEphemeralCookieMaxSizeBytes(); method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); method public abstract java.lang.String getInstallerPackageName(java.lang.String); @@ -9523,7 +9521,6 @@ package android.content.pm { method public abstract java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle); method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo); method public abstract boolean hasSystemFeature(java.lang.String); - method public abstract boolean isEphemeralApplication(); method public abstract boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String); method public abstract boolean isSafeMode(); method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int); @@ -9541,7 +9538,6 @@ package android.content.pm { method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int); method public abstract void setApplicationEnabledSetting(java.lang.String, int, int); method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int); - method public abstract boolean setEphemeralCookie(byte[]); method public abstract void setInstallerPackageName(java.lang.String, java.lang.String); method public abstract void verifyPendingInstall(int, int); field public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; // 0x0 @@ -33616,6 +33612,7 @@ package android.service.notification { field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb field public static final int REASON_PACKAGE_BANNED = 7; // 0x7 field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5 + field public static final int REASON_TOPIC_BANNED = 14; // 0xe field public static final int REASON_USER_STOPPED = 6; // 0x6 field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; } @@ -36840,8 +36837,6 @@ package android.test.mock { method public int getComponentEnabledSetting(android.content.ComponentName); method public android.graphics.drawable.Drawable getDefaultActivityIcon(); method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo); - method public byte[] getEphemeralCookie(); - method public int getEphemeralCookieMaxSizeBytes(); method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); method public java.lang.String getInstallerPackageName(java.lang.String); @@ -36874,7 +36869,6 @@ package android.test.mock { method public java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle); method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo); method public boolean hasSystemFeature(java.lang.String); - method public boolean isEphemeralApplication(); method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String); method public boolean isSafeMode(); method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int); @@ -36892,7 +36886,6 @@ package android.test.mock { method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int); method public void setApplicationEnabledSetting(java.lang.String, int, int); method public void setComponentEnabledSetting(android.content.ComponentName, int, int); - method public boolean setEphemeralCookie(byte[]); method public void setInstallerPackageName(java.lang.String, java.lang.String); method public void verifyPendingInstall(int, int); } diff --git a/api/system-current.txt b/api/system-current.txt index d4a97b650fa2..bd7dc9742a5a 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -9790,8 +9790,6 @@ package android.content.pm { method public abstract int getComponentEnabledSetting(android.content.ComponentName); method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon(); method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo); - method public abstract byte[] getEphemeralCookie(); - method public abstract int getEphemeralCookieMaxSizeBytes(); method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); method public abstract java.lang.String getInstallerPackageName(java.lang.String); @@ -9827,7 +9825,6 @@ package android.content.pm { method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo); method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public abstract boolean hasSystemFeature(java.lang.String); - method public abstract boolean isEphemeralApplication(); method public abstract boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String); method public abstract boolean isSafeMode(); method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int); @@ -9847,7 +9844,6 @@ package android.content.pm { method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public abstract void setApplicationEnabledSetting(java.lang.String, int, int); method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int); - method public abstract boolean setEphemeralCookie(byte[]); method public abstract void setInstallerPackageName(java.lang.String, java.lang.String); method public abstract void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle); method public abstract void verifyIntentFilter(int, int, java.util.List<java.lang.String>); @@ -35753,6 +35749,7 @@ package android.service.notification { field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb field public static final int REASON_PACKAGE_BANNED = 7; // 0x7 field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5 + field public static final int REASON_TOPIC_BANNED = 14; // 0xe field public static final int REASON_USER_STOPPED = 6; // 0x6 field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; } @@ -39183,8 +39180,6 @@ package android.test.mock { method public int getComponentEnabledSetting(android.content.ComponentName); method public android.graphics.drawable.Drawable getDefaultActivityIcon(); method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo); - method public byte[] getEphemeralCookie(); - method public int getEphemeralCookieMaxSizeBytes(); method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); method public java.lang.String getInstallerPackageName(java.lang.String); @@ -39219,7 +39214,6 @@ package android.test.mock { method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo); method public void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public boolean hasSystemFeature(java.lang.String); - method public boolean isEphemeralApplication(); method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String); method public boolean isSafeMode(); method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int); @@ -39239,7 +39233,6 @@ package android.test.mock { method public void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public void setApplicationEnabledSetting(java.lang.String, int, int); method public void setComponentEnabledSetting(android.content.ComponentName, int, int); - method public boolean setEphemeralCookie(byte[]); method public void setInstallerPackageName(java.lang.String, java.lang.String); method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle); method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>); diff --git a/api/test-current.txt b/api/test-current.txt index 2f24953ce7da..13d98f49b0e8 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -9496,8 +9496,6 @@ package android.content.pm { method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon(); method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int); method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo); - method public abstract byte[] getEphemeralCookie(); - method public abstract int getEphemeralCookieMaxSizeBytes(); method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); method public abstract java.lang.String getInstallerPackageName(java.lang.String); @@ -9531,7 +9529,6 @@ package android.content.pm { method public abstract java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle); method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo); method public abstract boolean hasSystemFeature(java.lang.String); - method public abstract boolean isEphemeralApplication(); method public abstract boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String); method public abstract boolean isSafeMode(); method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int); @@ -9549,7 +9546,6 @@ package android.content.pm { method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int); method public abstract void setApplicationEnabledSetting(java.lang.String, int, int); method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int); - method public abstract boolean setEphemeralCookie(byte[]); method public abstract void setInstallerPackageName(java.lang.String, java.lang.String); method public abstract void verifyPendingInstall(int, int); field public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; // 0x0 @@ -33630,6 +33626,7 @@ package android.service.notification { field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb field public static final int REASON_PACKAGE_BANNED = 7; // 0x7 field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5 + field public static final int REASON_TOPIC_BANNED = 14; // 0xe field public static final int REASON_USER_STOPPED = 6; // 0x6 field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; } @@ -36856,8 +36853,6 @@ package android.test.mock { method public android.graphics.drawable.Drawable getDefaultActivityIcon(); method public java.lang.String getDefaultBrowserPackageNameAsUser(int); method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo); - method public byte[] getEphemeralCookie(); - method public int getEphemeralCookieMaxSizeBytes(); method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); method public java.lang.String getInstallerPackageName(java.lang.String); @@ -36890,7 +36885,6 @@ package android.test.mock { method public java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle); method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo); method public boolean hasSystemFeature(java.lang.String); - method public boolean isEphemeralApplication(); method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String); method public boolean isSafeMode(); method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int); @@ -36908,7 +36902,6 @@ package android.test.mock { method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int); method public void setApplicationEnabledSetting(java.lang.String, int, int); method public void setComponentEnabledSetting(android.content.ComponentName, int, int); - method public boolean setEphemeralCookie(byte[]); method public void setInstallerPackageName(java.lang.String, java.lang.String); method public void verifyPendingInstall(int, int); } diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java index 8928e99cecd2..e993cca9e325 100644 --- a/core/java/android/animation/PropertyValuesHolder.java +++ b/core/java/android/animation/PropertyValuesHolder.java @@ -861,22 +861,23 @@ public class PropertyValuesHolder implements Cloneable { if (mProperty != null) { Object value = convertBack(mProperty.get(target)); kf.setValue(value); - } - try { - if (mGetter == null) { - Class targetClass = target.getClass(); - setupGetter(targetClass); + } else { + try { if (mGetter == null) { - // Already logged the error - just return to avoid NPE - return; + Class targetClass = target.getClass(); + setupGetter(targetClass); + if (mGetter == null) { + // Already logged the error - just return to avoid NPE + return; + } } + Object value = convertBack(mGetter.invoke(target)); + kf.setValue(value); + } catch (InvocationTargetException e) { + Log.e("PropertyValuesHolder", e.toString()); + } catch (IllegalAccessException e) { + Log.e("PropertyValuesHolder", e.toString()); } - Object value = convertBack(mGetter.invoke(target)); - kf.setValue(value); - } catch (InvocationTargetException e) { - Log.e("PropertyValuesHolder", e.toString()); - } catch (IllegalAccessException e) { - Log.e("PropertyValuesHolder", e.toString()); } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 64642ac5122d..34527c2623c6 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2843,16 +2843,15 @@ public class Activity extends ContextThemeWrapper if (keyCode == KeyEvent.KEYCODE_MENU && mActionBar != null && mActionBar.onMenuKeyEvent(event)) { return true; - } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { - // Capture the Alt-up and send focus to the ActionBar + } else if (event.isCtrlPressed() && + event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_CTRL_MASK) == '<') { + // Capture the Control-< and send focus to the ActionBar final int action = event.getAction(); if (action == KeyEvent.ACTION_DOWN) { - if (event.hasModifiers(KeyEvent.META_ALT_ON)) { - final ActionBar actionBar = getActionBar(); - if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) { - mEatKeyUpEvent = true; - return true; - } + final ActionBar actionBar = getActionBar(); + if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) { + mEatKeyUpEvent = true; + return true; } } else if (action == KeyEvent.ACTION_UP && mEatKeyUpEvent) { mEatKeyUpEvent = false; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index f41928ebe9d7..38abac7e741b 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3258,6 +3258,8 @@ public abstract class PackageManager { * @see #setEphemeralCookie(byte[]) * @see #getEphemeralCookie() * @see #getEphemeralCookieMaxSizeBytes() + * + * @hide */ public abstract boolean isEphemeralApplication(); @@ -3270,6 +3272,8 @@ public abstract class PackageManager { * @see #isEphemeralApplication() * @see #setEphemeralCookie(byte[]) * @see #getEphemeralCookie() + * + * @hide */ public abstract int getEphemeralCookieMaxSizeBytes(); @@ -3286,6 +3290,8 @@ public abstract class PackageManager { * @see #isEphemeralApplication() * @see #setEphemeralCookie(byte[]) * @see #getEphemeralCookieMaxSizeBytes() + * + * @hide */ public abstract @NonNull byte[] getEphemeralCookie(); @@ -3304,6 +3310,8 @@ public abstract class PackageManager { * @see #isEphemeralApplication() * @see #getEphemeralCookieMaxSizeBytes() * @see #getEphemeralCookie() + * + * @hide */ public abstract boolean setEphemeralCookie(@NonNull byte[] cookie); diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index aba82fad0bfa..035462d56fc5 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -95,6 +95,9 @@ public abstract class NotificationAssistantService extends NotificationListenerS /** Notification was canceled because it was an invisible member of a group. */ public static final int REASON_GROUP_OPTIMIZATION = 13; + /** Notification was canceled by the user banning the topic. */ + public static final int REASON_TOPIC_BANNED = 14; + public class Adjustment { int mImportance; CharSequence mExplanation; diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index ecec25852cbb..a78b56ab194c 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -1328,4 +1328,15 @@ public interface WindowManagerPolicy { * @param fadeoutDuration the duration of the exit animation, in milliseconds */ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration); + + /** + * Calculates the stable insets without running a layout. + * + * @param displayRotation the current display rotation + * @param outInsets the insets to return + * @param displayWidth the current display width + * @param displayHeight the current display height + */ + public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight, + Rect outInsets); } diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index ba5d6c2896ef..6e5e5915bb6f 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -201,10 +201,17 @@ public class BaseInputConnection implements InputConnection { } /** - * The default implementation performs the deletion around the current - * selection position of the editable text. - * @param beforeLength - * @param afterLength + * The default implementation performs the deletion around the current selection position of the + * editable text. + * + * @param beforeLength The number of characters before the cursor to be deleted, in code unit. + * If this is greater than the number of existing characters between the beginning of the + * text and the cursor, then this method does not fail but deletes all the characters in + * that range. + * @param afterLength The number of characters after the cursor to be deleted, in code unit. + * If this is greater than the number of existing characters between the cursor and + * the end of the text, then this method does not fail but deletes all the characters in + * that range. */ public boolean deleteSurroundingText(int beforeLength, int afterLength) { if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength @@ -213,7 +220,7 @@ public class BaseInputConnection implements InputConnection { if (content == null) return false; beginBatchEdit(); - + int a = Selection.getSelectionStart(content); int b = Selection.getSelectionEnd(content); @@ -253,9 +260,9 @@ public class BaseInputConnection implements InputConnection { content.delete(b, end); } - + endBatchEdit(); - + return true; } diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index ff992d39fc66..be7bc14f0515 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -335,12 +335,15 @@ public interface InputConnection { * but be careful to wait until the batch edit is over if one is * in progress.</p> * - * @param beforeLength The number of characters to be deleted before the - * current cursor position. - * @param afterLength The number of characters to be deleted after the - * current cursor position. - * @return true on success, false if the input connection is no longer - * valid. + * @param beforeLength The number of characters before the cursor to be deleted, in code unit. + * If this is greater than the number of existing characters between the beginning of the + * text and the cursor, then this method does not fail but deletes all the characters in + * that range. + * @param afterLength The number of characters after the cursor to be deleted, in code unit. + * If this is greater than the number of existing characters between the cursor and + * the end of the text, then this method does not fail but deletes all the characters in + * that range. + * @return true on success, false if the input connection is no longer valid. */ public boolean deleteSurroundingText(int beforeLength, int afterLength); diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java index e43d531c7a5c..e79f1b8a3133 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java +++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java @@ -14,18 +14,19 @@ * limitations under the License. */ -package com.android.systemui.stackdivider; +package com.android.internal.policy; import android.content.Context; +import android.content.res.Resources; import android.graphics.Rect; -import com.android.systemui.statusbar.FlingAnimationUtils; - import java.util.ArrayList; /** * Calculates the snap targets and the snap position given a position and a velocity. All positions * here are to be interpreted as the left/top edge of the divider rectangle. + * + * @hide */ public class DividerSnapAlgorithm { @@ -44,8 +45,7 @@ public class DividerSnapAlgorithm { */ private static final int SNAP_ONLY_1_1 = 2; - private final Context mContext; - private final FlingAnimationUtils mFlingAnimationUtils; + private final float mMinFlingVelocityPxPerSecond; private final int mDisplayWidth; private final int mDisplayHeight; private final int mDividerSize; @@ -63,18 +63,17 @@ public class DividerSnapAlgorithm { private final SnapTarget mDismissStartTarget; private final SnapTarget mDismissEndTarget; - public DividerSnapAlgorithm(Context ctx, FlingAnimationUtils flingAnimationUtils, + public DividerSnapAlgorithm(Resources res, float minFlingVelocityPxPerSecond, int displayWidth, int displayHeight, int dividerSize, boolean isHorizontalDivision, Rect insets) { - mContext = ctx; - mFlingAnimationUtils = flingAnimationUtils; + mMinFlingVelocityPxPerSecond = minFlingVelocityPxPerSecond; mDividerSize = dividerSize; mDisplayWidth = displayWidth; mDisplayHeight = displayHeight; mInsets.set(insets); - mSnapMode = ctx.getResources().getInteger( + mSnapMode = res.getInteger( com.android.internal.R.integer.config_dockedStackDividerSnapMode); - mFixedRatio = ctx.getResources().getFraction( + mFixedRatio = res.getFraction( com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1); calculateTargets(isHorizontalDivision); mFirstSplitTarget = mTargets.get(1); @@ -84,7 +83,7 @@ public class DividerSnapAlgorithm { } public SnapTarget calculateSnapTarget(int position, float velocity) { - if (Math.abs(velocity) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { + if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) { return snap(position); } if (position < mFirstSplitTarget.position && velocity < 0) { @@ -100,6 +99,17 @@ public class DividerSnapAlgorithm { } } + public SnapTarget calculateNonDismissingSnapTarget(int position) { + SnapTarget target = snap(position); + if (target == mDismissStartTarget) { + return mFirstSplitTarget; + } else if (target == mDismissEndTarget) { + return mLastSplitTarget; + } else { + return target; + } + } + public float calculateDismissingFraction(int position) { if (position < mFirstSplitTarget.position) { return 1f - (float) position / mFirstSplitTarget.position; diff --git a/core/java/com/android/internal/policy/DockedDividerUtils.java b/core/java/com/android/internal/policy/DockedDividerUtils.java new file mode 100644 index 000000000000..25a060e0a0a7 --- /dev/null +++ b/core/java/com/android/internal/policy/DockedDividerUtils.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy; + +import android.graphics.Rect; +import android.view.WindowManager; + +/** + * Utility functions for docked stack divider used by both window manager and System UI. + * + * @hide + */ +public class DockedDividerUtils { + + public static void calculateBoundsForPosition(int position, int dockSide, Rect outRect, + int displayWidth, int displayHeight, int dividerSize) { + outRect.set(0, 0, displayWidth, displayHeight); + switch (dockSide) { + case WindowManager.DOCKED_LEFT: + outRect.right = position; + break; + case WindowManager.DOCKED_TOP: + outRect.bottom = position; + break; + case WindowManager.DOCKED_RIGHT: + outRect.left = position + dividerSize; + break; + case WindowManager.DOCKED_BOTTOM: + outRect.top = position + dividerSize; + break; + } + if (outRect.left > outRect.right) { + outRect.left = outRect.right; + } + if (outRect.top > outRect.bottom) { + outRect.top = outRect.bottom; + } + if (outRect.right < outRect.left) { + outRect.right = outRect.left; + } + if (outRect.bottom < outRect.top) { + outRect.bottom = outRect.top; + } + } + + public static int calculatePositionForBounds(Rect bounds, int dockSide, int dividerSize) { + switch (dockSide) { + case WindowManager.DOCKED_LEFT: + return bounds.right; + case WindowManager.DOCKED_TOP: + return bounds.bottom; + case WindowManager.DOCKED_RIGHT: + return bounds.left - dividerSize; + case WindowManager.DOCKED_BOTTOM: + return bounds.top - dividerSize; + default: + return 0; + } + } +} diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 11056d4fac97..0932e8998ce2 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -109,7 +109,8 @@ ifeq (true, $(HWUI_NEW_OPS)) BakedOpDispatcher.cpp \ BakedOpRenderer.cpp \ BakedOpState.cpp \ - OpReorderer.cpp \ + FrameReorderer.cpp \ + LayerReorderer.cpp \ RecordingCanvas.cpp hwui_cflags += -DHWUI_NEW_OPS @@ -236,8 +237,8 @@ LOCAL_SRC_FILES += \ ifeq (true, $(HWUI_NEW_OPS)) LOCAL_SRC_FILES += \ tests/unit/BakedOpStateTests.cpp \ - tests/unit/RecordingCanvasTests.cpp \ - tests/unit/OpReordererTests.cpp + tests/unit/FrameReordererTests.cpp \ + tests/unit/RecordingCanvasTests.cpp endif include $(BUILD_NATIVE_TEST) @@ -298,7 +299,7 @@ LOCAL_SRC_FILES += \ ifeq (true, $(HWUI_NEW_OPS)) LOCAL_SRC_FILES += \ - tests/microbench/OpReordererBench.cpp + tests/microbench/FrameReordererBench.cpp endif include $(BUILD_EXECUTABLE) diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp index 195d235fc7c1..23aca899a485 100644 --- a/libs/hwui/BakedOpDispatcher.cpp +++ b/libs/hwui/BakedOpDispatcher.cpp @@ -338,8 +338,8 @@ static void renderConvexPath(BakedOpRenderer& renderer, const BakedOpState& stat static void renderPathTexture(BakedOpRenderer& renderer, const BakedOpState& state, PathTexture& texture, const RecordedOp& op) { Rect dest(texture.width, texture.height); - dest.translate(texture.left + op.unmappedBounds.left - texture.offset, - texture.top + op.unmappedBounds.top - texture.offset); + dest.translate(texture.left - texture.offset, + texture.top - texture.offset); Glop glop; GlopBuilder(renderer.renderState(), renderer.caches(), &glop) .setRoundRectClipState(state.roundRectClipState) diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/FrameReorderer.cpp index cff4f3c47d60..4bfc0b413c56 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/FrameReorderer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "OpReorderer.h" +#include "FrameReorderer.h" #include "LayerUpdateQueue.h" #include "RenderNode.h" @@ -30,343 +30,7 @@ namespace android { namespace uirenderer { -class BatchBase { -public: - BatchBase(batchid_t batchId, BakedOpState* op, bool merging) - : mBatchId(batchId) - , mMerging(merging) { - mBounds = op->computedState.clippedBounds; - mOps.push_back(op); - } - - bool intersects(const Rect& rect) const { - if (!rect.intersects(mBounds)) return false; - - for (const BakedOpState* op : mOps) { - if (rect.intersects(op->computedState.clippedBounds)) { - return true; - } - } - return false; - } - - batchid_t getBatchId() const { return mBatchId; } - bool isMerging() const { return mMerging; } - - const std::vector<BakedOpState*>& getOps() const { return mOps; } - - void dump() const { - ALOGD(" Batch %p, id %d, merging %d, count %d, bounds " RECT_STRING, - this, mBatchId, mMerging, mOps.size(), RECT_ARGS(mBounds)); - } -protected: - batchid_t mBatchId; - Rect mBounds; - std::vector<BakedOpState*> mOps; - bool mMerging; -}; - -class OpBatch : public BatchBase { -public: - static void* operator new(size_t size, LinearAllocator& allocator) { - return allocator.alloc(size); - } - - OpBatch(batchid_t batchId, BakedOpState* op) - : BatchBase(batchId, op, false) { - } - - void batchOp(BakedOpState* op) { - mBounds.unionWith(op->computedState.clippedBounds); - mOps.push_back(op); - } -}; - -class MergingOpBatch : public BatchBase { -public: - static void* operator new(size_t size, LinearAllocator& allocator) { - return allocator.alloc(size); - } - - MergingOpBatch(batchid_t batchId, BakedOpState* op) - : BatchBase(batchId, op, true) - , mClipSideFlags(op->computedState.clipSideFlags) { - } - - /* - * Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds - * and clip side flags. Positive bounds delta means new bounds fit in old. - */ - static inline bool checkSide(const int currentFlags, const int newFlags, const int side, - float boundsDelta) { - bool currentClipExists = currentFlags & side; - bool newClipExists = newFlags & side; - - // if current is clipped, we must be able to fit new bounds in current - if (boundsDelta > 0 && currentClipExists) return false; - - // if new is clipped, we must be able to fit current bounds in new - if (boundsDelta < 0 && newClipExists) return false; - - return true; - } - - static bool paintIsDefault(const SkPaint& paint) { - return paint.getAlpha() == 255 - && paint.getColorFilter() == nullptr - && paint.getShader() == nullptr; - } - - static bool paintsAreEquivalent(const SkPaint& a, const SkPaint& b) { - return a.getAlpha() == b.getAlpha() - && a.getColorFilter() == b.getColorFilter() - && a.getShader() == b.getShader(); - } - - /* - * Checks if a (mergeable) op can be merged into this batch - * - * If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is - * important to consider all paint attributes used in the draw calls in deciding both a) if an - * op tries to merge at all, and b) if the op can merge with another set of ops - * - * False positives can lead to information from the paints of subsequent merged operations being - * dropped, so we make simplifying qualifications on the ops that can merge, per op type. - */ - bool canMergeWith(BakedOpState* op) const { - bool isTextBatch = getBatchId() == OpBatchType::Text - || getBatchId() == OpBatchType::ColorText; - - // Overlapping other operations is only allowed for text without shadow. For other ops, - // multiDraw isn't guaranteed to overdraw correctly - if (!isTextBatch || PaintUtils::hasTextShadow(op->op->paint)) { - if (intersects(op->computedState.clippedBounds)) return false; - } - - const BakedOpState* lhs = op; - const BakedOpState* rhs = mOps[0]; - - if (!MathUtils::areEqual(lhs->alpha, rhs->alpha)) return false; - - // Identical round rect clip state means both ops will clip in the same way, or not at all. - // As the state objects are const, we can compare their pointers to determine mergeability - if (lhs->roundRectClipState != rhs->roundRectClipState) return false; - if (lhs->projectionPathMask != rhs->projectionPathMask) return false; - - /* Clipping compatibility check - * - * Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its - * clip for that side. - */ - const int currentFlags = mClipSideFlags; - const int newFlags = op->computedState.clipSideFlags; - if (currentFlags != OpClipSideFlags::None || newFlags != OpClipSideFlags::None) { - const Rect& opBounds = op->computedState.clippedBounds; - float boundsDelta = mBounds.left - opBounds.left; - if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Left, boundsDelta)) return false; - boundsDelta = mBounds.top - opBounds.top; - if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Top, boundsDelta)) return false; - - // right and bottom delta calculation reversed to account for direction - boundsDelta = opBounds.right - mBounds.right; - if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Right, boundsDelta)) return false; - boundsDelta = opBounds.bottom - mBounds.bottom; - if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Bottom, boundsDelta)) return false; - } - - const SkPaint* newPaint = op->op->paint; - const SkPaint* oldPaint = mOps[0]->op->paint; - - if (newPaint == oldPaint) { - // if paints are equal, then modifiers + paint attribs don't need to be compared - return true; - } else if (newPaint && !oldPaint) { - return paintIsDefault(*newPaint); - } else if (!newPaint && oldPaint) { - return paintIsDefault(*oldPaint); - } - return paintsAreEquivalent(*newPaint, *oldPaint); - } - - void mergeOp(BakedOpState* op) { - mBounds.unionWith(op->computedState.clippedBounds); - mOps.push_back(op); - - // Because a new op must have passed canMergeWith(), we know it's passed the clipping compat - // check, and doesn't extend past a side of the clip that's in use by the merged batch. - // Therefore it's safe to simply always merge flags, and use the bounds as the clip rect. - mClipSideFlags |= op->computedState.clipSideFlags; - } - - int getClipSideFlags() const { return mClipSideFlags; } - const Rect& getClipRect() const { return mBounds; } - -private: - int mClipSideFlags; -}; - -OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height, - const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode) - : width(width) - , height(height) - , repaintRect(repaintRect) - , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr) - , beginLayerOp(beginLayerOp) - , renderNode(renderNode) - , viewportClip(Rect(width, height)) {} - -// iterate back toward target to see if anything drawn since should overlap the new op -// if no target, merging ops still iterate to find similar batch to insert after -void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds, - BatchBase** targetBatch, size_t* insertBatchIndex) const { - for (int i = mBatches.size() - 1; i >= 0; i--) { - BatchBase* overBatch = mBatches[i]; - - if (overBatch == *targetBatch) break; - - // TODO: also consider shader shared between batch types - if (batchId == overBatch->getBatchId()) { - *insertBatchIndex = i + 1; - if (!*targetBatch) break; // found insert position, quit - } - - if (overBatch->intersects(clippedBounds)) { - // NOTE: it may be possible to optimize for special cases where two operations - // of the same batch/paint could swap order, such as with a non-mergeable - // (clipped) and a mergeable text operation - *targetBatch = nullptr; - break; - } - } -} - -void OpReorderer::LayerReorderer::deferLayerClear(const Rect& rect) { - mClearRects.push_back(rect); -} - -void OpReorderer::LayerReorderer::flushLayerClears(LinearAllocator& allocator) { - if (CC_UNLIKELY(!mClearRects.empty())) { - const int vertCount = mClearRects.size() * 4; - // put the verts in the frame allocator, since - // 1) SimpleRectsOps needs verts, not rects - // 2) even if mClearRects stored verts, std::vectors will move their contents - Vertex* const verts = (Vertex*) allocator.alloc(vertCount * sizeof(Vertex)); - - Vertex* currentVert = verts; - Rect bounds = mClearRects[0]; - for (auto&& rect : mClearRects) { - bounds.unionWith(rect); - Vertex::set(currentVert++, rect.left, rect.top); - Vertex::set(currentVert++, rect.right, rect.top); - Vertex::set(currentVert++, rect.left, rect.bottom); - Vertex::set(currentVert++, rect.right, rect.bottom); - } - mClearRects.clear(); // discard rects before drawing so this method isn't reentrant - - // One or more unclipped saveLayers have been enqueued, with deferred clears. - // Flush all of these clears with a single draw - SkPaint* paint = allocator.create<SkPaint>(); - paint->setXfermodeMode(SkXfermode::kClear_Mode); - SimpleRectsOp* op = new (allocator) SimpleRectsOp(bounds, - Matrix4::identity(), nullptr, paint, - verts, vertCount); - BakedOpState* bakedState = BakedOpState::directConstruct(allocator, - &viewportClip, bounds, *op); - - - deferUnmergeableOp(allocator, bakedState, OpBatchType::Vertices); - } -} - -void OpReorderer::LayerReorderer::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); - } - - OpBatch* targetBatch = mBatchLookup[batchId]; - - size_t insertBatchIndex = mBatches.size(); - if (targetBatch) { - locateInsertIndex(batchId, op->computedState.clippedBounds, - (BatchBase**)(&targetBatch), &insertBatchIndex); - } - - if (targetBatch) { - targetBatch->batchOp(op); - } else { - // new non-merging batch - targetBatch = new (allocator) OpBatch(batchId, op); - mBatchLookup[batchId] = targetBatch; - mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch); - } -} - -void OpReorderer::LayerReorderer::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); - } - MergingOpBatch* targetBatch = nullptr; - - // Try to merge with any existing batch with same mergeId - auto getResult = mMergingBatchLookup[batchId].find(mergeId); - if (getResult != mMergingBatchLookup[batchId].end()) { - targetBatch = getResult->second; - if (!targetBatch->canMergeWith(op)) { - targetBatch = nullptr; - } - } - - size_t insertBatchIndex = mBatches.size(); - locateInsertIndex(batchId, op->computedState.clippedBounds, - (BatchBase**)(&targetBatch), &insertBatchIndex); - - if (targetBatch) { - targetBatch->mergeOp(op); - } else { - // new merging batch - targetBatch = new (allocator) MergingOpBatch(batchId, op); - mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch)); - - mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch); - } -} - -void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, - BakedOpReceiver* unmergedReceivers, MergedOpReceiver* mergedReceivers) const { - ATRACE_NAME("flush drawing commands"); - for (const BatchBase* batch : mBatches) { - size_t size = batch->getOps().size(); - if (size > 1 && batch->isMerging()) { - int opId = batch->getOps()[0]->op->opId; - const MergingOpBatch* mergingBatch = static_cast<const MergingOpBatch*>(batch); - MergedBakedOpList data = { - batch->getOps().data(), - size, - mergingBatch->getClipSideFlags(), - mergingBatch->getClipRect() - }; - mergedReceivers[opId](arg, data); - } else { - for (const BakedOpState* op : batch->getOps()) { - unmergedReceivers[op->op->opId](arg, *op); - } - } - } -} - -void OpReorderer::LayerReorderer::dump() const { - ALOGD("LayerReorderer %p, %ux%u buffer %p, blo %p, rn %p", - this, width, height, offscreenBuffer, beginLayerOp, renderNode); - for (const BatchBase* batch : mBatches) { - batch->dump(); - } -} - -OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip, +FrameReorderer::FrameReorderer(const LayerUpdateQueue& layers, const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight, const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter) : mCanvasState(*this) { @@ -413,11 +77,11 @@ OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip, } } -void OpReorderer::onViewportInitialized() {} +void FrameReorderer::onViewportInitialized() {} -void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} +void FrameReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} -void OpReorderer::deferNodePropsAndOps(RenderNode& node) { +void FrameReorderer::deferNodePropsAndOps(RenderNode& node) { const RenderProperties& properties = node.properties(); const Outline& outline = properties.getOutline(); if (properties.getAlpha() <= 0 @@ -549,7 +213,7 @@ static size_t findNonNegativeIndex(const V& zTranslatedNodes) { } template <typename V> -void OpReorderer::defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes) { +void FrameReorderer::defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes) { const int size = zTranslatedNodes.size(); if (size == 0 || (mode == ChildrenSelectMode::Negative&& zTranslatedNodes[0].key > 0.0f) @@ -599,7 +263,7 @@ void OpReorderer::defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedN } } -void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) { +void FrameReorderer::deferShadow(const RenderNodeOp& casterNodeOp) { auto& node = *casterNodeOp.renderNode; auto& properties = node.properties(); @@ -655,7 +319,7 @@ void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) { } } -void OpReorderer::deferProjectedChildren(const RenderNode& renderNode) { +void FrameReorderer::deferProjectedChildren(const RenderNode& renderNode) { const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath(); int count = mCanvasState.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); @@ -688,15 +352,15 @@ void OpReorderer::deferProjectedChildren(const RenderNode& renderNode) { } /** - * Used to define a list of lambdas referencing private OpReorderer::onXX::defer() methods. + * Used to define a list of lambdas referencing private FrameReorderer::onXX::defer() methods. * * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. - * E.g. a BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&) + * E.g. a BitmapOp op then would be dispatched to FrameReorderer::onBitmapOp(const BitmapOp&) */ #define OP_RECEIVER(Type) \ - [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.defer##Type(static_cast<const Type&>(op)); }, -void OpReorderer::deferNodeOps(const RenderNode& renderNode) { - typedef void (*OpDispatcher) (OpReorderer& reorderer, const RecordedOp& op); + [](FrameReorderer& reorderer, const RecordedOp& op) { reorderer.defer##Type(static_cast<const Type&>(op)); }, +void FrameReorderer::deferNodeOps(const RenderNode& renderNode) { + typedef void (*OpDispatcher) (FrameReorderer& reorderer, const RecordedOp& op); static OpDispatcher receivers[] = BUILD_DEFERRABLE_OP_LUT(OP_RECEIVER); // can't be null, since DL=null node rejection happens before deferNodePropsAndOps @@ -720,7 +384,7 @@ void OpReorderer::deferNodeOps(const RenderNode& renderNode) { } } -void OpReorderer::deferRenderNodeOpImpl(const RenderNodeOp& op) { +void FrameReorderer::deferRenderNodeOpImpl(const RenderNodeOp& op) { if (op.renderNode->nothingToDraw()) return; int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); @@ -735,7 +399,7 @@ void OpReorderer::deferRenderNodeOpImpl(const RenderNodeOp& op) { mCanvasState.restoreToCount(count); } -void OpReorderer::deferRenderNodeOp(const RenderNodeOp& op) { +void FrameReorderer::deferRenderNodeOp(const RenderNodeOp& op) { if (!op.skipInOrderDraw) { deferRenderNodeOpImpl(op); } @@ -745,7 +409,7 @@ void OpReorderer::deferRenderNodeOp(const RenderNodeOp& op) { * Defers an unmergeable, strokeable op, accounting correctly * for paint's style on the bounds being computed. */ -void OpReorderer::deferStrokeableOp(const RecordedOp& op, batchid_t batchId, +void FrameReorderer::deferStrokeableOp(const RecordedOp& op, batchid_t batchId, BakedOpState::StrokeBehavior strokeBehavior) { // Note: here we account for stroke when baking the op BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct( @@ -767,7 +431,7 @@ static batchid_t tessBatchId(const RecordedOp& op) { : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices); } -void OpReorderer::deferArcOp(const ArcOp& op) { +void FrameReorderer::deferArcOp(const ArcOp& op) { deferStrokeableOp(op, tessBatchId(op)); } @@ -776,7 +440,7 @@ static bool hasMergeableClip(const BakedOpState& state) { || state.computedState.clipState->mode == ClipMode::Rectangle; } -void OpReorderer::deferBitmapOp(const BitmapOp& op) { +void FrameReorderer::deferBitmapOp(const BitmapOp& op) { BakedOpState* bakedState = tryBakeOpState(op); if (!bakedState) return; // quick rejected @@ -796,19 +460,19 @@ void OpReorderer::deferBitmapOp(const BitmapOp& op) { } } -void OpReorderer::deferBitmapMeshOp(const BitmapMeshOp& op) { +void FrameReorderer::deferBitmapMeshOp(const BitmapMeshOp& op) { BakedOpState* bakedState = tryBakeOpState(op); if (!bakedState) return; // quick rejected currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); } -void OpReorderer::deferBitmapRectOp(const BitmapRectOp& op) { +void FrameReorderer::deferBitmapRectOp(const BitmapRectOp& op) { BakedOpState* bakedState = tryBakeOpState(op); if (!bakedState) return; // quick rejected currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); } -void OpReorderer::deferCirclePropsOp(const CirclePropsOp& op) { +void FrameReorderer::deferCirclePropsOp(const CirclePropsOp& op) { // allocate a temporary oval op (with mAllocator, so it persists until render), so the // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple. float x = *(op.x); @@ -823,22 +487,22 @@ void OpReorderer::deferCirclePropsOp(const CirclePropsOp& op) { deferOvalOp(*resolvedOp); } -void OpReorderer::deferFunctorOp(const FunctorOp& op) { +void FrameReorderer::deferFunctorOp(const FunctorOp& op) { BakedOpState* bakedState = tryBakeOpState(op); if (!bakedState) return; // quick rejected currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Functor); } -void OpReorderer::deferLinesOp(const LinesOp& op) { +void FrameReorderer::deferLinesOp(const LinesOp& op) { batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices; deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced); } -void OpReorderer::deferOvalOp(const OvalOp& op) { +void FrameReorderer::deferOvalOp(const OvalOp& op) { deferStrokeableOp(op, tessBatchId(op)); } -void OpReorderer::deferPatchOp(const PatchOp& op) { +void FrameReorderer::deferPatchOp(const PatchOp& op) { BakedOpState* bakedState = tryBakeOpState(op); if (!bakedState) return; // quick rejected @@ -856,24 +520,24 @@ void OpReorderer::deferPatchOp(const PatchOp& op) { } } -void OpReorderer::deferPathOp(const PathOp& op) { +void FrameReorderer::deferPathOp(const PathOp& op) { deferStrokeableOp(op, OpBatchType::Bitmap); } -void OpReorderer::deferPointsOp(const PointsOp& op) { +void FrameReorderer::deferPointsOp(const PointsOp& op) { batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices; deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced); } -void OpReorderer::deferRectOp(const RectOp& op) { +void FrameReorderer::deferRectOp(const RectOp& op) { deferStrokeableOp(op, tessBatchId(op)); } -void OpReorderer::deferRoundRectOp(const RoundRectOp& op) { +void FrameReorderer::deferRoundRectOp(const RoundRectOp& op) { deferStrokeableOp(op, tessBatchId(op)); } -void OpReorderer::deferRoundRectPropsOp(const RoundRectPropsOp& op) { +void FrameReorderer::deferRoundRectPropsOp(const RoundRectPropsOp& op) { // allocate a temporary round rect op (with mAllocator, so it persists until render), so the // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple. const RoundRectOp* resolvedOp = new (mAllocator) RoundRectOp( @@ -884,7 +548,7 @@ void OpReorderer::deferRoundRectPropsOp(const RoundRectPropsOp& op) { deferRoundRectOp(*resolvedOp); } -void OpReorderer::deferSimpleRectsOp(const SimpleRectsOp& op) { +void FrameReorderer::deferSimpleRectsOp(const SimpleRectsOp& op) { BakedOpState* bakedState = tryBakeOpState(op); if (!bakedState) return; // quick rejected currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices); @@ -895,7 +559,7 @@ static batchid_t textBatchId(const SkPaint& paint) { return paint.getColor() == SK_ColorBLACK ? OpBatchType::Text : OpBatchType::ColorText; } -void OpReorderer::deferTextOp(const TextOp& op) { +void FrameReorderer::deferTextOp(const TextOp& op) { BakedOpState* bakedState = tryBakeOpState(op); if (!bakedState) return; // quick rejected @@ -910,19 +574,19 @@ void OpReorderer::deferTextOp(const TextOp& op) { } } -void OpReorderer::deferTextOnPathOp(const TextOnPathOp& op) { +void FrameReorderer::deferTextOnPathOp(const TextOnPathOp& op) { BakedOpState* bakedState = tryBakeOpState(op); if (!bakedState) return; // quick rejected currentLayer().deferUnmergeableOp(mAllocator, bakedState, textBatchId(*(op.paint))); } -void OpReorderer::deferTextureLayerOp(const TextureLayerOp& op) { +void FrameReorderer::deferTextureLayerOp(const TextureLayerOp& op) { BakedOpState* bakedState = tryBakeOpState(op); if (!bakedState) return; // quick rejected currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::TextureLayer); } -void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, +void FrameReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, float contentTranslateX, float contentTranslateY, const Rect& repaintRect, const Vector3& lightCenter, @@ -941,7 +605,7 @@ void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, mLayerReorderers.emplace_back(layerWidth, layerHeight, repaintRect, beginLayerOp, renderNode); } -void OpReorderer::restoreForLayer() { +void FrameReorderer::restoreForLayer() { // restore canvas, and pop finished layer off of the stack mCanvasState.restore(); mLayerStack.pop_back(); @@ -949,7 +613,7 @@ void OpReorderer::restoreForLayer() { // TODO: defer time rejection (when bounds become empty) + tests // Option - just skip layers with no bounds at playback + defer? -void OpReorderer::deferBeginLayerOp(const BeginLayerOp& op) { +void FrameReorderer::deferBeginLayerOp(const BeginLayerOp& op) { uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth(); uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight(); @@ -994,7 +658,7 @@ void OpReorderer::deferBeginLayerOp(const BeginLayerOp& op) { &op, nullptr); } -void OpReorderer::deferEndLayerOp(const EndLayerOp& /* ignored */) { +void FrameReorderer::deferEndLayerOp(const EndLayerOp& /* ignored */) { const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp; int finishedLayerIndex = mLayerStack.back(); @@ -1022,7 +686,7 @@ void OpReorderer::deferEndLayerOp(const EndLayerOp& /* ignored */) { } } -void OpReorderer::deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp& op) { +void FrameReorderer::deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp& op) { Matrix4 boundsTransform(*(mCanvasState.currentSnapshot()->transform)); boundsTransform.multiply(op.localMatrix); @@ -1057,7 +721,7 @@ void OpReorderer::deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp& op) { currentLayer().activeUnclippedSaveLayers.push_back(bakedState); } -void OpReorderer::deferEndUnclippedLayerOp(const EndUnclippedLayerOp& /* ignored */) { +void FrameReorderer::deferEndUnclippedLayerOp(const EndUnclippedLayerOp& /* ignored */) { LOG_ALWAYS_FATAL_IF(currentLayer().activeUnclippedSaveLayers.empty(), "no layer to end!"); BakedOpState* copyFromLayerOp = currentLayer().activeUnclippedSaveLayers.back(); diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/FrameReorderer.h index 8d9d90be057c..562e6a1a3d37 100644 --- a/libs/hwui/OpReorderer.h +++ b/libs/hwui/FrameReorderer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,12 @@ * limitations under the License. */ -#ifndef ANDROID_HWUI_OP_REORDERER_H -#define ANDROID_HWUI_OP_REORDERER_H +#pragma once #include "BakedOpState.h" #include "CanvasState.h" #include "DisplayList.h" +#include "LayerReorderer.h" #include "RecordedOp.h" #include <vector> @@ -31,114 +31,34 @@ namespace android { namespace uirenderer { class BakedOpState; -class BatchBase; class LayerUpdateQueue; -class MergingOpBatch; class OffscreenBuffer; -class OpBatch; class Rect; -typedef int batchid_t; -typedef const void* mergeid_t; - -namespace OpBatchType { - enum { - Bitmap, - MergedPatch, - AlphaVertices, - Vertices, - AlphaMaskTexture, - Text, - ColorText, - Shadow, - TextureLayer, - Functor, - CopyToLayer, - CopyFromLayer, - - Count // must be last - }; -} - -class OpReorderer : public CanvasStateClient { - typedef void (*BakedOpReceiver)(void*, const BakedOpState&); - typedef void (*MergedOpReceiver)(void*, const MergedBakedOpList& opList); - - /** - * Stores the deferred render operations and state used to compute ordering - * for a single FBO/layer. - */ - class LayerReorderer { - public: - // Create LayerReorderer for Fbo0 - LayerReorderer(uint32_t width, uint32_t height, const Rect& repaintRect) - : LayerReorderer(width, height, repaintRect, nullptr, nullptr) {}; - - // Create LayerReorderer for an offscreen layer, where beginLayerOp is present for a - // saveLayer, renderNode is present for a HW layer. - LayerReorderer(uint32_t width, uint32_t height, - const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode); - - // iterate back toward target to see if anything drawn since should overlap the new op - // if no target, merging ops still iterate to find similar batch to insert after - void locateInsertIndex(int batchId, const Rect& clippedBounds, - BatchBase** targetBatch, size_t* insertBatchIndex) const; - - void deferUnmergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId); - - // insertion point of a new batch, will hopefully be immediately after similar batch - // (generally, should be similar shader) - void deferMergeableOp(LinearAllocator& allocator, - BakedOpState* op, batchid_t batchId, mergeid_t mergeId); - - void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers, MergedOpReceiver*) const; - - void deferLayerClear(const Rect& dstRect); - - bool empty() const { - return mBatches.empty(); - } - - void clear() { - mBatches.clear(); - } - - void dump() const; - - const uint32_t width; - const uint32_t height; - const Rect repaintRect; - OffscreenBuffer* offscreenBuffer; - const BeginLayerOp* beginLayerOp; - const RenderNode* renderNode; - const ClipRect viewportClip; - - // list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps - std::vector<BakedOpState*> activeUnclippedSaveLayers; - private: - void flushLayerClears(LinearAllocator& allocator); - - std::vector<BatchBase*> mBatches; - - /** - * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen - * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not - * collide, which avoids the need to resolve mergeid collisions. - */ - std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatchLookup[OpBatchType::Count]; - - // Maps batch ids to the most recent *non-merging* batch of that id - OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr }; - - std::vector<Rect> mClearRects; - }; - +/** + * Traverses all of the drawing commands from the layers and RenderNodes passed into it, preparing + * them to be rendered. + * + * Resolves final drawing state for each operation (including clip, alpha and matrix), and then + * reorder and merge each op as it is resolved for drawing efficiency. Each layer of content (either + * from the LayerUpdateQueue, or temporary layers created by saveLayer operations in the + * draw stream) will create different reorder contexts, each in its own LayerReorderer. + * + * Then the prepared or 'baked' drawing commands can be issued by calling the templated + * replayBakedOps() function, which will dispatch them (including any created merged op collections) + * to a Dispatcher and Renderer. See BakedOpDispatcher for how these baked drawing operations are + * resolved into Glops and rendered via BakedOpRenderer. + * + * This class is also the authoritative source for traversing RenderNodes, both for standard op + * traversal within a DisplayList, and for out of order RenderNode traversal for Z and projection. + */ +class FrameReorderer : public CanvasStateClient { public: - OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip, + FrameReorderer(const LayerUpdateQueue& layers, const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight, const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter); - virtual ~OpReorderer() {} + virtual ~FrameReorderer() {} /** * replayBakedOps() is templated based on what class will receive ops being replayed. @@ -253,7 +173,7 @@ private: BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined); /** - * Declares all OpReorderer::deferXXXXOp() methods for every RecordedOp type. + * Declares all FrameReorderer::deferXXXXOp() methods for every RecordedOp type. * * These private methods are called from within deferImpl to defer each individual op * type differently. @@ -287,5 +207,3 @@ private: }; // namespace uirenderer }; // namespace android - -#endif // ANDROID_HWUI_OP_REORDERER_H diff --git a/libs/hwui/LayerReorderer.cpp b/libs/hwui/LayerReorderer.cpp new file mode 100644 index 000000000000..9a17e9349e56 --- /dev/null +++ b/libs/hwui/LayerReorderer.cpp @@ -0,0 +1,367 @@ +/* + * 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 "LayerReorderer.h" + +#include "BakedOpState.h" +#include "RenderNode.h" +#include "utils/PaintUtils.h" +#include "utils/TraceUtils.h" + +#include <utils/TypeHelpers.h> + +namespace android { +namespace uirenderer { + +class BatchBase { +public: + BatchBase(batchid_t batchId, BakedOpState* op, bool merging) + : mBatchId(batchId) + , mMerging(merging) { + mBounds = op->computedState.clippedBounds; + mOps.push_back(op); + } + + bool intersects(const Rect& rect) const { + if (!rect.intersects(mBounds)) return false; + + for (const BakedOpState* op : mOps) { + if (rect.intersects(op->computedState.clippedBounds)) { + return true; + } + } + return false; + } + + batchid_t getBatchId() const { return mBatchId; } + bool isMerging() const { return mMerging; } + + const std::vector<BakedOpState*>& getOps() const { return mOps; } + + void dump() const { + ALOGD(" Batch %p, id %d, merging %d, count %d, bounds " RECT_STRING, + this, mBatchId, mMerging, mOps.size(), RECT_ARGS(mBounds)); + } +protected: + batchid_t mBatchId; + Rect mBounds; + std::vector<BakedOpState*> mOps; + bool mMerging; +}; + +class OpBatch : public BatchBase { +public: + static void* operator new(size_t size, LinearAllocator& allocator) { + return allocator.alloc(size); + } + + OpBatch(batchid_t batchId, BakedOpState* op) + : BatchBase(batchId, op, false) { + } + + void batchOp(BakedOpState* op) { + mBounds.unionWith(op->computedState.clippedBounds); + mOps.push_back(op); + } +}; + +class MergingOpBatch : public BatchBase { +public: + static void* operator new(size_t size, LinearAllocator& allocator) { + return allocator.alloc(size); + } + + MergingOpBatch(batchid_t batchId, BakedOpState* op) + : BatchBase(batchId, op, true) + , mClipSideFlags(op->computedState.clipSideFlags) { + } + + /* + * Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds + * and clip side flags. Positive bounds delta means new bounds fit in old. + */ + static inline bool checkSide(const int currentFlags, const int newFlags, const int side, + float boundsDelta) { + bool currentClipExists = currentFlags & side; + bool newClipExists = newFlags & side; + + // if current is clipped, we must be able to fit new bounds in current + if (boundsDelta > 0 && currentClipExists) return false; + + // if new is clipped, we must be able to fit current bounds in new + if (boundsDelta < 0 && newClipExists) return false; + + return true; + } + + static bool paintIsDefault(const SkPaint& paint) { + return paint.getAlpha() == 255 + && paint.getColorFilter() == nullptr + && paint.getShader() == nullptr; + } + + static bool paintsAreEquivalent(const SkPaint& a, const SkPaint& b) { + // Note: don't check color, since all currently mergeable ops can merge across colors + return a.getAlpha() == b.getAlpha() + && a.getColorFilter() == b.getColorFilter() + && a.getShader() == b.getShader(); + } + + /* + * Checks if a (mergeable) op can be merged into this batch + * + * If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is + * important to consider all paint attributes used in the draw calls in deciding both a) if an + * op tries to merge at all, and b) if the op can merge with another set of ops + * + * False positives can lead to information from the paints of subsequent merged operations being + * dropped, so we make simplifying qualifications on the ops that can merge, per op type. + */ + bool canMergeWith(BakedOpState* op) const { + bool isTextBatch = getBatchId() == OpBatchType::Text + || getBatchId() == OpBatchType::ColorText; + + // Overlapping other operations is only allowed for text without shadow. For other ops, + // multiDraw isn't guaranteed to overdraw correctly + if (!isTextBatch || PaintUtils::hasTextShadow(op->op->paint)) { + if (intersects(op->computedState.clippedBounds)) return false; + } + + const BakedOpState* lhs = op; + const BakedOpState* rhs = mOps[0]; + + if (!MathUtils::areEqual(lhs->alpha, rhs->alpha)) return false; + + // Identical round rect clip state means both ops will clip in the same way, or not at all. + // As the state objects are const, we can compare their pointers to determine mergeability + if (lhs->roundRectClipState != rhs->roundRectClipState) return false; + if (lhs->projectionPathMask != rhs->projectionPathMask) return false; + + /* Clipping compatibility check + * + * Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its + * clip for that side. + */ + const int currentFlags = mClipSideFlags; + const int newFlags = op->computedState.clipSideFlags; + if (currentFlags != OpClipSideFlags::None || newFlags != OpClipSideFlags::None) { + const Rect& opBounds = op->computedState.clippedBounds; + float boundsDelta = mBounds.left - opBounds.left; + if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Left, boundsDelta)) return false; + boundsDelta = mBounds.top - opBounds.top; + if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Top, boundsDelta)) return false; + + // right and bottom delta calculation reversed to account for direction + boundsDelta = opBounds.right - mBounds.right; + if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Right, boundsDelta)) return false; + boundsDelta = opBounds.bottom - mBounds.bottom; + if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Bottom, boundsDelta)) return false; + } + + const SkPaint* newPaint = op->op->paint; + const SkPaint* oldPaint = mOps[0]->op->paint; + + if (newPaint == oldPaint) { + // if paints are equal, then modifiers + paint attribs don't need to be compared + return true; + } else if (newPaint && !oldPaint) { + return paintIsDefault(*newPaint); + } else if (!newPaint && oldPaint) { + return paintIsDefault(*oldPaint); + } + return paintsAreEquivalent(*newPaint, *oldPaint); + } + + void mergeOp(BakedOpState* op) { + mBounds.unionWith(op->computedState.clippedBounds); + mOps.push_back(op); + + // Because a new op must have passed canMergeWith(), we know it's passed the clipping compat + // check, and doesn't extend past a side of the clip that's in use by the merged batch. + // Therefore it's safe to simply always merge flags, and use the bounds as the clip rect. + mClipSideFlags |= op->computedState.clipSideFlags; + } + + int getClipSideFlags() const { return mClipSideFlags; } + const Rect& getClipRect() const { return mBounds; } + +private: + int mClipSideFlags; +}; + +LayerReorderer::LayerReorderer(uint32_t width, uint32_t height, + const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode) + : width(width) + , height(height) + , repaintRect(repaintRect) + , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr) + , beginLayerOp(beginLayerOp) + , renderNode(renderNode) + , viewportClip(Rect(width, height)) {} + +// iterate back toward target to see if anything drawn since should overlap the new op +// if no target, merging ops still iterate to find similar batch to insert after +void LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds, + BatchBase** targetBatch, size_t* insertBatchIndex) const { + for (int i = mBatches.size() - 1; i >= 0; i--) { + BatchBase* overBatch = mBatches[i]; + + if (overBatch == *targetBatch) break; + + // TODO: also consider shader shared between batch types + if (batchId == overBatch->getBatchId()) { + *insertBatchIndex = i + 1; + if (!*targetBatch) break; // found insert position, quit + } + + if (overBatch->intersects(clippedBounds)) { + // NOTE: it may be possible to optimize for special cases where two operations + // of the same batch/paint could swap order, such as with a non-mergeable + // (clipped) and a mergeable text operation + *targetBatch = nullptr; + break; + } + } +} + +void LayerReorderer::deferLayerClear(const Rect& rect) { + mClearRects.push_back(rect); +} + +void LayerReorderer::flushLayerClears(LinearAllocator& allocator) { + if (CC_UNLIKELY(!mClearRects.empty())) { + const int vertCount = mClearRects.size() * 4; + // put the verts in the frame allocator, since + // 1) SimpleRectsOps needs verts, not rects + // 2) even if mClearRects stored verts, std::vectors will move their contents + Vertex* const verts = (Vertex*) allocator.alloc(vertCount * sizeof(Vertex)); + + Vertex* currentVert = verts; + Rect bounds = mClearRects[0]; + for (auto&& rect : mClearRects) { + bounds.unionWith(rect); + Vertex::set(currentVert++, rect.left, rect.top); + Vertex::set(currentVert++, rect.right, rect.top); + Vertex::set(currentVert++, rect.left, rect.bottom); + Vertex::set(currentVert++, rect.right, rect.bottom); + } + mClearRects.clear(); // discard rects before drawing so this method isn't reentrant + + // One or more unclipped saveLayers have been enqueued, with deferred clears. + // Flush all of these clears with a single draw + SkPaint* paint = allocator.create<SkPaint>(); + paint->setXfermodeMode(SkXfermode::kClear_Mode); + SimpleRectsOp* op = new (allocator) SimpleRectsOp(bounds, + Matrix4::identity(), nullptr, paint, + verts, vertCount); + BakedOpState* bakedState = BakedOpState::directConstruct(allocator, + &viewportClip, bounds, *op); + + + deferUnmergeableOp(allocator, bakedState, OpBatchType::Vertices); + } +} + +void LayerReorderer::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); + } + + OpBatch* targetBatch = mBatchLookup[batchId]; + + size_t insertBatchIndex = mBatches.size(); + if (targetBatch) { + locateInsertIndex(batchId, op->computedState.clippedBounds, + (BatchBase**)(&targetBatch), &insertBatchIndex); + } + + if (targetBatch) { + targetBatch->batchOp(op); + } else { + // new non-merging batch + targetBatch = new (allocator) OpBatch(batchId, op); + mBatchLookup[batchId] = targetBatch; + mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch); + } +} + +void LayerReorderer::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); + } + MergingOpBatch* targetBatch = nullptr; + + // Try to merge with any existing batch with same mergeId + auto getResult = mMergingBatchLookup[batchId].find(mergeId); + if (getResult != mMergingBatchLookup[batchId].end()) { + targetBatch = getResult->second; + if (!targetBatch->canMergeWith(op)) { + targetBatch = nullptr; + } + } + + size_t insertBatchIndex = mBatches.size(); + locateInsertIndex(batchId, op->computedState.clippedBounds, + (BatchBase**)(&targetBatch), &insertBatchIndex); + + if (targetBatch) { + targetBatch->mergeOp(op); + } else { + // new merging batch + targetBatch = new (allocator) MergingOpBatch(batchId, op); + mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch)); + + mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch); + } +} + +void LayerReorderer::replayBakedOpsImpl(void* arg, + BakedOpReceiver* unmergedReceivers, MergedOpReceiver* mergedReceivers) const { + ATRACE_NAME("flush drawing commands"); + for (const BatchBase* batch : mBatches) { + size_t size = batch->getOps().size(); + if (size > 1 && batch->isMerging()) { + int opId = batch->getOps()[0]->op->opId; + const MergingOpBatch* mergingBatch = static_cast<const MergingOpBatch*>(batch); + MergedBakedOpList data = { + batch->getOps().data(), + size, + mergingBatch->getClipSideFlags(), + mergingBatch->getClipRect() + }; + mergedReceivers[opId](arg, data); + } else { + for (const BakedOpState* op : batch->getOps()) { + unmergedReceivers[op->op->opId](arg, *op); + } + } + } +} + +void LayerReorderer::dump() const { + ALOGD("LayerReorderer %p, %ux%u buffer %p, blo %p, rn %p", + this, width, height, offscreenBuffer, beginLayerOp, renderNode); + for (const BatchBase* batch : mBatches) { + batch->dump(); + } +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/LayerReorderer.h b/libs/hwui/LayerReorderer.h new file mode 100644 index 000000000000..83cda812035d --- /dev/null +++ b/libs/hwui/LayerReorderer.h @@ -0,0 +1,135 @@ +/* + * 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. + */ + +#pragma once + +#include "ClipArea.h" +#include "Rect.h" + +#include <vector> +#include <unordered_map> + +struct SkRect; + +namespace android { +namespace uirenderer { + +class BakedOpState; +struct BeginLayerOp; +class BatchBase; +class LinearAllocator; +struct MergedBakedOpList; +class MergingOpBatch; +class OffscreenBuffer; +class OpBatch; +class RenderNode; + +typedef int batchid_t; +typedef const void* mergeid_t; + +namespace OpBatchType { + enum { + Bitmap, + MergedPatch, + AlphaVertices, + Vertices, + AlphaMaskTexture, + Text, + ColorText, + Shadow, + TextureLayer, + Functor, + CopyToLayer, + CopyFromLayer, + + Count // must be last + }; +} + +typedef void (*BakedOpReceiver)(void*, const BakedOpState&); +typedef void (*MergedOpReceiver)(void*, const MergedBakedOpList& opList); + +/** + * Stores the deferred render operations and state used to compute ordering + * for a single FBO/layer. + */ +class LayerReorderer { +public: + // Create LayerReorderer for Fbo0 + LayerReorderer(uint32_t width, uint32_t height, const Rect& repaintRect) + : LayerReorderer(width, height, repaintRect, nullptr, nullptr) {}; + + // Create LayerReorderer for an offscreen layer, where beginLayerOp is present for a + // saveLayer, renderNode is present for a HW layer. + LayerReorderer(uint32_t width, uint32_t height, + const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode); + + // iterate back toward target to see if anything drawn since should overlap the new op + // if no target, merging ops still iterate to find similar batch to insert after + void locateInsertIndex(int batchId, const Rect& clippedBounds, + BatchBase** targetBatch, size_t* insertBatchIndex) const; + + void deferUnmergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId); + + // insertion point of a new batch, will hopefully be immediately after similar batch + // (generally, should be similar shader) + void deferMergeableOp(LinearAllocator& allocator, + BakedOpState* op, batchid_t batchId, mergeid_t mergeId); + + void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers, MergedOpReceiver*) const; + + void deferLayerClear(const Rect& dstRect); + + bool empty() const { + return mBatches.empty(); + } + + void clear() { + mBatches.clear(); + } + + void dump() const; + + const uint32_t width; + const uint32_t height; + const Rect repaintRect; + OffscreenBuffer* offscreenBuffer; + const BeginLayerOp* beginLayerOp; + const RenderNode* renderNode; + const ClipRect viewportClip; + + // list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps + std::vector<BakedOpState*> activeUnclippedSaveLayers; +private: + void flushLayerClears(LinearAllocator& allocator); + + std::vector<BatchBase*> mBatches; + + /** + * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen + * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not + * collide, which avoids the need to resolve mergeid collisions. + */ + std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatchLookup[OpBatchType::Count]; + + // Maps batch ids to the most recent *non-merging* batch of that id + OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr }; + + std::vector<Rect> mClearRects; +}; + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index b6f50b111ab5..612cdfdc7185 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -87,7 +87,7 @@ class RenderNode; */ class RenderNode : public VirtualLightRefBase { friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties -friend class OpReorderer; +friend class FrameReorderer; public: enum DirtyPropertyMask { GENERIC = 1 << 1, diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 1af8f804e72a..fff8e0968ee6 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -31,7 +31,7 @@ #include "utils/TimeUtils.h" #if HWUI_NEW_OPS -#include "OpReorderer.h" +#include "FrameReorderer.h" #endif #include <cutils/properties.h> @@ -338,7 +338,7 @@ void CanvasContext::draw() { mEglManager.damageFrame(frame, dirty); #if HWUI_NEW_OPS - OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), + FrameReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), mRenderNodes, mLightCenter); mLayerUpdateQueue.clear(); BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), diff --git a/libs/hwui/tests/microbench/OpReordererBench.cpp b/libs/hwui/tests/microbench/FrameReordererBench.cpp index 6bfe5a9a1028..b4c9a3626597 100644 --- a/libs/hwui/tests/microbench/OpReordererBench.cpp +++ b/libs/hwui/tests/microbench/FrameReordererBench.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ #include "BakedOpState.h" #include "BakedOpDispatcher.h" #include "BakedOpRenderer.h" +#include "FrameReorderer.h" #include "LayerUpdateQueue.h" -#include "OpReorderer.h" #include "RecordedOp.h" #include "RecordingCanvas.h" #include "tests/common/TestContext.h" @@ -61,20 +61,20 @@ static std::vector<sp<RenderNode>> createTestNodeList() { return vec; } -BENCHMARK_NO_ARG(BM_OpReorderer_defer); -void BM_OpReorderer_defer::Run(int iters) { +BENCHMARK_NO_ARG(BM_FrameBuilder_defer); +void BM_FrameBuilder_defer::Run(int iters) { auto nodes = createTestNodeList(); StartBenchmarkTiming(); for (int i = 0; i < iters; i++) { - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, nodes, sLightCenter); MicroBench::DoNotOptimize(&reorderer); } StopBenchmarkTiming(); } -BENCHMARK_NO_ARG(BM_OpReorderer_deferAndRender); -void BM_OpReorderer_deferAndRender::Run(int iters) { +BENCHMARK_NO_ARG(BM_FrameBuilder_deferAndRender); +void BM_FrameBuilder_deferAndRender::Run(int iters) { TestUtils::runOnRenderThread([this, iters](RenderThread& thread) { auto nodes = createTestNodeList(); BakedOpRenderer::LightInfo lightInfo = {50.0f, 128, 128 }; @@ -84,7 +84,7 @@ void BM_OpReorderer_deferAndRender::Run(int iters) { StartBenchmarkTiming(); for (int i = 0; i < iters; i++) { - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, nodes, sLightCenter); BakedOpRenderer renderer(caches, renderState, true, lightInfo); @@ -117,7 +117,7 @@ static void benchDeferScene(testing::Benchmark& benchmark, int iters, const char auto nodes = getSyncedSceneNodes(sceneName); benchmark.StartBenchmarkTiming(); for (int i = 0; i < iters; i++) { - OpReorderer reorderer(sEmptyLayerUpdateQueue, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h, nodes, sLightCenter); MicroBench::DoNotOptimize(&reorderer); @@ -136,7 +136,7 @@ static void benchDeferAndRenderScene(testing::Benchmark& benchmark, benchmark.StartBenchmarkTiming(); for (int i = 0; i < iters; i++) { - OpReorderer reorderer(sEmptyLayerUpdateQueue, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h, nodes, sLightCenter); @@ -148,13 +148,13 @@ static void benchDeferAndRenderScene(testing::Benchmark& benchmark, }); } -BENCHMARK_NO_ARG(BM_OpReorderer_listview_defer); -void BM_OpReorderer_listview_defer::Run(int iters) { +BENCHMARK_NO_ARG(BM_FrameBuilder_listview_defer); +void BM_FrameBuilder_listview_defer::Run(int iters) { benchDeferScene(*this, iters, "listview"); } -BENCHMARK_NO_ARG(BM_OpReorderer_listview_deferAndRender); -void BM_OpReorderer_listview_deferAndRender::Run(int iters) { +BENCHMARK_NO_ARG(BM_FrameBuilder_listview_deferAndRender); +void BM_FrameBuilder_listview_deferAndRender::Run(int iters) { benchDeferAndRenderScene(*this, iters, "listview"); } diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/FrameReordererTests.cpp index 701e4460c35f..9d2eb98a011d 100644 --- a/libs/hwui/tests/unit/OpReordererTests.cpp +++ b/libs/hwui/tests/unit/FrameReordererTests.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,8 @@ #include <BakedOpState.h> #include <DeferredLayerUpdater.h> +#include <FrameReorderer.h> #include <LayerUpdateQueue.h> -#include <OpReorderer.h> #include <RecordedOp.h> #include <RecordingCanvas.h> #include <tests/common/TestUtils.h> @@ -113,7 +113,7 @@ public: class FailRenderer : public TestRendererBase {}; -TEST(OpReorderer, simple) { +TEST(FrameReorderer, simple) { class SimpleTestRenderer : public TestRendererBase { public: void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { @@ -138,14 +138,14 @@ TEST(OpReorderer, simple) { canvas.drawRect(0, 0, 100, 200, SkPaint()); canvas.drawBitmap(bitmap, 10, 10, nullptr); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, createSyncedNodeList(node), sLightCenter); SimpleTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end } -TEST(OpReorderer, simpleStroke) { +TEST(FrameReorderer, simpleStroke) { class SimpleStrokeTestRenderer : public TestRendererBase { public: void onPointsOp(const PointsOp& op, const BakedOpState& state) override { @@ -164,14 +164,14 @@ TEST(OpReorderer, simpleStroke) { strokedPaint.setStrokeWidth(10); canvas.drawPoint(50, 50, strokedPaint); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, createSyncedNodeList(node), sLightCenter); SimpleStrokeTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(1, renderer.getIndex()); } -TEST(OpReorderer, simpleRejection) { +TEST(FrameReorderer, simpleRejection) { auto node = TestUtils::createNode(0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); @@ -179,14 +179,14 @@ TEST(OpReorderer, simpleRejection) { canvas.drawRect(0, 0, 400, 400, SkPaint()); canvas.restore(); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(node), sLightCenter); FailRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); } -TEST(OpReorderer, simpleBatching) { +TEST(FrameReorderer, simpleBatching) { const int LOOPS = 5; class SimpleBatchingTestRenderer : public TestRendererBase { public: @@ -214,7 +214,7 @@ TEST(OpReorderer, simpleBatching) { canvas.restore(); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(node), sLightCenter); SimpleBatchingTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -222,7 +222,7 @@ TEST(OpReorderer, simpleBatching) { << "Expect number of ops = 2 * loop count"; } -TEST(OpReorderer, clippedMerging) { +TEST(FrameReorderer, clippedMerging) { class ClippedMergingTestRenderer : public TestRendererBase { public: void onMergedBitmapOps(const MergedBakedOpList& opList) override { @@ -255,14 +255,14 @@ TEST(OpReorderer, clippedMerging) { canvas.drawBitmap(bitmap, 40, 70, nullptr); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, createSyncedNodeList(node), sLightCenter); ClippedMergingTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(4, renderer.getIndex()); } -TEST(OpReorderer, textMerging) { +TEST(FrameReorderer, textMerging) { class TextMergingTestRenderer : public TestRendererBase { public: void onMergedTextOps(const MergedBakedOpList& opList) override { @@ -283,14 +283,14 @@ TEST(OpReorderer, textMerging) { TestUtils::drawTextToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped TestUtils::drawTextToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400, createSyncedNodeList(node), sLightCenter); TextMergingTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops"; } -TEST(OpReorderer, textStrikethrough) { +TEST(FrameReorderer, textStrikethrough) { const int LOOPS = 5; class TextStrikethroughTestRenderer : public TestRendererBase { public: @@ -314,7 +314,7 @@ TEST(OpReorderer, textStrikethrough) { TestUtils::drawTextToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1)); } }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 2000), 200, 2000, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 2000), 200, 2000, createSyncedNodeList(node), sLightCenter); TextStrikethroughTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -322,7 +322,7 @@ TEST(OpReorderer, textStrikethrough) { << "Expect number of ops = 2 * loop count"; } -RENDERTHREAD_TEST(OpReorderer, textureLayer) { +RENDERTHREAD_TEST(FrameReorderer, textureLayer) { class TextureLayerTestRenderer : public TestRendererBase { public: void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override { @@ -348,14 +348,14 @@ RENDERTHREAD_TEST(OpReorderer, textureLayer) { canvas.drawLayer(layerUpdater.get()); canvas.restore(); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(node), sLightCenter); TextureLayerTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(1, renderer.getIndex()); } -TEST(OpReorderer, renderNode) { +TEST(FrameReorderer, renderNode) { class RenderNodeTestRenderer : public TestRendererBase { public: void onRectOp(const RectOp& op, const BakedOpState& state) override { @@ -393,13 +393,13 @@ TEST(OpReorderer, renderNode) { canvas.restore(); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(parent), sLightCenter); RenderNodeTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); } -TEST(OpReorderer, clipped) { +TEST(FrameReorderer, clipped) { class ClippedTestRenderer : public TestRendererBase { public: void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { @@ -416,14 +416,14 @@ TEST(OpReorderer, clipped) { canvas.drawBitmap(bitmap, 0, 0, nullptr); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver 200, 200, createSyncedNodeList(node), sLightCenter); ClippedTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); } -TEST(OpReorderer, saveLayer_simple) { +TEST(FrameReorderer, saveLayer_simple) { class SaveLayerSimpleTestRenderer : public TestRendererBase { public: OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { @@ -459,14 +459,14 @@ TEST(OpReorderer, saveLayer_simple) { canvas.drawRect(10, 10, 190, 190, SkPaint()); canvas.restore(); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(node), sLightCenter); SaveLayerSimpleTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(4, renderer.getIndex()); } -TEST(OpReorderer, saveLayer_nested) { +TEST(FrameReorderer, saveLayer_nested) { /* saveLayer1 { rect1, saveLayer2 { rect2 } } will play back as: * - startTemporaryLayer2, rect2 endLayer2 * - startTemporaryLayer1, rect1, drawLayer2, endLayer1 @@ -531,14 +531,14 @@ TEST(OpReorderer, saveLayer_nested) { canvas.restore(); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(800, 800), 800, 800, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(800, 800), 800, 800, createSyncedNodeList(node), sLightCenter); SaveLayerNestedTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(10, renderer.getIndex()); } -TEST(OpReorderer, saveLayer_contentRejection) { +TEST(FrameReorderer, saveLayer_contentRejection) { auto node = TestUtils::createNode(0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); @@ -551,7 +551,7 @@ TEST(OpReorderer, saveLayer_contentRejection) { canvas.restore(); canvas.restore(); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(node), sLightCenter); FailRenderer renderer; @@ -559,7 +559,7 @@ TEST(OpReorderer, saveLayer_contentRejection) { reorderer.replayBakedOps<TestDispatcher>(renderer); } -TEST(OpReorderer, saveLayerUnclipped_simple) { +TEST(FrameReorderer, saveLayerUnclipped_simple) { class SaveLayerUnclippedSimpleTestRenderer : public TestRendererBase { public: void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override { @@ -594,14 +594,14 @@ TEST(OpReorderer, saveLayerUnclipped_simple) { canvas.drawRect(0, 0, 200, 200, SkPaint()); canvas.restore(); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(node), sLightCenter); SaveLayerUnclippedSimpleTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(4, renderer.getIndex()); } -TEST(OpReorderer, saveLayerUnclipped_mergedClears) { +TEST(FrameReorderer, saveLayerUnclipped_mergedClears) { class SaveLayerUnclippedMergedClearsTestRenderer : public TestRendererBase { public: void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override { @@ -648,7 +648,7 @@ TEST(OpReorderer, saveLayerUnclipped_mergedClears) { canvas.drawRect(0, 0, 100, 100, SkPaint()); canvas.restoreToCount(restoreTo); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(node), sLightCenter); SaveLayerUnclippedMergedClearsTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -660,7 +660,7 @@ TEST(OpReorderer, saveLayerUnclipped_mergedClears) { * - startTemporaryLayer, onCopyToLayer, onSimpleRects, onRect, onCopyFromLayer, endLayer * - startFrame, onCopyToLayer, onSimpleRects, drawLayer, onCopyFromLayer, endframe */ -TEST(OpReorderer, saveLayerUnclipped_complex) { +TEST(FrameReorderer, saveLayerUnclipped_complex) { class SaveLayerUnclippedComplexTestRenderer : public TestRendererBase { public: OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) { @@ -710,14 +710,14 @@ TEST(OpReorderer, saveLayerUnclipped_complex) { canvas.restore(); canvas.restore(); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(600, 600), 600, 600, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(600, 600), 600, 600, createSyncedNodeList(node), sLightCenter); SaveLayerUnclippedComplexTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(12, renderer.getIndex()); } -RENDERTHREAD_TEST(OpReorderer, hwLayer_simple) { +RENDERTHREAD_TEST(FrameReorderer, hwLayer_simple) { class HwLayerSimpleTestRenderer : public TestRendererBase { public: void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { @@ -768,7 +768,7 @@ RENDERTHREAD_TEST(OpReorderer, hwLayer_simple) { LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75)); - OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + FrameReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, syncedNodeList, sLightCenter); HwLayerSimpleTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -778,7 +778,7 @@ RENDERTHREAD_TEST(OpReorderer, hwLayer_simple) { *layerHandle = nullptr; } -RENDERTHREAD_TEST(OpReorderer, hwLayer_complex) { +RENDERTHREAD_TEST(FrameReorderer, hwLayer_complex) { /* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as: * - startRepaintLayer(child), rect(grey), endLayer * - startTemporaryLayer, drawLayer(child), endLayer @@ -869,7 +869,7 @@ RENDERTHREAD_TEST(OpReorderer, hwLayer_complex) { layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100)); layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200)); - OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + FrameReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, syncedList, sLightCenter); HwLayerComplexTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -894,7 +894,7 @@ static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, node->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z); canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership } -TEST(OpReorderer, zReorder) { +TEST(FrameReorderer, zReorder) { class ZReorderTestRenderer : public TestRendererBase { public: void onRectOp(const RectOp& op, const BakedOpState& state) override { @@ -918,14 +918,14 @@ TEST(OpReorderer, zReorder) { drawOrderedRect(&canvas, 8); drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, createSyncedNodeList(parent), sLightCenter); ZReorderTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(10, renderer.getIndex()); }; -TEST(OpReorderer, projectionReorder) { +TEST(FrameReorderer, projectionReorder) { static const int scrollX = 5; static const int scrollY = 10; class ProjectionReorderTestRenderer : public TestRendererBase { @@ -1001,7 +1001,7 @@ TEST(OpReorderer, projectionReorder) { canvas.restore(); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, createSyncedNodeList(parent), sLightCenter); ProjectionReorderTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -1020,7 +1020,7 @@ static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) { }); } -TEST(OpReorderer, shadow) { +TEST(FrameReorderer, shadow) { class ShadowTestRenderer : public TestRendererBase { public: void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { @@ -1044,14 +1044,14 @@ TEST(OpReorderer, shadow) { canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(parent), sLightCenter); ShadowTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(2, renderer.getIndex()); } -TEST(OpReorderer, shadowSaveLayer) { +TEST(FrameReorderer, shadowSaveLayer) { class ShadowSaveLayerTestRenderer : public TestRendererBase { public: OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { @@ -1085,14 +1085,14 @@ TEST(OpReorderer, shadowSaveLayer) { canvas.restoreToCount(count); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(parent), (Vector3) { 100, 100, 100 }); ShadowSaveLayerTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(5, renderer.getIndex()); } -RENDERTHREAD_TEST(OpReorderer, shadowHwLayer) { +RENDERTHREAD_TEST(FrameReorderer, shadowHwLayer) { class ShadowHwLayerTestRenderer : public TestRendererBase { public: void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { @@ -1135,7 +1135,7 @@ RENDERTHREAD_TEST(OpReorderer, shadowHwLayer) { auto syncedList = createSyncedNodeList(parent); LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100)); - OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + FrameReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, syncedList, (Vector3) { 100, 100, 100 }); ShadowHwLayerTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -1145,7 +1145,7 @@ RENDERTHREAD_TEST(OpReorderer, shadowHwLayer) { *layerHandle = nullptr; } -TEST(OpReorderer, shadowLayering) { +TEST(FrameReorderer, shadowLayering) { class ShadowLayeringTestRenderer : public TestRendererBase { public: void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { @@ -1164,7 +1164,7 @@ TEST(OpReorderer, shadowLayering) { canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get()); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(parent), sLightCenter); ShadowLayeringTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -1192,14 +1192,14 @@ static void testProperty(std::function<void(RenderProperties&)> propSetupCallbac canvas.drawRect(0, 0, 100, 100, paint); }); - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200, + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200, createSyncedNodeList(node), sLightCenter); PropertyTestRenderer renderer(opValidateCallback); reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op"; } -TEST(OpReorderer, renderPropOverlappingRenderingAlpha) { +TEST(FrameReorderer, renderPropOverlappingRenderingAlpha) { testProperty([](RenderProperties& properties) { properties.setAlpha(0.5f); properties.setHasOverlappingRendering(false); @@ -1208,7 +1208,7 @@ TEST(OpReorderer, renderPropOverlappingRenderingAlpha) { }); } -TEST(OpReorderer, renderPropClipping) { +TEST(FrameReorderer, renderPropClipping) { testProperty([](RenderProperties& properties) { properties.setClipToBounds(true); properties.setClipBounds(Rect(10, 20, 300, 400)); @@ -1218,7 +1218,7 @@ TEST(OpReorderer, renderPropClipping) { }); } -TEST(OpReorderer, renderPropRevealClip) { +TEST(FrameReorderer, renderPropRevealClip) { testProperty([](RenderProperties& properties) { properties.mutableRevealClip().set(true, 50, 50, 25); }, [](const RectOp& op, const BakedOpState& state) { @@ -1229,7 +1229,7 @@ TEST(OpReorderer, renderPropRevealClip) { }); } -TEST(OpReorderer, renderPropOutlineClip) { +TEST(FrameReorderer, renderPropOutlineClip) { testProperty([](RenderProperties& properties) { properties.mutableOutline().setShouldClip(true); properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f); @@ -1241,7 +1241,7 @@ TEST(OpReorderer, renderPropOutlineClip) { }); } -TEST(OpReorderer, renderPropTransform) { +TEST(FrameReorderer, renderPropTransform) { testProperty([](RenderProperties& properties) { properties.setLeftTopRightBottom(10, 10, 110, 110); @@ -1334,7 +1334,7 @@ void testSaveLayerAlphaClip(SaveLayerAlphaData* outObservedData, }); auto nodes = createSyncedNodeList(node); // sync before querying height - OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes, sLightCenter); + FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes, sLightCenter); SaveLayerAlphaClipTestRenderer renderer(outObservedData); reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -1342,7 +1342,7 @@ void testSaveLayerAlphaClip(SaveLayerAlphaData* outObservedData, ASSERT_EQ(4, renderer.getIndex()) << "Test must trigger saveLayer alpha behavior."; } -TEST(OpReorderer, renderPropSaveLayerAlphaClipBig) { +TEST(FrameReorderer, renderPropSaveLayerAlphaClipBig) { SaveLayerAlphaData observedData; testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { properties.setTranslationX(10); // offset rendering content @@ -1358,7 +1358,7 @@ TEST(OpReorderer, renderPropSaveLayerAlphaClipBig) { << "expect content to be translated as part of being clipped"; } -TEST(OpReorderer, renderPropSaveLayerAlphaRotate) { +TEST(FrameReorderer, renderPropSaveLayerAlphaRotate) { SaveLayerAlphaData observedData; testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { // Translate and rotate the view so that the only visible part is the top left corner of @@ -1377,7 +1377,7 @@ TEST(OpReorderer, renderPropSaveLayerAlphaRotate) { EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix); } -TEST(OpReorderer, renderPropSaveLayerAlphaScale) { +TEST(FrameReorderer, renderPropSaveLayerAlphaScale) { SaveLayerAlphaData observedData; testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { properties.setPivotX(0); diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml index 7e0649be722d..a3cfde825f47 100644 --- a/packages/DocumentsUI/res/menu/activity.xml +++ b/packages/DocumentsUI/res/menu/activity.xml @@ -15,11 +15,19 @@ --> <menu xmlns:android="http://schemas.android.com/apk/res/android"> +<!-- showAsAction flag impacts the behavior of SearchView. + When set to collapseActionView, collapsing SearchView to icon is the + default behavior. It would fit UX, however after expanding SearchView is + shown on the left site of the toolbar (replacing title). Since no way to + prevent this behavior was found, the flag is set to always. SearchView is + always visible by default and it is being collapse manually by calling + setIconified() method +--> <item android:id="@+id/menu_search" android:title="@string/menu_search" android:icon="@drawable/ic_menu_search" - android:showAsAction="always|collapseActionView" + android:showAsAction="always" android:actionViewClass="android.widget.SearchView" android:imeOptions="actionSearch" /> <item diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml index 153c673caf56..c868d340e901 100644 --- a/packages/DocumentsUI/res/values/colors.xml +++ b/packages/DocumentsUI/res/values/colors.xml @@ -33,4 +33,6 @@ <color name="item_doc_background">#fffafafa</color> <color name="item_doc_background_selected">#ffe0f2f1</color> + <color name="menu_search_background">#ff676f74</color> + </resources> diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java index 7f710fc58181..180a48eaca99 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java @@ -38,12 +38,14 @@ import android.provider.DocumentsContract; import android.provider.DocumentsContract.Root; import android.support.annotation.LayoutRes; import android.support.annotation.Nullable; +import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; -import android.view.MenuItem.OnActionExpandListener; import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnFocusChangeListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; @@ -218,6 +220,7 @@ public abstract class BaseActivity extends Activity { case R.id.menu_advanced: case R.id.menu_file_size: case R.id.menu_new_window: + case R.id.menu_search: break; default: item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); @@ -318,6 +321,8 @@ public abstract class BaseActivity extends Activity { * the (abstract) directoryChanged method will be called. * @param anim */ + // TODO: Refactor the usage of the method - now it is called not only when the directory + // changed, but also to refresh the content of the directory while searching final void onCurrentDirectoryChanged(int anim) { mDirectoryContainer.setDrawDisappearingFirst(anim == ANIM_DOWN); onDirectoryChanged(anim); @@ -328,7 +333,11 @@ public abstract class BaseActivity extends Activity { } updateActionBar(); - invalidateOptionsMenu(); + + // Prevents searchView from being recreated while searching + if (!mSearchManager.isSearching()) { + invalidateOptionsMenu(); + } } final List<String> getExcludedAuthorities() { @@ -720,7 +729,7 @@ public abstract class BaseActivity extends Activity { * Facade over the various search parts in the menu. */ final class SearchManager implements - SearchView.OnCloseListener, OnActionExpandListener, OnQueryTextListener, + SearchView.OnCloseListener, OnQueryTextListener, OnClickListener, OnFocusChangeListener, DocumentsToolBar.OnActionViewCollapsedListener { private boolean mSearchExpanded; @@ -738,9 +747,10 @@ public abstract class BaseActivity extends Activity { mView = (SearchView) mMenu.getActionView(); mActionBar.setOnActionViewCollapsedListener(this); - mMenu.setOnActionExpandListener(this); mView.setOnQueryTextListener(this); mView.setOnCloseListener(this); + mView.setOnSearchClickListener(this); + mView.setOnQueryTextFocusChangeListener(this); } /** @@ -793,19 +803,13 @@ public abstract class BaseActivity extends Activity { * search currently. */ boolean cancelSearch() { - boolean collapsed = false; - boolean closed = false; - - if (mActionBar.hasExpandedActionView()) { - mActionBar.collapseActionView(); - collapsed = true; - } - if (isExpanded() || isSearching()) { - onClose(); - closed = true; + // If the query string is not empty search view won't get iconified + mView.setQuery("", false); + mView.setIconified(true); + return true; } - return collapsed || closed; + return false; } boolean isSearching() { @@ -816,6 +820,11 @@ public abstract class BaseActivity extends Activity { return mSearchExpanded; } + /** + * Clears the search. + * @return True if the default behavior of clearing/dismissing SearchView should be + * overridden. False otherwise. + */ @Override public boolean onClose() { mSearchExpanded = false; @@ -824,33 +833,33 @@ public abstract class BaseActivity extends Activity { return false; } - mState.currentSearch = null; - onCurrentDirectoryChanged(ANIM_NONE); + mView.setBackgroundColor( + getResources().getColor(android.R.color.transparent, null)); + + // Refresh the directory if a search was done + if(mState.currentSearch != null) { + mState.currentSearch = null; + onCurrentDirectoryChanged(ANIM_NONE); + } + return false; } + /** + * Sets mSearchExpanded. + * Called when search icon is clicked to start search. + * Used to detect when the view expanded instead of onMenuItemActionExpand, because + * SearchView has showAsAction set to always and onMenuItemAction* methods are not called. + */ @Override - public boolean onMenuItemActionExpand(MenuItem item) { + public void onClick (View v) { mSearchExpanded = true; - updateActionBar(); - return true; - } - - @Override - public boolean onMenuItemActionCollapse(MenuItem item) { - mSearchExpanded = false; - if (mIgnoreNextCollapse) { - mIgnoreNextCollapse = false; - return true; - } - mState.currentSearch = null; - onCurrentDirectoryChanged(ANIM_NONE); - return true; + mView.setBackgroundColor( + getResources().getColor(R.color.menu_search_background, null)); } @Override public boolean onQueryTextSubmit(String query) { - mSearchExpanded = true; mState.currentSearch = query; mView.clearFocus(); onCurrentDirectoryChanged(ANIM_NONE); @@ -863,6 +872,18 @@ public abstract class BaseActivity extends Activity { } @Override + public void onFocusChange(View v, boolean hasFocus) { + if(!hasFocus) { + if(mState.currentSearch == null) { + mView.setIconified(true); + } + else if(TextUtils.isEmpty(mView.getQuery())) { + cancelSearch(); + } + } + } + + @Override public void onActionViewCollapsed() { updateActionBar(); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index ca8ef2e65bcb..223af896ae23 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -308,8 +308,10 @@ public class DocumentsActivity extends BaseActivity { mSearchManager.showMenu(!picking); // No display options in recent directories - grid.setVisible(!(picking && recents)); - list.setVisible(!(picking && recents)); + if (picking && recents) { + grid.setVisible(false); + list.setVisible(false); + } fileSize.setVisible(fileSize.isVisible() && !picking); settings.setVisible(false); diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index 84ab85e35dc0..898713f75e62 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -407,7 +407,6 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi state.derivedMode = result.mode; } state.derivedSortOrder = result.sortOrder; - ((BaseActivity) context).onStateChanged(); updateDisplayState(); diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index 109cf4726101..824d10a3aa19 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -28,7 +28,6 @@ import android.graphics.Rect; import android.graphics.Region.Op; import android.hardware.display.DisplayManager; import android.util.AttributeSet; -import android.util.MathUtils; import android.view.Display; import android.view.DisplayInfo; import android.view.MotionEvent; @@ -39,7 +38,6 @@ import android.view.View.OnTouchListener; import android.view.ViewConfiguration; import android.view.ViewTreeObserver.InternalInsetsInfo; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; -import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; import android.view.animation.AnimationUtils; @@ -48,8 +46,10 @@ import android.view.animation.PathInterpolator; import android.widget.FrameLayout; import android.widget.ImageButton; +import com.android.internal.policy.DividerSnapAlgorithm; +import com.android.internal.policy.DockedDividerUtils; import com.android.systemui.R; -import com.android.systemui.stackdivider.DividerSnapAlgorithm.SnapTarget; +import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; import com.android.systemui.statusbar.FlingAnimationUtils; import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW; @@ -167,7 +167,8 @@ public class DividerView extends FrameLayout implements OnTouchListener, public boolean startDragging(boolean animate) { mHandle.setTouching(true, animate); mDockSide = mWindowManagerProxy.getDockSide(); - mSnapAlgorithm = new DividerSnapAlgorithm(getContext(), mFlingAnimationUtils, mDisplayWidth, + mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), + mFlingAnimationUtils.getMinVelocityPxPerSecond(), mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets); if (mDockSide != WindowManager.DOCKED_INVALID) { mWindowManagerProxy.setResizing(true); @@ -362,36 +363,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, return mStartPosition + touchY - mStartY; } - public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) { - outRect.set(0, 0, mDisplayWidth, mDisplayHeight); - switch (dockSide) { - case WindowManager.DOCKED_LEFT: - outRect.right = position; - break; - case WindowManager.DOCKED_TOP: - outRect.bottom = position; - break; - case WindowManager.DOCKED_RIGHT: - outRect.left = position + mDividerWindowWidth - 2 * mDividerInsets; - break; - case WindowManager.DOCKED_BOTTOM: - outRect.top = position + mDividerWindowWidth - 2 * mDividerInsets; - break; - } - if (outRect.left > outRect.right) { - outRect.left = outRect.right; - } - if (outRect.top > outRect.bottom) { - outRect.top = outRect.bottom; - } - if (outRect.right < outRect.left) { - outRect.right = outRect.left; - } - if (outRect.bottom < outRect.top) { - outRect.bottom = outRect.top; - } - } - private int invertDockSide(int dockSide) { switch (dockSide) { case WindowManager.DOCKED_LEFT: @@ -421,6 +392,11 @@ public class DividerView extends FrameLayout implements OnTouchListener, containingRect.right, containingRect.bottom); } + public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) { + DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth, + mDisplayHeight, mDividerSize); + } + public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) { calculateBoundsForPosition(position, mDockSide, mDockedRect); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java index fb2bc175b829..e5b4f4d0369d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java @@ -29,8 +29,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.stackdivider.Divider; -import com.android.systemui.stackdivider.DividerSnapAlgorithm.SnapTarget; -import com.android.systemui.stackdivider.DividerView; +import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; import com.android.systemui.tuner.TunerService; import static android.view.WindowManager.*; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f21eba1460ec..a4b13ed96e00 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -515,9 +515,9 @@ public final class ActivityManagerService extends ActivityManagerNative private Installer mInstaller; /** Run all ActivityStacks through this */ - ActivityStackSupervisor mStackSupervisor; + final ActivityStackSupervisor mStackSupervisor; - ActivityStarter mActivityStarter; + final ActivityStarter mActivityStarter; /** Task stack change listeners. */ private RemoteCallbackList<ITaskStackListener> mTaskStackListeners = @@ -3643,11 +3643,9 @@ public final class ActivityManagerService extends ActivityManagerNative return false; } Intent intent = getHomeIntent(); - ActivityInfo aInfo = - resolveActivityInfo(intent, STOCK_PM_FLAGS, userId); + ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId); if (aInfo != null) { - intent.setComponent(new ComponentName( - aInfo.applicationInfo.packageName, aInfo.name)); + intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name)); // Don't do this if the home app is currently being // instrumented. aInfo = new ActivityInfo(aInfo); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index e123dbd3d84a..c44b4cfa7b26 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -3368,9 +3368,9 @@ final class ActivityStack { try { ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo( destIntent.getComponent(), 0, srec.userId); - int res = mService.mActivityStarter.startActivityLocked(srec.app.thread, destIntent, - null /*ephemeralIntent*/, null, aInfo, null /*rInfo*/, null, null, - parent.appToken, null, 0, -1, parent.launchedFromUid, + int res = mService.mActivityStarter.startActivityLocked(srec.app.thread, + destIntent, null /*ephemeralIntent*/, null, aInfo, null /*rInfo*/, null, + null, parent.appToken, null, 0, -1, parent.launchedFromUid, parent.launchedFromPackage, -1, parent.launchedFromUid, 0, null, false, true, null, null, null); foundParentInTask = res == ActivityManager.START_SUCCESS; diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index f712613a2037..3ea11b6d3047 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -32,12 +32,12 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; -import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP; import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT; import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED; import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP; +import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; @@ -45,6 +45,7 @@ import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RESULTS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS; @@ -64,7 +65,6 @@ import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS; import static com.android.server.am.ActivityStackSupervisor.ON_TOP; import static com.android.server.am.ActivityStackSupervisor.TAG_TASKS; -import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.AppGlobals; @@ -529,7 +529,10 @@ class ActivityStarter { // switch... just dismiss the keyguard now, because we // probably want to see whatever is behind it. mSupervisor.notifyActivityDrawnForKeyguard(); + } else { + launchRecentsAppIfNeeded(stack); } + return err; } @@ -1006,6 +1009,16 @@ class ActivityStarter { return START_SUCCESS; } + private void launchRecentsAppIfNeeded(ActivityStack topStack) { + if (topStack.mStackId == HOME_STACK_ID && mTargetStack.mStackId == DOCKED_STACK_ID) { + // We launch an activity while being in home stack, which means either launcher or + // recents into docked stack. We don't want the launched activity to be alone in a + // docked stack, so we want to immediately launch recents too. + if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch."); + mWindowManager.showRecentApps(); + } + } + private void setInitialState(ActivityRecord r, ActivityOptions options, TaskRecord inTask, boolean doResume, int startFlags, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e787eda171ea..018bf2d2cb27 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -28,6 +28,7 @@ import static android.service.notification.NotificationAssistantService.REASON_L import static android.service.notification.NotificationAssistantService.REASON_LISTENER_CANCEL_ALL; import static android.service.notification.NotificationAssistantService.REASON_PACKAGE_BANNED; import static android.service.notification.NotificationAssistantService.REASON_PACKAGE_CHANGED; +import static android.service.notification.NotificationAssistantService.REASON_TOPIC_BANNED; import static android.service.notification.NotificationAssistantService.REASON_USER_STOPPED; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_HIGH; @@ -741,7 +742,7 @@ public class NotificationManagerService extends SystemService { for (String pkgName : pkgList) { if (cancelNotifications) { cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, 0, 0, !queryRestart, - changeUserId, REASON_PACKAGE_CHANGED, null); + changeUserId, REASON_PACKAGE_CHANGED, null, null); } } } @@ -774,7 +775,7 @@ public class NotificationManagerService extends SystemService { int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (userHandle >= 0) { cancelAllNotificationsInt(MY_UID, MY_PID, null, 0, 0, true, userHandle, - REASON_USER_STOPPED, null); + REASON_USER_STOPPED, null, null); } } else if (action.equals(Intent.ACTION_USER_PRESENT)) { // turn off LED when user passes through lock screen @@ -1051,7 +1052,7 @@ public class NotificationManagerService extends SystemService { // Now, cancel any outstanding notifications that are part of a just-disabled app if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) { cancelAllNotificationsInt(MY_UID, MY_PID, pkg, 0, 0, true, UserHandle.getUserId(uid), - REASON_PACKAGE_BANNED, null); + REASON_PACKAGE_BANNED, null, null); } } @@ -1209,7 +1210,7 @@ public class NotificationManagerService extends SystemService { // running foreground services. cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(), pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId, - REASON_APP_CANCEL_ALL, null); + REASON_APP_CANCEL_ALL, null, null); } @Override @@ -1266,6 +1267,11 @@ public class NotificationManagerService extends SystemService { public void setTopicImportance(String pkg, int uid, Notification.Topic topic, int importance) { enforceSystemOrSystemUI("Caller not system or systemui"); + if (NotificationListenerService.Ranking.IMPORTANCE_NONE == importance) { + cancelAllNotificationsInt(MY_UID, MY_PID, pkg, 0, 0, true, + UserHandle.getUserId(uid), + REASON_TOPIC_BANNED, topic, null); + } mRankingHelper.setTopicImportance(pkg, uid, topic, importance); savePolicyFile(); } @@ -2284,8 +2290,9 @@ public class NotificationManagerService extends SystemService { mRankingHelper.extractSignals(r); savePolicyFile(); - // blocked apps - if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) { + // blocked apps/topics + if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE + || !noteNotificationOp(pkg, callingUid)) { if (!isSystemNotification) { Slog.e(TAG, "Suppressing notification from package " + pkg + " by user request."); @@ -3067,11 +3074,11 @@ public class NotificationManagerService extends SystemService { } /** - * Cancels all notifications from a given package that have all of the + * Cancels all notifications from a given package or topic that have all of the * {@code mustHaveFlags}. */ boolean cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, int mustHaveFlags, - int mustNotHaveFlags, boolean doit, int userId, int reason, + int mustNotHaveFlags, boolean doit, int userId, int reason, Notification.Topic topic, ManagedServiceInfo listener) { String listenerName = listener == null ? null : listener.component.toShortString(); EventLogTags.writeNotificationCancelAll(callingUid, callingPid, @@ -3099,6 +3106,10 @@ public class NotificationManagerService extends SystemService { if (pkg != null && !r.sbn.getPackageName().equals(pkg)) { continue; } + if (topic != null + && !topic.getId().equals(r.getNotification().getTopic().getId())) { + continue; + } if (canceledNotifications == null) { canceledNotifications = new ArrayList<>(); } diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index f1fd42c69101..ce4ecd39cd58 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -423,14 +423,16 @@ public class RankingHelper implements RankingConfig { /** * Sets the default importance for all new topics that appear in the future, and resets - * the importance of all current topics. + * the importance of all current topics (unless the app is being blocked). */ @Override public void setAppImportance(String pkgName, int uid, int importance) { final Record r = getOrCreateRecord(pkgName, uid); r.importance = importance; - for (Topic t : r.topics.values()) { - t.importance = importance; + if (Ranking.IMPORTANCE_NONE != importance) { + for (Topic t : r.topics.values()) { + t.importance = importance; + } } updateConfig(); } @@ -483,7 +485,9 @@ public class RankingHelper implements RankingConfig { pw.print(prefix); pw.println("per-package config:"); } + pw.println("Records:"); dumpRecords(pw, prefix, filter, mRecords); + pw.println("Restored without uid:"); dumpRecords(pw, prefix, filter, mRestoredWithoutUids); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 870ae892d435..41627fd58f01 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -318,6 +318,8 @@ public class PackageManagerService extends IPackageManager.Stub { static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false; + private static final boolean DISABLE_EPHEMERAL_APPS = true; + private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; private static final int NFC_UID = Process.NFC_UID; @@ -4469,6 +4471,9 @@ public class PackageManagerService extends IPackageManager.Stub { private boolean isEphemeralAllowed( Intent intent, List<ResolveInfo> resolvedActivites, int userId) { // Short circuit and return early if possible. + if (DISABLE_EPHEMERAL_APPS) { + return false; + } final int callingUser = UserHandle.getCallingUserId(); if (callingUser != UserHandle.USER_SYSTEM) { return false; @@ -5803,6 +5808,10 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public ParceledListSlice<EphemeralApplicationInfo> getEphemeralApplications(int userId) { + if (DISABLE_EPHEMERAL_APPS) { + return null; + } + mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_EPHEMERAL_APPS, "getEphemeralApplications"); enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, @@ -5821,6 +5830,10 @@ public class PackageManagerService extends IPackageManager.Stub { public boolean isEphemeralApplication(String packageName, int userId) { enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "isEphemeral"); + if (DISABLE_EPHEMERAL_APPS) { + return false; + } + if (!isCallerSameApp(packageName)) { return false; } @@ -5835,6 +5848,10 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public byte[] getEphemeralApplicationCookie(String packageName, int userId) { + if (DISABLE_EPHEMERAL_APPS) { + return null; + } + enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getCookie"); if (!isCallerSameApp(packageName)) { @@ -5848,6 +5865,10 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public boolean setEphemeralApplicationCookie(String packageName, byte[] cookie, int userId) { + if (DISABLE_EPHEMERAL_APPS) { + return true; + } + enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "setCookie"); if (!isCallerSameApp(packageName)) { @@ -5861,6 +5882,10 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public Bitmap getEphemeralApplicationIcon(String packageName, int userId) { + if (DISABLE_EPHEMERAL_APPS) { + return null; + } + mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_EPHEMERAL_APPS, "getEphemeralApplicationIcon"); enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index f13d964e1e1b..de1c1ea091df 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3788,7 +3788,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // size. We need to do this directly, instead of relying on // it to bubble up from the nav bar, because this needs to // change atomically with screen rotations. - mNavigationBarOnBottom = (!mNavigationBarCanMove || displayWidth < displayHeight); + mNavigationBarOnBottom = isNavigationBarOnBottom(displayWidth, displayHeight); if (mNavigationBarOnBottom) { // It's a system nav bar or a portrait screen; nav bar goes on bottom. int top = displayHeight - overscanBottom @@ -3859,6 +3859,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { return false; } + private boolean isNavigationBarOnBottom(int displayWidth, int displayHeight) { + return !mNavigationBarCanMove || displayWidth < displayHeight; + } + /** {@inheritDoc} */ @Override public int getSystemDecorLayerLw() { @@ -5931,6 +5935,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + @Override + public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight, + Rect outInsets) { + outInsets.setEmpty(); + if (mStatusBar != null) { + outInsets.top = mStatusBarHeight; + } + if (mNavigationBar != null) { + if (isNavigationBarOnBottom(displayWidth, displayHeight)) { + outInsets.bottom = getNavigationBarHeight(displayRotation, mUiMode); + } else { + outInsets.right = getNavigationBarWidth(displayRotation, mUiMode); + } + } + } + void sendCloseSystemWindows() { PhoneWindow.sendCloseSystemWindows(mContext, null); } diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index fb778ec94f7f..e75780f6a095 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -18,6 +18,7 @@ package com.android.server.wm; import android.app.ActivityManager.StackId; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Rect; import android.os.Debug; import android.util.EventLog; @@ -26,6 +27,9 @@ import android.util.SparseArray; import android.view.DisplayInfo; import android.view.Surface; +import com.android.internal.policy.DividerSnapAlgorithm; +import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; +import com.android.internal.policy.DockedDividerUtils; import com.android.server.EventLogTags; import java.io.PrintWriter; @@ -245,19 +249,66 @@ public class TaskStack implements DimLayer.DimLayerUser { setBounds(null); } else { mTmpRect2.set(mBounds); - mDisplayContent.rotateBounds( - mRotation, mDisplayContent.getDisplayInfo().rotation, mTmpRect2); - if (setBounds(mTmpRect2)) { - // Post message to inform activity manager of the bounds change simulating - // a one-way call. We do this to prevent a deadlock between window manager - // lock and activity manager lock been held. - mService.mH.sendMessage(mService.mH.obtainMessage( - RESIZE_STACK, mStackId, 0 /*allowResizeInDockedMode*/, mBounds)); + final int newRotation = mDisplayContent.getDisplayInfo().rotation; + if (mRotation == newRotation) { + setBounds(mTmpRect2); } + + // If the rotation changes, we'll handle it in updateBoundsAfterRotation } } } + /** + * Updates the bounds after rotating the screen. We can't handle it in + * {@link #updateDisplayInfo} because at that point the configuration might not be fully updated + * yet. + */ + void updateBoundsAfterRotation() { + final int newRotation = getDisplayInfo().rotation; + mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2); + if (mStackId == DOCKED_STACK_ID) { + snapDockedStackAfterRotation(mTmpRect2); + } + + // Post message to inform activity manager of the bounds change simulating + // a one-way call. We do this to prevent a deadlock between window manager + // lock and activity manager lock been held. + mService.mH.sendMessage(mService.mH.obtainMessage( + RESIZE_STACK, mStackId, 0 /*allowResizeInDockedMode*/, mTmpRect2)); + } + + /** + * Snaps the bounds after rotation to the closest snap target for the docked stack. + */ + private void snapDockedStackAfterRotation(Rect outBounds) { + + // Calculate the current position. + final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo(); + final int dividerSize = mService.getDefaultDisplayContentLocked() + .getDockedDividerController().getContentWidth(); + final int dockSide = getDockSide(outBounds); + final int dividerPosition = DockedDividerUtils.calculatePositionForBounds(outBounds, + dockSide, dividerSize); + final int displayWidth = mDisplayContent.getDisplayInfo().logicalWidth; + final int displayHeight = mDisplayContent.getDisplayInfo().logicalHeight; + + // Snap the position to a target. + final int rotation = displayInfo.rotation; + final int orientation = mService.mCurConfiguration.orientation; + mService.mPolicy.getStableInsetsLw(rotation, displayWidth, displayHeight, outBounds); + final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm( + mService.mContext.getResources(), + 0 /* minFlingVelocityPxPerSecond */, displayWidth, displayHeight, + dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds); + final SnapTarget target = algorithm.calculateNonDismissingSnapTarget(dividerPosition); + + // Recalculate the bounds based on the position of the target. + DockedDividerUtils.calculateBoundsForPosition(target.position, dockSide, + outBounds, displayInfo.logicalWidth, displayInfo.logicalHeight, + dividerSize); + } + boolean isAnimating() { for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) { final ArrayList<AppWindowToken> activities = mTasks.get(taskNdx).mAppTokens; @@ -682,6 +733,10 @@ public class TaskStack implements DimLayer.DimLayerUser { * information which side of the screen was the dock anchored. */ int getDockSide() { + return getDockSide(mBounds); + } + + int getDockSide(Rect bounds) { if (mStackId != DOCKED_STACK_ID && !StackId.isResizeableByDockedStack(mStackId)) { return DOCKED_INVALID; } @@ -692,14 +747,14 @@ public class TaskStack implements DimLayer.DimLayerUser { final int orientation = mService.mCurConfiguration.orientation; if (orientation == Configuration.ORIENTATION_PORTRAIT) { // Portrait mode, docked either at the top or the bottom. - if (mBounds.top - mTmpRect.top < mTmpRect.bottom - mBounds.bottom) { + if (bounds.top - mTmpRect.top < mTmpRect.bottom - bounds.bottom) { return DOCKED_TOP; } else { return DOCKED_BOTTOM; } } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) { // Landscape mode, docked either on the left or on the right. - if (mBounds.left - mTmpRect.left < mTmpRect.right - mBounds.right) { + if (bounds.left - mTmpRect.left < mTmpRect.right - bounds.right) { return DOCKED_LEFT; } else { return DOCKED_RIGHT; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 71cd66043331..685df25910d6 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3527,6 +3527,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override public void setNewConfiguration(Configuration config) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "setNewConfiguration()")) { @@ -3540,10 +3541,20 @@ public class WindowManagerService extends IWindowManager.Stub mWaitingForConfig = false; mLastFinishedFreezeSource = "new-config"; } + if (orientationChanged) { + updateTaskStackBoundsAfterRotation(); + } mWindowPlacerLocked.performSurfacePlacement(); } } + private void updateTaskStackBoundsAfterRotation() { + for (int stackNdx = mStackIdToStack.size() - 1; stackNdx >= 0; stackNdx--) { + final TaskStack stack = mStackIdToStack.valueAt(stackNdx); + stack.updateBoundsAfterRotation(); + } + } + @Override public void setAppOrientation(IApplicationToken token, int requestedOrientation) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index 01583f56ef73..81aa6c6eec3c 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -322,21 +322,25 @@ public class MockPackageManager extends PackageManager { throw new UnsupportedOperationException(); } + /** @hide */ @Override public byte[] getEphemeralCookie() { return new byte[0]; } + /** @hide */ @Override public boolean isEphemeralApplication() { return false; } + /** @hide */ @Override public int getEphemeralCookieMaxSizeBytes() { return 0; } + /** @hide */ @Override public boolean setEphemeralCookie(@NonNull byte[] cookie) { return false; diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java index fecfdf98d0e7..3a30230833ed 100644 --- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java +++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java @@ -175,7 +175,7 @@ public class NotificationTestList extends TestActivity .setTopic(new Notification.Topic("hello", "Hello")) .build(); - mNM.notify(999, n); + mNM.notify(70, n); } }, @@ -194,7 +194,7 @@ public class NotificationTestList extends TestActivity .setStyle(picture) .build(); - mNM.notify(9999, n); + mNM.notify(71, n); } }, new Test("with topic Bananas") { @@ -211,10 +211,28 @@ public class NotificationTestList extends TestActivity .setTopic(new Notification.Topic("bananas", "Bananas")) .build(); - mNM.notify(999, n); + mNM.notify(72, n); } }, + new Test("with delete intent") { + public void run() { + Notification.BigTextStyle bigText = new Notification.BigTextStyle(); + bigText.bigText("bananas are great\nso tasty\nyum\nyum\nyum\n"); + Notification n = new Notification.Builder(NotificationTestList.this) + .setSmallIcon(R.drawable.icon1) + .setStyle(bigText) + .setWhen(mActivityCreateTime) + .setContentTitle("bananananana") + .setContentText("This is a banana!!!") + .setTopic(new Notification.Topic("bananas", "Bananas")) + .setDeleteIntent(makeIntent2()) + .build(); + + mNM.notify(73, n); + } + }, + new Test("Whens") { public void run() { |