Merge "Show 'end now' in expanded volume panel."
diff --git a/Android.mk b/Android.mk
index aff6cf2..c1c74ea 100644
--- a/Android.mk
+++ b/Android.mk
@@ -261,7 +261,7 @@
core/java/android/view/IApplicationToken.aidl \
core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \
core/java/android/view/IAssetAtlas.aidl \
- core/java/android/view/IDockDividerVisibilityListener.aidl \
+ core/java/android/view/IDockedStackListener.aidl \
core/java/android/view/IGraphicsStats.aidl \
core/java/android/view/IInputFilter.aidl \
core/java/android/view/IInputFilterHost.aidl \
diff --git a/api/current.txt b/api/current.txt
index 30d3020..730e84e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5197,6 +5197,7 @@
field public static final int SUPPRESSED_EFFECTS_UNSET = -1; // 0xffffffff
field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
+ field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 4; // 0x4
field public final int priorityCallSenders;
field public final int priorityCategories;
field public final int priorityMessageSenders;
@@ -33560,6 +33561,7 @@
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
+ field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 4; // 0x4
}
public static class NotificationListenerService.Ranking {
diff --git a/api/system-current.txt b/api/system-current.txt
index d7393b7..6f5db72 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5317,6 +5317,7 @@
field public static final int SUPPRESSED_EFFECTS_UNSET = -1; // 0xffffffff
field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
+ field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 4; // 0x4
field public final int priorityCallSenders;
field public final int priorityCategories;
field public final int priorityMessageSenders;
@@ -35707,6 +35708,7 @@
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
+ field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 4; // 0x4
field public static final int TRIM_FULL = 0; // 0x0
field public static final int TRIM_LIGHT = 1; // 0x1
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 9058fe7..ac4ec6e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5197,6 +5197,7 @@
field public static final int SUPPRESSED_EFFECTS_UNSET = -1; // 0xffffffff
field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
+ field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 4; // 0x4
field public final int priorityCallSenders;
field public final int priorityCategories;
field public final int priorityMessageSenders;
@@ -33563,6 +33564,7 @@
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
+ field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 4; // 0x4
}
public static class NotificationListenerService.Ranking {
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 4f7a109..6302d74 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -159,6 +159,7 @@
" am stack start <DISPLAY_ID> <INTENT>\n" +
" am stack movetask <TASK_ID> <STACK_ID> [true|false]\n" +
" am stack resize <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" +
+ " am stack resize-docked-stack <LEFT,TOP,RIGHT,BOTTOM> [<TASK_LEFT,TASK_TOP,TASK_RIGHT,TASK_BOTTOM>]\n" +
" am stack size-docked-stack-test: <STEP_SIZE> <l|t|r|b> [DELAY_MS]\n" +
" am stack move-top-activity-to-pinned-stack: <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" +
" am stack positiontask <TASK_ID> <STACK_ID> <POSITION>\n" +
@@ -299,7 +300,11 @@
"am stack movetask: move <TASK_ID> from its current stack to the top (true) or" +
" bottom (false) of <STACK_ID>.\n" +
"\n" +
- "am stack resize: change <STACK_ID> size and position to <LEFT,TOP,RIGHT,BOTTOM>." +
+ "am stack resize: change <STACK_ID> size and position to <LEFT,TOP,RIGHT,BOTTOM>.\n" +
+ "\n" +
+ "am stack resize-docked-stack: change docked stack to <LEFT,TOP,RIGHT,BOTTOM>\n" +
+ " and supplying temporary different task bounds indicated by\n" +
+ " <TASK_LEFT,TOP,RIGHT,BOTTOM>\n" +
"\n" +
"am stack size-docked-stack-test: test command for sizing docked stack by\n" +
" <STEP_SIZE> increments from the side <l>eft, <t>op, <r>ight, or <b>ottom\n" +
@@ -1683,6 +1688,9 @@
case "resize":
runStackResize();
break;
+ case "resize-docked-stack":
+ runStackResizeDocked();
+ break;
case "positiontask":
runStackPositionTask();
break;
@@ -1751,6 +1759,20 @@
resizeStack(stackId, bounds, 0);
}
+ private void runStackResizeDocked() throws Exception {
+ final Rect bounds = getBounds();
+ final Rect taskBounds = getBounds();
+ if (bounds == null || taskBounds == null) {
+ System.err.println("Error: invalid input bounds");
+ return;
+ }
+ try {
+ mAm.resizeDockedStack(bounds, taskBounds, null, null, null);
+ } catch (RemoteException e) {
+ showError("Error: resizing docked stack " + e);
+ }
+ }
+
private void resizeStack(int stackId, Rect bounds, int delayMs) throws Exception {
if (bounds == null) {
showError("Error: invalid input bounds");
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 3e6b595..6fc0d74 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -1082,6 +1082,10 @@
return false;
}
+ /** @hide */
+ public void onDestroy() {
+ }
+
/**
* Common implementation for requestFocus that takes in the Toolbar and moves focus
* to the contents. This makes the ViewGroups containing the toolbar allow focus while it stays
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 800ebc6..8346161 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1703,6 +1703,10 @@
mSearchManager.stopSearch();
}
+ if (mActionBar != null) {
+ mActionBar.onDestroy();
+ }
+
getApplication().dispatchActivityDestroyed(this);
}
@@ -2208,14 +2212,22 @@
* @param toolbar Toolbar to set as the Activity's action bar
*/
public void setActionBar(@Nullable Toolbar toolbar) {
- if (getActionBar() instanceof WindowDecorActionBar) {
+ final ActionBar ab = getActionBar();
+ if (ab instanceof WindowDecorActionBar) {
throw new IllegalStateException("This Activity already has an action bar supplied " +
"by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " +
"android:windowActionBar to false in your theme to use a Toolbar instead.");
}
- // Clear out the MenuInflater to make sure that it is valid for the new Action Bar
+
+ // If we reach here then we're setting a new action bar
+ // First clear out the MenuInflater to make sure that it is valid for the new Action Bar
mMenuInflater = null;
+ // If we have an action bar currently, destroy it
+ if (ab != null) {
+ ab.onDestroy();
+ }
+
ToolbarActionBar tbab = new ToolbarActionBar(toolbar, getTitle(), this);
mActionBar = tbab;
mWindow.setCallback(tbab.getWrappedWindowCallback());
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index d2d7b2c..da21099 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -570,6 +570,14 @@
return true;
}
+ case ACTIVITY_RELAUNCHED_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ activityRelaunched(token);
+ reply.writeNoException();
+ return true;
+ }
+
case GET_CALLING_PACKAGE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -785,6 +793,39 @@
return true;
}
+ case RESIZE_DOCKED_STACK_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final boolean hasBounds = data.readInt() != 0;
+ Rect bounds = null;
+ if (hasBounds) {
+ bounds = Rect.CREATOR.createFromParcel(data);
+ }
+ final boolean hasTempDockedTaskBounds = data.readInt() != 0;
+ Rect tempDockedTaskBounds = null;
+ if (hasTempDockedTaskBounds) {
+ tempDockedTaskBounds = Rect.CREATOR.createFromParcel(data);
+ }
+ final boolean hasTempDockedTaskInsetBounds = data.readInt() != 0;
+ Rect tempDockedTaskInsetBounds = null;
+ if (hasTempDockedTaskInsetBounds) {
+ tempDockedTaskInsetBounds = Rect.CREATOR.createFromParcel(data);
+ }
+ final boolean hasTempOtherTaskBounds = data.readInt() != 0;
+ Rect tempOtherTaskBounds = null;
+ if (hasTempOtherTaskBounds) {
+ tempOtherTaskBounds = Rect.CREATOR.createFromParcel(data);
+ }
+ final boolean hasTempOtherTaskInsetBounds = data.readInt() != 0;
+ Rect tempOtherTaskInsetBounds = null;
+ if (hasTempOtherTaskInsetBounds) {
+ tempOtherTaskInsetBounds = Rect.CREATOR.createFromParcel(data);
+ }
+ resizeDockedStack(bounds, tempDockedTaskBounds, tempDockedTaskInsetBounds,
+ tempOtherTaskBounds, tempOtherTaskInsetBounds);
+ reply.writeNoException();
+ return true;
+ }
+
case POSITION_TASK_IN_STACK_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int taskId = data.readInt();
@@ -3380,6 +3421,17 @@
data.recycle();
reply.recycle();
}
+ public void activityRelaunched(IBinder token) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ mRemote.transact(ACTIVITY_RELAUNCHED_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
public String getCallingPackage(IBinder token) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -3690,6 +3742,50 @@
reply.recycle();
}
@Override
+ public void resizeDockedStack(Rect dockedBounds, Rect tempDockedTaskBounds,
+ Rect tempDockedTaskInsetBounds,
+ Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds)
+ throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ if (dockedBounds != null) {
+ data.writeInt(1);
+ dockedBounds.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
+ if (tempDockedTaskBounds != null) {
+ data.writeInt(1);
+ tempDockedTaskBounds.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
+ if (tempDockedTaskInsetBounds != null) {
+ data.writeInt(1);
+ tempDockedTaskInsetBounds.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
+ if (tempOtherTaskBounds != null) {
+ data.writeInt(1);
+ tempOtherTaskBounds.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
+ if (tempOtherTaskInsetBounds != null) {
+ data.writeInt(1);
+ tempOtherTaskInsetBounds.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
+ mRemote.transact(RESIZE_DOCKED_STACK_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+ @Override
public void positionTaskInStack(int taskId, int stackId, int position) throws RemoteException
{
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ed168d1..177fabe 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4161,6 +4161,15 @@
r.pendingIntents = pendingNewIntents;
}
}
+
+ // For each relaunch request, activity manager expects an answer
+ if (!r.onlyLocalRequest && fromServer) {
+ try {
+ ActivityManagerNative.getDefault().activityRelaunched(token);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
break;
}
}
@@ -4275,6 +4284,13 @@
ActivityClientRecord r = mActivities.get(tmp.token);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handling relaunch of " + r);
if (r == null) {
+ if (!tmp.onlyLocalRequest) {
+ try {
+ ActivityManagerNative.getDefault().activityRelaunched(tmp.token);
+ } catch (RemoteException e) {
+ // If the system process has died, it's game over for everyone.
+ }
+ }
return;
}
@@ -4320,6 +4336,14 @@
r.overrideConfig = tmp.overrideConfig;
handleLaunchActivity(r, currentIntent);
+
+ if (!tmp.onlyLocalRequest) {
+ try {
+ ActivityManagerNative.getDefault().activityRelaunched(r.token);
+ } catch (RemoteException e) {
+ // If the system process has died, it's game over for everyone.
+ }
+ }
}
private void callCallActivityOnSaveInstanceState(ActivityClientRecord r) {
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 5ab8694..ceb14ad 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -122,6 +122,7 @@
PersistableBundle persistentState, CharSequence description) throws RemoteException;
public void activitySlept(IBinder token) throws RemoteException;
public void activityDestroyed(IBinder token) throws RemoteException;
+ public void activityRelaunched(IBinder token) throws RemoteException;
public void reportSizeConfigurations(IBinder token, int[] horizontalSizeConfiguration,
int[] verticalSizeConfigurations, int[] smallestWidthConfigurations)
throws RemoteException;
@@ -145,7 +146,31 @@
public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate,
Rect initialBounds) throws RemoteException;
public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) throws RemoteException;
- public void resizeStack(int stackId, Rect bounds, boolean allowResizeInDockedMode) throws RemoteException;
+ public void resizeStack(int stackId, Rect bounds, boolean allowResizeInDockedMode)
+ throws RemoteException;
+
+ /**
+ * Resizes the docked stack, and all other stacks as the result of the dock stack bounds change.
+ *
+ * @param dockedBounds The bounds for the docked stack.
+ * @param tempDockedTaskBounds The temporary bounds for the tasks in the docked stack, which
+ * might be different from the stack bounds to allow more
+ * flexibility while resizing, or {@code null} if they should be the
+ * same as the stack bounds.
+ * @param tempDockedTaskInsetBounds The temporary bounds for the tasks to calculate the insets.
+ * When resizing, we usually "freeze" the layout of a task. To
+ * achieve that, we also need to "freeze" the insets, which
+ * gets achieved by changing task bounds but not bounds used
+ * to calculate the insets in this transient state
+ * @param tempOtherTaskBounds The temporary bounds for the tasks in all other stacks, or
+ * {@code null} if they should be the same as the stack bounds.
+ * @param tempOtherTaskInsetBounds Like {@code tempDockedTaskInsetBounds}, but for the other
+ * stacks.
+ * @throws RemoteException
+ */
+ public void resizeDockedStack(Rect dockedBounds, Rect tempDockedTaskBounds,
+ Rect tempDockedTaskInsetBounds,
+ Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds) throws RemoteException;
public void positionTaskInStack(int taskId, int stackId, int position) throws RemoteException;
public List<StackInfo> getAllStackInfos() throws RemoteException;
public StackInfo getStackInfo(int stackId) throws RemoteException;
@@ -920,6 +945,8 @@
int IN_PICTURE_IN_PICTURE_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 353;
int KILL_PACKAGE_DEPENDENTS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 354;
int ENTER_PICTURE_IN_PICTURE_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 355;
- int SET_VR_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 356;
+ int ACTIVITY_RELAUNCHED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 356;
int GET_URI_PERMISSION_OWNER_FOR_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 357;
+ int RESIZE_DOCKED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 358;
+ int SET_VR_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 359;
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 85d9831..9a3c820 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -637,10 +637,12 @@
public static final int SUPPRESSED_EFFECTS_UNSET = -1;
public static final int SUPPRESSED_EFFECT_LIGHTS = 1 << 0;
public static final int SUPPRESSED_EFFECT_PEEK = 1 << 1;
+ public static final int SUPPRESSED_EFFECT_SCREEN_ON = 1 << 2;
private static final int[] ALL_SUPPRESSED_EFFECTS = {
SUPPRESSED_EFFECT_LIGHTS,
SUPPRESSED_EFFECT_PEEK,
+ SUPPRESSED_EFFECT_SCREEN_ON,
};
/**
@@ -750,6 +752,7 @@
switch (effect) {
case SUPPRESSED_EFFECT_LIGHTS: return "SUPPRESSED_EFFECT_LIGHTS";
case SUPPRESSED_EFFECT_PEEK: return "SUPPRESSED_EFFECT_PEEK";
+ case SUPPRESSED_EFFECT_SCREEN_ON: return "SUPPRESSED_EFFECT_SCREEN_ON";
case SUPPRESSED_EFFECTS_UNSET: return "SUPPRESSED_EFFECTS_UNSET";
default: return "UNKNOWN_" + effect;
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 7669053c..ee6aec2 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -698,6 +698,18 @@
*/
public native final String[] getLocales();
+ /**
+ * Same as getLocales(), except that locales that are only provided by the system (i.e. those
+ * present in framework-res.apk or its overlays) will not be listed.
+ *
+ * For example, if the "system" assets support English, French, and German, and the additional
+ * assets support Cherokee and French, getLocales() would return
+ * [Cherokee, English, French, German], while getNonSystemLocales() would return
+ * [Cherokee, French].
+ * {@hide}
+ */
+ public native final String[] getNonSystemLocales();
+
/** {@hide} */
public native final Configuration[] getSizeConfigurations();
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 60c6e82..c460746 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1976,7 +1976,21 @@
if (setLocalesToDefault || mResolvedLocale == null
|| (configChanges & Configuration.NATIVE_CONFIG_LOCALE) != 0) {
- mResolvedLocale = locales.getFirstMatch(mAssets.getLocales());
+ if (locales.size() == 1) {
+ // This is an optimization to avoid the JNI call(s) when the result of
+ // getFirstMatch() does not depend on the supported locales.
+ mResolvedLocale = locales.getPrimary();
+ } else {
+ String[] supportedLocales = mAssets.getNonSystemLocales();
+ if (LocaleList.isPseudoLocalesOnly(supportedLocales)) {
+ // We fallback to all locales (including system locales) if there was no
+ // locale specifically supported by the assets. This is to properly support
+ // apps that only rely on the shared system assets and don't need assets of
+ // their own.
+ supportedLocales = mAssets.getLocales();
+ }
+ mResolvedLocale = locales.getFirstMatch(supportedLocales);
+ }
}
mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
adjustLanguageTag(mResolvedLocale.toLanguageTag()),
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b883f9c..4eaee0b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6374,6 +6374,13 @@
public static final String DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES
= "force_resizable_activities";
+ /**
+ * Whether to enable experimental freeform support for windows.
+ * @hide
+ */
+ public static final String DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT
+ = "enable_freeform_support";
+
/**
* Whether user has enabled development settings.
*/
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 6c99489..b42d9ea 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -123,6 +123,8 @@
NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
public static final int SUPPRESSED_EFFECT_PEEK =
NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+ public static final int SUPPRESSED_EFFECT_SCREEN_ON =
+ NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
/**
* The full trim of the StatusBarNotification including all its features.
@@ -961,7 +963,8 @@
/**
* Returns the type(s) of visual effects that should be suppressed for this notification.
- * See {@link #SUPPRESSED_EFFECT_LIGHTS}, {@link #SUPPRESSED_EFFECT_PEEK}}.
+ * See {@link #SUPPRESSED_EFFECT_LIGHTS}, {@link #SUPPRESSED_EFFECT_PEEK},
+ * {@link #SUPPRESSED_EFFECT_SCREEN_ON}.
*/
public int getSuppressedVisualEffects() {
return mSuppressedVisualEffects;
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 541623d..4688843 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -79,6 +79,7 @@
private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false;
private static final boolean DEFAULT_ALLOW_PEEK = true;
private static final boolean DEFAULT_ALLOW_LIGHTS = true;
+ private static final boolean DEFAULT_ALLOW_SCREEN_ON = true;
private static final int XML_VERSION = 2;
private static final String ZEN_TAG = "zen";
@@ -95,6 +96,7 @@
private static final String ALLOW_ATT_EVENTS = "events";
private static final String ALLOW_ATT_PEEK = "peek";
private static final String ALLOW_ATT_LIGHTS = "lights";
+ private static final String ALLOW_ATT_SCREEN_ON = "screen_on";
private static final String CONDITION_TAG = "condition";
private static final String CONDITION_ATT_COMPONENT = "component";
@@ -128,6 +130,7 @@
public int user = UserHandle.USER_SYSTEM;
public boolean allowPeek = DEFAULT_ALLOW_PEEK;
public boolean allowLights = DEFAULT_ALLOW_LIGHTS;
+ public boolean allowScreenOn = DEFAULT_ALLOW_SCREEN_ON;
public ZenRule manualRule;
public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();
@@ -156,6 +159,7 @@
}
allowPeek = source.readInt() == 1;
allowLights = source.readInt() == 1;
+ allowScreenOn = source.readInt() == 1;
}
@Override
@@ -185,6 +189,7 @@
}
dest.writeInt(allowPeek ? 1 : 0);
dest.writeInt(allowLights ? 1 : 0);
+ dest.writeInt(allowScreenOn ? 1 : 0);
}
@Override
@@ -200,6 +205,7 @@
.append(",allowEvents=").append(allowEvents)
.append(",allowPeek=").append(allowPeek)
.append(",allowLights=").append(allowLights)
+ .append(",allowScreenOn=").append(allowScreenOn)
.append(",automaticRules=").append(automaticRules)
.append(",manualRule=").append(manualRule)
.append(']').toString();
@@ -240,6 +246,9 @@
if (allowLights != to.allowLights) {
d.addLine("allowLights", allowLights, to.allowLights);
}
+ if (allowScreenOn != to.allowScreenOn) {
+ d.addLine("allowScreenOn", allowScreenOn, to.allowScreenOn);
+ }
final ArraySet<String> allRules = new ArraySet<>();
addKeys(allRules, automaticRules);
addKeys(allRules, to.automaticRules);
@@ -339,6 +348,7 @@
&& other.allowEvents == allowEvents
&& other.allowPeek == allowPeek
&& other.allowLights == allowLights
+ && other.allowScreenOn == allowScreenOn
&& other.user == user
&& Objects.equals(other.automaticRules, automaticRules)
&& Objects.equals(other.manualRule, manualRule);
@@ -348,7 +358,7 @@
public int hashCode() {
return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom,
allowMessagesFrom, allowReminders, allowEvents, allowPeek, allowLights,
- user, automaticRules, manualRule);
+ allowScreenOn, user, automaticRules, manualRule);
}
private static String toDayList(int[] days) {
@@ -435,6 +445,8 @@
}
rt.allowPeek = safeBoolean(parser, ALLOW_ATT_PEEK, DEFAULT_ALLOW_PEEK);
rt.allowLights = safeBoolean(parser, ALLOW_ATT_LIGHTS, DEFAULT_ALLOW_LIGHTS);
+ rt.allowScreenOn =
+ safeBoolean(parser, ALLOW_ATT_SCREEN_ON, DEFAULT_ALLOW_SCREEN_ON);
} else if (MANUAL_TAG.equals(tag)) {
rt.manualRule = readRuleXml(parser);
} else if (AUTOMATIC_TAG.equals(tag)) {
@@ -465,6 +477,7 @@
out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom));
out.attribute(null, ALLOW_ATT_PEEK, Boolean.toString(allowPeek));
out.attribute(null, ALLOW_ATT_LIGHTS, Boolean.toString(allowLights));
+ out.attribute(null, ALLOW_ATT_SCREEN_ON, Boolean.toString(allowScreenOn));
out.endTag(null, ALLOW_TAG);
if (manualRule != null) {
@@ -643,6 +656,9 @@
if (!allowLights) {
suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS;
}
+ if (!allowScreenOn) {
+ suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_SCREEN_ON;
+ }
priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
@@ -681,6 +697,8 @@
if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
allowPeek = (policy.suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_PEEK) == 0;
allowLights = (policy.suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_LIGHTS) == 0;
+ allowScreenOn =
+ (policy.suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_SCREEN_ON) == 0;
}
}
diff --git a/core/java/android/util/LocaleList.java b/core/java/android/util/LocaleList.java
index 7bc06b9..3d8e330 100644
--- a/core/java/android/util/LocaleList.java
+++ b/core/java/android/util/LocaleList.java
@@ -213,6 +213,20 @@
}
}
+ private static final String STRING_EN_XA = "en-XA";
+ private static final String STRING_AR_XB = "ar-XB";
+ private static final Locale LOCALE_EN_XA = new Locale("en", "XA");
+ private static final Locale LOCALE_AR_XB = new Locale("ar", "XB");
+ private static final int NUM_PSEUDO_LOCALES = 2;
+
+ private static boolean isPseudoLocale(String locale) {
+ return STRING_EN_XA.equals(locale) || STRING_AR_XB.equals(locale);
+ }
+
+ private static boolean isPseudoLocale(Locale locale) {
+ return LOCALE_EN_XA.equals(locale) || LOCALE_AR_XB.equals(locale);
+ }
+
private static int matchScore(Locale supported, Locale desired) {
if (supported.equals(desired)) {
return 1; // return early so we don't do unnecessary computation
@@ -220,6 +234,11 @@
if (!supported.getLanguage().equals(desired.getLanguage())) {
return 0;
}
+ if (isPseudoLocale(supported) || isPseudoLocale(desired)) {
+ // The locales are not the same, but the languages are the same, and one of the locales
+ // is a pseudo-locale. So this is not a match.
+ return 0;
+ }
// There is no match if the two locales use different scripts. This will most imporantly
// take care of traditional vs simplified Chinese.
final String supportedScr = getLikelyScript(supported);
@@ -241,7 +260,6 @@
if (mList.length == 0) { // empty locale list
return null;
}
- // TODO: Figure out what to if en-XA or ar-XB are in the locale list
int bestIndex = Integer.MAX_VALUE;
for (String tag : supportedLocales) {
final Locale supportedLocale = Locale.forLanguageTag(tag);
@@ -265,6 +283,27 @@
}
}
+ /**
+ * Returns true if the array of locale tags only contains empty locales and pseudolocales.
+ * Assumes that there is no repetition in the input.
+ * {@hide}
+ */
+ public static boolean isPseudoLocalesOnly(String[] supportedLocales) {
+ if (supportedLocales.length > NUM_PSEUDO_LOCALES + 1) {
+ // This is for optimization. Since there's no repetition in the input, if we have more
+ // than the number of pseudo-locales plus one for the empty string, it's guaranteed
+ // that we have some meaninful locale in the list, so the list is not "practically
+ // empty".
+ return false;
+ }
+ for (String locale : supportedLocales) {
+ if (!locale.isEmpty() && !isPseudoLocale(locale)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private final static Object sLock = new Object();
@GuardedBy("sLock")
diff --git a/core/java/android/view/IDockDividerVisibilityListener.aidl b/core/java/android/view/IDockedStackListener.aidl
similarity index 70%
rename from core/java/android/view/IDockDividerVisibilityListener.aidl
rename to core/java/android/view/IDockedStackListener.aidl
index a7d5cda..77fa7e2 100644
--- a/core/java/android/view/IDockDividerVisibilityListener.aidl
+++ b/core/java/android/view/IDockedStackListener.aidl
@@ -22,6 +22,15 @@
*
* @hide
*/
-oneway interface IDockDividerVisibilityListener {
- void onDockDividerVisibilityChanged(boolean visible);
+oneway interface IDockedStackListener {
+
+ /**
+ * Will fire when an app is shown in side by side mode and a divider should be shown.
+ */
+ void onDividerVisibilityChanged(boolean visible);
+
+ /**
+ * Called when the docked stack gets created or removed.
+ */
+ void onDockedStackExistsChanged(boolean exists);
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 2ef082c..84d312d 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -29,7 +29,7 @@
import android.os.IRemoteCallback;
import android.view.IApplicationToken;
import android.view.IAppTransitionAnimationSpecsFuture;
-import android.view.IDockDividerVisibilityListener;
+import android.view.IDockedStackListener;
import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
import android.view.IWindowSession;
@@ -357,7 +357,17 @@
void setDockedStackResizing(boolean resizing);
/**
- * Registers a listener that will be called when the dock divider changes its visibility.
+ * Registers a listener that will be called when the dock divider changes its visibility or when
+ * the docked stack gets added/removed.
*/
- void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener);
+ void registerDockedStackListener(IDockedStackListener listener);
+
+ /**
+ * Updates the dim layer used while resizing.
+ *
+ * @param visible Whether the dim layer should be visible.
+ * @param targetStackId The id of the task stack the dim layer should be placed on.
+ * @param alpha The translucency of the dim layer, between 0 and 1.
+ */
+ void setResizeDimLayer(boolean visible, int targetStackId, float alpha);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f2b4fb3..1c9f3b4 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3315,6 +3315,7 @@
&& mPendingStableInsets.equals(args.arg6)
&& mPendingVisibleInsets.equals(args.arg3)
&& mPendingOutsets.equals(args.arg7)
+ && mPendingBackDropFrame.equals(args.arg8)
&& args.arg4 == null) {
break;
}
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index 4b6e7e4..575ef02 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -483,6 +483,12 @@
return true;
}
+ @Override
+ public void onDestroy() {
+ // Remove any invalidation callbacks
+ mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator);
+ }
+
public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
mMenuVisibilityListeners.add(listener);
}
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 5fc7448..4b821ab 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -37,6 +37,7 @@
public static final int ACTION_DEFAULT_SMS_APP_CHANGED = 264;
public static final int QS_COLOR_MATRIX = 265;
public static final int QS_CUSTOM = 266;
+ public static final int ACTION_ZEN_ALLOW_SCREEN_ON = 267;
/**
* Logged when the user docks a window from recents by longpressing a task and dragging it to
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index ab0df55..7c8dbe8 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -36,12 +36,12 @@
namespace android {
static jlong FontFamily_create(JNIEnv* env, jobject clazz, jstring lang, jint variant) {
- FontLanguage fontLanguage;
- if (lang != NULL) {
- ScopedUtfChars str(env, lang);
- fontLanguage = FontLanguage(str.c_str(), str.size());
+ if (lang == NULL) {
+ return (jlong)new FontFamily(variant);
}
- return (jlong)new FontFamily(fontLanguage, variant);
+ ScopedUtfChars str(env, lang);
+ uint32_t langId = FontStyle::registerLanguageList(str.c_str());
+ return (jlong)new FontFamily(langId, variant);
}
static void FontFamily_unref(JNIEnv* env, jobject clazz, jlong familyPtr) {
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 654d148..0a25a0a 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -313,94 +313,10 @@
obj->setTextAlign(align);
}
- // generate bcp47 identifier for the supplied locale
- static void toLanguageTag(char* output, size_t outSize,
- const char* locale) {
- if (output == NULL || outSize <= 0) {
- return;
- }
- if (locale == NULL) {
- output[0] = '\0';
- return;
- }
- char canonicalChars[ULOC_FULLNAME_CAPACITY];
- UErrorCode uErr = U_ZERO_ERROR;
- uloc_canonicalize(locale, canonicalChars, ULOC_FULLNAME_CAPACITY,
- &uErr);
- if (U_SUCCESS(uErr)) {
- char likelyChars[ULOC_FULLNAME_CAPACITY];
- uErr = U_ZERO_ERROR;
- uloc_addLikelySubtags(canonicalChars, likelyChars,
- ULOC_FULLNAME_CAPACITY, &uErr);
- if (U_SUCCESS(uErr)) {
- uErr = U_ZERO_ERROR;
- uloc_toLanguageTag(likelyChars, output, outSize, FALSE, &uErr);
- if (U_SUCCESS(uErr)) {
- return;
- } else {
- ALOGD("uloc_toLanguageTag(\"%s\") failed: %s", likelyChars,
- u_errorName(uErr));
- }
- } else {
- ALOGD("uloc_addLikelySubtags(\"%s\") failed: %s",
- canonicalChars, u_errorName(uErr));
- }
- } else {
- ALOGD("uloc_canonicalize(\"%s\") failed: %s", locale,
- u_errorName(uErr));
- }
- // unable to build a proper language identifier
- output[0] = '\0';
- }
-
- static void toLanguageTags(std::string* output, const char* locales) {
- if (output == NULL) {
- return;
- }
- if (locales == NULL) {
- output->clear();
- return;
- }
-
- char langTag[ULOC_FULLNAME_CAPACITY];
- const char* commaLoc = strchr(locales, ',');
- if (commaLoc == NULL) {
- assert(locales[0] != '\0'); // the string should not be empty
- toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locales);
- *output = langTag;
- return;
- }
-
- size_t len = strlen(locales);
- char locale[len];
- output->clear();
- output->reserve(len);
- const char* lastStart = locales;
- do {
- assert(lastStart > commaLoc); // the substring should not be empty
- strncpy(locale, lastStart, commaLoc - lastStart);
- locale[commaLoc - lastStart] = '\0';
- toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);
- if (langTag[0] != '\0') {
- output->append(langTag);
- output->push_back(',');
- }
- lastStart = commaLoc + 1;
- commaLoc = strchr(lastStart, ',');
- } while (commaLoc != NULL);
- assert(lastStart[0] != '\0'); // the final substring should not be empty
- toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, lastStart);
- if (langTag[0] != '\0') {
- output->append(langTag);
- }
- }
-
static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
Paint* obj = reinterpret_cast<Paint*>(objHandle);
ScopedUtfChars localesChars(env, locales);
- std::string buf;
- toLanguageTags(&buf, localesChars.c_str());
- jint minikinLangListId = FontStyle::registerLanguageList(buf);
+ jint minikinLangListId = FontStyle::registerLanguageList(localesChars.c_str());
obj->setMinikinLangListId(minikinLangListId);
return minikinLangListId;
}
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 90606a35..3473d9d 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -578,7 +578,7 @@
return am->isUpToDate() ? JNI_TRUE : JNI_FALSE;
}
-static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz)
+static jobjectArray getLocales(JNIEnv* env, jobject clazz, bool includeSystemLocales)
{
Vector<String8> locales;
@@ -587,7 +587,7 @@
return NULL;
}
- am->getLocales(&locales);
+ am->getLocales(&locales, includeSystemLocales);
const int N = locales.size();
@@ -608,6 +608,16 @@
return result;
}
+static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz)
+{
+ return getLocales(env, clazz, true /* include system locales */);
+}
+
+static jobjectArray android_content_AssetManager_getNonSystemLocales(JNIEnv* env, jobject clazz)
+{
+ return getLocales(env, clazz, false /* don't include system locales */);
+}
+
static jobject constructConfigurationObject(JNIEnv* env, const ResTable_config& config) {
jobject result = env->NewObject(gConfigurationOffsets.classObject,
gConfigurationOffsets.constructor);
@@ -2154,6 +2164,8 @@
// Resources.
{ "getLocales", "()[Ljava/lang/String;",
(void*) android_content_AssetManager_getLocales },
+ { "getNonSystemLocales", "()[Ljava/lang/String;",
+ (void*) android_content_AssetManager_getNonSystemLocales },
{ "getSizeConfigurations", "()[Landroid/content/res/Configuration;",
(void*) android_content_AssetManager_getSizeConfigurations },
{ "setConfiguration", "!(IILjava/lang/String;IIIIIIIIIIIIII)V",
diff --git a/core/res/res/values-land/config.xml b/core/res/res/values-land/config.xml
new file mode 100644
index 0000000..7308dc5
--- /dev/null
+++ b/core/res/res/values-land/config.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<resources>
+ <integer name="config_dockedStackDividerSnapMode">2</integer>
+</resources>
\ No newline at end of file
diff --git a/core/res/res/values-sw600dp/config.xml b/core/res/res/values-sw600dp/config.xml
index 5a007ce..7f57ded 100644
--- a/core/res/res/values-sw600dp/config.xml
+++ b/core/res/res/values-sw600dp/config.xml
@@ -40,5 +40,6 @@
<!-- Use a larger scaling span for larger screen devices. -->
<dimen name="config_minScalingSpan">32mm</dimen>
+ <integer name="config_dockedStackDividerSnapMode">1</integer>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c03d471..d13a622 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2410,4 +2410,11 @@
<!-- Default bounds [left top right bottom] on screen for picture-in-picture windows. -->
<string translatable="false" name="config_defaultPictureInPictureBounds">"0 0 100 100"</string>
+
+ <!-- Controls the snap mode for the docked stack divider
+ 0 - 3 snap targets: left/top has 16:9 ratio, 1:1, and right/bottom has 16:9 ratio
+ 1 - 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio)
+ 2 - 1 snap target: 1:1
+ -->
+ <integer name="config_dockedStackDividerSnapMode">0</integer>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index ef6b467..37b2c12 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -429,4 +429,6 @@
<item type="dimen" format="integer" name="time_picker_column_start_material">0</item>
<item type="dimen" format="integer" name="time_picker_column_end_material">1</item>
+
+ <item type="fraction" name="docked_stack_divider_fixed_ratio">34.15%</item>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3c1d944..845c8c9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1492,6 +1492,8 @@
<java-symbol type="bool" name="target_honeycomb_needs_options_menu" />
<java-symbol type="dimen" name="docked_stack_divider_thickness" />
<java-symbol type="dimen" name="docked_stack_divider_insets" />
+ <java-symbol type="integer" name="config_dockedStackDividerSnapMode" />
+ <java-symbol type="fraction" name="docked_stack_divider_fixed_ratio" />
<java-symbol type="dimen" name="navigation_bar_height" />
<java-symbol type="dimen" name="navigation_bar_height_landscape" />
<java-symbol type="dimen" name="navigation_bar_width" />
diff --git a/include/androidfw/AssetManager.h b/include/androidfw/AssetManager.h
index 3d4e47d..914ac3d 100644
--- a/include/androidfw/AssetManager.h
+++ b/include/androidfw/AssetManager.h
@@ -100,16 +100,17 @@
* then on success, *cookie is set to the value corresponding to the
* newly-added asset source.
*/
- bool addAssetPath(const String8& path, int32_t* cookie, bool appAsLib=false);
+ bool addAssetPath(const String8& path, int32_t* cookie,
+ bool appAsLib=false, bool isSystemAsset=false);
bool addOverlayPath(const String8& path, int32_t* cookie);
- /*
+ /*
* Convenience for adding the standard system assets. Uses the
* ANDROID_ROOT environment variable to find them.
*/
bool addDefaultAssets();
- /*
+ /*
* Iterate over the asset paths in this manager. (Previously
* added via addAssetPath() and addDefaultAssets().) On first call,
* 'cookie' must be 0, resulting in the first cookie being returned.
@@ -118,7 +119,7 @@
*/
int32_t nextAssetPath(const int32_t cookie) const;
- /*
+ /*
* Return an asset path in the manager. 'which' must be between 0 and
* countAssetPaths().
*/
@@ -221,11 +222,11 @@
* the current data.
*/
bool isUpToDate();
-
+
/**
* Get the known locales for this asset manager object.
*/
- void getLocales(Vector<String8>* locales) const;
+ void getLocales(Vector<String8>* locales, bool includeSystemLocales=true) const;
/**
* Generate idmap data to translate resources IDs between a package and a
@@ -237,11 +238,13 @@
private:
struct asset_path
{
- asset_path() : path(""), type(kFileTypeRegular), idmap(""), isSystemOverlay(false) {}
+ asset_path() : path(""), type(kFileTypeRegular), idmap(""),
+ isSystemOverlay(false), isSystemAsset(false) {}
String8 path;
FileType type;
String8 idmap;
bool isSystemOverlay;
+ bool isSystemAsset;
};
Asset* openInPathLocked(const char* fileName, AccessMode mode,
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index 49b6333..428a2b8 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -1552,9 +1552,9 @@
status_t add(Asset* asset, const int32_t cookie=-1, bool copyData=false);
status_t add(Asset* asset, Asset* idmapAsset, const int32_t cookie=-1, bool copyData=false,
- bool appAsLib=false);
+ bool appAsLib=false, bool isSystemAsset=false);
- status_t add(ResTable* src);
+ status_t add(ResTable* src, bool isSystemAsset=false);
status_t addEmpty(const int32_t cookie);
status_t getError() const;
@@ -1822,9 +1822,9 @@
// Return the configurations (ResTable_config) that we know about
void getConfigurations(Vector<ResTable_config>* configs, bool ignoreMipmap=false,
- bool ignoreAndroidPackage=false) const;
+ bool ignoreAndroidPackage=false, bool includeSystemConfigs=true) const;
- void getLocales(Vector<String8>* locales) const;
+ void getLocales(Vector<String8>* locales, bool includeSystemLocales=true) const;
// Generate an idmap.
//
@@ -1860,7 +1860,7 @@
typedef Vector<Type*> TypeList;
status_t addInternal(const void* data, size_t size, const void* idmapData, size_t idmapDataSize,
- bool appAsLib, const int32_t cookie, bool copyData);
+ bool appAsLib, const int32_t cookie, bool copyData, bool isSystemAsset=false);
ssize_t getResourcePackageIndex(uint32_t resID) const;
@@ -1873,10 +1873,11 @@
size_t nameLen, uint32_t* outTypeSpecFlags) const;
status_t parsePackage(
- const ResTable_package* const pkg, const Header* const header, bool appAsLib);
+ const ResTable_package* const pkg, const Header* const header,
+ bool appAsLib, bool isSystemAsset);
void print_value(const Package* pkg, const Res_value& value) const;
-
+
mutable Mutex mLock;
status_t mError;
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 8a03b94..6913f43 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -176,7 +176,8 @@
delete[] mVendor;
}
-bool AssetManager::addAssetPath(const String8& path, int32_t* cookie, bool appAsLib)
+bool AssetManager::addAssetPath(
+ const String8& path, int32_t* cookie, bool appAsLib, bool isSystemAsset)
{
AutoMutex _l(mLock);
@@ -222,6 +223,7 @@
}
delete manifestAsset;
+ ap.isSystemAsset = isSystemAsset;
mAssetPaths.add(ap);
// new paths are always added at the end
@@ -233,6 +235,7 @@
// Load overlays, if any
asset_path oap;
for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {
+ oap.isSystemAsset = isSystemAsset;
mAssetPaths.add(oap);
}
#endif
@@ -340,7 +343,7 @@
String8 path(root);
path.appendPath(kSystemAssets);
- return addAssetPath(path, NULL);
+ return addAssetPath(path, NULL, false /* appAsLib */, true /* isSystemAsset */);
}
int32_t AssetManager::nextAssetPath(const int32_t cookie) const
@@ -682,10 +685,10 @@
ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
if (sharedRes != NULL) {
ALOGV("Copying existing resources for %s", ap.path.string());
- mResources->add(sharedRes);
+ mResources->add(sharedRes, ap.isSystemAsset);
} else {
ALOGV("Parsing resources for %s", ap.path.string());
- mResources->add(ass, idmap, nextEntryIdx + 1, !shared, appAsLib);
+ mResources->add(ass, idmap, nextEntryIdx + 1, !shared, appAsLib, ap.isSystemAsset);
}
onlyEmptyResources = false;
@@ -831,11 +834,11 @@
return mZipSet.isUpToDate();
}
-void AssetManager::getLocales(Vector<String8>* locales) const
+void AssetManager::getLocales(Vector<String8>* locales, bool includeSystemLocales) const
{
ResTable* res = mResources;
if (res != NULL) {
- res->getLocales(locales);
+ res->getLocales(locales, includeSystemLocales);
}
const size_t numLocales = locales->size();
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 21b543e..44f92c7 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -3080,13 +3080,16 @@
// table that defined the package); the ones after are skins on top of it.
struct ResTable::PackageGroup
{
- PackageGroup(ResTable* _owner, const String16& _name, uint32_t _id, bool appAsLib)
+ PackageGroup(
+ ResTable* _owner, const String16& _name, uint32_t _id,
+ bool appAsLib, bool _isSystemAsset)
: owner(_owner)
, name(_name)
, id(_id)
, largestTypeId(0)
, bags(NULL)
, dynamicRefTable(static_cast<uint8_t>(_id), appAsLib)
+ , isSystemAsset(_isSystemAsset)
{ }
~PackageGroup() {
@@ -3178,6 +3181,10 @@
// by having these tables in a per-package scope rather than
// per-package-group.
DynamicRefTable dynamicRefTable;
+
+ // If the package group comes from a system asset. Used in
+ // determining non-system locales.
+ const bool isSystemAsset;
};
struct ResTable::bag_set
@@ -3572,8 +3579,9 @@
copyData);
}
-status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData,
- bool appAsLib) {
+status_t ResTable::add(
+ Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData,
+ bool appAsLib, bool isSystemAsset) {
const void* data = asset->getBuffer(true);
if (data == NULL) {
ALOGW("Unable to get buffer of resource asset file");
@@ -3592,20 +3600,21 @@
}
return addInternal(data, static_cast<size_t>(asset->getLength()),
- idmapData, idmapSize, appAsLib, cookie, copyData);
+ idmapData, idmapSize, appAsLib, cookie, copyData, isSystemAsset);
}
-status_t ResTable::add(ResTable* src)
+status_t ResTable::add(ResTable* src, bool isSystemAsset)
{
mError = src->mError;
- for (size_t i=0; i<src->mHeaders.size(); i++) {
+ for (size_t i=0; i < src->mHeaders.size(); i++) {
mHeaders.add(src->mHeaders[i]);
}
- for (size_t i=0; i<src->mPackageGroups.size(); i++) {
+ for (size_t i=0; i < src->mPackageGroups.size(); i++) {
PackageGroup* srcPg = src->mPackageGroups[i];
- PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id, false);
+ PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id,
+ false /* appAsLib */, isSystemAsset || srcPg->isSystemAsset);
for (size_t j=0; j<srcPg->packages.size(); j++) {
pg->packages.add(srcPg->packages[j]);
}
@@ -3646,7 +3655,7 @@
}
status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize,
- bool appAsLib, const int32_t cookie, bool copyData)
+ bool appAsLib, const int32_t cookie, bool copyData, bool isSystemAsset)
{
if (!data) {
return NO_ERROR;
@@ -3749,7 +3758,8 @@
return (mError=BAD_TYPE);
}
- if (parsePackage((ResTable_package*)chunk, header, appAsLib) != NO_ERROR) {
+ if (parsePackage(
+ (ResTable_package*)chunk, header, appAsLib, isSystemAsset) != NO_ERROR) {
return mError;
}
curPackage++;
@@ -5663,7 +5673,7 @@
}
void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMipmap,
- bool ignoreAndroidPackage) const {
+ bool ignoreAndroidPackage, bool includeSystemConfigs) const {
const size_t packageCount = mPackageGroups.size();
String16 android("android");
for (size_t i = 0; i < packageCount; i++) {
@@ -5671,6 +5681,9 @@
if (ignoreAndroidPackage && android == packageGroup->name) {
continue;
}
+ if (!includeSystemConfigs && packageGroup->isSystemAsset) {
+ continue;
+ }
const size_t typeCount = packageGroup->types.size();
for (size_t j = 0; j < typeCount; j++) {
const TypeList& typeList = packageGroup->types[j];
@@ -5707,11 +5720,14 @@
}
}
-void ResTable::getLocales(Vector<String8>* locales) const
+void ResTable::getLocales(Vector<String8>* locales, bool includeSystemLocales) const
{
Vector<ResTable_config> configs;
ALOGV("calling getConfigurations");
- getConfigurations(&configs);
+ getConfigurations(&configs,
+ false /* ignoreMipmap */,
+ false /* ignoreAndroidPackage */,
+ includeSystemLocales /* includeSystemConfigs */);
ALOGV("called getConfigurations size=%d", (int)configs.size());
const size_t I = configs.size();
@@ -5937,7 +5953,7 @@
}
status_t ResTable::parsePackage(const ResTable_package* const pkg,
- const Header* const header, bool appAsLib)
+ const Header* const header, bool appAsLib, bool isSystemAsset)
{
const uint8_t* base = (const uint8_t*)pkg;
status_t err = validate_chunk(&pkg->header, sizeof(*pkg) - sizeof(pkg->typeIdOffset),
@@ -5985,8 +6001,8 @@
if (id >= 256) {
LOG_ALWAYS_FATAL("Package id out of range");
return NO_ERROR;
- } else if (id == 0 || appAsLib) {
- // This is a library so assign an ID
+ } else if (id == 0 || appAsLib || isSystemAsset) {
+ // This is a library or a system asset, so assign an ID
id = mNextPackageId++;
}
@@ -6018,7 +6034,7 @@
char16_t tmpName[sizeof(pkg->name)/sizeof(pkg->name[0])];
strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(pkg->name[0]));
- group = new PackageGroup(this, String16(tmpName), id, appAsLib);
+ group = new PackageGroup(this, String16(tmpName), id, appAsLib, isSystemAsset);
if (group == NULL) {
delete package;
return (mError=NO_MEMORY);
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index f48c509..11056d4 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -108,6 +108,7 @@
hwui_src_files += \
BakedOpDispatcher.cpp \
BakedOpRenderer.cpp \
+ BakedOpState.cpp \
OpReorderer.cpp \
RecordingCanvas.cpp
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 0f0768f..332a204 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -79,7 +79,9 @@
.setTransform(Matrix4::identity(), TransformFlags::None)
.setModelViewIdentityEmptyBounds()
.build();
- renderer.renderGlop(nullptr, opList.clipSideFlags ? &opList.clip : nullptr, glop);
+ ClipRect renderTargetClip(opList.clip);
+ const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
+ renderer.renderGlop(nullptr, clip, glop);
}
void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer,
@@ -183,7 +185,9 @@
.setTransform(Matrix4::identity(), TransformFlags::None)
.setModelViewIdentityEmptyBounds()
.build();
- renderer.renderGlop(nullptr, opList.clipSideFlags ? &opList.clip : nullptr, glop);
+ ClipRect renderTargetClip(opList.clip);
+ const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
+ renderer.renderGlop(nullptr, clip, glop);
}
static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
@@ -224,7 +228,7 @@
};
static void renderTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state,
- const Rect* renderClip, TextRenderType renderType) {
+ const ClipBase* renderClip, TextRenderType renderType) {
FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
@@ -272,7 +276,7 @@
bool forceFinish = (renderType == TextRenderType::Flush);
bool mustDirtyRenderTarget = renderer.offscreenRenderTarget();
- const Rect* localOpClip = pureTranslate ? &state.computedState.clipRect : nullptr;
+ const Rect* localOpClip = pureTranslate ? &state.computedState.clipRect() : nullptr;
fontRenderer.renderPosText(op.paint, localOpClip,
(const char*) op.glyphs, op.glyphCount, x, y,
op.positions, mustDirtyRenderTarget ? &layerBounds : nullptr, &functor, forceFinish);
@@ -287,7 +291,8 @@
void BakedOpDispatcher::onMergedTextOps(BakedOpRenderer& renderer,
const MergedBakedOpList& opList) {
- const Rect* clip = opList.clipSideFlags ? &opList.clip : nullptr;
+ ClipRect renderTargetClip(opList.clip);
+ const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
for (size_t i = 0; i < opList.count; i++) {
const BakedOpState& state = *(opList.states[i]);
const TextOp& op = *(static_cast<const TextOp*>(state.op));
@@ -701,14 +706,13 @@
}
void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) {
- const Rect* clip = state.computedState.clipSideFlags ? &state.computedState.clipRect : nullptr;
- renderTextOp(renderer, op, state, clip, TextRenderType::Flush);
+ renderTextOp(renderer, op, state, state.computedState.getClipIfNeeded(), TextRenderType::Flush);
}
void BakedOpDispatcher::onTextOnPathOp(BakedOpRenderer& renderer, const TextOnPathOp& op, const BakedOpState& state) {
// Note: can't trust clipSideFlags since we record with unmappedBounds == clip.
// TODO: respect clipSideFlags, once we record with bounds
- const Rect* renderTargetClip = &state.computedState.clipRect;
+ auto renderTargetClip = state.computedState.clipState;
FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
fontRenderer.setFont(op.paint, SkMatrix::I());
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index f8282dc..a0d5fae 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -60,27 +60,67 @@
}
void BakedOpRenderer::endLayer() {
+ if (mRenderTarget.stencil) {
+ // if stencil was used for clipping, detach it and return it to pool
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
+ LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "glfbrb endlayer failed");
+ mCaches.renderBufferCache.put(mRenderTarget.stencil);
+ mRenderTarget.stencil = nullptr;
+ }
+ mRenderTarget.lastStencilClip = nullptr;
+
mRenderTarget.offscreenBuffer->updateMeshFromRegion();
- mRenderTarget.offscreenBuffer = nullptr;
+ mRenderTarget.offscreenBuffer = nullptr; // It's in drawLayerOp's hands now.
// Detach the texture from the FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "endLayer FAILED");
mRenderState.deleteFramebuffer(mRenderTarget.frameBufferId);
- mRenderTarget.frameBufferId = -1;
+ mRenderTarget.frameBufferId = 0;
}
void BakedOpRenderer::startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {
+ LOG_ALWAYS_FATAL_IF(mRenderTarget.frameBufferId != 0, "primary framebufferId must be 0");
mRenderState.bindFramebuffer(0);
setViewport(width, height);
- mCaches.clearGarbage();
if (!mOpaque) {
clearColorBuffer(repaintRect);
}
+
+ mRenderState.debugOverdraw(true, true);
}
-void BakedOpRenderer::endFrame() {
+void BakedOpRenderer::endFrame(const Rect& repaintRect) {
+ if (CC_UNLIKELY(Properties::debugOverdraw)) {
+ ClipRect overdrawClip(repaintRect);
+ Rect viewportRect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight);
+ // overdraw visualization
+ for (int i = 1; i <= 4; i++) {
+ if (i < 4) {
+ // nth level of overdraw tests for n+1 draws per pixel
+ mRenderState.stencil().enableDebugTest(i + 1, false);
+ } else {
+ // 4th level tests for 4 or higher draws per pixel
+ mRenderState.stencil().enableDebugTest(4, true);
+ }
+
+ SkPaint paint;
+ paint.setColor(mCaches.getOverdrawColor(i));
+ Glop glop;
+ GlopBuilder(mRenderState, mCaches, &glop)
+ .setRoundRectClipState(nullptr)
+ .setMeshUnitQuad()
+ .setFillPaint(paint, 1.0f)
+ .setTransform(Matrix4::identity(), TransformFlags::None)
+ .setModelViewMapUnitToRect(viewportRect)
+ .build();
+ renderGlop(nullptr, &overdrawClip, glop);
+ }
+ mRenderState.stencil().disable();
+ }
+
+ mCaches.clearGarbage();
mCaches.pathCache.trim();
mCaches.tessellationCache.trim();
@@ -128,12 +168,121 @@
return texture;
}
-void BakedOpRenderer::prepareRender(const Rect* dirtyBounds, const Rect* clip) {
+// clears and re-fills stencil with provided rendertarget space quads,
+// and then put stencil into test mode
+void BakedOpRenderer::setupStencilQuads(std::vector<Vertex>& quadVertices,
+ int incrementThreshold) {
+ mRenderState.stencil().enableWrite(incrementThreshold);
+ mRenderState.stencil().clear();
+ Glop glop;
+ GlopBuilder(mRenderState, mCaches, &glop)
+ .setRoundRectClipState(nullptr)
+ .setMeshIndexedQuads(quadVertices.data(), quadVertices.size() / 4)
+ .setFillBlack()
+ .setTransform(Matrix4::identity(), TransformFlags::None)
+ .setModelViewIdentityEmptyBounds()
+ .build();
+ mRenderState.render(glop, mRenderTarget.orthoMatrix);
+ mRenderState.stencil().enableTest(incrementThreshold);
+}
+
+void BakedOpRenderer::setupStencilRectList(const ClipBase* clip) {
+ auto&& rectList = reinterpret_cast<const ClipRectList*>(clip)->rectList;
+ int quadCount = rectList.getTransformedRectanglesCount();
+ std::vector<Vertex> rectangleVertices;
+ rectangleVertices.reserve(quadCount * 4);
+ for (int i = 0; i < quadCount; i++) {
+ const TransformedRectangle& tr(rectList.getTransformedRectangle(i));
+ const Matrix4& transform = tr.getTransform();
+ Rect bounds = tr.getBounds();
+ if (transform.rectToRect()) {
+ // If rectToRect, can simply map bounds before storing verts
+ transform.mapRect(bounds);
+ bounds.doIntersect(clip->rect);
+ if (bounds.isEmpty()) {
+ continue; // will be outside of scissor, skip
+ }
+ }
+
+ rectangleVertices.push_back(Vertex{bounds.left, bounds.top});
+ rectangleVertices.push_back(Vertex{bounds.right, bounds.top});
+ rectangleVertices.push_back(Vertex{bounds.left, bounds.bottom});
+ rectangleVertices.push_back(Vertex{bounds.right, bounds.bottom});
+
+ if (!transform.rectToRect()) {
+ // If not rectToRect, must map each point individually
+ for (auto cur = rectangleVertices.end() - 4; cur < rectangleVertices.end(); cur++) {
+ transform.mapPoint(cur->x, cur->y);
+ }
+ }
+ }
+ setupStencilQuads(rectangleVertices, rectList.getTransformedRectanglesCount());
+}
+
+void BakedOpRenderer::setupStencilRegion(const ClipBase* clip) {
+ auto&& region = reinterpret_cast<const ClipRegion*>(clip)->region;
+
+ std::vector<Vertex> regionVertices;
+ SkRegion::Cliperator it(region, clip->rect.toSkIRect());
+ while (!it.done()) {
+ const SkIRect& r = it.rect();
+ regionVertices.push_back(Vertex{(float)r.fLeft, (float)r.fTop});
+ regionVertices.push_back(Vertex{(float)r.fRight, (float)r.fTop});
+ regionVertices.push_back(Vertex{(float)r.fLeft, (float)r.fBottom});
+ regionVertices.push_back(Vertex{(float)r.fRight, (float)r.fBottom});
+ it.next();
+ }
+ setupStencilQuads(regionVertices, 0);
+}
+
+void BakedOpRenderer::prepareRender(const Rect* dirtyBounds, const ClipBase* clip) {
+ // prepare scissor / stencil
mRenderState.scissor().setEnabled(clip != nullptr);
if (clip) {
- mRenderState.scissor().set(clip->left, mRenderTarget.viewportHeight - clip->bottom,
- clip->getWidth(), clip->getHeight());
+ mRenderState.scissor().set(mRenderTarget.viewportHeight, clip->rect);
}
+
+ if (CC_LIKELY(!Properties::debugOverdraw)) {
+ // only modify stencil mode and content when it's not used for overdraw visualization
+ if (CC_UNLIKELY(clip && clip->mode != ClipMode::Rectangle)) {
+ // NOTE: this pointer check is only safe for non-rect clips,
+ // since rect clips may be created on the stack
+ if (mRenderTarget.lastStencilClip != clip) {
+ // Stencil needed, but current stencil isn't up to date
+ mRenderTarget.lastStencilClip = clip;
+
+ if (mRenderTarget.frameBufferId != 0 && !mRenderTarget.stencil) {
+ OffscreenBuffer* layer = mRenderTarget.offscreenBuffer;
+ mRenderTarget.stencil = mCaches.renderBufferCache.get(
+ Stencil::getLayerStencilFormat(),
+ layer->texture.width, layer->texture.height);
+ // stencil is bound + allocated - associate it with current FBO
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, mRenderTarget.stencil->getName());
+ }
+
+ if (clip->mode == ClipMode::RectangleList) {
+ setupStencilRectList(clip);
+ } else {
+ setupStencilRegion(clip);
+ }
+ } else {
+ // stencil is up to date - just need to ensure it's enabled (since an unclipped
+ // or scissor-only clipped op may have been drawn, disabling the stencil)
+ int incrementThreshold = 0;
+ if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) {
+ auto&& rectList = reinterpret_cast<const ClipRectList*>(clip)->rectList;
+ incrementThreshold = rectList.getTransformedRectanglesCount();
+ }
+ mRenderState.stencil().enableTest(incrementThreshold);
+ }
+ } else {
+ // either scissor or no clip, so disable stencil test
+ mRenderState.stencil().disable();
+ }
+ }
+
+ // dirty offscreenbuffer
if (dirtyBounds && mRenderTarget.offscreenBuffer) {
// register layer damage to draw-back region
android::Rect dirty(dirtyBounds->left, dirtyBounds->top,
@@ -142,17 +291,18 @@
}
}
-void BakedOpRenderer::renderGlop(const Rect* dirtyBounds, const Rect* clip, const Glop& glop) {
+void BakedOpRenderer::renderGlop(const Rect* dirtyBounds, const ClipBase* clip,
+ const Glop& glop) {
prepareRender(dirtyBounds, clip);
mRenderState.render(glop, mRenderTarget.orthoMatrix);
if (!mRenderTarget.frameBufferId) mHasDrawn = true;
}
void BakedOpRenderer::renderFunctor(const FunctorOp& op, const BakedOpState& state) {
- prepareRender(&state.computedState.clippedBounds, &state.computedState.clipRect);
+ prepareRender(&state.computedState.clippedBounds, state.computedState.getClipIfNeeded());
DrawGlInfo info;
- auto&& clip = state.computedState.clipRect;
+ auto&& clip = state.computedState.clipRect();
info.clipLeft = clip.left;
info.clipTop = clip.top;
info.clipRight = clip.right;
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index f158e8b..65e8b29 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -27,6 +27,7 @@
struct Glop;
class Layer;
class RenderState;
+struct ClipBase;
/**
* Main rendering manager for a collection of work - one frame + any contained FBOs.
@@ -59,7 +60,7 @@
Caches& caches() { return mCaches; }
void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect);
- void endFrame();
+ void endFrame(const Rect& repaintRect);
OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height);
void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect);
void endLayer();
@@ -68,21 +69,23 @@
const LightInfo& getLightInfo() const { return mLightInfo; }
void renderGlop(const BakedOpState& state, const Glop& glop) {
- bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None;
renderGlop(&state.computedState.clippedBounds,
- useScissor ? &state.computedState.clipRect : nullptr,
+ state.computedState.getClipIfNeeded(),
glop);
}
void renderFunctor(const FunctorOp& op, const BakedOpState& state);
- void renderGlop(const Rect* dirtyBounds, const Rect* clip, const Glop& glop);
+ void renderGlop(const Rect* dirtyBounds, const ClipBase* clip, const Glop& glop);
bool offscreenRenderTarget() { return mRenderTarget.offscreenBuffer != nullptr; }
void dirtyRenderTarget(const Rect& dirtyRect);
bool didDraw() const { return mHasDrawn; }
private:
void setViewport(uint32_t width, uint32_t height);
void clearColorBuffer(const Rect& clearRect);
- void prepareRender(const Rect* dirtyBounds, const Rect* clip);
+ void prepareRender(const Rect* dirtyBounds, const ClipBase* clip);
+ void setupStencilRectList(const ClipBase* clip);
+ void setupStencilRegion(const ClipBase* clip);
+ void setupStencilQuads(std::vector<Vertex>& quadVertices, int incrementThreshold);
RenderState& mRenderState;
Caches& mCaches;
@@ -92,10 +95,23 @@
// render target state - setup by start/end layer/frame
// only valid to use in between start/end pairs.
struct {
+ // If not drawing to a layer: fbo = 0, offscreenBuffer = null,
+ // Otherwise these refer to currently painting layer's state
GLuint frameBufferId = 0;
OffscreenBuffer* offscreenBuffer = nullptr;
+
+ // Used when drawing to a layer and using stencil clipping. otherwise null.
+ RenderBuffer* stencil = nullptr;
+
+ // value representing the ClipRectList* or ClipRegion* currently stored in
+ // the stencil of the current render target
+ const ClipBase* lastStencilClip = nullptr;
+
+ // Size of renderable region in current render target - for layers, may not match actual
+ // bounds of FBO texture. offscreenBuffer->texture has this information.
uint32_t viewportWidth = 0;
uint32_t viewportHeight = 0;
+
Matrix4 orthoMatrix;
} mRenderTarget;
diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp
new file mode 100644
index 0000000..e6b943a
--- /dev/null
+++ b/libs/hwui/BakedOpState.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#include "BakedOpState.h"
+
+#include "ClipArea.h"
+
+namespace android {
+namespace uirenderer {
+
+ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
+ const RecordedOp& recordedOp, bool expandForStroke) {
+ // resolvedMatrix = parentMatrix * localMatrix
+ transform.loadMultiply(*snapshot.transform, recordedOp.localMatrix);
+
+ // resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
+ clippedBounds = recordedOp.unmappedBounds;
+ if (CC_UNLIKELY(expandForStroke)) {
+ // account for non-hairline stroke
+ clippedBounds.outset(recordedOp.paint->getStrokeWidth() * 0.5f);
+ }
+ transform.mapRect(clippedBounds);
+ if (CC_UNLIKELY(expandForStroke
+ && (!transform.isPureTranslate() || recordedOp.paint->getStrokeWidth() < 1.0f))) {
+ // account for hairline stroke when stroke may be < 1 scaled pixel
+ // Non translate || strokeWidth < 1 is conservative, but will cover all cases
+ clippedBounds.outset(0.5f);
+ }
+
+ // resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
+ clipState = snapshot.mutateClipArea().serializeIntersectedClip(allocator,
+ recordedOp.localClip, *(snapshot.transform));
+ LOG_ALWAYS_FATAL_IF(!clipState, "must clip!");
+
+ const Rect& clipRect = clipState->rect;
+ if (CC_UNLIKELY(clipRect.isEmpty() || !clippedBounds.intersects(clipRect))) {
+ // Rejected based on either empty clip, or bounds not intersecting with clip
+ if (clipState) {
+ allocator.rewindIfLastAlloc(clipState);
+ clipState = nullptr;
+ }
+ clippedBounds.setEmpty();
+ } else {
+ // Not rejected! compute true clippedBounds and clipSideFlags
+ if (clipRect.left > clippedBounds.left) clipSideFlags |= OpClipSideFlags::Left;
+ if (clipRect.top > clippedBounds.top) clipSideFlags |= OpClipSideFlags::Top;
+ if (clipRect.right < clippedBounds.right) clipSideFlags |= OpClipSideFlags::Right;
+ if (clipRect.bottom < clippedBounds.bottom) clipSideFlags |= OpClipSideFlags::Bottom;
+ clippedBounds.doIntersect(clipRect);
+ }
+}
+
+ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot) {
+ transform = *snapshot.transform;
+
+ // Since the op doesn't have known bounds, we conservatively set the mapped bounds
+ // to the current clipRect, and clipSideFlags to Full.
+ clipState = snapshot.mutateClipArea().serializeClip(allocator);
+ LOG_ALWAYS_FATAL_IF(!clipState, "clipState required");
+ clippedBounds = clipState->rect;
+ transform.mapRect(clippedBounds);
+ clipSideFlags = OpClipSideFlags::Full;
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index b12c0c9..9df4e3a 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -52,89 +52,35 @@
*/
class ResolvedRenderState {
public:
- // TODO: remove the mapRects/matrix multiply when snapshot & recorded transforms are translates
- ResolvedRenderState(const Snapshot& snapshot, const RecordedOp& recordedOp, bool expandForStroke) {
- /* TODO: benchmark a fast path for translate-only matrices, such as:
- if (CC_LIKELY(snapshot.transform->getType() == Matrix4::kTypeTranslate
- && recordedOp.localMatrix.getType() == Matrix4::kTypeTranslate)) {
- float translateX = snapshot.transform->getTranslateX() + recordedOp.localMatrix.getTranslateX();
- float translateY = snapshot.transform->getTranslateY() + recordedOp.localMatrix.getTranslateY();
- transform.loadTranslate(translateX, translateY, 0);
+ ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
+ const RecordedOp& recordedOp, bool expandForStroke);
- // resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
- clipRect = recordedOp.localClipRect;
- clipRect.translate(translateX, translateY);
- clipRect.doIntersect(snapshot.getClipRect());
- clipRect.snapToPixelBoundaries();
-
- // resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
- clippedBounds = recordedOp.unmappedBounds;
- clippedBounds.translate(translateX, translateY);
- } ... */
-
- // resolvedMatrix = parentMatrix * localMatrix
- transform.loadMultiply(*snapshot.transform, recordedOp.localMatrix);
-
- // resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
- clipRect = recordedOp.localClipRect;
- snapshot.transform->mapRect(clipRect);
- clipRect.doIntersect(snapshot.getRenderTargetClip());
- clipRect.snapToPixelBoundaries();
-
- // resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
- clippedBounds = recordedOp.unmappedBounds;
- if (CC_UNLIKELY(expandForStroke)) {
- // account for non-hairline stroke
- clippedBounds.outset(recordedOp.paint->getStrokeWidth() * 0.5f);
- }
- transform.mapRect(clippedBounds);
- if (CC_UNLIKELY(expandForStroke
- && (!transform.isPureTranslate() || recordedOp.paint->getStrokeWidth() < 1.0f))) {
- // account for hairline stroke when stroke may be < 1 scaled pixel
- // Non translate || strokeWidth < 1 is conservative, but will cover all cases
- clippedBounds.outset(0.5f);
- }
-
- if (clipRect.left > clippedBounds.left) clipSideFlags |= OpClipSideFlags::Left;
- if (clipRect.top > clippedBounds.top) clipSideFlags |= OpClipSideFlags::Top;
- if (clipRect.right < clippedBounds.right) clipSideFlags |= OpClipSideFlags::Right;
- if (clipRect.bottom < clippedBounds.bottom) clipSideFlags |= OpClipSideFlags::Bottom;
- clippedBounds.doIntersect(clipRect);
-
- /**
- * TODO: once we support complex clips, we may want to reject to avoid that work where
- * possible. Should we:
- * 1 - quickreject based on clippedBounds, quick early (duplicating logic in resolvedOp)
- * 2 - merge stuff into tryConstruct factory method, so it can handle quickRejection
- * and early return null in one place.
- */
- }
-
- /**
- * Constructor for unbounded ops without transform/clip (namely shadows)
- *
- * Since the op doesn't have known bounds, we conservatively set the mapped bounds
- * to the current clipRect, and clipSideFlags to Full.
- */
- ResolvedRenderState(const Snapshot& snapshot) {
- transform = *snapshot.transform;
- clipRect = snapshot.getRenderTargetClip();
- clippedBounds = clipRect;
- transform.mapRect(clippedBounds);
- clipSideFlags = OpClipSideFlags::Full;
- }
+ // Constructor for unbounded ops without transform/clip (namely shadows)
+ ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot);
Rect computeLocalSpaceClip() const {
Matrix4 inverse;
inverse.loadInverse(transform);
- Rect outClip(clipRect);
+ Rect outClip(clipRect());
inverse.mapRect(outClip);
return outClip;
}
Matrix4 transform;
- Rect clipRect;
+ const Rect& clipRect() const {
+ return clipState->rect;
+ }
+ bool requiresClip() const {
+ return clipSideFlags != OpClipSideFlags::None
+ || CC_UNLIKELY(clipState->mode != ClipMode::Rectangle);
+ }
+
+ // returns the clip if it's needed to draw the operation, otherwise nullptr
+ const ClipBase* getClipIfNeeded() const {
+ return requiresClip() ? clipState : nullptr;
+ }
+ const ClipBase* clipState = nullptr;
int clipSideFlags = 0;
Rect clippedBounds;
};
@@ -147,8 +93,9 @@
class BakedOpState {
public:
static BakedOpState* tryConstruct(LinearAllocator& allocator,
- const Snapshot& snapshot, const RecordedOp& recordedOp) {
- BakedOpState* bakedState = new (allocator) BakedOpState(snapshot, recordedOp, false);
+ Snapshot& snapshot, const RecordedOp& recordedOp) {
+ BakedOpState* bakedState = new (allocator) BakedOpState(
+ allocator, snapshot, recordedOp, false);
if (bakedState->computedState.clippedBounds.isEmpty()) {
// bounds are empty, so op is rejected
allocator.rewindIfLastAlloc(bakedState);
@@ -165,13 +112,13 @@
};
static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator,
- const Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
+ Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined)
? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
: true;
BakedOpState* bakedState = new (allocator) BakedOpState(
- snapshot, recordedOp, expandForStroke);
+ allocator, snapshot, recordedOp, expandForStroke);
if (bakedState->computedState.clippedBounds.isEmpty()) {
// bounds are empty, so op is rejected
allocator.rewindIfLastAlloc(bakedState);
@@ -181,11 +128,11 @@
}
static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
- const Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
+ Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
if (snapshot.getRenderTargetClip().isEmpty()) return nullptr;
// clip isn't empty, so construct the op
- return new (allocator) BakedOpState(snapshot, shadowOpPtr);
+ return new (allocator) BakedOpState(allocator, snapshot, shadowOpPtr);
}
static void* operator new(size_t size, LinearAllocator& allocator) {
@@ -202,15 +149,16 @@
const RecordedOp* op;
private:
- BakedOpState(const Snapshot& snapshot, const RecordedOp& recordedOp, bool expandForStroke)
- : computedState(snapshot, recordedOp, expandForStroke)
+ BakedOpState(LinearAllocator& allocator, Snapshot& snapshot,
+ const RecordedOp& recordedOp, bool expandForStroke)
+ : computedState(allocator, snapshot, recordedOp, expandForStroke)
, alpha(snapshot.alpha)
, roundRectClipState(snapshot.roundRectClipState)
, projectionPathMask(snapshot.projectionPathMask)
, op(&recordedOp) {}
- BakedOpState(const Snapshot& snapshot, const ShadowOp* shadowOpPtr)
- : computedState(snapshot)
+ BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const ShadowOp* shadowOpPtr)
+ : computedState(allocator, snapshot)
, alpha(snapshot.alpha)
, roundRectClipState(snapshot.roundRectClipState)
, projectionPathMask(snapshot.projectionPathMask)
diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp
index cf76e6b..cf2726b 100644
--- a/libs/hwui/CanvasState.cpp
+++ b/libs/hwui/CanvasState.cpp
@@ -45,6 +45,21 @@
}
}
+void CanvasState::initializeRecordingSaveStack(int viewportWidth, int viewportHeight) {
+ if (mWidth != viewportWidth || mHeight != viewportHeight) {
+ mWidth = viewportWidth;
+ mHeight = viewportHeight;
+ mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight);
+ mCanvas.onViewportInitialized();
+ }
+
+ freeAllSnapshots();
+ mSnapshot = allocSnapshot(&mFirstSnapshot,
+ SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ mSnapshot->setRelativeLightCenter(Vector3());
+ mSaveCount = 1;
+}
+
void CanvasState::initializeSaveStack(
int viewportWidth, int viewportHeight,
float clipLeft, float clipTop,
diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h
index 4709ef4..b9e87ae 100644
--- a/libs/hwui/CanvasState.h
+++ b/libs/hwui/CanvasState.h
@@ -80,6 +80,12 @@
* Initializes the first snapshot, computing the projection matrix,
* and stores the dimensions of the render target.
*/
+ void initializeRecordingSaveStack(int viewportWidth, int viewportHeight);
+
+ /**
+ * Initializes the first snapshot, computing the projection matrix,
+ * and stores the dimensions of the render target.
+ */
void initializeSaveStack(int viewportWidth, int viewportHeight,
float clipLeft, float clipTop, float clipRight, float clipBottom,
const Vector3& lightCenter);
@@ -168,6 +174,7 @@
void freeAllSnapshots();
/// indicates that the clip has been changed since the last time it was consumed
+ // TODO: delete when switching to HWUI_NEW_OPS
bool mDirtyClip;
/// Dimensions of the drawing surface
diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp
index 5f166ca..160090d 100644
--- a/libs/hwui/ClipArea.cpp
+++ b/libs/hwui/ClipArea.cpp
@@ -15,10 +15,11 @@
*/
#include "ClipArea.h"
+#include "utils/LinearAllocator.h"
+
#include <SkPath.h>
#include <limits>
-
-#include "Rect.h"
+#include <type_traits>
namespace android {
namespace uirenderer {
@@ -171,12 +172,18 @@
return rectangleListAsRegion;
}
+void RectangleList::transform(const Matrix4& transform) {
+ for (int index = 0; index < mTransformedRectanglesCount; index++) {
+ mTransformedRectangles[index].transform(transform);
+ }
+}
+
/*
* ClipArea
*/
ClipArea::ClipArea()
- : mMode(Mode::Rectangle) {
+ : mMode(ClipMode::Rectangle) {
}
/*
@@ -184,39 +191,44 @@
*/
void ClipArea::setViewportDimensions(int width, int height) {
+ mPostViewportClipObserved = false;
mViewportBounds.set(0, 0, width, height);
mClipRect = mViewportBounds;
}
void ClipArea::setEmpty() {
- mMode = Mode::Rectangle;
+ onClipUpdated();
+ mMode = ClipMode::Rectangle;
mClipRect.setEmpty();
mClipRegion.setEmpty();
mRectangleList.setEmpty();
}
void ClipArea::setClip(float left, float top, float right, float bottom) {
- mMode = Mode::Rectangle;
+ onClipUpdated();
+ mMode = ClipMode::Rectangle;
mClipRect.set(left, top, right, bottom);
mClipRegion.setEmpty();
}
void ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform,
SkRegion::Op op) {
+ onClipUpdated();
switch (mMode) {
- case Mode::Rectangle:
+ case ClipMode::Rectangle:
rectangleModeClipRectWithTransform(r, transform, op);
break;
- case Mode::RectangleList:
+ case ClipMode::RectangleList:
rectangleListModeClipRectWithTransform(r, transform, op);
break;
- case Mode::Region:
+ case ClipMode::Region:
regionModeClipRectWithTransform(r, transform, op);
break;
}
}
void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
+ onClipUpdated();
enterRegionMode();
mClipRegion.op(region, op);
onClipRegionUpdated();
@@ -224,6 +236,7 @@
void ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform,
SkRegion::Op op) {
+ onClipUpdated();
SkMatrix skTransform;
transform->copyTo(skTransform);
SkPath transformed;
@@ -241,7 +254,7 @@
// Entering rectangle mode discards any
// existing clipping information from the other modes.
// The only way this occurs is by a clip setting operation.
- mMode = Mode::Rectangle;
+ mMode = ClipMode::Rectangle;
}
void ClipArea::rectangleModeClipRectWithTransform(const Rect& r,
@@ -276,8 +289,8 @@
// Is is only legal to enter rectangle list mode from
// rectangle mode, since rectangle list mode cannot represent
// all clip areas that can be represented by a region.
- ALOG_ASSERT(mMode == Mode::Rectangle);
- mMode = Mode::RectangleList;
+ ALOG_ASSERT(mMode == ClipMode::Rectangle);
+ mMode = ClipMode::RectangleList;
mRectangleList.set(mClipRect, Matrix4::identity());
}
@@ -295,12 +308,11 @@
*/
void ClipArea::enterRegionMode() {
- Mode oldMode = mMode;
- mMode = Mode::Region;
- if (oldMode != Mode::Region) {
- if (oldMode == Mode::Rectangle) {
- mClipRegion.setRect(mClipRect.left, mClipRect.top,
- mClipRect.right, mClipRect.bottom);
+ ClipMode oldMode = mMode;
+ mMode = ClipMode::Region;
+ if (oldMode != ClipMode::Region) {
+ if (oldMode == ClipMode::Rectangle) {
+ mClipRegion.setRect(mClipRect.toSkIRect());
} else {
mClipRegion = mRectangleList.convertToRegion(createViewportRegion());
onClipRegionUpdated();
@@ -330,5 +342,172 @@
}
}
+/**
+ * Clip serialization
+ */
+
+const ClipBase* ClipArea::serializeClip(LinearAllocator& allocator) {
+ if (!mPostViewportClipObserved) {
+ // Only initial clip-to-viewport observed, so no serialization of clip necessary
+ return nullptr;
+ }
+
+ static_assert(std::is_trivially_destructible<Rect>::value,
+ "expect Rect to be trivially destructible");
+ static_assert(std::is_trivially_destructible<RectangleList>::value,
+ "expect RectangleList to be trivially destructible");
+
+ if (mLastSerialization == nullptr) {
+ switch (mMode) {
+ case ClipMode::Rectangle:
+ mLastSerialization = allocator.create<ClipRect>(mClipRect);
+ break;
+ case ClipMode::RectangleList:
+ mLastSerialization = allocator.create<ClipRectList>(mRectangleList);
+ break;
+ case ClipMode::Region:
+ mLastSerialization = allocator.create<ClipRegion>(mClipRegion);
+ break;
+ }
+ }
+ return mLastSerialization;
+}
+
+inline static const Rect& getRect(const ClipBase* scb) {
+ return reinterpret_cast<const ClipRect*>(scb)->rect;
+}
+
+inline static const RectangleList& getRectList(const ClipBase* scb) {
+ return reinterpret_cast<const ClipRectList*>(scb)->rectList;
+}
+
+inline static const SkRegion& getRegion(const ClipBase* scb) {
+ return reinterpret_cast<const ClipRegion*>(scb)->region;
+}
+
+// Conservative check for too many rectangles to fit in rectangle list.
+// For simplicity, doesn't account for rect merging
+static bool cannotFitInRectangleList(const ClipArea& clipArea, const ClipBase* scb) {
+ int currentRectCount = clipArea.isRectangleList()
+ ? clipArea.getRectangleList().getTransformedRectanglesCount()
+ : 1;
+ int recordedRectCount = (scb->mode == ClipMode::RectangleList)
+ ? getRectList(scb).getTransformedRectanglesCount()
+ : 1;
+ return currentRectCount + recordedRectCount > RectangleList::kMaxTransformedRectangles;
+}
+
+const ClipBase* ClipArea::serializeIntersectedClip(LinearAllocator& allocator,
+ const ClipBase* recordedClip, const Matrix4& recordedClipTransform) {
+ // if no recordedClip passed, just serialize current state
+ if (!recordedClip) return serializeClip(allocator);
+
+ if (!mLastResolutionResult
+ || recordedClip != mLastResolutionClip
+ || recordedClipTransform != mLastResolutionTransform) {
+ mLastResolutionClip = recordedClip;
+ mLastResolutionTransform = recordedClipTransform;
+
+ if (CC_LIKELY(mMode == ClipMode::Rectangle
+ && recordedClip->mode == ClipMode::Rectangle
+ && recordedClipTransform.rectToRect())) {
+ // common case - result is a single rectangle
+ auto rectClip = allocator.create<ClipRect>(getRect(recordedClip));
+ recordedClipTransform.mapRect(rectClip->rect);
+ rectClip->rect.doIntersect(mClipRect);
+ mLastResolutionResult = rectClip;
+ } else if (CC_UNLIKELY(mMode == ClipMode::Region
+ || recordedClip->mode == ClipMode::Region
+ || cannotFitInRectangleList(*this, recordedClip))) {
+ // region case
+ SkRegion other;
+ switch (recordedClip->mode) {
+ case ClipMode::Rectangle:
+ if (CC_LIKELY(recordedClipTransform.rectToRect())) {
+ // simple transform, skip creating SkPath
+ Rect resultClip(getRect(recordedClip));
+ recordedClipTransform.mapRect(resultClip);
+ other.setRect(resultClip.toSkIRect());
+ } else {
+ SkPath transformedRect = pathFromTransformedRectangle(getRect(recordedClip),
+ recordedClipTransform);
+ other.setPath(transformedRect, createViewportRegion());
+ }
+ break;
+ case ClipMode::RectangleList: {
+ RectangleList transformedList(getRectList(recordedClip));
+ transformedList.transform(recordedClipTransform);
+ other = transformedList.convertToRegion(createViewportRegion());
+ break;
+ }
+ case ClipMode::Region:
+ other = getRegion(recordedClip);
+
+ // TODO: handle non-translate transforms properly!
+ other.translate(recordedClipTransform.getTranslateX(),
+ recordedClipTransform.getTranslateY());
+ }
+
+ ClipRegion* regionClip = allocator.create<ClipRegion>();
+ switch (mMode) {
+ case ClipMode::Rectangle:
+ regionClip->region.op(mClipRect.toSkIRect(), other, SkRegion::kIntersect_Op);
+ break;
+ case ClipMode::RectangleList:
+ regionClip->region.op(mRectangleList.convertToRegion(createViewportRegion()),
+ other, SkRegion::kIntersect_Op);
+ break;
+ case ClipMode::Region:
+ regionClip->region.op(mClipRegion, other, SkRegion::kIntersect_Op);
+ break;
+ }
+ regionClip->rect.set(regionClip->region.getBounds());
+ mLastResolutionResult = regionClip;
+ } else {
+ auto rectListClip = allocator.create<ClipRectList>(mRectangleList);
+ auto&& rectList = rectListClip->rectList;
+ if (mMode == ClipMode::Rectangle) {
+ rectList.set(mClipRect, Matrix4::identity());
+ }
+
+ if (recordedClip->mode == ClipMode::Rectangle) {
+ rectList.intersectWith(getRect(recordedClip), recordedClipTransform);
+ } else {
+ const RectangleList& other = getRectList(recordedClip);
+ for (int i = 0; i < other.getTransformedRectanglesCount(); i++) {
+ auto&& tr = other.getTransformedRectangle(i);
+ Matrix4 totalTransform(recordedClipTransform);
+ totalTransform.multiply(tr.getTransform());
+ rectList.intersectWith(tr.getBounds(), totalTransform);
+ }
+ }
+ rectListClip->rect = rectList.calculateBounds();
+ mLastResolutionResult = rectListClip;
+ }
+ }
+ return mLastResolutionResult;
+}
+
+void ClipArea::applyClip(const ClipBase* clip, const Matrix4& transform) {
+ if (!clip) return; // nothing to do
+
+ if (CC_LIKELY(clip->mode == ClipMode::Rectangle)) {
+ clipRectWithTransform(getRect(clip), &transform, SkRegion::kIntersect_Op);
+ } else if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) {
+ auto&& rectList = getRectList(clip);
+ for (int i = 0; i < rectList.getTransformedRectanglesCount(); i++) {
+ auto&& tr = rectList.getTransformedRectangle(i);
+ Matrix4 totalTransform(transform);
+ totalTransform.multiply(tr.getTransform());
+ clipRectWithTransform(tr.getBounds(), &totalTransform, SkRegion::kIntersect_Op);
+ }
+ } else {
+ SkRegion region(getRegion(clip));
+ // TODO: handle non-translate transforms properly!
+ region.translate(transform.getTranslateX(), transform.getTranslateY());
+ clipRegion(region, SkRegion::kIntersect_Op);
+ }
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/ClipArea.h b/libs/hwui/ClipArea.h
index 268301c..479796d 100644
--- a/libs/hwui/ClipArea.h
+++ b/libs/hwui/ClipArea.h
@@ -16,15 +16,17 @@
#ifndef CLIPAREA_H
#define CLIPAREA_H
-#include <SkRegion.h>
-
#include "Matrix.h"
#include "Rect.h"
#include "utils/Pair.h"
+#include <SkRegion.h>
+
namespace android {
namespace uirenderer {
+class LinearAllocator;
+
Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform);
class TransformedRectangle {
@@ -50,6 +52,12 @@
return mTransform;
}
+ void transform(const Matrix4& transform) {
+ Matrix4 t;
+ t.loadMultiply(transform, mTransform);
+ mTransform = t;
+ }
+
private:
Rect mBounds;
Matrix4 mTransform;
@@ -66,27 +74,62 @@
void setEmpty();
void set(const Rect& bounds, const Matrix4& transform);
bool intersectWith(const Rect& bounds, const Matrix4& transform);
+ void transform(const Matrix4& transform);
SkRegion convertToRegion(const SkRegion& clip) const;
Rect calculateBounds() const;
-private:
enum {
kMaxTransformedRectangles = 5
};
+private:
int mTransformedRectanglesCount;
TransformedRectangle mTransformedRectangles[kMaxTransformedRectangles];
};
-class ClipArea {
-private:
- enum class Mode {
- Rectangle,
- Region,
- RectangleList
- };
+enum class ClipMode {
+ Rectangle,
+ RectangleList,
+ // region and path - intersected. if either is empty, don't use
+ Region
+};
+
+struct ClipBase {
+ ClipBase(ClipMode mode)
+ : mode(mode) {}
+ ClipBase(const Rect& rect)
+ : mode(ClipMode::Rectangle)
+ , rect(rect) {}
+ const ClipMode mode;
+ // Bounds of the clipping area, used to define the scissor, and define which
+ // portion of the stencil is updated/used
+ Rect rect;
+};
+
+struct ClipRect : ClipBase {
+ ClipRect(const Rect& rect)
+ : ClipBase(rect) {}
+};
+
+struct ClipRectList : ClipBase {
+ ClipRectList(const RectangleList& rectList)
+ : ClipBase(ClipMode::RectangleList)
+ , rectList(rectList) {}
+ RectangleList rectList;
+};
+
+struct ClipRegion : ClipBase {
+ ClipRegion(const SkRegion& region)
+ : ClipBase(ClipMode::Region)
+ , region(region) {}
+ ClipRegion()
+ : ClipBase(ClipMode::Region) {}
+ SkRegion region;
+};
+
+class ClipArea {
public:
ClipArea();
@@ -117,17 +160,22 @@
}
bool isRegion() const {
- return Mode::Region == mMode;
+ return ClipMode::Region == mMode;
}
bool isSimple() const {
- return mMode == Mode::Rectangle;
+ return mMode == ClipMode::Rectangle;
}
bool isRectangleList() const {
- return mMode == Mode::RectangleList;
+ return mMode == ClipMode::RectangleList;
}
+ const ClipBase* serializeClip(LinearAllocator& allocator);
+ const ClipBase* serializeIntersectedClip(LinearAllocator& allocator,
+ const ClipBase* recordedClip, const Matrix4& recordedClipTransform);
+ void applyClip(const ClipBase* recordedClip, const Matrix4& recordedClipTransform);
+
private:
void enterRectangleMode();
void rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op);
@@ -145,6 +193,13 @@
void ensureClipRegion();
void onClipRegionUpdated();
+ // Called by every state modifying public method.
+ void onClipUpdated() {
+ mPostViewportClipObserved = true;
+ mLastSerialization = nullptr;
+ mLastResolutionResult = nullptr;
+ }
+
SkRegion createViewportRegion() {
return SkRegion(mViewportBounds.toSkIRect());
}
@@ -155,7 +210,22 @@
pathAsRegion.setPath(path, createViewportRegion());
}
- Mode mMode;
+ ClipMode mMode;
+ bool mPostViewportClipObserved = false;
+
+ /**
+ * If mLastSerialization is non-null, it represents an already serialized copy
+ * of the current clip state. If null, it has not been computed.
+ */
+ const ClipBase* mLastSerialization = nullptr;
+
+ /**
+ * This pair of pointers is a single entry cache of most recently seen
+ */
+ const ClipBase* mLastResolutionResult = nullptr;
+ const ClipBase* mLastResolutionClip = nullptr;
+ Matrix4 mLastResolutionTransform;
+
Rect mViewportBounds;
Rect mClipRect;
SkRegion mClipRegion;
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index ff4dc4a..9994498 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -47,6 +47,7 @@
#if HWUI_NEW_OPS
class BakedOpState;
class BakedOpRenderer;
+struct ClipBase;
#else
class OpenGLRenderer;
#endif
@@ -57,7 +58,7 @@
#if HWUI_NEW_OPS
BakedOpRenderer* renderer,
const BakedOpState* bakedState,
- const Rect* clip,
+ const ClipBase* clip,
#else
OpenGLRenderer* renderer,
#endif
@@ -81,7 +82,7 @@
#if HWUI_NEW_OPS
BakedOpRenderer* renderer;
const BakedOpState* bakedState;
- const Rect* clip;
+ const ClipBase* clip;
#else
OpenGLRenderer* renderer;
#endif
diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h
index bcf819e..e72f396 100644
--- a/libs/hwui/Glop.h
+++ b/libs/hwui/Glop.h
@@ -64,7 +64,7 @@
// Canvas transform isn't applied to the mesh at draw time,
//since it's already built in.
- MeshIgnoresCanvasTransform = 1 << 1, // TODO: remove
+ MeshIgnoresCanvasTransform = 1 << 1, // TODO: remove for HWUI_NEW_OPS
};
};
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index 2507ff3..45fc16c 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -78,7 +78,7 @@
mOutGlop->mesh.vertices = {
vbo,
VertexAttribFlags::TextureCoord,
- nullptr, nullptr, nullptr,
+ nullptr, (const void*) kMeshTextureOffset, nullptr,
kTextureVertexStride };
mOutGlop->mesh.elementCount = elementCount;
return *this;
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index ad9559f..11b2c8a 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -460,7 +460,7 @@
deferBeginLayerOp(*new (mAllocator) BeginLayerOp(
saveLayerBounds,
Matrix4::identity(),
- saveLayerBounds,
+ nullptr, // no record-time clip - need only respect defer-time one
&saveLayerPaint));
deferNodeOps(node);
deferEndLayerOp(*new (mAllocator) EndLayerOp());
@@ -604,7 +604,7 @@
mCanvasState.getLocalClipBounds(),
mCanvasState.currentSnapshot()->getRelativeLightCenter());
BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
- mAllocator, *mCanvasState.currentSnapshot(), shadowOp);
+ mAllocator, *mCanvasState.writableSnapshot(), shadowOp);
if (CC_LIKELY(bakedOpState)) {
currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow);
}
@@ -681,10 +681,10 @@
if (op.renderNode->nothingToDraw()) return;
int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
- // apply state from RecordedOp
+ // apply state from RecordedOp (clip first, since op's clip is transformed by current matrix)
+ mCanvasState.writableSnapshot()->mutateClipArea().applyClip(op.localClip,
+ *mCanvasState.currentSnapshot()->transform);
mCanvasState.concatMatrix(op.localMatrix);
- mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top,
- op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op);
// then apply state from node properties, and defer ops
deferNodePropsAndOps(*op.renderNode);
@@ -706,7 +706,7 @@
BakedOpState::StrokeBehavior strokeBehavior) {
// Note: here we account for stroke when baking the op
BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
- mAllocator, *mCanvasState.currentSnapshot(), op, strokeBehavior);
+ mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior);
if (!bakedState) return; // quick rejected
currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
}
@@ -769,7 +769,7 @@
const OvalOp* resolvedOp = new (mAllocator) OvalOp(
unmappedBounds,
op.localMatrix,
- op.localClipRect,
+ op.localClip,
op.paint);
deferOvalOp(*resolvedOp);
}
@@ -829,7 +829,7 @@
const RoundRectOp* resolvedOp = new (mAllocator) RoundRectOp(
Rect(*(op.left), *(op.top), *(op.right), *(op.bottom)),
op.localMatrix,
- op.localClipRect,
+ op.localClip,
op.paint, *op.rx, *op.ry);
deferRoundRectOp(*resolvedOp);
}
@@ -953,7 +953,7 @@
LayerOp* drawLayerOp = new (mAllocator) LayerOp(
beginLayerOp.unmappedBounds,
beginLayerOp.localMatrix,
- beginLayerOp.localClipRect,
+ beginLayerOp.localClip,
beginLayerOp.paint,
&mLayerReorderers[finishedLayerIndex].offscreenBuffer);
BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index dbbce8b..4e9b5e6 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -192,7 +192,7 @@
const LayerReorderer& fbo0 = mLayerReorderers[0];
renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
fbo0.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
- renderer.endFrame();
+ renderer.endFrame(fbo0.repaintRect);
}
void dump() const {
@@ -223,7 +223,7 @@
LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; }
BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) {
- return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
+ return BakedOpState::tryConstruct(mAllocator, *mCanvasState.writableSnapshot(), recordedOp);
}
// should always be surrounded by a save/restore pair, and not called if DisplayList is null
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index cfdd0d2..1971530 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -33,6 +33,7 @@
namespace android {
namespace uirenderer {
+struct ClipBase;
class OffscreenBuffer;
class RenderNode;
struct Vertex;
@@ -91,10 +92,10 @@
static_assert(RecordedOpId::ArcOp == 0,
"First index must be zero for LUTs to work");
-#define BASE_PARAMS const Rect& unmappedBounds, const Matrix4& localMatrix, const Rect& localClipRect, const SkPaint* paint
-#define BASE_PARAMS_PAINTLESS const Rect& unmappedBounds, const Matrix4& localMatrix, const Rect& localClipRect
-#define SUPER(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClipRect, paint)
-#define SUPER_PAINTLESS(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClipRect, nullptr)
+#define BASE_PARAMS const Rect& unmappedBounds, const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint
+#define BASE_PARAMS_PAINTLESS const Rect& unmappedBounds, const Matrix4& localMatrix, const ClipBase* localClip
+#define SUPER(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClip, paint)
+#define SUPER_PAINTLESS(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClip, nullptr)
struct RecordedOp {
/* ID from RecordedOpId - generally used for jumping into function tables */
@@ -106,8 +107,8 @@
/* transform in recording space (vs DisplayList origin) */
const Matrix4 localMatrix;
- /* clip in recording space */
- const Rect localClipRect;
+ /* clip in recording space - nullptr if not clipped */
+ const ClipBase* localClip;
/* optional paint, stored in base object to simplify merging logic */
const SkPaint* paint;
@@ -116,7 +117,7 @@
: opId(opId)
, unmappedBounds(unmappedBounds)
, localMatrix(localMatrix)
- , localClipRect(localClipRect)
+ , localClip(localClip)
, paint(paint) {}
};
@@ -187,9 +188,9 @@
};
struct CirclePropsOp : RecordedOp {
- CirclePropsOp(const Matrix4& localMatrix, const Rect& localClipRect, const SkPaint* paint,
+ CirclePropsOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint,
float* x, float* y, float* radius)
- : RecordedOp(RecordedOpId::CirclePropsOp, Rect(), localMatrix, localClipRect, paint)
+ : RecordedOp(RecordedOpId::CirclePropsOp, Rect(), localMatrix, localClip, paint)
, x(x)
, y(y)
, radius(radius) {}
@@ -259,9 +260,9 @@
};
struct RoundRectPropsOp : RecordedOp {
- RoundRectPropsOp(const Matrix4& localMatrix, const Rect& localClipRect, const SkPaint* paint,
+ RoundRectPropsOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint,
float* left, float* top, float* right, float* bottom, float *rx, float *ry)
- : RecordedOp(RecordedOpId::RoundRectPropsOp, Rect(), localMatrix, localClipRect, paint)
+ : RecordedOp(RecordedOpId::RoundRectPropsOp, Rect(), localMatrix, localClip, paint)
, left(left)
, top(top)
, right(right)
@@ -286,12 +287,13 @@
*/
struct ShadowOp : RecordedOp {
ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath,
- const Rect& clipRect, const Vector3& lightCenter)
- : RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), clipRect, nullptr)
+ const Rect& localClipRect, const Vector3& lightCenter)
+ : RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), nullptr, nullptr)
, shadowMatrixXY(casterOp.localMatrix)
, shadowMatrixZ(casterOp.localMatrix)
, casterAlpha(casterAlpha)
, casterPath(casterPath)
+ , localClipRect(localClipRect)
, lightCenter(lightCenter) {
const RenderNode& node = *casterOp.renderNode;
node.applyViewPropertyTransforms(shadowMatrixXY, false);
@@ -301,6 +303,7 @@
Matrix4 shadowMatrixZ;
const float casterAlpha;
const SkPath* casterPath;
+ const Rect localClipRect;
const Vector3 lightCenter;
};
@@ -374,7 +377,7 @@
*/
struct EndLayerOp : RecordedOp {
EndLayerOp()
- : RecordedOp(RecordedOpId::EndLayerOp, Rect(), Matrix4::identity(), Rect(), nullptr) {}
+ : RecordedOp(RecordedOpId::EndLayerOp, Rect(), Matrix4::identity(), nullptr, nullptr) {}
};
/**
@@ -388,13 +391,13 @@
LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle)
: SUPER_PAINTLESS(LayerOp)
, layerHandle(layerHandle)
- , alpha(paint->getAlpha() / 255.0f)
+ , alpha(paint ? paint->getAlpha() / 255.0f : 1.0f)
, mode(PaintUtils::getXfermodeDirect(paint))
- , colorFilter(paint->getColorFilter())
+ , colorFilter(paint ? paint->getColorFilter() : nullptr)
, destroy(true) {}
LayerOp(RenderNode& node)
- : RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), Rect(node.getWidth(), node.getHeight()), nullptr)
+ : RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), nullptr, nullptr)
, layerHandle(node.getLayerHandle())
, alpha(node.properties().layerProperties().alpha() / 255.0f)
, mode(node.properties().layerProperties().xferMode())
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index f75d8d4..f7f6caf 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -39,7 +39,7 @@
"prepareDirty called a second time during a recording!");
mDisplayList = new DisplayList();
- mState.initializeSaveStack(width, height, 0, 0, width, height, Vector3());
+ mState.initializeRecordingSaveStack(width, height);
mDeferredBarrierType = DeferredBarrierType::InOrder;
mState.setDirtyClip(false);
@@ -155,6 +155,8 @@
return saveValue;
}
+ auto previousClip = getRecordedClip(); // note: done while snapshot == previous
+
snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight());
snapshot.transform->loadTranslate(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
@@ -167,7 +169,7 @@
addOp(new (alloc()) BeginLayerOp(
Rect(left, top, right, bottom),
*previous.transform, // transform to *draw* with
- previous.getRenderTargetClip(), // clip to *draw* with
+ previousClip, // clip to *draw* with
refPaint(paint)));
return saveValue;
@@ -229,11 +231,10 @@
}
void RecordingCanvas::drawPaint(const SkPaint& paint) {
- // TODO: more efficient recording?
addOp(new (alloc()) RectOp(
- mState.getRenderTargetClipBounds(),
+ mState.getRenderTargetClipBounds(), // OK, since we've not passed transform
Matrix4::identity(),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(&paint)));
}
@@ -253,7 +254,7 @@
addOp(new (alloc()) PointsOp(
calcBoundsOfPoints(points, floatCount),
*mState.currentSnapshot()->transform,
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
}
@@ -264,7 +265,7 @@
addOp(new (alloc()) LinesOp(
calcBoundsOfPoints(points, floatCount),
*mState.currentSnapshot()->transform,
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
}
@@ -272,7 +273,7 @@
addOp(new (alloc()) RectOp(
Rect(left, top, right, bottom),
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(&paint)));
}
@@ -305,7 +306,7 @@
addOp(new (alloc()) SimpleRectsOp(
Rect(left, top, right, bottom),
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(paint), rectData, vertexCount));
}
@@ -339,7 +340,7 @@
addOp(new (alloc()) RoundRectOp(
Rect(left, top, right, bottom),
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(&paint), rx, ry));
}
@@ -358,7 +359,7 @@
refBitmapsInShader(paint->value.getShader());
addOp(new (alloc()) RoundRectPropsOp(
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
&paint->value,
&left->value, &top->value, &right->value, &bottom->value,
&rx->value, &ry->value));
@@ -380,7 +381,7 @@
refBitmapsInShader(paint->value.getShader());
addOp(new (alloc()) CirclePropsOp(
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
&paint->value,
&x->value, &y->value, &radius->value));
}
@@ -390,7 +391,7 @@
addOp(new (alloc()) OvalOp(
Rect(left, top, right, bottom),
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(&paint)));
}
@@ -399,7 +400,7 @@
addOp(new (alloc()) ArcOp(
Rect(left, top, right, bottom),
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(&paint),
startAngle, sweepAngle, useCenter));
}
@@ -408,7 +409,7 @@
addOp(new (alloc()) PathOp(
Rect(path.getBounds()),
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(&paint), refPath(&path)));
}
@@ -459,7 +460,7 @@
addOp(new (alloc()) BitmapRectOp(
Rect(dstLeft, dstTop, dstRight, dstBottom),
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(paint), refBitmap(bitmap),
Rect(srcLeft, srcTop, srcRight, srcBottom)));
}
@@ -471,7 +472,7 @@
addOp(new (alloc()) BitmapMeshOp(
calcBoundsOfPoints(vertices, vertexCount * 2),
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(paint), refBitmap(bitmap), meshWidth, meshHeight,
refBuffer<float>(vertices, vertexCount * 2), // 2 floats per vertex
refBuffer<int>(colors, vertexCount))); // 1 color per vertex
@@ -483,7 +484,7 @@
addOp(new (alloc()) PatchOp(
Rect(dstLeft, dstTop, dstRight, dstBottom),
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(paint), refBitmap(bitmap), refPatch(&patch)));
}
@@ -499,7 +500,7 @@
addOp(new (alloc()) TextOp(
Rect(boundsLeft, boundsTop, boundsRight, boundsBottom),
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(&paint), glyphs, positions, glyphCount, x, y));
drawTextDecorations(x, y, totalAdvance, paint);
}
@@ -509,9 +510,9 @@
if (!glyphs || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
addOp(new (alloc()) TextOnPathOp(
- mState.getRenderTargetClipBounds(), // TODO: explicitly define bounds
+ mState.getLocalClipBounds(), // TODO: explicitly define bounds
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(&paint), glyphs, glyphCount, refPath(&path), hOffset, vOffset));
}
@@ -519,7 +520,7 @@
addOp(new (alloc()) BitmapOp(
Rect(bitmap->width(), bitmap->height()),
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
refPaint(paint), refBitmap(*bitmap)));
}
@@ -528,7 +529,7 @@
RenderNodeOp* op = new (alloc()) RenderNodeOp(
Rect(stagingProps.getWidth(), stagingProps.getHeight()),
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
renderNode);
int opIndex = addOp(op);
int childIndex = mDisplayList->addChild(op);
@@ -554,16 +555,16 @@
addOp(new (alloc()) TextureLayerOp(
Rect(layer->getWidth(), layer->getHeight()),
totalTransform,
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
layer));
}
void RecordingCanvas::callDrawGLFunction(Functor* functor) {
mDisplayList->functors.push_back(functor);
addOp(new (alloc()) FunctorOp(
- mState.getRenderTargetClipBounds(), // TODO: explicitly define bounds
+ mState.getLocalClipBounds(), // TODO: explicitly define bounds
*(mState.currentSnapshot()->transform),
- mState.getRenderTargetClipBounds(),
+ getRecordedClip(),
functor));
}
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 470f9ec..1a2ac97f 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -36,6 +36,7 @@
namespace android {
namespace uirenderer {
+struct ClipBase;
class DeferredLayerUpdater;
struct RecordedOp;
@@ -199,6 +200,9 @@
virtual bool drawTextAbsolutePos() const override { return false; }
private:
+ const ClipBase* getRecordedClip() {
+ return mState.writableSnapshot()->mutateClipArea().serializeClip(alloc());
+ }
void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint);
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 194aa57..5fac3a1 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -167,6 +167,7 @@
const SkRegion& getClipRegion() const { return mClipArea->getClipRegion(); }
bool clipIsSimple() const { return mClipArea->isSimple(); }
const ClipArea& getClipArea() const { return *mClipArea; }
+ ClipArea& mutateClipArea() { return *mClipArea; }
/**
* Resets the clip to the specified rect.
diff --git a/libs/hwui/renderstate/Scissor.cpp b/libs/hwui/renderstate/Scissor.cpp
index 95dcd18..61dd8c3 100644
--- a/libs/hwui/renderstate/Scissor.cpp
+++ b/libs/hwui/renderstate/Scissor.cpp
@@ -15,6 +15,8 @@
*/
#include "renderstate/Scissor.h"
+#include "Rect.h"
+
#include <utils/Log.h>
namespace android {
@@ -71,6 +73,26 @@
return false;
}
+void Scissor::set(int viewportHeight, const Rect& clip) {
+ // transform to Y-flipped GL space, and prevent negatives
+ GLint x = std::max(0, (int)clip.left);
+ GLint y = std::max(0, viewportHeight - (int)clip.bottom);
+ GLint width = std::max(0, ((int)clip.right) - x);
+ GLint height = std::max(0, (viewportHeight - (int)clip.top) - y);
+
+ if (x != mScissorX
+ || y != mScissorY
+ || width != mScissorWidth
+ || height != mScissorHeight) {
+ glScissor(x, y, width, height);
+
+ mScissorX = x;
+ mScissorY = y;
+ mScissorWidth = width;
+ mScissorHeight = height;
+ }
+}
+
void Scissor::reset() {
mScissorX = mScissorY = mScissorWidth = mScissorHeight = 0;
}
diff --git a/libs/hwui/renderstate/Scissor.h b/libs/hwui/renderstate/Scissor.h
index b37ec58..f302244 100644
--- a/libs/hwui/renderstate/Scissor.h
+++ b/libs/hwui/renderstate/Scissor.h
@@ -22,11 +22,14 @@
namespace android {
namespace uirenderer {
+class Rect;
+
class Scissor {
friend class RenderState;
public:
bool setEnabled(bool enabled);
bool set(GLint x, GLint y, GLint width, GLint height);
+ void set(int viewportHeight, const Rect& clip);
void reset();
bool isEnabled() { return mEnabled; }
void dump();
diff --git a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
new file mode 100644
index 0000000..db6402c
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#include "TestSceneBase.h"
+
+class ClippingAnimation;
+
+static TestScene::Registrar _RectGrid(TestScene::Info{
+ "clip",
+ "Complex clip cases"
+ "Low CPU/GPU load.",
+ TestScene::simpleCreateScene<ClippingAnimation>
+});
+
+class ClippingAnimation : public TestScene {
+public:
+ sp<RenderNode> card;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+ card = TestUtils::createNode(0, 0, 200, 400,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ {
+ canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op);
+ canvas.translate(100, 100);
+ canvas.rotate(45);
+ canvas.translate(-100, -100);
+ canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op);
+ canvas.drawColor(Color::Blue_500, SkXfermode::kSrcOver_Mode);
+ }
+ canvas.restore();
+
+ canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ {
+ SkPath clipCircle;
+ clipCircle.addCircle(100, 300, 100);
+ canvas.clipPath(&clipCircle, SkRegion::kIntersect_Op);
+ canvas.drawColor(Color::Red_500, SkXfermode::kSrcOver_Mode);
+ }
+ canvas.restore();
+
+ // put on a layer, to test stencil attachment
+ props.mutateLayerProperties().setType(LayerType::RenderLayer);
+ props.setAlpha(0.9f);
+ });
+ canvas.drawRenderNode(card.get());
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+};
diff --git a/libs/hwui/tests/common/scenes/TestSceneBase.h b/libs/hwui/tests/common/scenes/TestSceneBase.h
index ac78124..935ddcf 100644
--- a/libs/hwui/tests/common/scenes/TestSceneBase.h
+++ b/libs/hwui/tests/common/scenes/TestSceneBase.h
@@ -22,6 +22,7 @@
#include "tests/common/TestContext.h"
#include "tests/common/TestScene.h"
#include "tests/common/TestUtils.h"
+#include "utils/Color.h"
#include <functional>
diff --git a/libs/hwui/tests/unit/BakedOpStateTests.cpp b/libs/hwui/tests/unit/BakedOpStateTests.cpp
index f9f5316..3fd822d 100644
--- a/libs/hwui/tests/unit/BakedOpStateTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpStateTests.cpp
@@ -17,6 +17,7 @@
#include <gtest/gtest.h>
#include <BakedOpState.h>
+#include <ClipArea.h>
#include <RecordedOp.h>
#include <tests/common/TestUtils.h>
@@ -24,31 +25,33 @@
namespace uirenderer {
TEST(ResolvedRenderState, construct) {
+ LinearAllocator allocator;
Matrix4 translate10x20;
translate10x20.loadTranslate(10, 20, 0);
SkPaint paint;
- RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, Rect(100, 200), &paint);
+ ClipRect clip(Rect(100, 200));
+ RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, &clip, &paint);
{
// recorded with transform, no parent transform
auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
- ResolvedRenderState state(*parentSnapshot, recordedOp, false);
+ ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
EXPECT_MATRIX_APPROX_EQ(state.transform, translate10x20);
- EXPECT_EQ(Rect(100, 200), state.clipRect);
+ EXPECT_EQ(Rect(100, 200), state.clipRect());
EXPECT_EQ(Rect(40, 60, 100, 200), state.clippedBounds); // translated and also clipped
EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags);
}
{
// recorded with transform and parent transform
auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
- ResolvedRenderState state(*parentSnapshot, recordedOp, false);
+ ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
Matrix4 expectedTranslate;
expectedTranslate.loadTranslate(20, 40, 0);
EXPECT_MATRIX_APPROX_EQ(expectedTranslate, state.transform);
// intersection of parent & transformed child clip
- EXPECT_EQ(Rect(10, 20, 100, 200), state.clipRect);
+ EXPECT_EQ(Rect(10, 20, 100, 200), state.clipRect());
// translated and also clipped
EXPECT_EQ(Rect(50, 80, 100, 200), state.clippedBounds);
@@ -57,22 +60,24 @@
}
TEST(ResolvedRenderState, computeLocalSpaceClip) {
+ LinearAllocator allocator;
Matrix4 translate10x20;
translate10x20.loadTranslate(10, 20, 0);
SkPaint paint;
- RectOp recordedOp(Rect(1000, 1000), translate10x20, Rect(100, 200), &paint);
+ ClipRect clip(Rect(100, 200));
+ RectOp recordedOp(Rect(1000, 1000), translate10x20, &clip, &paint);
{
// recorded with transform, no parent transform
auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
- ResolvedRenderState state(*parentSnapshot, recordedOp, false);
+ ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
EXPECT_EQ(Rect(-10, -20, 90, 180), state.computeLocalSpaceClip())
<< "Local clip rect should be 100x200, offset by -10,-20";
}
{
// recorded with transform + parent transform
auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
- ResolvedRenderState state(*parentSnapshot, recordedOp, false);
+ ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
EXPECT_EQ(Rect(-10, -20, 80, 160), state.computeLocalSpaceClip())
<< "Local clip rect should be 90x190, offset by -10,-20";
}
@@ -149,6 +154,7 @@
};
TEST(ResolvedRenderState, construct_expandForStroke) {
+ LinearAllocator allocator;
// Loop over table of test cases and verify different combinations of stroke width and transform
for (auto&& testCase : sStrokeTestCases) {
SkPaint strokedPaint;
@@ -156,14 +162,15 @@
strokedPaint.setStyle(SkPaint::kStroke_Style);
strokedPaint.setStrokeWidth(testCase.strokeWidth);
+ ClipRect clip(Rect(200, 200));
RectOp recordedOp(Rect(50, 50, 150, 150),
- Matrix4::identity(), Rect(200, 200), &strokedPaint);
+ Matrix4::identity(), &clip, &strokedPaint);
Matrix4 snapshotMatrix;
snapshotMatrix.loadScale(testCase.scale, testCase.scale, 1);
auto parentSnapshot = TestUtils::makeSnapshot(snapshotMatrix, Rect(200, 200));
- ResolvedRenderState state(*parentSnapshot, recordedOp, true);
+ ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, true);
testCase.validator(state);
}
}
@@ -175,8 +182,9 @@
translate100x0.loadTranslate(100, 0, 0);
SkPaint paint;
+ ClipRect clip(Rect(100, 200));
{
- RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, Rect(100, 200), &paint);
+ RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, &clip, &paint);
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
BakedOpState* bakedState = BakedOpState::tryConstruct(allocator, *snapshot, rejectOp);
@@ -184,7 +192,7 @@
EXPECT_GT(8u, allocator.usedSize()); // no significant allocation space used for rejected op
}
{
- RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), Rect(100, 200), &paint);
+ RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), &clip, &paint);
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
BakedOpState* bakedState = BakedOpState::tryConstruct(allocator, *snapshot, successOp);
@@ -218,7 +226,8 @@
SkPaint paint;
paint.setStyle(SkPaint::kStrokeAndFill_Style);
paint.setStrokeWidth(0.0f);
- RectOp rejectOp(Rect(100, 200), Matrix4::identity(), Rect(100, 200), &paint);
+ ClipRect clip(Rect(100, 200));
+ RectOp rejectOp(Rect(100, 200), Matrix4::identity(), &clip, &paint);
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect()); // Note: empty clip
auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp,
BakedOpState::StrokeBehavior::StyleDefined);
@@ -231,7 +240,8 @@
SkPaint paint;
paint.setStyle(SkPaint::kStrokeAndFill_Style);
paint.setStrokeWidth(10.0f);
- RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), Rect(200, 200), &paint);
+ ClipRect clip(Rect(200, 200));
+ RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint);
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200));
auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp,
BakedOpState::StrokeBehavior::StyleDefined);
@@ -245,7 +255,8 @@
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setStrokeWidth(10.0f);
- RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), Rect(200, 200), &paint);
+ ClipRect clip(Rect(200, 200));
+ RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint);
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200));
auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp,
BakedOpState::StrokeBehavior::Forced);
diff --git a/libs/hwui/tests/unit/ClipAreaTests.cpp b/libs/hwui/tests/unit/ClipAreaTests.cpp
index c4d305e..4cae737 100644
--- a/libs/hwui/tests/unit/ClipAreaTests.cpp
+++ b/libs/hwui/tests/unit/ClipAreaTests.cpp
@@ -119,5 +119,122 @@
EXPECT_EQ(expected, area.getClipRect());
}
+TEST(ClipArea, serializeClip) {
+ ClipArea area(createClipArea());
+ LinearAllocator allocator;
+
+ // unset clip
+ EXPECT_EQ(nullptr, area.serializeClip(allocator));
+
+ // rect clip
+ area.setClip(0, 0, 200, 200);
+ {
+ auto serializedClip = area.serializeClip(allocator);
+ ASSERT_NE(nullptr, serializedClip);
+ ASSERT_EQ(ClipMode::Rectangle, serializedClip->mode);
+ auto clipRect = reinterpret_cast<const ClipRect*>(serializedClip);
+ ASSERT_EQ(Rect(200, 200), clipRect->rect);
+ EXPECT_EQ(serializedClip, area.serializeClip(allocator))
+ << "Requery of clip on unmodified ClipArea must return same pointer.";
+ }
+
+ // rect list
+ Matrix4 rotate;
+ rotate.loadRotate(2.0f);
+ area.clipRectWithTransform(Rect(200, 200), &rotate, SkRegion::kIntersect_Op);
+ {
+ auto serializedClip = area.serializeClip(allocator);
+ ASSERT_NE(nullptr, serializedClip);
+ ASSERT_EQ(ClipMode::RectangleList, serializedClip->mode);
+ auto clipRectList = reinterpret_cast<const ClipRectList*>(serializedClip);
+ ASSERT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount());
+ EXPECT_EQ(serializedClip, area.serializeClip(allocator))
+ << "Requery of clip on unmodified ClipArea must return same pointer.";
+ }
+
+ // region
+ SkPath circlePath;
+ circlePath.addCircle(100, 100, 100);
+ area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kReplace_Op);
+ {
+ auto serializedClip = area.serializeClip(allocator);
+ ASSERT_NE(nullptr, serializedClip);
+ ASSERT_EQ(ClipMode::Region, serializedClip->mode);
+ auto clipRegion = reinterpret_cast<const ClipRegion*>(serializedClip);
+ ASSERT_EQ(SkIRect::MakeWH(200, 200), clipRegion->region.getBounds())
+ << "Clip region should be 200x200";
+ EXPECT_EQ(serializedClip, area.serializeClip(allocator))
+ << "Requery of clip on unmodified ClipArea must return same pointer.";
+ }
}
+
+TEST(ClipArea, serializeIntersectedClip) {
+ ClipArea area(createClipArea());
+ LinearAllocator allocator;
+
+ // simple state;
+ EXPECT_EQ(nullptr, area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity()));
+ area.setClip(0, 0, 200, 200);
+ {
+ auto origRectClip = area.serializeClip(allocator);
+ ASSERT_NE(nullptr, origRectClip);
+ EXPECT_EQ(origRectClip, area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity()));
+ }
+
+ // rect
+ {
+ ClipRect recordedClip(Rect(100, 100));
+ Matrix4 translateScale;
+ translateScale.loadTranslate(100, 100, 0);
+ translateScale.scale(2, 3, 1);
+ auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, translateScale);
+ ASSERT_NE(nullptr, resolvedClip);
+ ASSERT_EQ(ClipMode::Rectangle, resolvedClip->mode);
+ EXPECT_EQ(Rect(100, 100, 200, 200),
+ reinterpret_cast<const ClipRect*>(resolvedClip)->rect);
+
+ EXPECT_EQ(resolvedClip, area.serializeIntersectedClip(allocator, &recordedClip, translateScale))
+ << "Must return previous serialization, since input is same";
+
+ ClipRect recordedClip2(Rect(100, 100));
+ EXPECT_NE(resolvedClip, area.serializeIntersectedClip(allocator, &recordedClip2, translateScale))
+ << "Shouldn't return previous serialization, since matrix location is different";
+ }
+
+ // rect list
+ Matrix4 rotate;
+ rotate.loadRotate(2.0f);
+ area.clipRectWithTransform(Rect(200, 200), &rotate, SkRegion::kIntersect_Op);
+ {
+ ClipRect recordedClip(Rect(100, 100));
+ auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, Matrix4::identity());
+ ASSERT_NE(nullptr, resolvedClip);
+ ASSERT_EQ(ClipMode::RectangleList, resolvedClip->mode);
+ auto clipRectList = reinterpret_cast<const ClipRectList*>(resolvedClip);
+ EXPECT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount());
+ }
+
+ // region
+ SkPath circlePath;
+ circlePath.addCircle(100, 100, 100);
+ area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kReplace_Op);
+ {
+ SkPath ovalPath;
+ ovalPath.addOval(SkRect::MakeLTRB(50, 0, 150, 200));
+
+ ClipRegion recordedClip;
+ recordedClip.region.setPath(ovalPath, SkRegion(SkIRect::MakeWH(200, 200)));
+
+ Matrix4 translate10x20;
+ translate10x20.loadTranslate(10, 20, 0);
+ auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip,
+ translate10x20); // Note: only translate for now, others not handled correctly
+ ASSERT_NE(nullptr, resolvedClip);
+ ASSERT_EQ(ClipMode::Region, resolvedClip->mode);
+ auto clipRegion = reinterpret_cast<const ClipRegion*>(resolvedClip);
+ EXPECT_EQ(SkIRect::MakeLTRB(60, 20, 160, 200), clipRegion->region.getBounds());
+ }
}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/tests/unit/LinearAllocatorTests.cpp b/libs/hwui/tests/unit/LinearAllocatorTests.cpp
index 78d65dd..5c4429010 100644
--- a/libs/hwui/tests/unit/LinearAllocatorTests.cpp
+++ b/libs/hwui/tests/unit/LinearAllocatorTests.cpp
@@ -27,7 +27,7 @@
int two = 2;
};
-TEST(LinearAllocator, alloc) {
+TEST(LinearAllocator, create) {
LinearAllocator la;
EXPECT_EQ(0u, la.usedSize());
la.alloc(64);
@@ -35,7 +35,7 @@
// so the usedSize isn't strictly defined
EXPECT_LE(64u, la.usedSize());
EXPECT_GT(80u, la.usedSize());
- auto pair = la.alloc<SimplePair>();
+ auto pair = la.create<SimplePair>();
EXPECT_LE(64u + sizeof(SimplePair), la.usedSize());
EXPECT_GT(80u + sizeof(SimplePair), la.usedSize());
EXPECT_EQ(1, pair->one);
@@ -47,8 +47,8 @@
{
LinearAllocator la;
for (int i = 0; i < 5; i++) {
- la.alloc<TestUtils::SignalingDtor>()->setSignal(destroyed + i);
- la.alloc<SimplePair>();
+ la.create<TestUtils::SignalingDtor>()->setSignal(destroyed + i);
+ la.create<SimplePair>();
}
la.alloc(100);
for (int i = 0; i < 5; i++) {
@@ -75,7 +75,7 @@
la.rewindIfLastAlloc(addr, 100);
EXPECT_GT(16u, la.usedSize());
size_t emptySize = la.usedSize();
- auto sigdtor = la.alloc<TestUtils::SignalingDtor>();
+ auto sigdtor = la.create<TestUtils::SignalingDtor>();
sigdtor->setSignal(&destroyed);
EXPECT_EQ(0, destroyed);
EXPECT_LE(emptySize, la.usedSize());
diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/OpReordererTests.cpp
index b28e436..0d13118 100644
--- a/libs/hwui/tests/unit/OpReordererTests.cpp
+++ b/libs/hwui/tests/unit/OpReordererTests.cpp
@@ -64,7 +64,7 @@
ADD_FAILURE() << "Layer updates not expected in this test";
}
virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {}
- virtual void endFrame() {}
+ virtual void endFrame(const Rect& repaintRect) {}
// define virtual defaults for single draw methods
#define X(Type) \
@@ -127,7 +127,7 @@
void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
EXPECT_EQ(2, mIndex++);
}
- void endFrame() override {
+ void endFrame(const Rect& repaintRect) override {
EXPECT_EQ(3, mIndex++);
}
};
@@ -327,7 +327,7 @@
public:
void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override {
EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipRect);
+ EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipRect());
EXPECT_EQ(Rect(50, 50, 105, 105), state.computedState.clippedBounds);
Matrix4 expected;
@@ -405,7 +405,7 @@
void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
EXPECT_EQ(0, mIndex++);
EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds);
- EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect);
+ EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect());
EXPECT_TRUE(state.computedState.transform.isIdentity());
}
};
@@ -439,7 +439,7 @@
EXPECT_EQ(1, mIndex++);
EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
EXPECT_EQ(Rect(180, 180), state.computedState.clippedBounds);
- EXPECT_EQ(Rect(180, 180), state.computedState.clipRect);
+ EXPECT_EQ(Rect(180, 180), state.computedState.clipRect());
Matrix4 expectedTransform;
expectedTransform.loadTranslate(-10, -10, 0);
@@ -448,7 +448,7 @@
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
EXPECT_EQ(3, mIndex++);
EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
- EXPECT_EQ(Rect(200, 200), state.computedState.clipRect);
+ EXPECT_EQ(Rect(200, 200), state.computedState.clipRect());
EXPECT_TRUE(state.computedState.transform.isIdentity());
}
};
@@ -494,7 +494,7 @@
void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
EXPECT_EQ(7, mIndex++);
}
- void endFrame() override {
+ void endFrame(const Rect& repaintRect) override {
EXPECT_EQ(9, mIndex++);
}
void onRectOp(const RectOp& op, const BakedOpState& state) override {
@@ -574,7 +574,7 @@
EXPECT_TRUE(state.computedState.transform.isIdentity())
<< "Transform should be reset within layer";
- EXPECT_EQ(state.computedState.clipRect, Rect(25, 25, 75, 75))
+ EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipRect())
<< "Damage rect should be used to clip layer content";
}
void endLayer() override {
@@ -586,7 +586,7 @@
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
EXPECT_EQ(4, mIndex++);
}
- void endFrame() override {
+ void endFrame(const Rect& repaintRect) override {
EXPECT_EQ(5, mIndex++);
}
};
@@ -675,7 +675,7 @@
EXPECT_EQ(200u, layer->viewportHeight);
} else { ADD_FAILURE(); }
}
- void endFrame() override {
+ void endFrame(const Rect& repaintRect) override {
EXPECT_EQ(12, mIndex++);
}
};
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index 08f927c..a63cb18 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -33,6 +33,14 @@
}
}
+#define EXPECT_CLIP_RECT(expRect, clipStatePtr) \
+ EXPECT_NE(nullptr, (clipStatePtr)) << "Op is unclipped"; \
+ if ((clipStatePtr)->mode == ClipMode::Rectangle) { \
+ EXPECT_EQ((expRect), reinterpret_cast<const ClipRect*>(clipStatePtr)->rect); \
+ } else { \
+ ADD_FAILURE() << "ClipState not a rect"; \
+ }
+
TEST(RecordingCanvas, emptyPlayback) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
@@ -41,6 +49,22 @@
playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
}
+TEST(RecordingCanvas, clipRect) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
+ canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ canvas.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op);
+ canvas.drawRect(0, 0, 50, 50, SkPaint());
+ canvas.drawRect(50, 50, 100, 100, SkPaint());
+ canvas.restore();
+ });
+
+ ASSERT_EQ(2u, dl->getOps().size()) << "Must be exactly two ops";
+ EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[0]->localClip);
+ EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[1]->localClip);
+ EXPECT_EQ(dl->getOps()[0]->localClip, dl->getOps()[1]->localClip)
+ << "Clip should be serialized once";
+}
+
TEST(RecordingCanvas, drawLines) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
SkPaint paint;
@@ -66,7 +90,7 @@
ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
auto op = *(dl->getOps()[0]);
ASSERT_EQ(RecordedOpId::RectOp, op.opId);
- EXPECT_EQ(Rect(100, 200), op.localClipRect);
+ EXPECT_EQ(nullptr, op.localClip);
EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
}
@@ -83,7 +107,7 @@
playbackOps(*dl, [&count](const RecordedOp& op) {
count++;
ASSERT_EQ(RecordedOpId::TextOp, op.opId);
- EXPECT_EQ(Rect(200, 200), op.localClipRect);
+ EXPECT_EQ(nullptr, op.localClip);
EXPECT_TRUE(op.localMatrix.isIdentity());
EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25))
<< "Op expected to be 25+ pixels wide, 10+ pixels tall";
@@ -185,7 +209,7 @@
ASSERT_NE(nullptr, op.paint);
EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
EXPECT_EQ(Rect(100, 200), op.unmappedBounds);
- EXPECT_EQ(Rect(100, 200), op.localClipRect);
+ EXPECT_EQ(nullptr, op.localClip);
Matrix4 expectedMatrix;
expectedMatrix.loadIdentity();
@@ -194,7 +218,7 @@
ASSERT_EQ(RecordedOpId::BitmapOp, op.opId);
EXPECT_EQ(nullptr, op.paint);
EXPECT_EQ(Rect(25, 25), op.unmappedBounds);
- EXPECT_EQ(Rect(100, 200), op.localClipRect);
+ EXPECT_EQ(nullptr, op.localClip);
Matrix4 expectedMatrix;
expectedMatrix.loadTranslate(25, 25, 0);
@@ -219,12 +243,12 @@
case 0:
EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
- EXPECT_EQ(Rect(200, 200), op.localClipRect);
+ EXPECT_EQ(nullptr, op.localClip);
EXPECT_TRUE(op.localMatrix.isIdentity());
break;
case 1:
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
- EXPECT_EQ(Rect(180, 160), op.localClipRect);
+ EXPECT_CLIP_RECT(Rect(180, 160), op.localClip);
EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
expectedMatrix.loadTranslate(-10, -20, 0);
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
@@ -254,8 +278,8 @@
if (count++ == 1) {
Matrix4 expectedMatrix;
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
- EXPECT_EQ(Rect(100, 100), op.localClipRect) << "Recorded clip rect should be"
- " intersection of viewport and saveLayer bounds, in layer space";
+ EXPECT_CLIP_RECT(Rect(100, 100), op.localClip) // Recorded clip rect should be
+ // intersection of viewport and saveLayer bounds, in layer space;
EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
expectedMatrix.loadTranslate(-100, -100, 0);
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
@@ -281,7 +305,7 @@
playbackOps(*dl, [&count](const RecordedOp& op) {
if (count++ == 1) {
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
- EXPECT_EQ(Rect(100, 100), op.localClipRect);
+ EXPECT_CLIP_RECT(Rect(100, 100), op.localClip);
EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix)
<< "Recorded op shouldn't see any canvas transform before the saveLayer";
@@ -312,7 +336,16 @@
// ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by
// the parent 200x200 viewport, but prior to rotation
- EXPECT_RECT_APPROX_EQ(Rect(58.57864, 58.57864, 341.42136, 341.42136), op.localClipRect);
+ ASSERT_NE(nullptr, op.localClip);
+ ASSERT_EQ(ClipMode::Rectangle, op.localClip->mode);
+ // NOTE: this check relies on saveLayer altering the clip post-viewport init. This
+ // causes the clip to be recorded by contained draw commands, though it's not necessary
+ // since the same clip will be computed at draw time. If such a change is made, this
+ // check could be done at record time by querying the clip, or the clip could be altered
+ // slightly so that it is serialized.
+ EXPECT_RECT_APPROX_EQ(Rect(58.57864, 58.57864, 341.42136, 341.42136),
+ (reinterpret_cast<const ClipRect*>(op.localClip))->rect);
+
EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
expectedMatrix.loadIdentity();
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h
index e1c6f6c..dcbc0dd 100644
--- a/libs/hwui/utils/LinearAllocator.h
+++ b/libs/hwui/utils/LinearAllocator.h
@@ -56,12 +56,12 @@
void* alloc(size_t size);
/**
- * Allocates an instance of the template type with the default constructor
+ * Allocates an instance of the template type with the given construction parameters
* and adds it to the automatic destruction list.
*/
- template<class T>
- T* alloc() {
- T* ret = new (*this) T;
+ template<class T, typename... Params>
+ T* create(Params... params) {
+ T* ret = new (*this) T(params...);
autoDestroy(ret);
return ret;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 0d5e34e..13cfbe4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -53,6 +53,7 @@
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;
@@ -99,18 +100,17 @@
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;
+
import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Set;
/**
* Display the documents inside a single directory.
*/
-public class DirectoryFragment extends Fragment {
+public class DirectoryFragment extends Fragment implements DocumentsAdapter.Environment {
public static final int TYPE_NORMAL = 1;
public static final int TYPE_SEARCH = 2;
@@ -126,7 +126,7 @@
private static final String TAG = "DirectoryFragment";
private static final int LOADER_ID = 42;
- private static final boolean DEBUG_ENABLE_DND = true;
+ static final boolean DEBUG_ENABLE_DND = true;
private static final String EXTRA_TYPE = "type";
private static final String EXTRA_ROOT = "root";
@@ -289,7 +289,11 @@
final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
- mAdapter = new DocumentsAdapter();
+ mIconHelper = new IconHelper(context, state.derivedMode);
+
+ mAdapter = new SectionBreakDocumentsAdapterWrapper(
+ this, new ModelBackedDocumentsAdapter(this, mIconHelper));
+
mRecView.setAdapter(mAdapter);
GestureDetector.SimpleOnGestureListener listener =
@@ -333,7 +337,7 @@
: MultiSelectManager.MODE_SINGLE);
mSelectionManager.addCallback(new SelectionModeListener());
- mModel = new Model(context, mAdapter);
+ mModel = new Model(context);
mModel.addUpdateListener(mAdapter);
mModel.addUpdateListener(mModelUpdateListener);
@@ -343,8 +347,6 @@
mTuner = FragmentTuner.pick(state);
mClipper = new DocumentClipper(context);
- mIconHelper = new IconHelper(context, state.derivedMode);
-
boolean hideGridTitles;
if (mType == TYPE_RECENT_OPEN) {
// Hide titles when showing recents for picking images/videos
@@ -574,7 +576,10 @@
case MODE_GRID:
if (mGridLayout == null) {
mGridLayout = new GridLayoutManager(getContext(), mColumnCount);
- mGridLayout.setSpanSizeLookup(mAdapter.createSpanSizeLookup());
+ SpanSizeLookup lookup = mAdapter.createSpanSizeLookup();
+ if (lookup != null) {
+ mGridLayout.setSpanSizeLookup(lookup);
+ }
}
layout = mGridLayout;
break;
@@ -609,6 +614,11 @@
return columnCount;
}
+ @Override
+ public int getColumnCount() {
+ return mColumnCount;
+ }
+
/**
* Manages the integration between our ActionMode and MultiSelectManager, initiating
* ActionMode when there is a selection, canceling it when there is no selection,
@@ -893,10 +903,34 @@
}.execute(selected);
}
- private State getDisplayState() {
+ @Override
+ public void initDocumentHolder(DocumentHolder holder) {
+ holder.addClickListener(mItemClickListener);
+ holder.addOnKeyListener(mSelectionManager);
+ }
+
+ @Override
+ public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {
+ if (DEBUG_ENABLE_DND) {
+ setupDragAndDropOnDocumentView(holder.itemView, cursor);
+ }
+ }
+
+ @Override
+ public State getDisplayState() {
return ((BaseActivity) getActivity()).getDisplayState();
}
+ @Override
+ public Model getModel() {
+ return mModel;
+ }
+
+ @Override
+ public boolean isDocumentEnabled(String docMimeType, int docFlags) {
+ return mTuner.isDocumentEnabled(docMimeType, docFlags);
+ }
+
void showEmptyView() {
mEmptyView.setVisibility(View.VISIBLE);
mRecView.setVisibility(View.GONE);
@@ -920,240 +954,6 @@
mRecView.setVisibility(View.VISIBLE);
}
- final class DocumentsAdapter
- extends RecyclerView.Adapter<DocumentHolder>
- implements Model.UpdateListener {
-
- private static final String TAG = "DocumentsAdapter";
- public static final int ITEM_TYPE_LAYOUT_DIVIDER = 0;
- public static final int ITEM_TYPE_DOCUMENT = 1;
- public static final int ITEM_TYPE_DIRECTORY = 2;
-
- /**
- * An ordered list of model IDs. This is the data structure that determines what shows up in
- * the UI, and where.
- */
- private List<String> mModelIds = new ArrayList<>();
-
- // The list is divided into two segments - directories, and everything else. Record the
- // position where the transition happens.
- private int mDividerPosition;
-
- public GridLayoutManager.SpanSizeLookup createSpanSizeLookup() {
- return new GridLayoutManager.SpanSizeLookup() {
- @Override
- public int getSpanSize(int position) {
- // Make layout whitespace span the grid. This has the effect of breaking
- // grid rows whenever layout whitespace is encountered.
- if (getItemViewType(position) == ITEM_TYPE_LAYOUT_DIVIDER) {
- return mColumnCount;
- } else {
- return 1;
- }
- }
- };
- }
-
- @Override
- public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- if (viewType == ITEM_TYPE_LAYOUT_DIVIDER) {
- return new EmptyDocumentHolder(getContext());
- };
-
- DocumentHolder holder = null;
- final State state = getDisplayState();
- switch (state.derivedMode) {
- case MODE_GRID:
- switch (viewType) {
- case ITEM_TYPE_DIRECTORY:
- holder = new GridDirectoryHolder(getContext(), parent);
- break;
- case ITEM_TYPE_DOCUMENT:
- holder = new GridDocumentHolder(getContext(), parent, mIconHelper);
- break;
- default:
- throw new IllegalStateException("Unsupported layout type.");
- }
- break;
- case MODE_LIST:
- holder = new ListDocumentHolder(getContext(), parent, mIconHelper);
- break;
- case MODE_UNKNOWN:
- default:
- throw new IllegalStateException("Unsupported layout mode.");
- }
-
- holder.addClickListener(mItemClickListener);
- holder.addOnKeyListener(mSelectionManager);
- return holder;
- }
-
- /**
- * Deal with selection changed events by using a custom ItemAnimator that just changes the
- * background color. This works around focus issues (otherwise items lose focus when their
- * selection state changes) but also optimizes change animations for selection.
- */
- @Override
- public void onBindViewHolder(DocumentHolder holder, int position, List<Object> payload) {
- if (holder.getItemViewType() == ITEM_TYPE_LAYOUT_DIVIDER) {
- // Whitespace items are hidden elements with no data to bind.
- return;
- }
-
- final View itemView = holder.itemView;
-
- if (payload.contains(MultiSelectManager.SELECTION_CHANGED_MARKER)) {
- final boolean selected = isSelected(mModelIds.get(position));
- itemView.setActivated(selected);
- return;
- } else {
- onBindViewHolder(holder, position);
- }
- }
-
- @Override
- public void onBindViewHolder(DocumentHolder holder, int position) {
- if (holder.getItemViewType() == ITEM_TYPE_LAYOUT_DIVIDER) {
- // Whitespace items are hidden elements with no data to bind.
- return;
- }
-
- String modelId = mModelIds.get(position);
- Cursor cursor = mModel.getItem(modelId);
- holder.bind(cursor, modelId, getDisplayState());
-
- final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
- final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
-
- holder.setSelected(isSelected(modelId));
- holder.setEnabled(mTuner.isDocumentEnabled(docMimeType, docFlags));
- if (DEBUG_ENABLE_DND) {
- setupDragAndDropOnDocumentView(holder.itemView, cursor);
- }
- }
-
- @Override
- public int getItemCount() {
- return mModelIds.size();
- }
-
- @Override
- public void onModelUpdate(Model model) {
- mModelIds = Lists.newArrayList(model.getModelIds());
- // Start the divider at the end. That way if the code below encounters no documents
- // (i.e. in a directory containing only directories), the divider is placed at the end
- // of the list, as expected.
- mDividerPosition = mModelIds.size();
-
- // Walk down the list of IDs till we encounter something that's not a directory, and
- // insert a whitespace element - this introduces a visual break in the grid between
- // folders and documents.
- // TODO: This code makes assumptions about the model, namely, that it performs a
- // bucketed sort where directories will always be ordered before other files. CBB.
- for (int i = 0; i < mModelIds.size(); ++i) {
- final String mimeType = getCursorString(
- model.getItem(mModelIds.get(i)), Document.COLUMN_MIME_TYPE);
- if (!Document.MIME_TYPE_DIR.equals(mimeType)) {
- mDividerPosition = i;
- break;
- }
- }
-
- mModelIds.add(mDividerPosition, null);
- }
-
- @Override
- public void onModelUpdateFailed(Exception e) {
- if (DEBUG) Log.d(TAG, "onModelUpdateFailed called ");
- mModelIds.clear();
- }
-
- /**
- * @return The model ID of the item at the given adapter position.
- */
- public String getModelId(int adapterPosition) {
- return mModelIds.get(adapterPosition);
- }
-
- /**
- * Hides a set of items from the associated RecyclerView.
- *
- * @param ids The Model IDs of the items to hide.
- * @return A SparseArray that maps the hidden IDs to their old positions. This can be used
- * to {@link #unhide} the items if necessary.
- */
- public SparseArray<String> hide(String... ids) {
- Set<String> toHide = Sets.newHashSet(ids);
-
- // Proceed backwards through the list of items, because each removal causes the
- // positions of all subsequent items to change.
- SparseArray<String> hiddenItems = new SparseArray<>();
- for (int i = mModelIds.size() - 1; i >= 0; --i) {
- String id = mModelIds.get(i);
- if (toHide.contains(id)) {
- hiddenItems.put(i, mModelIds.remove(i));
- notifyItemRemoved(i);
- }
- }
-
- return hiddenItems;
- }
-
- /**
- * Unhides a set of previously hidden items.
- *
- * @param ids A sparse array of IDs from a previous call to {@link #hide}.
- */
- public void unhide(SparseArray<String> ids) {
- // Proceed backwards through the list of items, because each addition causes the
- // positions of all subsequent items to change.
- for (int i = ids.size() - 1; i >= 0; --i) {
- int pos = ids.keyAt(i);
- String id = ids.get(pos);
- mModelIds.add(pos, id);
- notifyItemInserted(pos);
- }
- }
-
- /**
- * Returns a list of model IDs of items currently in the adapter. Excludes items that are
- * currently hidden (see {@link #hide(String...)}).
- *
- * @return A list of Model IDs.
- */
- public List<String> getModelIds() {
- return mModelIds;
- }
-
- @Override
- public int getItemViewType(int position) {
- if (position < mDividerPosition) {
- return ITEM_TYPE_DIRECTORY;
- } else if (position == mDividerPosition) {
- return ITEM_TYPE_LAYOUT_DIVIDER;
- } else {
- return ITEM_TYPE_DOCUMENT;
- }
- }
-
- /**
- * Triggers item-change notifications by stable ID. Passing an unrecognized ID will result
- * in a warning in logcat, but no other error.
- *
- * @param id
- * @param selectionChangedMarker
- */
- public void notifyItemChanged(String id, String selectionChangedMarker) {
- int position = mModelIds.indexOf(id);
-
- if (position >= 0) {
- notifyItemChanged(position, selectionChangedMarker);
- } else {
- Log.w(TAG, "Item change notification received for unknown item: " + id);
- }
- }
- }
-
private String findCommonMimeType(List<String> mimeTypes) {
String[] commonType = mimeTypes.get(0).split("/");
if (commonType.length != 2) {
@@ -1504,7 +1304,8 @@
abstract void onDocumentsReady(List<DocumentInfo> docs);
}
- boolean isSelected(String modelId) {
+ @Override
+ public boolean isSelected(String modelId) {
return mSelectionManager.getSelection().contains(modelId);
}
@@ -1520,7 +1321,7 @@
}
}
- private class ModelUpdateListener implements Model.UpdateListener {
+ private final class ModelUpdateListener implements Model.UpdateListener {
@Override
public void onModelUpdate(Model model) {
if (model.info != null || model.error != null) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java
new file mode 100644
index 0000000..2001b29
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.dirlist;
+
+import static com.android.documentsui.model.DocumentInfo.getCursorString;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.DocumentsContract.Document;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.SparseArray;
+
+import com.android.documentsui.State;
+
+import java.nio.channels.UnsupportedAddressTypeException;
+import java.util.List;
+
+/**
+ * DocumentsAdapter provides glue between a directory Model, and RecylcerView. We've
+ * abstracted this a bit in order to decompose some specialized support
+ * for adding dummy layout objects (@see SectionBreakDocumentsAdapter). Handling of the
+ * dummy layout objects was error prone when interspersed with the core mode / adapter code.
+ *
+ * @see ModelBackedDocumentsAdapter
+ * @see SectionBreakDocumentsAdapter
+ */
+abstract class DocumentsAdapter
+ extends RecyclerView.Adapter<DocumentHolder>
+ implements Model.UpdateListener {
+
+ // Payloads for notifyItemChange to distinguish between selection and other events.
+ static final String SELECTION_CHANGED_MARKER = "Selection-Changed";
+
+ /**
+ * Returns a list of model IDs of items currently in the adapter. Excludes items that are
+ * currently hidden (see {@link #hide(String...)}).
+ *
+ * @return A list of Model IDs.
+ */
+ abstract List<String> getModelIds();
+
+ /**
+ * Triggers item-change notifications by stable ID (as opposed to position).
+ * Passing an unrecognized ID will result in a warning in logcat, but no other error.
+ */
+ abstract void notifyItemSelectionChanged(String id);
+
+ /**
+ * @return The model ID of the item at the given adapter position.
+ */
+ abstract String getModelId(int position);
+
+ /**
+ * Hides a set of items from the associated RecyclerView.
+ *
+ * @param ids The Model IDs of the items to hide.
+ * @return A SparseArray that maps the hidden IDs to their old positions. This can be used
+ * to {@link #unhide} the items if necessary.
+ */
+ abstract public SparseArray<String> hide(String... ids);
+
+ /**
+ * Unhides a set of previously hidden items.
+ *
+ * @param ids A sparse array of IDs from a previous call to {@link #hide}.
+ */
+ abstract void unhide(SparseArray<String> ids);
+
+ /**
+ * Returns a class that yields the span size for a particular element. This is
+ * primarily useful in {@link SectionBreakDocumentsAdapterWrapper} where
+ * we adjust sizes.
+ */
+ GridLayoutManager.SpanSizeLookup createSpanSizeLookup() {
+ throw new UnsupportedAddressTypeException();
+ }
+
+ static boolean isDirectory(Cursor cursor) {
+ final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+ return Document.MIME_TYPE_DIR.equals(mimeType);
+ }
+
+ boolean isDirectory(Model model, int position) {
+ String modelId = getModelIds().get(position);
+ Cursor cursor = model.getItem(modelId);
+ return isDirectory(cursor);
+ }
+
+ /**
+ * Environmental access for View adapter implementations.
+ */
+ interface Environment {
+ Context getContext();
+ int getColumnCount();
+ State getDisplayState();
+ boolean isSelected(String id);
+ Model getModel();
+ boolean isDocumentEnabled(String mimeType, int flags);
+ void initDocumentHolder(DocumentHolder holder);
+ void onBindDocumentHolder(DocumentHolder holder, Cursor cursor);
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java
index bea38c6..864f405 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java
@@ -35,7 +35,6 @@
import android.provider.DocumentsContract.Document;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
-import android.support.v7.widget.RecyclerView;
import android.util.Log;
import com.android.documentsui.BaseActivity.SiblingProvider;
@@ -74,7 +73,7 @@
@Nullable String info;
@Nullable String error;
- Model(Context context, RecyclerView.Adapter<?> viewAdapter) {
+ Model(Context context) {
mContext = context;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
new file mode 100644
index 0000000..bb0d729
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.dirlist;
+
+import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.State.MODE_GRID;
+import static com.android.documentsui.State.MODE_LIST;
+import static com.android.documentsui.State.MODE_UNKNOWN;
+import static com.android.documentsui.model.DocumentInfo.getCursorInt;
+import static com.android.documentsui.model.DocumentInfo.getCursorString;
+
+import android.database.Cursor;
+import android.provider.DocumentsContract.Document;
+import android.support.v7.widget.GridLayoutManager;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.ViewGroup;
+
+import com.android.documentsui.State;
+
+import com.google.common.collect.Sets;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Adapts from dirlist.Model to something RecyclerView understands.
+ */
+final class ModelBackedDocumentsAdapter extends DocumentsAdapter {
+
+ private static final String TAG = "ModelBackedDocumentsAdapter";
+ public static final int ITEM_TYPE_DOCUMENT = 1;
+ public static final int ITEM_TYPE_DIRECTORY = 2;
+
+ // Provides access to information needed when creating and view holders. This
+ // isn't an ideal pattern (more transitive dependency stuff) but good enough for now.
+ private final Environment mEnv;
+ private final IconHelper mIconHelper; // a transitive dependency of the holders.
+
+ /**
+ * An ordered list of model IDs. This is the data structure that determines what shows up in
+ * the UI, and where.
+ */
+ private List<String> mModelIds = new ArrayList<>();
+
+ // List of files that have been deleted. Some transient directory updates
+ // may happen while files are being deleted. During this time we don't
+ // want once-hidden files to be re-shown. We only remove
+ // items from this list when we get a model update where the model
+ // does not contain a corresponding id. This ensures hidden entries
+ // don't momentarily re-appear if we get intermediate updates from
+ // the file system.
+ private Set<String> mHiddenIds = new HashSet<>();
+
+ public ModelBackedDocumentsAdapter(Environment env, IconHelper iconHelper) {
+ mEnv = env;
+ mIconHelper = iconHelper;
+ }
+
+ @Override
+ public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ DocumentHolder holder = null;
+ final State state = mEnv.getDisplayState();
+ switch (state.derivedMode) {
+ case MODE_GRID:
+ switch (viewType) {
+ case ITEM_TYPE_DIRECTORY:
+ holder = new GridDirectoryHolder(mEnv.getContext(), parent);
+ break;
+ case ITEM_TYPE_DOCUMENT:
+ holder = new GridDocumentHolder(mEnv.getContext(), parent, mIconHelper);
+ break;
+ default:
+ throw new IllegalStateException("Unsupported layout type.");
+ }
+ break;
+ case MODE_LIST:
+ holder = new ListDocumentHolder(mEnv.getContext(), parent, mIconHelper);
+ break;
+ case MODE_UNKNOWN:
+ default:
+ throw new IllegalStateException("Unsupported layout mode.");
+ }
+
+ mEnv.initDocumentHolder(holder);
+ return holder;
+ }
+
+ @Override
+ public void onBindViewHolder(DocumentHolder holder, int position, List<Object> payload) {
+ if (payload.contains(SELECTION_CHANGED_MARKER)) {
+ final boolean selected = mEnv.isSelected(mModelIds.get(position));
+ holder.setSelected(selected);
+ } else {
+ onBindViewHolder(holder, position);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(DocumentHolder holder, int position) {
+ String modelId = mModelIds.get(position);
+ Cursor cursor = mEnv.getModel().getItem(modelId);
+ holder.bind(cursor, modelId, mEnv.getDisplayState());
+
+ final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+ final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
+
+ holder.setSelected(mEnv.isSelected(modelId));
+ holder.setEnabled(mEnv.isDocumentEnabled(docMimeType, docFlags));
+
+ mEnv.onBindDocumentHolder(holder, cursor);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mModelIds.size();
+ }
+
+ @Override
+ public void onModelUpdate(Model model) {
+ if (DEBUG && mHiddenIds.size() > 0) {
+ Log.d(TAG, "Updating model with hidden ids: " + mHiddenIds);
+ }
+
+ List<String> modelIds = model.getModelIds();
+ mModelIds = new ArrayList<>(modelIds.size());
+ for (String id : modelIds) {
+ if (!mHiddenIds.contains(id)) {
+ mModelIds.add(id);
+ } else {
+ if (DEBUG) Log.d(TAG, "Omitting hidden id from model during update: " + id);
+ }
+ }
+
+ // Finally remove any hidden ids that aren't present in the model.
+ // This assumes that model updates represent a complete set of files.
+ mHiddenIds.retainAll(mModelIds);
+ }
+
+ @Override
+ public void onModelUpdateFailed(Exception e) {
+ Log.w(TAG, "Model update failed.", e);
+ mModelIds.clear();
+ }
+
+ @Override
+ public String getModelId(int adapterPosition) {
+ return mModelIds.get(adapterPosition);
+ }
+
+ @Override
+ public SparseArray<String> hide(String... ids) {
+ if (DEBUG) Log.d(TAG, "Hiding ids: " + ids);
+ Set<String> toHide = Sets.newHashSet(ids);
+
+ // Proceed backwards through the list of items, because each removal causes the
+ // positions of all subsequent items to change.
+ SparseArray<String> hiddenItems = new SparseArray<>();
+ for (int i = mModelIds.size() - 1; i >= 0; --i) {
+ String id = mModelIds.get(i);
+ if (toHide.contains(id)) {
+ mHiddenIds.add(id);
+ hiddenItems.put(i, mModelIds.remove(i));
+ notifyItemRemoved(i);
+ }
+ }
+
+ return hiddenItems;
+ }
+
+ @Override
+ public void unhide(SparseArray<String> ids) {
+ if (DEBUG) Log.d(TAG, "Un-iding ids: " + ids);
+ // Proceed backwards through the list of items, because each addition causes the
+ // positions of all subsequent items to change.
+ for (int i = ids.size() - 1; i >= 0; --i) {
+ int pos = ids.keyAt(i);
+ String id = ids.get(pos);
+ mHiddenIds.remove(id);
+ mModelIds.add(pos, id);
+ notifyItemInserted(pos);
+ }
+ }
+
+ @Override
+ public List<String> getModelIds() {
+ return mModelIds;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return isDirectory(mEnv.getModel(), position)
+ ? ITEM_TYPE_DIRECTORY
+ : ITEM_TYPE_DOCUMENT;
+ }
+
+ @Override
+ public void notifyItemSelectionChanged(String id) {
+ int position = mModelIds.indexOf(id);
+
+ if (position >= 0) {
+ notifyItemChanged(position, SELECTION_CHANGED_MARKER);
+ } else {
+ Log.w(TAG, "Item change notification received for unknown item: " + id);
+ }
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index e47af67..4b3bf1e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -75,9 +75,6 @@
private boolean mSingleSelect;
- // Payloads for notifyItemChange to distinguish between selection and other events.
- public static final String SELECTION_CHANGED_MARKER = "Selection-Changed";
-
@Nullable private BandController mBandManager;
/**
@@ -339,7 +336,10 @@
if (DEBUG) Log.d(TAG, "Ignoring toggle for element with no position.");
return;
}
- toggleSelection(mEnvironment.getModelIdFromAdapterPosition(position));
+ String id = mEnvironment.getModelIdFromAdapterPosition(position);
+ if (id != null) {
+ toggleSelection(id);
+ }
}
/**
@@ -348,6 +348,7 @@
* @param modelId
*/
public void toggleSelection(String modelId) {
+ checkNotNull(modelId);
boolean changed = false;
if (mSelection.contains(modelId)) {
changed = attemptDeselect(modelId);
@@ -405,6 +406,10 @@
checkState(end >= begin);
for (int i = begin; i <= end; i++) {
String id = mEnvironment.getModelIdFromAdapterPosition(i);
+ if (id == null) {
+ continue;
+ }
+
if (selected) {
boolean canSelect = notifyBeforeItemStateChange(id, true);
if (canSelect) {
@@ -436,6 +441,7 @@
* @return True if the update was applied.
*/
private boolean attemptDeselect(String id) {
+ checkArgument(id != null);
if (notifyBeforeItemStateChange(id, false)) {
mSelection.remove(id);
notifyItemStateChanged(id, false);
@@ -462,6 +468,7 @@
* (identified by {@code position}) changes.
*/
private void notifyItemStateChanged(String id, boolean selected) {
+ checkArgument(id != null);
int lastListener = mCallbacks.size() - 1;
for (int i = lastListener; i > -1; i--) {
mCallbacks.get(i).onItemStateChanged(id, selected);
@@ -613,7 +620,7 @@
* @param id
* @return true if the position is currently selected.
*/
- public boolean contains(String id) {
+ public boolean contains(@Nullable String id) {
return mTotalSelection.contains(id);
}
@@ -804,7 +811,12 @@
int getChildCount();
int getVisibleChildCount();
void focusItem(int position);
- String getModelIdFromAdapterPosition(int position);
+ /**
+ * Returns null if non-useful item.
+ * @param position
+ * @return
+ */
+ @Nullable String getModelIdFromAdapterPosition(int position);
int getItemCount();
List<String> getModelIds();
void notifyItemChanged(String id);
@@ -818,11 +830,11 @@
private final Drawable mBand;
private boolean mIsOverlayShown = false;
- private DirectoryFragment.DocumentsAdapter mAdapter;
+ private DocumentsAdapter mAdapter;
RuntimeSelectionEnvironment(RecyclerView rv) {
mView = rv;
- mAdapter = (DirectoryFragment.DocumentsAdapter) rv.getAdapter();
+ mAdapter = (DocumentsAdapter) rv.getAdapter();
mBand = mView.getContext().getTheme().getDrawable(R.drawable.band_select_overlay);
}
@@ -841,7 +853,7 @@
}
@Override
- public String getModelIdFromAdapterPosition(int position) {
+ public @Nullable String getModelIdFromAdapterPosition(int position) {
return mAdapter.getModelId(position);
}
@@ -964,7 +976,7 @@
@Override
public void notifyItemChanged(String id) {
- mAdapter.notifyItemChanged(id, SELECTION_CHANGED_MARKER);
+ mAdapter.notifyItemSelectionChanged(id);
}
@Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java
new file mode 100644
index 0000000..ae6ada9
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.dirlist;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView.AdapterDataObserver;
+import android.util.SparseArray;
+import android.view.ViewGroup;
+
+import java.util.List;
+
+/**
+ * Adapter wrapper that inserts a sort of line break item between directories and regular files.
+ * Only needs to be used in GRID mode...at this time.
+ */
+final class SectionBreakDocumentsAdapterWrapper extends DocumentsAdapter {
+
+ private static final String TAG = "SectionBreakDocumentsAdapterWrapper";
+ private static final int ITEM_TYPE_SECTION_BREAK = Integer.MAX_VALUE;
+
+ private final Environment mEnv;
+ private final DocumentsAdapter mDelegate;
+
+ private int mBreakPosition = -1;
+
+ SectionBreakDocumentsAdapterWrapper(Environment environment, DocumentsAdapter delegate) {
+ mEnv = environment;
+ mDelegate = delegate;
+
+ // Events and information flows two ways between recycler view and adapter.
+ // So we need to listen to events on our delegate and forward them
+ // to our listeners with a corrected position.
+ AdapterDataObserver adapterDataObserver = new AdapterDataObserver() {
+ public void onChanged() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ checkArgument(itemCount == 1);
+ }
+
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ checkArgument(itemCount == 1);
+ if (positionStart < mBreakPosition) {
+ mBreakPosition++;
+ }
+ notifyItemRangeInserted(toViewPosition(positionStart), itemCount);
+ }
+
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ checkArgument(itemCount == 1);
+ if (positionStart < mBreakPosition) {
+ mBreakPosition--;
+ }
+ notifyItemRangeRemoved(toViewPosition(positionStart), itemCount);
+ }
+
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ mDelegate.registerAdapterDataObserver(adapterDataObserver);
+ }
+
+ public GridLayoutManager.SpanSizeLookup createSpanSizeLookup() {
+ return new GridLayoutManager.SpanSizeLookup() {
+ @Override
+ public int getSpanSize(int position) {
+ // Make layout whitespace span the grid. This has the effect of breaking
+ // grid rows whenever layout whitespace is encountered.
+ if (getItemViewType(position) == ITEM_TYPE_SECTION_BREAK) {
+ return mEnv.getColumnCount();
+ } else {
+ return 1;
+ }
+ }
+ };
+ }
+
+ @Override
+ public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ if (viewType == ITEM_TYPE_SECTION_BREAK) {
+ return new EmptyDocumentHolder(mEnv.getContext());
+ } else {
+ return mDelegate.createViewHolder(parent, viewType);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(DocumentHolder holder, int p, List<Object> payload) {
+ if (holder.getItemViewType() != ITEM_TYPE_SECTION_BREAK) {
+ mDelegate.onBindViewHolder(holder, toDelegatePosition(p), payload);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(DocumentHolder holder, int p) {
+ if (holder.getItemViewType() != ITEM_TYPE_SECTION_BREAK) {
+ mDelegate.onBindViewHolder(holder, toDelegatePosition(p));
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mBreakPosition == -1
+ ? mDelegate.getItemCount()
+ : mDelegate.getItemCount() + 1;
+ }
+
+ @Override
+ public void onModelUpdate(Model model) {
+ mDelegate.onModelUpdate(model);
+ mBreakPosition = -1;
+
+ // Walk down the list of IDs till we encounter something that's not a directory, and
+ // insert a whitespace element - this introduces a visual break in the grid between
+ // folders and documents.
+ // TODO: This code makes assumptions about the model, namely, that it performs a
+ // bucketed sort where directories will always be ordered before other files. CBB.
+ List<String> modelIds = mDelegate.getModelIds();
+ for (int i = 0; i < modelIds.size(); i++) {
+ if (!isDirectory(model, i)) {
+ mBreakPosition = i;
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onModelUpdateFailed(Exception e) {
+ mDelegate.onModelUpdateFailed(e);
+ }
+
+ @Override
+ public int getItemViewType(int p) {
+ if (p == mBreakPosition) {
+ return ITEM_TYPE_SECTION_BREAK;
+ } else {
+ return mDelegate.getItemViewType(toDelegatePosition(p));
+ }
+ }
+
+ /**
+ * Returns the position of an item in the delegate, adjusting
+ * values that are greater than the break position.
+ *
+ * @param p Position within the view
+ * @return Position within the delegate
+ */
+ private int toDelegatePosition(int p) {
+ return (mBreakPosition != -1 && p > mBreakPosition) ? p - 1 : p;
+ }
+
+ /**
+ * Returns the position of an item in the view, adjusting
+ * values that are greater than the break position.
+ *
+ * @param p Position within the delegate
+ * @return Position within the view
+ */
+ private int toViewPosition(int p) {
+ // If position is greater than or equal to the break, increase by one.
+ return (mBreakPosition != -1 && p >= mBreakPosition) ? p + 1 : p;
+ }
+
+ @Override
+ public SparseArray<String> hide(String... ids) {
+ // NOTE: We hear about these changes and adjust break position
+ // in our AdapterDataObserver.
+ return mDelegate.hide(ids);
+ }
+
+ @Override
+ void unhide(SparseArray<String> ids) {
+ // NOTE: We hear about these changes and adjust break position
+ // in our AdapterDataObserver.
+ mDelegate.unhide(ids);
+ }
+
+ @Override
+ List<String> getModelIds() {
+ return mDelegate.getModelIds();
+ }
+
+ @Override
+ String getModelId(int p) {
+ return (p == mBreakPosition) ? null : mDelegate.getModelId(toDelegatePosition(p));
+ }
+
+ @Override
+ public void notifyItemSelectionChanged(String id) {
+ mDelegate.notifyItemSelectionChanged(id);
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
new file mode 100644
index 0000000..92668a8
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.dirlist;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v7.widget.RecyclerView;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.ViewGroup;
+
+import com.android.documentsui.State;
+
+import java.util.List;
+
+@SmallTest
+public class ModelBackedDocumentsAdapterTest extends AndroidTestCase {
+
+ private static final String AUTHORITY = "test_authority";
+ private static final String[] NAMES = new String[] {
+ "4",
+ "foo",
+ "1",
+ "bar",
+ "*(Ljifl;a",
+ "0",
+ "baz",
+ "2",
+ "3",
+ "%$%VD"
+ };
+
+ private TestModel model;
+ private ModelBackedDocumentsAdapter adapter;
+
+ public void setUp() {
+
+ final Context testContext = TestContext.createStorageTestContext(getContext(), AUTHORITY);
+ model = new TestModel(testContext, AUTHORITY);
+ model.update(NAMES);
+
+ DocumentsAdapter.Environment env = new TestEnvironment(testContext);
+
+ adapter = new ModelBackedDocumentsAdapter(
+ env, new IconHelper(testContext, State.MODE_GRID));
+ adapter.onModelUpdate(model);
+ }
+
+ // Tests that the item count is correct.
+ public void testItemCount() {
+ assertEquals(model.getItemCount(), adapter.getItemCount());
+ }
+
+ // Tests that the item count is correct.
+ public void testHide_ItemCount() {
+ List<String> ids = model.getModelIds();
+ adapter.hide(ids.get(0), ids.get(1));
+ assertEquals(model.getItemCount() - 2, adapter.getItemCount());
+ }
+
+ private final class TestEnvironment implements DocumentsAdapter.Environment {
+ private final Context testContext;
+
+ private TestEnvironment(Context testContext) {
+ this.testContext = testContext;
+ }
+
+ @Override
+ public boolean isSelected(String id) {
+ return false;
+ }
+
+ @Override
+ public boolean isDocumentEnabled(String mimeType, int flags) {
+ return true;
+ }
+
+ @Override
+ public void initDocumentHolder(DocumentHolder holder) {}
+
+ @Override
+ public Model getModel() {
+ return model;
+ }
+
+ @Override
+ public State getDisplayState() {
+ return null;
+ }
+
+ @Override
+ public Context getContext() {
+ return testContext;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 4;
+ }
+
+ @Override
+ public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {}
+ }
+
+ private static class DummyListener implements Model.UpdateListener {
+ public void onModelUpdate(Model model) {}
+ public void onModelUpdateFailed(Exception e) {}
+ }
+
+ private static class DummyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ public int getItemCount() { return 0; }
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {}
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return null;
+ }
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java
index 121eb41..bed7c9c 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java
@@ -21,16 +21,10 @@
import android.content.ContextWrapper;
import android.database.Cursor;
import android.database.MatrixCursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
-import android.support.v7.widget.RecyclerView;
import android.test.AndroidTestCase;
-import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.SmallTest;
-import android.view.ViewGroup;
import com.android.documentsui.DirectoryResult;
import com.android.documentsui.RootCursorWrapper;
@@ -49,6 +43,7 @@
private static final int ITEM_COUNT = 10;
private static final String AUTHORITY = "test_authority";
+
private static final String[] COLUMNS = new String[]{
RootCursorWrapper.COLUMN_AUTHORITY,
Document.COLUMN_DOCUMENT_ID,
@@ -57,23 +52,24 @@
Document.COLUMN_SIZE,
Document.COLUMN_MIME_TYPE
};
- private static Cursor cursor;
+ private static final String[] NAMES = new String[] {
+ "4",
+ "foo",
+ "1",
+ "bar",
+ "*(Ljifl;a",
+ "0",
+ "baz",
+ "2",
+ "3",
+ "%$%VD"
+ };
+
+ private Cursor cursor;
private Context context;
private Model model;
private TestContentProvider provider;
- private static final String[] NAMES = new String[] {
- "4",
- "foo",
- "1",
- "bar",
- "*(Ljifl;a",
- "0",
- "baz",
- "2",
- "3",
- "%$%VD"
- };
public void setUp() {
setupTestContext();
@@ -97,7 +93,7 @@
r.cursor = cursor;
// Instantiate the model with a dummy view adapter and listener that (for now) do nothing.
- model = new Model(context, new DummyAdapter());
+ model = new Model(context);
model.addUpdateListener(new DummyListener());
model.update(r);
}
@@ -326,32 +322,4 @@
public void onModelUpdate(Model model) {}
public void onModelUpdateFailed(Exception e) {}
}
-
- private static class DummyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
- public int getItemCount() { return 0; }
- public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {}
- public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- return null;
- }
- }
-
- private static class TestContentProvider extends MockContentProvider {
- List<Uri> mDeleted = new ArrayList<>();
-
- @Override
- public Bundle call(String method, String arg, Bundle extras) {
- // Intercept and log delete method calls.
- if (DocumentsContract.METHOD_DELETE_DOCUMENT.equals(method)) {
- final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
- mDeleted.add(documentUri);
- return new Bundle();
- } else {
- return super.call(method, arg, extras);
- }
- }
-
- public void assertWasDeleted(DocumentInfo doc) {
- assertTrue(mDeleted.contains(doc.derivedUri));
- }
- }
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestContentProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestContentProvider.java
new file mode 100644
index 0000000..c8d424f
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestContentProvider.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.dirlist;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.DocumentsContract;
+import android.test.mock.MockContentProvider;
+
+import com.android.documentsui.model.DocumentInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A very simple test double for ContentProvider. Useful in this package only.
+ */
+class TestContentProvider extends MockContentProvider {
+ List<Uri> mDeleted = new ArrayList<>();
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ // Intercept and log delete method calls.
+ if (DocumentsContract.METHOD_DELETE_DOCUMENT.equals(method)) {
+ final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
+ mDeleted.add(documentUri);
+ return new Bundle();
+ } else {
+ return super.call(method, arg, extras);
+ }
+ }
+
+ public void assertWasDeleted(DocumentInfo doc) {
+ ModelTest.assertTrue(mDeleted.contains(doc.derivedUri));
+ }
+}
\ No newline at end of file
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestContext.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestContext.java
new file mode 100644
index 0000000..714062d
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestContext.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.dirlist;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.test.mock.MockContentResolver;
+
+public final class TestContext {
+
+ /**
+ * Returns a Context configured with test provider for authority.
+ */
+ static Context createStorageTestContext(Context context, String authority) {
+ final MockContentResolver testResolver = new MockContentResolver();
+ TestContentProvider provider = new TestContentProvider();
+ testResolver.addProvider(authority, provider);
+
+ return new ContextWrapper(context) {
+ @Override
+ public ContentResolver getContentResolver() {
+ return testResolver;
+ }
+ };
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestModel.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestModel.java
new file mode 100644
index 0000000..f861c73
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestModel.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.dirlist;
+
+import android.content.Context;
+import android.database.MatrixCursor;
+import android.provider.DocumentsContract.Document;
+
+import com.android.documentsui.DirectoryResult;
+import com.android.documentsui.RootCursorWrapper;
+import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+
+import java.util.Random;
+import java.util.Set;
+
+public class TestModel extends Model {
+
+ private static final String[] COLUMNS = new String[]{
+ RootCursorWrapper.COLUMN_AUTHORITY,
+ Document.COLUMN_DOCUMENT_ID,
+ Document.COLUMN_FLAGS,
+ Document.COLUMN_DISPLAY_NAME,
+ Document.COLUMN_SIZE,
+ Document.COLUMN_MIME_TYPE
+ };
+
+ private final String mAuthority;
+ private Set<String> mDeleted;
+
+ /**
+ * Creates a new context. context must be configured with provider for authority.
+ * @see TestContext#createStorageTestContext(Context, String).
+ */
+ public TestModel(Context context, String authority) {
+ super(context);
+ mAuthority = authority;
+ }
+
+ void update(String... names) {
+ Random rand = new Random();
+
+ MatrixCursor c = new MatrixCursor(COLUMNS);
+ for (int i = 0; i < names.length; i++) {
+ MatrixCursor.RowBuilder row = c.newRow();
+ row.add(RootCursorWrapper.COLUMN_AUTHORITY, mAuthority);
+ row.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i));
+ row.add(Document.COLUMN_FLAGS, Document.FLAG_SUPPORTS_DELETE);
+ // Generate random document names and sizes. This forces the model's internal sort code
+ // to actually do something.
+ row.add(Document.COLUMN_DISPLAY_NAME, names[i]);
+ row.add(Document.COLUMN_SIZE, rand.nextInt());
+ }
+
+ DirectoryResult r = new DirectoryResult();
+ r.cursor = c;
+ update(r);
+ }
+
+ @Override
+ public void delete(Selection selected, DeletionListener listener) {
+ for (String id : selected.getAll()) {
+ mDeleted.add(id);
+ }
+ listener.onCompletion();
+ }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 7e22881..f7e25db 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -624,6 +624,11 @@
<!-- UI debug setting: force allow on external summary [CHAR LIMIT=150] -->
<string name="force_resizable_activities_summary">Makes all activities resizable for multi-window, regardless of manifest values.</string>
+ <!-- UI debug setting: enable freeform window support [CHAR LIMIT=50] -->
+ <string name="enable_freeform_support">Enable freeform windows</string>
+ <!-- UI debug setting: enable freeform window support summary [CHAR LIMIT=150] -->
+ <string name="enable_freeform_support_summary">Enables support for experimental freeform windows.</string>
+
<!-- Local (desktop) backup password menu title [CHAR LIMIT=25] -->
<string name="local_backup_password_title">Desktop backup password</string>
<!-- Summary text of the "local backup password" setting when the user has not supplied a password -->
diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_docked.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_docked.png
new file mode 100644
index 0000000..f3be2ee
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_docked.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/docked_divider_handle.xml b/packages/SystemUI/res/drawable/docked_divider_handle.xml
deleted file mode 100644
index 84c0343..0000000
--- a/packages/SystemUI/res/drawable/docked_divider_handle.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2 (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.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <size android:height="@dimen/docked_divider_handle_height"
- android:width="@dimen/docked_divider_handle_width" />
- <corners android:radius="1dp" />
- <solid android:color="@color/docked_divider_handle" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/docked_stack_divider.xml b/packages/SystemUI/res/layout/docked_stack_divider.xml
index 22ed216..7ea5027 100644
--- a/packages/SystemUI/res/layout/docked_stack_divider.xml
+++ b/packages/SystemUI/res/layout/docked_stack_divider.xml
@@ -24,10 +24,9 @@
android:id="@+id/docked_divider_background"
android:background="@color/docked_divider_background"/>
- <ImageButton
+ <com.android.systemui.stackdivider.DividerHandleView
style="@style/DockedDividerHandle"
android:id="@+id/docked_divider_handle"
- android:background="@null"
- android:src="@drawable/docked_divider_handle"/>
+ android:background="@null"/>
</com.android.systemui.stackdivider.DividerView>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index ffcc805..57074df 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -77,6 +77,7 @@
import com.android.systemui.recents.views.RecentsView;
import com.android.systemui.recents.views.SystemBarScrimViews;
import com.android.systemui.recents.views.ViewAnimation;
+import com.android.systemui.statusbar.BaseStatusBar;
import java.util.ArrayList;
@@ -298,12 +299,23 @@
*/
void dismissRecentsToHome(boolean animated) {
if (animated) {
- ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(null,
- mFinishLaunchHomeRunnable, null);
+ ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger();
+ exitTrigger.increment();
+ exitTrigger.addLastDecrementRunnable(mFinishLaunchHomeRunnable);
+ exitTrigger.addLastDecrementRunnable(new Runnable() {
+ @Override
+ public void run() {
+ Recents.getSystemServices().sendCloseSystemWindows(
+ BaseStatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
+ }
+ });
mRecentsView.startExitToHomeAnimation(
new ViewAnimation.TaskViewExitContext(exitTrigger));
+ exitTrigger.decrement();
} else {
mFinishLaunchHomeRunnable.run();
+ Recents.getSystemServices().sendCloseSystemWindows(
+ BaseStatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
}
}
@@ -343,7 +355,7 @@
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
// Initialize the widget host (the host id is static and does not change)
- if (!RecentsDebugFlags.Static.DisableSearchBar) {
+ if (RecentsDebugFlags.Static.EnableSearchBar) {
mAppWidgetHost = new RecentsAppWidgetHost(this, RecentsAppWidgetHost.HOST_ID);
}
mPackageMonitor = new RecentsPackageMonitor();
@@ -368,14 +380,14 @@
mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent);
// Bind the search app widget when we first start up
- if (!RecentsDebugFlags.Static.DisableSearchBar) {
+ if (RecentsDebugFlags.Static.EnableSearchBar) {
mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(this, mAppWidgetHost);
}
// Register the broadcast receiver to handle messages when the screen is turned off
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
- if (!RecentsDebugFlags.Static.DisableSearchBar) {
+ if (RecentsDebugFlags.Static.EnableSearchBar) {
filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
}
registerReceiver(mSystemBroadcastReceiver, filter);
@@ -475,7 +487,7 @@
mPackageMonitor.unregister();
// Stop listening for widget package changes if there was one bound
- if (!RecentsDebugFlags.Static.DisableSearchBar) {
+ if (RecentsDebugFlags.Static.EnableSearchBar) {
mAppWidgetHost.stopListening();
}
@@ -656,8 +668,8 @@
ReferenceCountedTrigger t = new ReferenceCountedTrigger();
ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
ctx.postAnimationTrigger.increment();
- if (mSearchWidgetInfo != null) {
- if (!RecentsDebugFlags.Static.DisableSearchBar) {
+ if (RecentsDebugFlags.Static.EnableSearchBar) {
+ if (mSearchWidgetInfo != null) {
ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index 40c84ba..c323522 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -32,8 +32,8 @@
public static class Static {
// Enables debug drawing for the transition thumbnail
public static final boolean EnableTransitionThumbnailDebugMode = false;
- // This disables the search bar integration
- public static final boolean DisableSearchBar = true;
+ // This enables the search bar integration
+ public static final boolean EnableSearchBar = false;
// This disables the bitmap and icon caches
public static final boolean DisableBackgroundCache = false;
// Enables the simulated task affiliations
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index a974c23..fd00289 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -19,6 +19,7 @@
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ITaskStackListener;
+import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
@@ -60,6 +61,7 @@
import com.android.systemui.recents.views.TaskStackView;
import com.android.systemui.recents.views.TaskViewHeader;
import com.android.systemui.recents.views.TaskViewTransform;
+import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.ArrayList;
@@ -364,6 +366,9 @@
// Otherwise, start the recents activity
startRecentsActivity(topTask, isTopTaskHome.value, true /* animate */);
+
+ // Only close the other system windows if we are actually showing recents
+ ssp.sendCloseSystemWindows(BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
mLastToggleTime = SystemClock.elapsedRealtime();
}
} catch (ActivityNotFoundException e) {
@@ -578,7 +583,7 @@
// Update the configuration for the current state
config.update(windowRect);
- if (!RecentsDebugFlags.Static.DisableSearchBar && tryAndBindSearchWidget) {
+ if (RecentsDebugFlags.Static.EnableSearchBar && tryAndBindSearchWidget) {
// Try and pre-emptively bind the search widget on startup to ensure that we
// have the right thumbnail bounds to animate to.
// Note: We have to reload the widget id before we get the task stack bounds below
@@ -854,11 +859,19 @@
if (!useThumbnailTransition) {
// If there is no thumbnail transition, but is launching from home into recents, then
// use a quick home transition and do the animation from home
- if (!RecentsDebugFlags.Static.DisableSearchBar && hasRecentTasks) {
+ if (hasRecentTasks) {
SystemServicesProxy ssp = Recents.getSystemServices();
String homeActivityPackage = ssp.getHomeActivityPackageName();
- String searchWidgetPackage = Prefs.getString(mContext,
- Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null);
+ String searchWidgetPackage = null;
+ if (RecentsDebugFlags.Static.EnableSearchBar) {
+ searchWidgetPackage = Prefs.getString(mContext,
+ Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null);
+ } else {
+ AppWidgetProviderInfo searchWidgetInfo = ssp.resolveSearchAppWidget();
+ if (searchWidgetInfo != null) {
+ searchWidgetPackage = searchWidgetInfo.provider.getPackageName();
+ }
+ }
// Determine whether we are coming from a search owned home activity
boolean fromSearchHome = (homeActivityPackage != null) &&
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index c86a885..108029d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -58,7 +58,7 @@
import android.util.MutableBoolean;
import android.util.Pair;
import android.view.Display;
-import android.view.IDockDividerVisibilityListener;
+import android.view.IDockedStackListener;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
@@ -68,6 +68,7 @@
import com.android.systemui.R;
import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.RecentsImpl;
+import com.android.systemui.statusbar.BaseStatusBar;
import java.io.IOException;
import java.util.ArrayList;
@@ -79,6 +80,7 @@
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
/**
* Acts as a shim around the real system services that we need to access data from, and provides
@@ -128,7 +130,9 @@
mDisplay = mWm.getDefaultDisplay();
mRecentsPackage = context.getPackageName();
mHasFreeformWorkspaceSupport =
- mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+ mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT) ||
+ Settings.Global.getInt(context.getContentResolver(),
+ DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
// Get the dummy thumbnail width/heights
Resources res = context.getResources();
@@ -499,6 +503,18 @@
}
/**
+ * Sends a message to close other system windows.
+ */
+ public void sendCloseSystemWindows(String reason) {
+ if (ActivityManagerNative.isSystemReady()) {
+ try {
+ ActivityManagerNative.getDefault().closeSystemDialogs(reason);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
* Returns the activity info for a given component name.
*
* @param cn The component name of the activity.
@@ -635,7 +651,7 @@
if (mPm == null) return null;
if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return null;
- ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
+ ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities);
if (defaultHomeActivity != null) {
return defaultHomeActivity.getPackageName();
@@ -723,7 +739,7 @@
/**
* Returns the first Recents widget from the same package as the global assist activity.
*/
- private AppWidgetProviderInfo resolveSearchAppWidget() {
+ public AppWidgetProviderInfo resolveSearchAppWidget() {
if (mAssistComponent == null) return null;
List<AppWidgetProviderInfo> widgets = mAwm.getInstalledProviders(
AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
@@ -879,12 +895,11 @@
}
}
- public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+ public void registerDockedStackListener(IDockedStackListener listener) {
if (mWm == null) return;
try {
- WindowManagerGlobal.getWindowManagerService().registerDockDividerVisibilityListener(
- listener);
+ WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(listener);
} catch (Exception e) {
e.printStackTrace();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 9b1315a..c95c73b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -317,7 +317,7 @@
* Hides the task stack and shows the empty view.
*/
public void showEmptyView() {
- if (!RecentsDebugFlags.Static.DisableSearchBar && (mSearchBar != null)) {
+ if (RecentsDebugFlags.Static.EnableSearchBar && (mSearchBar != null)) {
mSearchBar.setVisibility(View.INVISIBLE);
}
mTaskStackView.setVisibility(View.INVISIBLE);
@@ -332,7 +332,7 @@
public void hideEmptyView() {
mEmptyView.setVisibility(View.INVISIBLE);
mTaskStackView.setVisibility(View.VISIBLE);
- if (!RecentsDebugFlags.Static.DisableSearchBar && (mSearchBar != null)) {
+ if (RecentsDebugFlags.Static.EnableSearchBar && (mSearchBar != null)) {
mSearchBar.setVisibility(View.VISIBLE);
}
mTaskStackView.bringToFront();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 10df156..9d391b0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -333,7 +333,6 @@
* including the search bar.
*/
public void initialize(Rect taskStackBounds, StackState state) {
- RecentsDebugFlags debugFlags = Recents.getDebugFlags();
RecentsConfiguration config = Recents.getConfiguration();
int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
int heightPadding = mContext.getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 6ff7a3e..189e651 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -17,7 +17,8 @@
package com.android.systemui.stackdivider;
import android.content.res.Configuration;
-import android.view.IDockDividerVisibilityListener;
+import android.os.RemoteException;
+import android.view.IDockedStackListener;
import android.view.LayoutInflater;
import android.view.View;
@@ -49,7 +50,7 @@
putComponent(Divider.class, this);
mDockDividerVisibilityListener = new DockDividerVisibilityListener();
SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.registerDockDividerVisibilityListener(mDockDividerVisibilityListener);
+ ssp.registerDockedStackListener(mDockDividerVisibilityListener);
}
@Override
@@ -94,10 +95,15 @@
});
}
- class DockDividerVisibilityListener extends IDockDividerVisibilityListener.Stub {
+ class DockDividerVisibilityListener extends IDockedStackListener.Stub {
+
@Override
- public void onDockDividerVisibilityChanged(boolean visible) {
+ public void onDividerVisibilityChanged(boolean visible) throws RemoteException {
updateVisibility(visible);
}
+
+ @Override
+ public void onDockedStackExistsChanged(boolean exists) throws RemoteException {
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerHandleView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerHandleView.java
new file mode 100644
index 0000000..5ef56f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerHandleView.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.stackdivider;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.ImageButton;
+
+import com.android.systemui.R;
+
+/**
+ * View for the handle in the docked stack divider.
+ */
+public class DividerHandleView extends ImageButton {
+
+ private final static Property<DividerHandleView, Integer> WIDTH_PROPERTY
+ = new Property<DividerHandleView, Integer>(Integer.class, "width") {
+
+ @Override
+ public Integer get(DividerHandleView object) {
+ return object.mCurrentWidth;
+ }
+
+ @Override
+ public void set(DividerHandleView object, Integer value) {
+ object.mCurrentWidth = value;
+ object.invalidate();
+ }
+ };
+
+ private final static Property<DividerHandleView, Integer> HEIGHT_PROPERTY
+ = new Property<DividerHandleView, Integer>(Integer.class, "height") {
+
+ @Override
+ public Integer get(DividerHandleView object) {
+ return object.mCurrentHeight;
+ }
+
+ @Override
+ public void set(DividerHandleView object, Integer value) {
+ object.mCurrentHeight = value;
+ object.invalidate();
+ }
+ };
+
+ private final Paint mPaint = new Paint();
+ private final int mWidth;
+ private final int mHeight;
+ private final int mCircleDiameter;
+ private final Interpolator mFastOutSlowInInterpolator;
+ private int mCurrentWidth;
+ private int mCurrentHeight;
+ private AnimatorSet mAnimator;
+
+ public DividerHandleView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ mPaint.setColor(getResources().getColor(R.color.docked_divider_handle, null));
+ mPaint.setAntiAlias(true);
+ mWidth = getResources().getDimensionPixelSize(R.dimen.docked_divider_handle_width);
+ mHeight = getResources().getDimensionPixelSize(R.dimen.docked_divider_handle_height);
+ mCurrentWidth = mWidth;
+ mCurrentHeight = mHeight;
+ mCircleDiameter = (mWidth + mHeight) / 3;
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
+ android.R.interpolator.fast_out_slow_in);
+ }
+
+ public void setTouching(boolean touching, boolean animate) {
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ mAnimator = null;
+ }
+ if (!animate) {
+ if (touching) {
+ mCurrentWidth = mCircleDiameter;
+ mCurrentHeight = mCircleDiameter;
+ } else {
+ mCurrentWidth = mWidth;
+ mCurrentHeight = mHeight;
+ }
+ invalidate();
+ } else {
+ animateToTarget(touching ? mCircleDiameter : mWidth,
+ touching ? mCircleDiameter : mHeight, touching);
+ }
+ }
+
+ private void animateToTarget(int targetWidth, int targetHeight, boolean touching) {
+ ObjectAnimator widthAnimator = ObjectAnimator.ofInt(this, WIDTH_PROPERTY,
+ mCurrentWidth, targetWidth);
+ ObjectAnimator heightAnimator = ObjectAnimator.ofInt(this, HEIGHT_PROPERTY,
+ mCurrentHeight, targetHeight);
+ mAnimator = new AnimatorSet();
+ mAnimator.playTogether(widthAnimator, heightAnimator);
+ mAnimator.setDuration(touching
+ ? DividerView.TOUCH_ANIMATION_DURATION
+ : DividerView.TOUCH_RELEASE_ANIMATION_DURATION);
+ mAnimator.setInterpolator(touching
+ ? DividerView.TOUCH_RESPONSE_INTERPOLATOR
+ : mFastOutSlowInInterpolator);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimator = null;
+ }
+ });
+ mAnimator.start();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ int left = getWidth() / 2 - mCurrentWidth / 2;
+ int top = getHeight() / 2 - mCurrentHeight / 2;
+ int radius = Math.min(mCurrentWidth, mCurrentHeight) / 2;
+ canvas.drawRoundRect(left, top, left + mCurrentWidth, top + mCurrentHeight,
+ radius, radius, mPaint);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
index 5af172c..e43d531 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
@@ -17,25 +17,42 @@
package com.android.systemui.stackdivider;
import android.content.Context;
-import android.util.DisplayMetrics;
-import android.view.DisplayInfo;
+import android.graphics.Rect;
import com.android.systemui.statusbar.FlingAnimationUtils;
import java.util.ArrayList;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-
/**
* 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.
*/
public class DividerSnapAlgorithm {
+ /**
+ * 3 snap targets: left/top has 16:9 ratio (for videos), 1:1, and right/bottom has 16:9 ratio
+ */
+ private static final int SNAP_MODE_16_9 = 0;
+
+ /**
+ * 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio)
+ */
+ private static final int SNAP_FIXED_RATIO = 1;
+
+ /**
+ * 1 snap target: 1:1
+ */
+ private static final int SNAP_ONLY_1_1 = 2;
+
private final Context mContext;
private final FlingAnimationUtils mFlingAnimationUtils;
+ private final int mDisplayWidth;
+ private final int mDisplayHeight;
private final int mDividerSize;
- private final ArrayList<SnapTarget> mTargets;
+ private final ArrayList<SnapTarget> mTargets = new ArrayList<>();
+ private final Rect mInsets = new Rect();
+ private final int mSnapMode;
+ private final float mFixedRatio;
/** The first target which is still splitting the screen */
private final SnapTarget mFirstSplitTarget;
@@ -47,11 +64,19 @@
private final SnapTarget mDismissEndTarget;
public DividerSnapAlgorithm(Context ctx, FlingAnimationUtils flingAnimationUtils,
- int dividerSize, boolean isHorizontalDivision) {
+ int displayWidth, int displayHeight, int dividerSize, boolean isHorizontalDivision,
+ Rect insets) {
mContext = ctx;
mFlingAnimationUtils = flingAnimationUtils;
mDividerSize = dividerSize;
- mTargets = calculateTargets(isHorizontalDivision);
+ mDisplayWidth = displayWidth;
+ mDisplayHeight = displayHeight;
+ mInsets.set(insets);
+ mSnapMode = ctx.getResources().getInteger(
+ com.android.internal.R.integer.config_dockedStackDividerSnapMode);
+ mFixedRatio = ctx.getResources().getFraction(
+ com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
+ calculateTargets(isHorizontalDivision);
mFirstSplitTarget = mTargets.get(1);
mLastSplitTarget = mTargets.get(mTargets.size() - 2);
mDismissStartTarget = mTargets.get(0);
@@ -75,6 +100,40 @@
}
}
+ public float calculateDismissingFraction(int position) {
+ if (position < mFirstSplitTarget.position) {
+ return 1f - (float) position / mFirstSplitTarget.position;
+ } else if (position > mLastSplitTarget.position) {
+ return (float) (position - mLastSplitTarget.position)
+ / (mDismissEndTarget.position - mLastSplitTarget.position);
+ }
+ return 0f;
+ }
+
+ public SnapTarget getClosestDismissTarget(int position) {
+ if (position - mDismissStartTarget.position < mDismissEndTarget.position - position) {
+ return mDismissStartTarget;
+ } else {
+ return mDismissEndTarget;
+ }
+ }
+
+ public SnapTarget getFirstSplitTarget() {
+ return mFirstSplitTarget;
+ }
+
+ public SnapTarget getLastSplitTarget() {
+ return mLastSplitTarget;
+ }
+
+ public SnapTarget getDismissStartTarget() {
+ return mDismissStartTarget;
+ }
+
+ public SnapTarget getDismissEndTarget() {
+ return mDismissEndTarget;
+ }
+
private SnapTarget snap(int position) {
int minIndex = -1;
int minDistance = Integer.MAX_VALUE;
@@ -89,22 +148,61 @@
return mTargets.get(minIndex);
}
- private ArrayList<SnapTarget> calculateTargets(boolean isHorizontalDivision) {
- ArrayList<SnapTarget> targets = new ArrayList<>();
- DisplayMetrics info = mContext.getResources().getDisplayMetrics();
+ private void calculateTargets(boolean isHorizontalDivision) {
+ mTargets.clear();
int dividerMax = isHorizontalDivision
- ? info.heightPixels
- : info.widthPixels;
+ ? mDisplayHeight
+ : mDisplayWidth;
+ mTargets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START));
+ switch (mSnapMode) {
+ case SNAP_MODE_16_9:
+ addRatio16_9Targets(isHorizontalDivision);
+ break;
+ case SNAP_FIXED_RATIO:
+ addFixedDivisionTargets(isHorizontalDivision);
+ break;
+ case SNAP_ONLY_1_1:
+ addMiddleTarget(isHorizontalDivision);
+ break;
+ }
+ mTargets.add(new SnapTarget(dividerMax, SnapTarget.FLAG_DISMISS_END));
+ }
- // TODO: Better calculation
- targets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START));
- targets.add(new SnapTarget((int) (0.3415f * dividerMax) - mDividerSize / 2,
+ private void addFixedDivisionTargets(boolean isHorizontalDivision) {
+ int start = isHorizontalDivision ? mInsets.top : mInsets.left;
+ int end = isHorizontalDivision
+ ? mDisplayHeight - mInsets.bottom
+ : mDisplayWidth - mInsets.right;
+ mTargets.add(new SnapTarget((int) (start + mFixedRatio * (end - start)) - mDividerSize / 2,
SnapTarget.FLAG_NONE));
- targets.add(new SnapTarget(dividerMax / 2 - mDividerSize / 2, SnapTarget.FLAG_NONE));
- targets.add(new SnapTarget((int) (0.6585f * dividerMax) - mDividerSize / 2,
+ addMiddleTarget(isHorizontalDivision);
+ mTargets.add(new SnapTarget((int) (start + (1 - mFixedRatio) * (end - start))
+ - mDividerSize / 2, SnapTarget.FLAG_NONE));
+ }
+
+ private void addRatio16_9Targets(boolean isHorizontalDivision) {
+ int start = isHorizontalDivision ? mInsets.top : mInsets.left;
+ int end = isHorizontalDivision
+ ? mDisplayHeight - mInsets.bottom
+ : mDisplayWidth - mInsets.right;
+ int startOther = isHorizontalDivision ? mInsets.left : mInsets.top;
+ int endOther = isHorizontalDivision
+ ? mDisplayWidth - mInsets.right
+ : mDisplayHeight - mInsets.bottom;
+ float size = 9.0f / 16.0f * (endOther - startOther);
+ int sizeInt = (int) Math.floor(size);
+ mTargets.add(new SnapTarget(start + sizeInt, SnapTarget.FLAG_NONE));
+ addMiddleTarget(isHorizontalDivision);
+ mTargets.add(new SnapTarget(end - sizeInt - mDividerSize, SnapTarget.FLAG_NONE));
+ }
+
+ private void addMiddleTarget(boolean isHorizontalDivision) {
+ int start = isHorizontalDivision ? mInsets.top : mInsets.left;
+ int end = isHorizontalDivision
+ ? mDisplayHeight - mInsets.bottom
+ : mDisplayWidth - mInsets.right;
+ mTargets.add(new SnapTarget(start + (end - start) / 2 - mDividerSize / 2,
SnapTarget.FLAG_NONE));
- targets.add(new SnapTarget(dividerMax, SnapTarget.FLAG_DISMISS_END));
- return targets;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 13642eb..4c83b51e 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -21,12 +21,14 @@
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
+import android.app.ActivityManager.StackId;
import android.content.Context;
import android.content.res.Configuration;
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;
@@ -36,6 +38,8 @@
import android.view.View.OnTouchListener;
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;
import android.view.animation.Interpolator;
@@ -56,9 +60,21 @@
public class DividerView extends FrameLayout implements OnTouchListener,
OnComputeInternalInsetsListener {
+ static final long TOUCH_ANIMATION_DURATION = 150;
+ static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
+ static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
+ new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+
private static final String TAG = "DividerView";
- private ImageButton mHandle;
+ private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
+ private static final float DIM_START_FRACTION = 0.5f;
+ private static final float DIM_DAMP_FACTOR = 1.7f;
+
+ private static final PathInterpolator SLOWDOWN_INTERPOLATOR =
+ new PathInterpolator(0.5f, 1f, 0.5f, 1f);
+
+ private DividerHandleView mHandle;
private View mBackground;
private int mStartX;
private int mStartY;
@@ -73,15 +89,20 @@
private int mDividerSize;
private int mTouchElevation;
- private final Rect mTmpRect = new Rect();
+ private final Rect mDockedRect = new Rect();
+ private final Rect mDockedTaskRect = new Rect();
+ private final Rect mOtherTaskRect = new Rect();
+ private final Rect mOtherRect = new Rect();
+ private final Rect mDockedInsetRect = new Rect();
+ private final Rect mOtherInsetRect = new Rect();
private final Rect mLastResizeRect = new Rect();
private final WindowManagerProxy mWindowManagerProxy = WindowManagerProxy.getInstance();
private Interpolator mFastOutSlowInInterpolator;
- private final Interpolator mTouchResponseInterpolator =
- new PathInterpolator(0.3f, 0f, 0.1f, 1f);
private DividerWindowManager mWindowManager;
private VelocityTracker mVelocityTracker;
private FlingAnimationUtils mFlingAnimationUtils;
+ private DividerSnapAlgorithm mSnapAlgorithm;
+ private final Rect mStableInsets = new Rect();
public DividerView(Context context) {
super(context);
@@ -103,7 +124,7 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mHandle = (ImageButton) findViewById(R.id.docked_divider_handle);
+ mHandle = (DividerHandleView) findViewById(R.id.docked_divider_handle);
mBackground = findViewById(R.id.docked_divider_background);
mHandle.setOnTouchListener(this);
mDividerWindowWidth = getResources().getDimensionPixelSize(
@@ -124,6 +145,13 @@
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
}
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
+ insets.getStableInsetRight(), insets.getStableInsetBottom());
+ return super.onApplyWindowInsets(insets);
+ }
+
public void setWindowManager(DividerWindowManager windowManager) {
mWindowManager = windowManager;
}
@@ -132,8 +160,11 @@
return mWindowManagerProxy;
}
- public boolean startDragging() {
+ public boolean startDragging(boolean animate) {
+ mHandle.setTouching(true, animate);
mDockSide = mWindowManagerProxy.getDockSide();
+ mSnapAlgorithm = new DividerSnapAlgorithm(getContext(), mFlingAnimationUtils, mDisplayWidth,
+ mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets);
if (mDockSide != WindowManager.DOCKED_INVALID) {
mWindowManagerProxy.setResizing(true);
mWindowManager.setSlippery(false);
@@ -145,11 +176,16 @@
}
public void stopDragging(int position, float velocity) {
+ mHandle.setTouching(false, true /* animate */);
fling(position, velocity);
mWindowManager.setSlippery(true);
releaseBackground();
}
+ public DividerSnapAlgorithm getSnapAlgorithm() {
+ return mSnapAlgorithm;
+ }
+
@Override
public boolean onTouch(View v, MotionEvent event) {
convertToScreenCoordinates(event);
@@ -161,7 +197,7 @@
mStartX = (int) event.getX();
mStartY = (int) event.getY();
getLocationOnScreen(mTempInt2);
- boolean result = startDragging();
+ boolean result = startDragging(true /* animate */);
if (isHorizontalDivision()) {
mStartPosition = mTempInt2[1] + mDividerInsets;
} else {
@@ -173,7 +209,10 @@
int x = (int) event.getX();
int y = (int) event.getY();
if (mDockSide != WindowManager.DOCKED_INVALID) {
- resizeStack(calculatePosition(x, y));
+ int position = calculatePosition(x, y);
+ SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position,
+ 0 /* velocity */);
+ resizeStack(calculatePosition(x, y), snapTarget.position, snapTarget);
}
break;
case MotionEvent.ACTION_UP:
@@ -197,14 +236,16 @@
}
private void fling(int position, float velocity) {
- final SnapTarget snapTarget = new DividerSnapAlgorithm(getContext(), mFlingAnimationUtils,
- mDividerSize, isHorizontalDivision()).calculateSnapTarget(position, velocity);
+ final SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity);
ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- resizeStack((Integer) animation.getAnimatedValue());
+ resizeStack((Integer) animation.getAnimatedValue(),
+ animation.getAnimatedFraction() == 1f
+ ? TASK_POSITION_SAME
+ : snapTarget.position, snapTarget);
}
});
anim.addListener(new AnimatorListenerAdapter() {
@@ -236,6 +277,7 @@
} else {
mWindowManagerProxy.maximizeDockedStack();
}
+ mWindowManagerProxy.setResizeDimLayer(false, -1, 0f);
}
private void liftBackground() {
@@ -245,29 +287,33 @@
mBackground.animate().scaleX(1.4f);
}
mBackground.animate()
- .setInterpolator(mTouchResponseInterpolator)
- .setDuration(150)
- .translationZ(mTouchElevation);
+ .setInterpolator(TOUCH_RESPONSE_INTERPOLATOR)
+ .setDuration(TOUCH_ANIMATION_DURATION)
+ .translationZ(mTouchElevation)
+ .start();
// Lift handle as well so it doesn't get behind the background, even though it doesn't
// cast shadow.
mHandle.animate()
- .setInterpolator(mTouchResponseInterpolator)
- .setDuration(150)
- .translationZ(mTouchElevation);
+ .setInterpolator(TOUCH_RESPONSE_INTERPOLATOR)
+ .setDuration(TOUCH_ANIMATION_DURATION)
+ .translationZ(mTouchElevation)
+ .start();
}
private void releaseBackground() {
mBackground.animate()
.setInterpolator(mFastOutSlowInInterpolator)
- .setDuration(200)
+ .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
.translationZ(0)
.scaleX(1f)
- .scaleY(1f);
+ .scaleY(1f)
+ .start();
mHandle.animate()
.setInterpolator(mFastOutSlowInInterpolator)
- .setDuration(200)
- .translationZ(0);
+ .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
+ .translationZ(0)
+ .start();
}
@Override
@@ -332,17 +378,184 @@
}
}
- public void resizeStack(int position) {
- calculateBoundsForPosition(position, mDockSide, mTmpRect);
- if (mTmpRect.equals(mLastResizeRect)) {
+ private int invertDockSide(int dockSide) {
+ switch (dockSide) {
+ case WindowManager.DOCKED_LEFT:
+ return WindowManager.DOCKED_RIGHT;
+ case WindowManager.DOCKED_TOP:
+ return WindowManager.DOCKED_BOTTOM;
+ case WindowManager.DOCKED_RIGHT:
+ return WindowManager.DOCKED_LEFT;
+ case WindowManager.DOCKED_BOTTOM:
+ return WindowManager.DOCKED_TOP;
+ default:
+ return WindowManager.DOCKED_INVALID;
+ }
+ }
+
+ private void alignTopLeft(Rect containingRect, Rect rect) {
+ int width = rect.width();
+ int height = rect.height();
+ rect.set(containingRect.left, containingRect.top,
+ containingRect.left + width, containingRect.top + height);
+ }
+
+ private void alignBottomRight(Rect containingRect, Rect rect) {
+ int width = rect.width();
+ int height = rect.height();
+ rect.set(containingRect.right - width, containingRect.bottom - height,
+ containingRect.right, containingRect.bottom);
+ }
+
+ public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) {
+ calculateBoundsForPosition(position, mDockSide, mDockedRect);
+
+ if (mDockedRect.equals(mLastResizeRect)) {
return;
}
// Make sure shadows are updated
mBackground.invalidate();
- mLastResizeRect.set(mTmpRect);
- mWindowManagerProxy.resizeDockedStack(mTmpRect);
+ mLastResizeRect.set(mDockedRect);
+ if (taskPosition != TASK_POSITION_SAME) {
+ calculateBoundsForPosition(position, invertDockSide(mDockSide), mOtherRect);
+ int dockSideInverted = invertDockSide(mDockSide);
+ int taskPositionDocked =
+ restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget);
+ int taskPositionOther =
+ restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget);
+ calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect);
+ calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect);
+ alignTopLeft(mDockedRect, mDockedTaskRect);
+ alignTopLeft(mOtherRect, mOtherTaskRect);
+ mDockedInsetRect.set(mDockedTaskRect);
+ mOtherInsetRect.set(mOtherTaskRect);
+ if (dockSideTopLeft(mDockSide)) {
+ alignTopLeft(mDockedRect, mDockedInsetRect);
+ alignBottomRight(mOtherRect, mOtherInsetRect);
+ } else {
+ alignBottomRight(mDockedRect, mDockedInsetRect);
+ alignTopLeft(mOtherRect, mOtherInsetRect);
+ }
+ applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position,
+ taskPositionDocked);
+ applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position,
+ taskPositionOther);
+ mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect,
+ mOtherTaskRect, mOtherInsetRect);
+ } else {
+ mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null);
+ }
+ float fraction = mSnapAlgorithm.calculateDismissingFraction(position);
+ fraction = Math.max(0,
+ Math.min((fraction / DIM_START_FRACTION - 1f) / DIM_DAMP_FACTOR, 1f));
+ mWindowManagerProxy.setResizeDimLayer(fraction != 0f,
+ getStackIdForDismissTarget(mSnapAlgorithm.getClosestDismissTarget(position)),
+ fraction);
+ }
+
+ /**
+ * When the snap target is dismissing one side, make sure that the dismissing side doesn't get
+ * 0 size.
+ */
+ private int restrictDismissingTaskPosition(int taskPosition, int dockSide,
+ SnapTarget snapTarget) {
+ if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) {
+ return mSnapAlgorithm.getFirstSplitTarget().position;
+ } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END
+ && dockSideBottomRight(dockSide)) {
+ return mSnapAlgorithm.getLastSplitTarget().position;
+ } else {
+ return taskPosition;
+ }
+ }
+
+ /**
+ * Applies a parallax to the task when dismissing.
+ */
+ private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget,
+ int position, int taskPosition) {
+ float fraction = Math.min(1, Math.max(0,
+ mSnapAlgorithm.calculateDismissingFraction(position)));
+ SnapTarget dismissTarget = null;
+ SnapTarget splitTarget = null;
+ if ((snapTarget.flag == SnapTarget.FLAG_DISMISS_START
+ || snapTarget == mSnapAlgorithm.getFirstSplitTarget())
+ && dockSideTopLeft(dockSide)) {
+ dismissTarget = mSnapAlgorithm.getDismissStartTarget();
+ splitTarget = mSnapAlgorithm.getFirstSplitTarget();
+ } else if ((snapTarget.flag == SnapTarget.FLAG_DISMISS_END
+ || snapTarget == mSnapAlgorithm.getLastSplitTarget())
+ && dockSideBottomRight(dockSide)) {
+ dismissTarget = mSnapAlgorithm.getDismissEndTarget();
+ splitTarget = mSnapAlgorithm.getLastSplitTarget();
+ }
+ if (dismissTarget != null && fraction > 0f
+ && isDismissing(splitTarget, position, dockSide)) {
+ fraction = calculateParallaxDismissingFraction(fraction);
+ int offsetPosition = (int) (taskPosition +
+ fraction * (dismissTarget.position - splitTarget.position));
+ int width = taskRect.width();
+ int height = taskRect.height();
+ switch (dockSide) {
+ case WindowManager.DOCKED_LEFT:
+ taskRect.left = offsetPosition - width;
+ taskRect.right = offsetPosition;
+ break;
+ case WindowManager.DOCKED_RIGHT:
+ taskRect.left = offsetPosition + mDividerSize;
+ taskRect.right = offsetPosition + width + mDividerSize;
+ break;
+ case WindowManager.DOCKED_TOP:
+ taskRect.top = offsetPosition - height;
+ taskRect.bottom = offsetPosition;
+ break;
+ case WindowManager.DOCKED_BOTTOM:
+ taskRect.top = offsetPosition + mDividerSize;
+ taskRect.bottom = offsetPosition + height + mDividerSize;
+ break;
+ }
+ }
+ }
+
+ /**
+ * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
+ * slowing down parallax effect
+ */
+ private static float calculateParallaxDismissingFraction(float fraction) {
+ return SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
+ }
+
+ private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) {
+ if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) {
+ return position < snapTarget.position;
+ } else {
+ return position > snapTarget.position;
+ }
+ }
+
+ private int getStackIdForDismissTarget(SnapTarget dismissTarget) {
+ if (dismissTarget.flag == SnapTarget.FLAG_DISMISS_START &&
+ (mDockSide == WindowManager.DOCKED_LEFT || mDockSide == WindowManager.DOCKED_TOP)) {
+ return StackId.DOCKED_STACK_ID;
+ } else {
+ return StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+ }
+ }
+
+ /**
+ * @return true if and only if {@code dockSide} is top or left
+ */
+ private static boolean dockSideTopLeft(int dockSide) {
+ return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT;
+ }
+
+ /**
+ * @return true if and only if {@code dockSide} is bottom or right
+ */
+ private static boolean dockSideBottomRight(int dockSide) {
+ return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
index 2251874..161f873 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
@@ -50,6 +50,9 @@
| FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
mLp.setTitle(WINDOW_TITLE);
+ view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
mWindowManager.addView(view, mLp);
mView = view;
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index ef47d8d..2791dfc 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -40,18 +40,41 @@
private static final WindowManagerProxy sInstance = new WindowManagerProxy();
@GuardedBy("mResizeRect")
- private final Rect mResizeRect = new Rect();
- private final Rect mTmpRect = new Rect();
+ private final Rect mDockedRect = new Rect();
+ private final Rect mTempDockedTaskRect = new Rect();
+ private final Rect mTempDockedInsetRect = new Rect();
+ private final Rect mTempOtherTaskRect = new Rect();
+ private final Rect mTempOtherInsetRect = new Rect();
+
+ private final Rect mTmpRect1 = new Rect();
+ private final Rect mTmpRect2 = new Rect();
+ private final Rect mTmpRect3 = new Rect();
+ private final Rect mTmpRect4 = new Rect();
+ private final Rect mTmpRect5 = new Rect();
+
+ private boolean mDimLayerVisible;
+ private int mDimLayerTargetStack;
+ private float mDimLayerAlpha;
+
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private final Runnable mResizeRunnable = new Runnable() {
@Override
public void run() {
- synchronized (mResizeRect) {
- mTmpRect.set(mResizeRect);
+ synchronized (mDockedRect) {
+ mTmpRect1.set(mDockedRect);
+ mTmpRect2.set(mTempDockedTaskRect);
+ mTmpRect3.set(mTempDockedInsetRect);
+ mTmpRect4.set(mTempOtherTaskRect);
+ mTmpRect5.set(mTempOtherInsetRect);
}
try {
- ActivityManagerNative.getDefault().resizeStack(DOCKED_STACK_ID, mTmpRect, true);
+ ActivityManagerNative.getDefault()
+ .resizeDockedStack(mTmpRect1,
+ mTmpRect2.isEmpty() ? null : mTmpRect2,
+ mTmpRect3.isEmpty() ? null : mTmpRect3,
+ mTmpRect4.isEmpty() ? null : mTmpRect4,
+ mTmpRect5.isEmpty() ? null : mTmpRect5);
} catch (RemoteException e) {
Log.w(TAG, "Failed to resize stack: " + e);
}
@@ -80,6 +103,18 @@
}
};
+ private final Runnable mDimLayerRunnable = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ WindowManagerGlobal.getWindowManagerService().setResizeDimLayer(mDimLayerVisible,
+ mDimLayerTargetStack, mDimLayerAlpha);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to resize stack: " + e);
+ }
+ }
+ };
+
private WindowManagerProxy() {
}
@@ -87,9 +122,30 @@
return sInstance;
}
- public void resizeDockedStack(Rect rect) {
- synchronized (mResizeRect) {
- mResizeRect.set(rect);
+ public void resizeDockedStack(Rect docked, Rect tempDockedTaskRect, Rect tempDockedInsetRect,
+ Rect tempOtherTaskRect, Rect tempOtherInsetRect) {
+ synchronized (mDockedRect) {
+ mDockedRect.set(docked);
+ if (tempDockedTaskRect != null) {
+ mTempDockedTaskRect.set(tempDockedTaskRect);
+ } else {
+ mTempDockedTaskRect.setEmpty();
+ }
+ if (tempDockedInsetRect != null) {
+ mTempDockedInsetRect.set(tempDockedInsetRect);
+ } else {
+ mTempDockedInsetRect.setEmpty();
+ }
+ if (tempOtherTaskRect != null) {
+ mTempOtherTaskRect.set(tempOtherTaskRect);
+ } else {
+ mTempOtherTaskRect.setEmpty();
+ }
+ if (tempOtherInsetRect != null) {
+ mTempOtherInsetRect.set(tempOtherInsetRect);
+ } else {
+ mTempOtherInsetRect.setEmpty();
+ }
}
mExecutor.execute(mResizeRunnable);
}
@@ -123,4 +179,11 @@
}
return DOCKED_INVALID;
}
+
+ public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+ mDimLayerVisible = visible;
+ mDimLayerTargetStack = targetStackId;
+ mDimLayerAlpha = alpha;
+ mExecutor.execute(mDimLayerRunnable);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 11d7b9b..5906bda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -148,8 +148,9 @@
protected static final int INTERRUPTION_THRESHOLD = 10;
protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
- // Should match the value in PhoneWindowManager
+ // Should match the values in PhoneWindowManager
public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
+ public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
private static final String BANNER_ACTION_CANCEL =
"com.android.systemui.statusbar.banner_action_cancel";
@@ -1189,7 +1190,6 @@
protected void toggleRecents() {
if (mRecents != null) {
- sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS);
mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index f7680a7..3cc1ab9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -292,6 +292,15 @@
return false;
}
+ public boolean shouldSuppressScreenOn(String key) {
+ if (mRankingMap != null) {
+ mRankingMap.getRanking(key, mTmpRanking);
+ return (mTmpRanking.getSuppressedVisualEffects()
+ & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON) != 0;
+ }
+ return false;
+ }
+
public int getImportance(String key) {
if (mRankingMap != null) {
mRankingMap.getRanking(key, mTmpRanking);
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 79bd626..cc85d0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -29,6 +29,8 @@
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.systemui.tuner.TunerService;
import static android.view.WindowManager.*;
@@ -189,7 +191,7 @@
mRecentsComponent.dockTopTask(mDragMode == DRAG_MODE_RECENTS, createMode,
initialBounds);
if (mDragMode == DRAG_MODE_DIVIDER) {
- mDivider.getView().startDragging();
+ mDivider.getView().startDragging(false /* animate */);
}
mDockWindowTouchSlopExceeded = true;
MetricsLogger.action(mContext,
@@ -198,8 +200,10 @@
}
} else {
if (mDragMode == DRAG_MODE_DIVIDER) {
- mDivider.getView().resizeStack(
- !mIsVertical ? (int) event.getRawY() : (int) event.getRawX());
+ int position = !mIsVertical ? (int) event.getRawY() : (int) event.getRawX();
+ SnapTarget snapTarget = mDivider.getView().getSnapAlgorithm()
+ .calculateSnapTarget(position, 0f /* velocity */);
+ mDivider.getView().resizeStack(position, snapTarget.position, snapTarget);
} else if (mDragMode == DRAG_MODE_RECENTS) {
mRecentsComponent.onDraggingInRecents(event.getRawY());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index cddb1fc..eade753 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -36,11 +36,13 @@
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
+import android.view.IDockedStackListener.Stub;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -227,8 +229,8 @@
return mCurrentView;
}
- public View getRecentsButton() {
- return mCurrentView.findViewById(R.id.recent_apps);
+ public KeyButtonView getRecentsButton() {
+ return (KeyButtonView) mCurrentView.findViewById(R.id.recent_apps);
}
public View getMenuButton() {
@@ -455,6 +457,27 @@
getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
updateRTLOrder();
+
+ try {
+ WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(new Stub() {
+ @Override
+ public void onDividerVisibilityChanged(boolean visible) throws RemoteException {
+ }
+
+ @Override
+ public void onDockedStackExistsChanged(boolean exists) throws RemoteException {
+ updateRecentsIcon(exists);
+ }
+ });
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed registering docked stack exists listener", e);
+ }
+ }
+
+ private void updateRecentsIcon(boolean dockedStackExists) {
+ getRecentsButton().setImageResource(dockedStackExists
+ ? R.drawable.ic_sysbar_docked
+ : R.drawable.ic_sysbar_recent);
}
public boolean isVertical() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 80fcba60e..78d09e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1269,19 +1269,26 @@
}
if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
- // Stop screensaver if the notification has a full-screen intent.
- // (like an incoming phone call)
- awakenDreams();
+ if (mNotificationData.shouldSuppressScreenOn(notification.getKey())) {
+ if (DEBUG) {
+ Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + notification.getKey());
+ }
+ } else {
+ // Stop screensaver if the notification has a full-screen intent.
+ // (like an incoming phone call)
+ awakenDreams();
- // not immersive & a full-screen alert should be shown
- if (DEBUG) Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
- try {
- EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
- notification.getKey());
- notification.getNotification().fullScreenIntent.send();
- shadeEntry.notifyFullScreenIntentLaunched();
- MetricsLogger.count(mContext, "note_fullscreen", 1);
- } catch (PendingIntent.CanceledException e) {
+ // not immersive & a full-screen alert should be shown
+ if (DEBUG)
+ Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
+ try {
+ EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
+ notification.getKey());
+ notification.getNotification().fullScreenIntent.send();
+ shadeEntry.notifyFullScreenIntentLaunched();
+ MetricsLogger.count(mContext, "note_fullscreen", 1);
+ } catch (PendingIntent.CanceledException e) {
+ }
}
}
addNotificationViews(shadeEntry, ranking);
@@ -4120,32 +4127,6 @@
return false;
}
- // Recents
-
- @Override
- protected void showRecents(boolean triggeredFromAltTab) {
- // Set the recents visibility flag
- mSystemUiVisibility |= View.RECENT_APPS_VISIBLE;
- notifyUiVisibilityChanged(mSystemUiVisibility);
- super.showRecents(triggeredFromAltTab);
- }
-
- @Override
- protected void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
- // Unset the recents visibility flag
- mSystemUiVisibility &= ~View.RECENT_APPS_VISIBLE;
- notifyUiVisibilityChanged(mSystemUiVisibility);
- super.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
- }
-
- @Override
- protected void toggleRecents() {
- // Toggle the recents visibility flag
- mSystemUiVisibility ^= View.RECENT_APPS_VISIBLE;
- notifyUiVisibilityChanged(mSystemUiVisibility);
- super.toggleRecents();
- }
-
public void updateRecentsVisibility(boolean visible) {
// Update the recents visibility flag
if (visible) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7557906..6171694 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -258,6 +258,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
import static android.provider.Settings.Global.DEBUG_APP;
+import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
@@ -6772,6 +6773,15 @@
}
@Override
+ public final void activityRelaunched(IBinder token) {
+ final long origId = Binder.clearCallingIdentity();
+ synchronized (this) {
+ mStackSupervisor.activityRelaunchedLocked(token);
+ }
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ @Override
public void reportSizeConfigurations(IBinder token, int[] horizontalSizeConfiguration,
int[] verticalSizeConfigurations, int[] smallestSizeConfigurations) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Report configuration: " + token + " "
@@ -9559,7 +9569,26 @@
try {
synchronized (this) {
mStackSupervisor.resizeStackLocked(
- stackId, bounds, !PRESERVE_WINDOWS, allowResizeInDockedMode);
+ stackId, bounds, null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
+ !PRESERVE_WINDOWS, allowResizeInDockedMode);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public void resizeDockedStack(Rect dockedBounds, Rect tempDockedTaskBounds,
+ Rect tempDockedTaskInsetBounds,
+ Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds) {
+ enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+ "resizeDockedStack()");
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ mStackSupervisor.resizeDockedStackLocked(dockedBounds, tempDockedTaskBounds,
+ tempDockedTaskInsetBounds, tempOtherTaskBounds, tempOtherTaskInsetBounds,
+ PRESERVE_WINDOWS);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -12176,7 +12205,9 @@
private void retrieveSettings() {
final ContentResolver resolver = mContext.getContentResolver();
final boolean freeformWindowManagement =
- mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+ mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
+ || Settings.Global.getInt(
+ resolver, DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
final boolean supportsPictureInPicture =
mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
@@ -12185,9 +12216,8 @@
final boolean alwaysFinishActivities =
Settings.Global.getInt(resolver, ALWAYS_FINISH_ACTIVITIES, 0) != 0;
final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0;
- final int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0;
final boolean forceResizable = Settings.Global.getInt(
- resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, defaultForceResizable) != 0;
+ resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
// Transfer any global setting for forcing RTL layout, into a System Property
SystemProperties.set(DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
@@ -18087,10 +18117,18 @@
EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);
if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
- if (mSupportedSystemLocales == null) {
- mSupportedSystemLocales = Resources.getSystem().getAssets().getLocales();
+ final Locale locale;
+ if (values.getLocales().size() == 1) {
+ // This is an optimization to avoid the JNI call when the result of
+ // getFirstMatch() does not depend on the supported locales.
+ locale = values.getLocales().getPrimary();
+ } else {
+ if (mSupportedSystemLocales == null) {
+ mSupportedSystemLocales =
+ Resources.getSystem().getAssets().getLocales();
+ }
+ locale = values.getLocales().getFirstMatch(mSupportedSystemLocales);
}
- final Locale locale = values.getLocales().getFirstMatch(mSupportedSystemLocales);
SystemProperties.set("persist.sys.locale", locale.toLanguageTag());
mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
locale));
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 4e9d1b1..8d9cb58 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -4314,6 +4314,7 @@
r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, changes,
!andResume, new Configuration(mService.mConfiguration),
new Configuration(r.task.mOverrideConfig), preserveWindow);
+ mStackSupervisor.activityRelaunchingLocked(r);
// Note: don't need to call pauseIfSleepingLocked() here, because
// the caller will only pass in 'andResume' if this activity is
// currently resumed, which implies we aren't sleeping.
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 4f8f670..80a75ce 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -16,70 +16,6 @@
package com.android.server.am;
-import static android.Manifest.permission.START_ANY_ACTIVITY;
-import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
-import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
-import static android.app.ActivityManager.RESIZE_MODE_FORCED;
-import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
-import static android.app.ActivityManager.StackId.FIRST_STATIC_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.LAST_STATIC_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONTAINERS;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_IDLE;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKSCREEN;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PAUSE;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RELEASE;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBLE_BEHIND;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONTAINERS;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IDLE;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PAUSE;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RELEASE;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STATES;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND;
-import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
-import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG;
-import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityStack.ActivityState.DESTROYED;
-import static com.android.server.am.ActivityStack.ActivityState.DESTROYING;
-import static com.android.server.am.ActivityStack.ActivityState.INITIALIZING;
-import static com.android.server.am.ActivityStack.ActivityState.PAUSED;
-import static com.android.server.am.ActivityStack.ActivityState.PAUSING;
-import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
-import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
-import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
-import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
-import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE;
-import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
-import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
-import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
-
import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
@@ -164,6 +100,70 @@
import java.util.List;
import java.util.Set;
+import static android.Manifest.permission.START_ANY_ACTIVITY;
+import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
+import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
+import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
+import static android.app.ActivityManager.RESIZE_MODE_FORCED;
+import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
+import static android.app.ActivityManager.StackId.FIRST_STATIC_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.LAST_STATIC_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONTAINERS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_IDLE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKSCREEN;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PAUSE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RELEASE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBLE_BEHIND;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONTAINERS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IDLE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PAUSE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RELEASE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STATES;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG;
+import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityStack.ActivityState.DESTROYED;
+import static com.android.server.am.ActivityStack.ActivityState.DESTROYING;
+import static com.android.server.am.ActivityStack.ActivityState.INITIALIZING;
+import static com.android.server.am.ActivityStack.ActivityState.PAUSED;
+import static com.android.server.am.ActivityStack.ActivityState.PAUSING;
+import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
+import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
+import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
+
public final class ActivityStackSupervisor implements DisplayListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM;
private static final String TAG_CONTAINERS = TAG + POSTFIX_CONTAINERS;
@@ -379,6 +379,7 @@
private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>();
private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
+ private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
// The default minimal size that will be used if the activity doesn't specify its minimal size.
// It will be calculated when the default display gets added.
@@ -389,6 +390,8 @@
private final ActivityMetricsLogger mActivityMetricsLogger;
+ private final ResizeDockedStackTimeout mResizeDockedStackTimeout;
+
static class FindTaskResult {
ActivityRecord r;
boolean matchedByRootAffinity;
@@ -432,6 +435,7 @@
mRecentTasks = recentTasks;
mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper());
mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext);
+ mResizeDockedStackTimeout = new ResizeDockedStackTimeout(service, this, mHandler);
}
/**
@@ -1799,16 +1803,20 @@
}
}
- void resizeStackLocked(int stackId, Rect bounds, boolean preserveWindows,
- boolean allowResizeInDockedMode) {
+ void resizeStackLocked(int stackId, Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds,
+ boolean preserveWindows, boolean allowResizeInDockedMode) {
+ if (stackId == DOCKED_STACK_ID) {
+ resizeDockedStackLocked(bounds, tempTaskBounds, tempTaskInsetBounds, null, null,
+ preserveWindows);
+ return;
+ }
final ActivityStack stack = getStack(stackId);
if (stack == null) {
Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
return;
}
- if (!allowResizeInDockedMode
- && stackId != DOCKED_STACK_ID && getStack(DOCKED_STACK_ID) != null) {
+ if (!allowResizeInDockedMode && getStack(DOCKED_STACK_ID) != null) {
// If the docked stack exist we don't allow resizes of stacks not caused by the docked
// stack size changing so things don't get out of sync.
return;
@@ -1817,96 +1825,139 @@
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId);
mWindowManager.deferSurfaceLayout();
try {
-
- if (bounds != null && mWindowManager.isFullscreenBounds(stackId, bounds)) {
- // The bounds passed in corresponds to the fullscreen bounds which we normally
- // represent with null. Go ahead and set it to null so that all tasks configuration
- // can have the right fullscreen state.
- bounds = null;
- }
-
- ActivityRecord r = stack.topRunningActivityLocked();
-
- mTmpBounds.clear();
- mTmpConfigs.clear();
- ArrayList<TaskRecord> tasks = stack.getAllTasks();
- for (int i = tasks.size() - 1; i >= 0; i--) {
- TaskRecord task = tasks.get(i);
- if (task.mResizeable) {
- if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
- // For freeform stack we don't adjust the size of the tasks to match that
- // of the stack, but we do try to make sure the tasks are still contained
- // with the bounds of the stack.
- tempRect2.set(task.mBounds);
- fitWithinBounds(tempRect2, bounds);
- task.updateOverrideConfiguration(tempRect2);
- } else {
- task.updateOverrideConfiguration(bounds);
- }
- }
-
- mTmpConfigs.put(task.taskId, task.mOverrideConfig);
- mTmpBounds.put(task.taskId, task.mBounds);
- }
- stack.mFullscreen = mWindowManager.resizeStack(stackId, bounds, mTmpConfigs, mTmpBounds);
- if (stack.mStackId == DOCKED_STACK_ID) {
- // Dock stack funness...Yay!
- if (stack.mFullscreen) {
- // The dock stack went fullscreen which is kinda like dismissing it.
- // In this case we make all other static stacks fullscreen and move all
- // docked stack tasks to the fullscreen stack.
- for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
- if (StackId.isResizeableByDockedStack(i) && getStack(i) != null) {
- resizeStackLocked(i, null, preserveWindows, true);
- }
- }
-
- final int count = tasks.size();
- for (int i = 0; i < count; i++) {
- moveTaskToStackLocked(tasks.get(i).taskId,
- FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS, "resizeStack",
- false /* animate */);
- }
-
- // stack shouldn't contain anymore activities, so nothing to resume.
- r = null;
- } else {
- // Docked stacks occupy a dedicated region on screen so the size of all other
- // static stacks need to be adjusted so they don't overlap with the docked stack.
- // We get the bounds to use from window manager which has been adjusted for any
- // screen controls and is also the same for all stacks.
- mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect);
-
- for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
- if (StackId.isResizeableByDockedStack(i)) {
- ActivityStack otherStack = getStack(i);
- if (otherStack != null) {
- resizeStackLocked(i, tempRect, PRESERVE_WINDOWS, true);
- }
- }
- }
- }
- // Since we are resizing the stack, all other operations should strive to preserve
- // windows.
- preserveWindows = true;
- }
- stack.setBounds(bounds);
-
- if (r != null) {
- final boolean updated = stack.ensureActivityConfigurationLocked(r, 0, preserveWindows);
- // And we need to make sure at this point that all other activities
- // are made visible with the correct configuration.
- ensureActivitiesVisibleLocked(r, 0, preserveWindows);
- if (!updated) {
- resumeFocusedStackTopActivityLocked();
- }
- }
+ resizeStackUncheckedLocked(stack, bounds, tempTaskBounds, tempTaskInsetBounds);
+ ensureConfigurationAndResume(stack, stack.topRunningActivityLocked(), preserveWindows);
} finally {
mWindowManager.continueSurfaceLayout();
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
+ private void resizeStackUncheckedLocked(ActivityStack stack, Rect bounds, Rect tempTaskBounds,
+ Rect tempTaskInsetBounds) {
+ if (bounds != null && mWindowManager.isFullscreenBounds(stack.mStackId, bounds)) {
+ // The bounds passed in corresponds to the fullscreen bounds which we normally
+ // represent with null. Go ahead and set it to null so that all tasks configuration
+ // can have the right fullscreen state.
+ bounds = null;
+ }
+
+ mTmpBounds.clear();
+ mTmpConfigs.clear();
+ mTmpInsetBounds.clear();
+ ArrayList<TaskRecord> tasks = stack.getAllTasks();
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ TaskRecord task = tasks.get(i);
+ if (task.mResizeable) {
+ if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+ // For freeform stack we don't adjust the size of the tasks to match that
+ // of the stack, but we do try to make sure the tasks are still contained
+ // with the bounds of the stack.
+ tempRect2.set(task.mBounds);
+ fitWithinBounds(tempRect2, bounds);
+ task.updateOverrideConfiguration(tempRect2);
+ } else {
+ task.updateOverrideConfiguration(tempTaskBounds != null
+ ? tempTaskBounds : bounds);
+ }
+ }
+
+ mTmpConfigs.put(task.taskId, task.mOverrideConfig);
+ mTmpBounds.put(task.taskId, task.mBounds);
+ if (tempTaskInsetBounds != null) {
+ mTmpInsetBounds.put(task.taskId, tempTaskInsetBounds);
+ }
+ }
+
+ // We might trigger a configuration change. Save the current task bounds for freezing.
+ mWindowManager.prepareFreezingTaskBounds(stack.mStackId);
+ stack.mFullscreen = mWindowManager.resizeStack(stack.mStackId, bounds, mTmpConfigs,
+ mTmpBounds, mTmpInsetBounds);
+ stack.setBounds(bounds);
+ }
+
+ private void ensureConfigurationAndResume(ActivityStack stack, ActivityRecord r,
+ boolean preserveWindows) {
+ if (r == null) {
+ return;
+ }
+ final boolean updated = stack.ensureActivityConfigurationLocked(r, 0,
+ preserveWindows);
+ // And we need to make sure at this point that all other activities
+ // are made visible with the correct configuration.
+ ensureActivitiesVisibleLocked(r, 0, preserveWindows);
+ if (!updated) {
+ resumeFocusedStackTopActivityLocked();
+ }
+ }
+
+ void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
+ Rect tempDockedTaskInsetBounds,
+ Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds, boolean preserveWindows) {
+ final ActivityStack stack = getStack(DOCKED_STACK_ID);
+ if (stack == null) {
+ Slog.w(TAG, "resizeDockedStackLocked: docked stack not found");
+ return;
+ }
+
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeDockedStack");
+ mWindowManager.deferSurfaceLayout();
+ try {
+ ActivityRecord r = stack.topRunningActivityLocked();
+ resizeStackUncheckedLocked(stack, dockedBounds, tempDockedTaskBounds,
+ tempDockedTaskInsetBounds);
+
+ if (stack.mFullscreen) {
+ // The dock stack went fullscreen which is kinda like dismissing it.
+ // In this case we make all other static stacks fullscreen and move all
+ // docked stack tasks to the fullscreen stack.
+ for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
+ if (StackId.isResizeableByDockedStack(i) && getStack(i) != null) {
+ resizeStackLocked(i, null, null, null, preserveWindows,
+ true /* allowResizeInDockedMode */);
+ }
+ }
+
+ ArrayList<TaskRecord> tasks = stack.getAllTasks();
+ final int count = tasks.size();
+ for (int i = 0; i < count; i++) {
+ moveTaskToStackLocked(tasks.get(i).taskId,
+ FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS, "resizeStack",
+ false /* animate */);
+ }
+
+ // stack shouldn't contain anymore activities, so nothing to resume.
+ r = null;
+ } else {
+ // Docked stacks occupy a dedicated region on screen so the size of all other
+ // static stacks need to be adjusted so they don't overlap with the docked stack.
+ // We get the bounds to use from window manager which has been adjusted for any
+ // screen controls and is also the same for all stacks.
+ mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect);
+ for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
+ if (StackId.isResizeableByDockedStack(i)) {
+ ActivityStack otherStack = getStack(i);
+ if (otherStack != null) {
+ resizeStackLocked(i, tempRect, tempOtherTaskBounds,
+ tempOtherTaskInsetBounds, preserveWindows,
+ true /* allowResizeInDockedMode */);
+ }
+ }
+ }
+ }
+ ensureConfigurationAndResume(stack, r, preserveWindows);
+ } finally {
+ mWindowManager.continueSurfaceLayout();
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ mResizeDockedStackTimeout.notifyResizing(dockedBounds,
+ tempDockedTaskBounds != null
+ || tempDockedTaskInsetBounds != null
+ || tempOtherTaskBounds != null
+ || tempOtherTaskInsetBounds != null);
+ }
+
void resizeTaskLocked(TaskRecord task, Rect bounds, int resizeMode, boolean preserveWindow) {
if (!task.mResizeable) {
Slog.w(TAG, "resizeTask: task " + task + " not resizeable.");
@@ -2193,7 +2244,8 @@
}
if (bounds != null) {
- resizeStackLocked(stackId, bounds, !PRESERVE_WINDOWS, true);
+ resizeStackLocked(stackId, bounds, null /* tempTaskBounds */,
+ null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS, true);
}
// The task might have already been running and its visibility needs to be synchronized with
@@ -3244,6 +3296,14 @@
return mLockTaskModeState;
}
+ void activityRelaunchedLocked(IBinder token) {
+ mWindowManager.notifyAppRelaunchingFinished(token);
+ }
+
+ void activityRelaunchingLocked(ActivityRecord r) {
+ mWindowManager.notifyAppRelaunching(r.appToken);
+ }
+
void logStackState() {
mActivityMetricsLogger.logWindowState();
}
diff --git a/services/core/java/com/android/server/am/ResizeDockedStackTimeout.java b/services/core/java/com/android/server/am/ResizeDockedStackTimeout.java
new file mode 100644
index 0000000..ff39589
--- /dev/null
+++ b/services/core/java/com/android/server/am/ResizeDockedStackTimeout.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.graphics.Rect;
+import android.os.Handler;
+
+import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+
+/**
+ * When resizing the docked stack, a caller can temporarily supply task bounds that are different
+ * from the stack bounds. In order to return to a sane state if the caller crashes or has a bug,
+ * this class manages this cycle.
+ */
+class ResizeDockedStackTimeout {
+
+ private static final long TIMEOUT_MS = 10 * 1000;
+ private final ActivityManagerService mService;
+ private final ActivityStackSupervisor mSupervisor;
+ private final Handler mHandler;
+ private final Rect mCurrentDockedBounds = new Rect();
+
+ private final Runnable mTimeoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mService) {
+ mSupervisor.resizeDockedStackLocked(mCurrentDockedBounds, null, null, null, null,
+ PRESERVE_WINDOWS);
+ }
+ }
+ };
+
+ ResizeDockedStackTimeout(ActivityManagerService service, ActivityStackSupervisor supervisor,
+ Handler handler) {
+ mService = service;
+ mSupervisor = supervisor;
+ mHandler = handler;
+ }
+
+ void notifyResizing(Rect dockedBounds, boolean hasTempBounds) {
+ mHandler.removeCallbacks(mTimeoutRunnable);
+ if (!hasTempBounds) {
+ return;
+ }
+ mCurrentDockedBounds.set(dockedBounds);
+ mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MS);
+ }
+
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 82c38af..8657049 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -33,6 +33,7 @@
import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_HIGH;
import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_LIGHTS;
import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_PEEK;
+import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.service.notification.NotificationListenerService.TRIM_FULL;
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -2563,9 +2564,14 @@
updateLightsLocked();
}
if (buzz || beep || blink) {
- EventLogTags.writeNotificationAlert(record.getKey(),
- buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
- mHandler.post(mBuzzBeepBlinked);
+ if (((record.getSuppressedVisualEffects()
+ & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON) != 0)) {
+ if (DBG) Slog.v(TAG, "Suppressed SystemUI from triggering screen on");
+ } else {
+ EventLogTags.writeNotificationAlert(record.getKey(),
+ buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
+ mHandler.post(mBuzzBeepBlinked);
+ }
}
}
@@ -2744,7 +2750,8 @@
record.setIntercepted(mZenModeHelper.shouldIntercept(record));
if (record.isIntercepted()) {
int suppressed = (mZenModeHelper.shouldSuppressLight() ? SUPPRESSED_EFFECT_LIGHTS : 0)
- | (mZenModeHelper.shouldSuppressPeek() ? SUPPRESSED_EFFECT_PEEK : 0);
+ | (mZenModeHelper.shouldSuppressPeek() ? SUPPRESSED_EFFECT_PEEK : 0)
+ | (mZenModeHelper.shouldSuppressScreenOn() ? SUPPRESSED_EFFECT_SCREEN_ON : 0);
record.setSuppressedVisualEffects(suppressed);
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 85c3cf8..276c6ba 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -155,6 +155,12 @@
}
}
+ public boolean shouldSuppressScreenOn() {
+ synchronized (mConfig) {
+ return !mConfig.allowScreenOn;
+ }
+ }
+
public void addCallback(Callback callback) {
mCallbacks.add(callback);
}
@@ -435,11 +441,12 @@
return;
}
pw.printf("allow(calls=%s,callsFrom=%s,repeatCallers=%s,messages=%s,messagesFrom=%s,"
- + "events=%s,reminders=%s,lights=%s,peek=%s)\n",
+ + "events=%s,reminders=%s,lights=%s,peek=%s,screenOn=%s)\n",
config.allowCalls, ZenModeConfig.sourceToString(config.allowCallsFrom),
config.allowRepeatCallers, config.allowMessages,
ZenModeConfig.sourceToString(config.allowMessagesFrom),
- config.allowEvents, config.allowReminders, config.allowLights, config.allowPeek);
+ config.allowEvents, config.allowReminders, config.allowLights, config.allowPeek,
+ config.allowScreenOn);
pw.print(prefix); pw.print(" manualRule="); pw.println(config.manualRule);
if (config.automaticRules.isEmpty()) return;
final int N = config.automaticRules.size();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 6fcf1d6..72611b7 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3381,7 +3381,6 @@
if (awakenFromDreams) {
awakenDreams();
}
- sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
hideRecentApps(false, true);
} else {
// Otherwise, just launch Home
@@ -4243,6 +4242,7 @@
&& (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
&& (attrs.type == TYPE_STATUS_BAR
|| attrs.type == TYPE_TOAST
+ || attrs.type == TYPE_DOCK_DIVIDER
|| attrs.type == TYPE_VOICE_INTERACTION_STARTING
|| (attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
&& attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW))) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 9b9f14b..573aaec 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -31,6 +31,7 @@
import android.annotation.NonNull;
import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
import android.os.Message;
import android.os.RemoteException;
import android.util.Slog;
@@ -39,6 +40,7 @@
import android.view.WindowManager;
import java.io.PrintWriter;
+import java.util.ArrayDeque;
import java.util.ArrayList;
class AppTokenList extends ArrayList<AppWindowToken> {
@@ -126,6 +128,8 @@
boolean mAlwaysFocusable;
+ ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
+
AppWindowToken(WindowManagerService _service, IApplicationToken _token,
boolean _voiceInteraction) {
super(_service, _token.asBinder(),
@@ -437,6 +441,23 @@
}
}
+ /**
+ * Freezes the task bounds. The size of this task reported the app will be fixed to the bounds
+ * freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even
+ * if they change in the meantime. If the bounds are already frozen, the bounds will be frozen
+ * with a queue.
+ */
+ void freezeBounds() {
+ mFrozenBounds.offer(new Rect(mTask.mPreparedFrozenBounds));
+ }
+
+ /**
+ * Unfreezes the previously frozen bounds. See {@link #freezeBounds}.
+ */
+ void unfreezeBounds() {
+ mFrozenBounds.remove();
+ }
+
@Override
void dump(PrintWriter pw, String prefix) {
super.dump(pw, prefix);
@@ -483,6 +504,9 @@
pw.print(" startingDisplayed="); pw.print(startingDisplayed);
pw.print(" startingMoved"); pw.println(startingMoved);
}
+ if (!mFrozenBounds.isEmpty()) {
+ pw.print(prefix); pw.print("mFrozenBounds="); pw.print(mFrozenBounds);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 8f3d3e3..7295318 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -18,11 +18,17 @@
import android.content.Context;
import android.graphics.Rect;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Slog;
-import android.view.IDockDividerVisibilityListener;
+import android.view.DisplayInfo;
+import android.view.IDockedStackListener;
+import android.view.SurfaceControl;
+
+import com.android.server.wm.DimLayer.DimLayerUser;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
@@ -33,7 +39,7 @@
/**
* Keeps information about the docked stack divider.
*/
-public class DockedStackDividerController {
+public class DockedStackDividerController implements DimLayerUser {
private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM;
@@ -44,9 +50,10 @@
private WindowState mWindow;
private final Rect mTmpRect = new Rect();
private final Rect mLastRect = new Rect();
- private IDockDividerVisibilityListener mListener;
private boolean mLastVisibility = false;
- private boolean mForceVisibilityReevaluation;
+ private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners
+ = new RemoteCallbackList<>();
+ private final DimLayer mDimLayer;
DockedStackDividerController(Context context, DisplayContent displayContent) {
mDisplayContent = displayContent;
@@ -54,6 +61,7 @@
com.android.internal.R.dimen.docked_stack_divider_thickness);
mDividerInsets = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_insets);
+ mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId());
}
boolean isResizing() {
@@ -83,15 +91,16 @@
return;
}
mLastVisibility = visible;
- if (mListener != null) {
- try {
- mListener.onDockDividerVisibilityChanged(visible);
- } catch (RemoteException e) {
- Slog.e(TAG, "visibility call failed: " + e);
- }
+ notifyDockedDividerVisibilityChanged(visible);
+ if (!visible) {
+ setResizeDimLayer(false, INVALID_STACK_ID, 0f);
}
}
+ boolean wasVisible() {
+ return mLastVisibility;
+ }
+
void positionDockedStackedDivider(Rect frame) {
TaskStack stack = mDisplayContent.getDockedStackLocked();
if (stack == null) {
@@ -127,11 +136,76 @@
mLastRect.set(frame);
}
- public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
- if (mListener != null && listener != null) {
- throw new IllegalStateException("Dock divider visibility listener already set!");
+ void notifyDockedDividerVisibilityChanged(boolean visible) {
+ final int size = mDockedStackListeners.beginBroadcast();
+ for (int i = 0; i < size; ++i) {
+ final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
+ try {
+ listener.onDividerVisibilityChanged(visible);
+ } catch (RemoteException e) {
+ Slog.e(TAG_WM, "Error delivering divider visibility changed event.", e);
+ }
}
- mListener = listener;
- reevaluateVisibility(true);
+ mDockedStackListeners.finishBroadcast();
+ }
+
+ void notifyDockedStackExistsChanged(boolean exists) {
+ final int size = mDockedStackListeners.beginBroadcast();
+ for (int i = 0; i < size; ++i) {
+ final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
+ try {
+ listener.onDockedStackExistsChanged(exists);
+ } catch (RemoteException e) {
+ Slog.e(TAG_WM, "Error delivering docked stack exists changed event.", e);
+ }
+ }
+ mDockedStackListeners.finishBroadcast();
+ }
+
+ void registerDockedStackListener(IDockedStackListener listener) {
+ mDockedStackListeners.register(listener);
+ notifyDockedDividerVisibilityChanged(wasVisible());
+ notifyDockedStackExistsChanged(
+ mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID) != null);
+ }
+
+ void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+ SurfaceControl.openTransaction();
+ TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(targetStackId);
+ boolean visibleAndValid = visible && stack != null;
+ if (visibleAndValid) {
+ stack.getDimBounds(mTmpRect);
+ if (mTmpRect.height() > 0 && mTmpRect.width() > 0) {
+ mDimLayer.setBounds(mTmpRect);
+ mDimLayer.show(mDisplayContent.mService.mLayersController.getResizeDimLayer(),
+ alpha, 0 /* duration */);
+ } else {
+ visibleAndValid = false;
+ }
+ }
+ if (!visibleAndValid) {
+ mDimLayer.hide();
+ }
+ SurfaceControl.closeTransaction();
+ }
+
+ @Override
+ public boolean isFullscreen() {
+ return false;
+ }
+
+ @Override
+ public DisplayInfo getDisplayInfo() {
+ return mDisplayContent.getDisplayInfo();
+ }
+
+ @Override
+ public void getDimBounds(Rect outBounds) {
+ // This dim layer user doesn't need this.
+ }
+
+ @Override
+ public String toShortString() {
+ return TAG;
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 27e7a31..223e03a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -61,6 +61,10 @@
// Content limits relative to the DisplayContent this sits in.
private Rect mBounds = new Rect();
+ final Rect mPreparedFrozenBounds = new Rect();
+
+ // Bounds used to calculate the insets.
+ private final Rect mTempInsetBounds = new Rect();
// Device rotation as of the last time {@link #mBounds} was set.
int mRotation;
@@ -197,8 +201,7 @@
boolean removeAppToken(AppWindowToken wtoken) {
boolean removed = mAppTokens.remove(wtoken);
if (mAppTokens.size() == 0) {
- EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId,
- "removeAppToken: last token");
+ EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "removeAppToken: last token");
if (mDeferRemoval) {
removeLocked();
}
@@ -267,6 +270,26 @@
return boundsChange;
}
+ /**
+ * Sets the bounds used to calculate the insets. See
+ * {@link android.app.IActivityManager#resizeDockedStack} why this is needed.
+ */
+ void setTempInsetBounds(Rect tempInsetBounds) {
+ if (tempInsetBounds != null) {
+ mTempInsetBounds.set(tempInsetBounds);
+ } else {
+ mTempInsetBounds.setEmpty();
+ }
+ }
+
+ /**
+ * Gets the bounds used to calculate the insets. See
+ * {@link android.app.IActivityManager#resizeDockedStack} why this is needed.
+ */
+ void getTempInsetBounds(Rect out) {
+ out.set(mTempInsetBounds);
+ }
+
void setResizeable(boolean resizeable) {
mResizeable = resizeable;
}
@@ -291,6 +314,14 @@
return true;
}
+ /**
+ * Prepares the task bounds to be frozen with the current size. See
+ * {@link AppWindowToken#freezeBounds}.
+ */
+ void prepareFreezingBounds() {
+ mPreparedFrozenBounds.set(mBounds);
+ }
+
boolean scrollLocked(Rect bounds) {
// shift the task bound if it doesn't fully cover the stack area
mStack.getDimBounds(mTmpRect);
@@ -357,7 +388,6 @@
mStack.getDisplayContent().getLogicalDisplayRect(out);
}
-
/**
* Calculate the maximum visible area of this task. If the task has only one app,
* the result will be visible frame of that app. If the task has more than one apps,
@@ -579,5 +609,6 @@
pw.print(prefix + prefix); pw.print("mBounds="); pw.println(mBounds.toShortString());
pw.print(prefix + prefix); pw.print("mdr="); pw.println(mDeferRemoval);
pw.print(prefix + prefix); pw.print("appTokens="); pw.println(mAppTokens);
+ pw.print(prefix + prefix); pw.print("mTempInsetBounds="); pw.println(mTempInsetBounds);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index b961879..67debe6 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -114,7 +114,8 @@
* @return True if the stack bounds was changed.
* */
boolean setBounds(
- Rect stackBounds, SparseArray<Configuration> configs, SparseArray<Rect> taskBounds) {
+ Rect stackBounds, SparseArray<Configuration> configs, SparseArray<Rect> taskBounds,
+ SparseArray<Rect> taskTempInsetBounds) {
if (!setBounds(stackBounds)) {
return false;
}
@@ -136,6 +137,9 @@
task.scrollLocked(mTmpRect);
} else {
task.setBounds(bounds, config);
+ task.setTempInsetBounds(
+ taskTempInsetBounds != null ? taskTempInsetBounds.get(task.mTaskId)
+ : null);
}
} else {
Slog.wtf(TAG_WM, "No config for task: " + task + ", is there a mismatch with AM?");
@@ -144,6 +148,13 @@
return true;
}
+ void prepareFreezingTaskBounds() {
+ for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
+ final Task task = mTasks.get(taskNdx);
+ task.prepareFreezingBounds();
+ }
+ }
+
boolean isFullscreenBounds(Rect bounds) {
if (mDisplayContent == null || bounds == null) {
return true;
diff --git a/services/core/java/com/android/server/wm/WindowLayersController.java b/services/core/java/com/android/server/wm/WindowLayersController.java
index 263b411..2cf2618 100644
--- a/services/core/java/com/android/server/wm/WindowLayersController.java
+++ b/services/core/java/com/android/server/wm/WindowLayersController.java
@@ -1,9 +1,20 @@
-package com.android.server.wm;
+/*
+ * 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.
+ */
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.WINDOW_LAYER_MULTIPLIER;
+package com.android.server.wm;
import android.app.ActivityManager.StackId;
import android.util.Slog;
@@ -11,6 +22,11 @@
import java.io.PrintWriter;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.WINDOW_LAYER_MULTIPLIER;
+
/**
* Controller for assigning layers to windows on the display.
*
@@ -42,7 +58,6 @@
private WindowState mPinnedWindow = null;
private WindowState mDockedWindow = null;
private WindowState mDockDivider = null;
- private WindowState mImeWindow = null;
private WindowState mReplacingWindow = null;
final void assignLayersLocked(WindowList windows) {
@@ -133,6 +148,14 @@
return 0;
}
+ /**
+ * @return The layer used for dimming the apps when dismissing docked/fullscreen stack. Just
+ * above all application surfaces.
+ */
+ int getResizeDimLayer() {
+ return mDockDivider.mLayer - 1;
+ }
+
private void logDebugLayers(WindowList windows) {
for (int i = 0, n = windows.size(); i < n; i++) {
final WindowState w = windows.get(i);
@@ -146,16 +169,13 @@
private void clear() {
mHighestApplicationLayer = 0;
- mImeWindow = null;
mPinnedWindow = null;
mDockedWindow = null;
mDockDivider = null;
}
private void collectSpecialWindows(WindowState w) {
- if (w.mIsImWindow) {
- mImeWindow = w;
- } else if (w.mAttrs.type == TYPE_DOCK_DIVIDER) {
+ if (w.mAttrs.type == TYPE_DOCK_DIVIDER) {
mDockDivider = w;
} else {
final TaskStack stack = w.getStack();
@@ -175,18 +195,17 @@
// For pinned and docked stack window, we want to make them above other windows
// also when these windows are animating.
layer = assignAndIncreaseLayerIfNeeded(mDockedWindow, layer);
+
+ // Leave some space here so the dim layer while dismissing docked/fullscreen stack has space
+ // below the divider but above the app windows. It needs to be below the divider in because
+ // the divider sometimes overlaps the app windows.
+ layer++;
layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer);
// We know that we will be animating a relaunching window in the near future,
// which will receive a z-order increase. We want the replaced window to
// immediately receive the same treatment, e.g. to be above the dock divider.
layer = assignAndIncreaseLayerIfNeeded(mReplacingWindow, layer);
layer = assignAndIncreaseLayerIfNeeded(mPinnedWindow, layer);
- final WindowState inputMethodTarget = mService.mInputMethodTarget;
- // There might be no applications windows yet, so we need to make sure we uplift the input
- // method above the target.
- layer = Math.max(layer,
- inputMethodTarget != null ? inputMethodTarget.mWinAnimator.mAnimLayer + 1 : 0);
- layer = assignAndIncreaseLayerIfNeeded(mImeWindow, layer);
}
private int assignAndIncreaseLayerIfNeeded(WindowState win, int layer) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 06e2e30..f7b81cb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -16,6 +16,145 @@
package com.android.server.wm;
+import android.Manifest;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager.StackId;
+import android.app.ActivityManagerNative;
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.SystemService;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.DisplayMetrics;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.util.TypedValue;
+import android.view.AppTransitionAnimationSpec;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.IApplicationToken;
+import android.view.IDockedStackListener;
+import android.view.IInputFilter;
+import android.view.IOnKeyguardExitResult;
+import android.view.IRotationWatcher;
+import android.view.IWindow;
+import android.view.IWindowId;
+import android.view.IWindowManager;
+import android.view.IWindowSession;
+import android.view.IWindowSessionCallback;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.KeyEvent;
+import android.view.MagnificationSpec;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.View;
+import android.view.WindowContentFrameStats;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerInternal;
+import android.view.WindowManagerPolicy;
+import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.animation.Animation;
+import android.view.inputmethod.InputMethodManagerInternal;
+import android.widget.Toast;
+
+import com.android.internal.R;
+import com.android.internal.app.IAssistScreenshotReceiver;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+import com.android.internal.view.IInputMethodManager;
+import com.android.internal.view.WindowManagerPolicyThread;
+import com.android.server.AttributeCache;
+import com.android.server.DisplayThread;
+import com.android.server.EventLogTags;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.UiThread;
+import com.android.server.Watchdog;
+import com.android.server.input.InputManagerService;
+import com.android.server.policy.PhoneWindowManager;
+import com.android.server.power.ShutdownThread;
+
+import java.io.BufferedWriter;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.Socket;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
@@ -87,143 +226,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_VERBOSE_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import android.Manifest;
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManagerNative;
-import android.app.AppOpsManager;
-import android.app.IActivityManager;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.CompatibilityInfo;
-import android.content.res.Configuration;
-import android.database.ContentObserver;
-import android.graphics.Bitmap;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManagerInternal;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Debug;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IRemoteCallback;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
-import android.os.PowerManager;
-import android.os.PowerManagerInternal;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.StrictMode;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.SystemService;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.os.WorkSource;
-import android.provider.Settings;
-import android.util.ArraySet;
-import android.util.DisplayMetrics;
-import android.util.EventLog;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.util.TimeUtils;
-import android.util.TypedValue;
-import android.view.AppTransitionAnimationSpec;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.DisplayInfo;
-import android.view.Gravity;
-import android.view.IAppTransitionAnimationSpecsFuture;
-import android.view.IApplicationToken;
-import android.view.IDockDividerVisibilityListener;
-import android.view.IInputFilter;
-import android.view.IOnKeyguardExitResult;
-import android.view.IRotationWatcher;
-import android.view.IWindow;
-import android.view.IWindowId;
-import android.view.IWindowManager;
-import android.view.IWindowSession;
-import android.view.IWindowSessionCallback;
-import android.view.InputChannel;
-import android.view.InputDevice;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
-import android.view.KeyEvent;
-import android.view.MagnificationSpec;
-import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.view.View;
-import android.view.WindowContentFrameStats;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.view.WindowManagerGlobal;
-import android.view.WindowManagerInternal;
-import android.view.WindowManagerPolicy;
-import android.view.WindowManagerPolicy.PointerEventListener;
-import android.view.animation.Animation;
-import android.view.inputmethod.InputMethodManagerInternal;
-import android.widget.Toast;
-
-import com.android.internal.R;
-import com.android.internal.app.IAssistScreenshotReceiver;
-import com.android.internal.util.FastPrintWriter;
-import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethodClient;
-import com.android.internal.view.IInputMethodManager;
-import com.android.internal.view.WindowManagerPolicyThread;
-import com.android.server.AttributeCache;
-import com.android.server.DisplayThread;
-import com.android.server.EventLogTags;
-import com.android.server.FgThread;
-import com.android.server.LocalServices;
-import com.android.server.UiThread;
-import com.android.server.Watchdog;
-import com.android.server.input.InputManagerService;
-import com.android.server.policy.PhoneWindowManager;
-import com.android.server.power.ShutdownThread;
-
-import java.io.BufferedWriter;
-import java.io.DataInputStream;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.net.Socket;
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-
/** {@hide} */
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
@@ -4653,6 +4655,10 @@
if (DEBUG_STACK) Slog.d(TAG_WM, "attachStack: stackId=" + stackId);
stack = new TaskStack(this, stackId);
mStackIdToStack.put(stackId, stack);
+ if (stackId == DOCKED_STACK_ID) {
+ getDefaultDisplayContentLocked().mDividerControllerLocked
+ .notifyDockedStackExistsChanged(true);
+ }
}
stack.attachDisplayContent(displayContent);
displayContent.attachStack(stack, onTop);
@@ -4678,6 +4684,10 @@
void detachStackLocked(DisplayContent displayContent, TaskStack stack) {
displayContent.detachStack(stack);
stack.detachDisplay();
+ if (stack.mStackId == DOCKED_STACK_ID) {
+ getDefaultDisplayContentLocked().mDividerControllerLocked
+ .notifyDockedStackExistsChanged(false);
+ }
}
public void detachStack(int stackId) {
@@ -4824,14 +4834,16 @@
* @return True if the stack is now fullscreen.
* */
public boolean resizeStack(int stackId, Rect bounds,
- SparseArray<Configuration> configs, SparseArray<Rect> taskBounds) {
+ SparseArray<Configuration> configs, SparseArray<Rect> taskBounds,
+ SparseArray<Rect> taskTempInsetBounds) {
synchronized (mWindowMap) {
final TaskStack stack = mStackIdToStack.get(stackId);
if (stack == null) {
throw new IllegalArgumentException("resizeStack: stackId " + stackId
+ " not found.");
}
- if (stack.setBounds(bounds, configs, taskBounds) && stack.isVisibleLocked()) {
+ if (stack.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
+ && stack.isVisibleLocked()) {
stack.resizeWindows();
stack.getDisplayContent().layoutNeeded = true;
mWindowPlacerLocked.performSurfacePlacement();
@@ -4840,6 +4852,17 @@
}
}
+ public void prepareFreezingTaskBounds(int stackId) {
+ synchronized (mWindowMap) {
+ final TaskStack stack = mStackIdToStack.get(stackId);
+ if (stack == null) {
+ throw new IllegalArgumentException("prepareFreezingTaskBounds: stackId " + stackId
+ + " not found.");
+ }
+ stack.prepareFreezingTaskBounds();
+ }
+ }
+
public void positionTaskInStack(int taskId, int stackId, int position, Rect bounds,
Configuration config) {
synchronized (mWindowMap) {
@@ -9466,6 +9489,32 @@
}
}
+ public void notifyAppRelaunching(IBinder token) {
+ synchronized (mWindowMap) {
+ AppWindowToken appWindow = findAppWindowToken(token);
+ if (canFreezeBounds(appWindow)) {
+ appWindow.freezeBounds();
+ }
+ }
+ }
+
+ public void notifyAppRelaunchingFinished(IBinder token) {
+ synchronized (mWindowMap) {
+ AppWindowToken appWindow = findAppWindowToken(token);
+ if (canFreezeBounds(appWindow)) {
+ appWindow.unfreezeBounds();
+ }
+ }
+ }
+
+ private boolean canFreezeBounds(AppWindowToken appWindow) {
+
+ // For freeform windows, we can't freeze the bounds at the moment because this would make
+ // the resizing unresponsive.
+ return appWindow != null && appWindow.mTask != null
+ && !appWindow.mTask.inFreeformWorkspace();
+ }
+
void dumpPolicyLocked(PrintWriter pw, String[] args, boolean dumpAll) {
pw.println("WINDOW MANAGER POLICY STATE (dumpsys window policy)");
mPolicy.dump(" ", pw, args);
@@ -10130,7 +10179,8 @@
synchronized (mWindowMap) {
appWindowToken = findAppWindowToken(token);
if (appWindowToken == null || !appWindowToken.isVisible()) {
- Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token " + token);
+ Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token "
+ + token);
return;
}
appWindowToken.setReplacingWindows(animate);
@@ -10159,6 +10209,14 @@
}
}
+ @Override
+ public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+ synchronized (mWindowMap) {
+ getDefaultDisplayContentLocked().getDockedDividerController().setResizeDimLayer(
+ visible, targetStackId, alpha);
+ }
+ }
+
public void setTaskResizeable(int taskId, boolean resizeable) {
synchronized (mWindowMap) {
Task task = mTaskIdToTask.get(taskId);
@@ -10177,26 +10235,14 @@
}
@Override
- public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+ public void registerDockedStackListener(IDockedStackListener listener) {
if (!checkCallingPermission(android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS,
- "registerDockDividerVisibilityListener()")) {
+ "registerDockedStackListener()")) {
return;
}
// TODO(multi-display): The listener is registered on the default display only.
- final DockedStackDividerController controller =
- getDefaultDisplayContentLocked().getDockedDividerController();
- controller.registerDockDividerVisibilityListener(listener);
- try {
- listener.asBinder().linkToDeath(new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- getDefaultDisplayContentLocked().getDockedDividerController()
- .registerDockDividerVisibilityListener(null);
- }
- }, 0);
- } catch (RemoteException e) {
- controller.registerDockDividerVisibilityListener(null);
- }
+ getDefaultDisplayContentLocked().mDividerControllerLocked.registerDockedStackListener(
+ listener);
}
private final class LocalService extends WindowManagerInternal {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a825e80..6e4e01f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -309,6 +309,12 @@
// possible to draw.
final Rect mOutsetFrame = new Rect();
+ /**
+ * Usually empty. Set to the task's tempInsetFrame. See
+ *{@link android.app.IActivityManager#resizeDockedStack}.
+ */
+ final Rect mInsetFrame = new Rect();
+
boolean mContentChanged;
// If a window showing a wallpaper: the requested offset for the
@@ -614,8 +620,18 @@
// We use the parent frame as the containing frame for fullscreen and child windows
mContainingFrame.set(pf);
mDisplayFrame.set(df);
+ mInsetFrame.setEmpty();
} else {
task.getBounds(mContainingFrame);
+ task.getTempInsetBounds(mInsetFrame);
+ if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
+
+ // If the bounds are frozen, we still want to translate the window freely and only
+ // freeze the size.
+ Rect frozen = mAppToken.mFrozenBounds.peek();
+ mContainingFrame.right = mContainingFrame.left + frozen.width();
+ mContainingFrame.bottom = mContainingFrame.top + frozen.height();
+ }
final WindowState imeWin = mService.mInputMethodWindow;
if (imeWin != null && imeWin.isVisibleNow() && mService.mInputMethodTarget == this
&& mContainingFrame.bottom > cf.bottom) {
@@ -674,6 +690,12 @@
mOutsets.set(0, 0, 0, 0);
}
+ // Denotes the actual frame used to calculate the insets. When resizing in docked mode,
+ // we'd like to freeze the layout, so we also need to freeze the insets temporarily. By the
+ // notion of a task having a different inset frame, we can achieve that while still moving
+ // the task around.
+ final Rect frame = !mInsetFrame.isEmpty() ? mInsetFrame : mFrame;
+
// Make sure the content and visible frames are inside of the
// final window frame.
if (freeformWorkspace && !mFrame.isEmpty()) {
@@ -709,42 +731,69 @@
mContentFrame.set(mFrame);
}
} else {
- mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
- Math.max(mContentFrame.top, mFrame.top),
- Math.min(mContentFrame.right, mFrame.right),
- Math.min(mContentFrame.bottom, mFrame.bottom));
+ mContentFrame.set(Math.max(mContentFrame.left, frame.left),
+ Math.max(mContentFrame.top, frame.top),
+ Math.min(mContentFrame.right, frame.right),
+ Math.min(mContentFrame.bottom, frame.bottom));
- mVisibleFrame.set(Math.max(mVisibleFrame.left, mFrame.left),
- Math.max(mVisibleFrame.top, mFrame.top),
- Math.min(mVisibleFrame.right, mFrame.right),
- Math.min(mVisibleFrame.bottom, mFrame.bottom));
+ mVisibleFrame.set(Math.max(mVisibleFrame.left, frame.left),
+ Math.max(mVisibleFrame.top, frame.top),
+ Math.min(mVisibleFrame.right, frame.right),
+ Math.min(mVisibleFrame.bottom, frame.bottom));
- mStableFrame.set(Math.max(mStableFrame.left, mFrame.left),
- Math.max(mStableFrame.top, mFrame.top),
- Math.min(mStableFrame.right, mFrame.right),
- Math.min(mStableFrame.bottom, mFrame.bottom));
+ mStableFrame.set(Math.max(mStableFrame.left, frame.left),
+ Math.max(mStableFrame.top, frame.top),
+ Math.min(mStableFrame.right, frame.right),
+ Math.min(mStableFrame.bottom, frame.bottom));
}
- mOverscanInsets.set(Math.max(mOverscanFrame.left - mFrame.left, 0),
- Math.max(mOverscanFrame.top - mFrame.top, 0),
- Math.max(mFrame.right - mOverscanFrame.right, 0),
- Math.max(mFrame.bottom - mOverscanFrame.bottom, 0));
+ mOverscanInsets.set(Math.max(mOverscanFrame.left - frame.left, 0),
+ Math.max(mOverscanFrame.top - frame.top, 0),
+ Math.max(frame.right - mOverscanFrame.right, 0),
+ Math.max(frame.bottom - mOverscanFrame.bottom, 0));
- mContentInsets.set(mContentFrame.left - mFrame.left,
- mContentFrame.top - mFrame.top,
- mFrame.right - mContentFrame.right,
- mFrame.bottom - mContentFrame.bottom);
+ mContentInsets.set(mContentFrame.left - frame.left,
+ mContentFrame.top - frame.top,
+ frame.right - mContentFrame.right,
+ frame.bottom - mContentFrame.bottom);
- mVisibleInsets.set(mVisibleFrame.left - mFrame.left,
- mVisibleFrame.top - mFrame.top,
- mFrame.right - mVisibleFrame.right,
- mFrame.bottom - mVisibleFrame.bottom);
+ mVisibleInsets.set(mVisibleFrame.left - frame.left,
+ mVisibleFrame.top - frame.top,
+ frame.right - mVisibleFrame.right,
+ frame.bottom - mVisibleFrame.bottom);
- mStableInsets.set(Math.max(mStableFrame.left - mFrame.left, 0),
- Math.max(mStableFrame.top - mFrame.top, 0),
- Math.max(mFrame.right - mStableFrame.right, 0),
- Math.max(mFrame.bottom - mStableFrame.bottom, 0));
+ if (mAttrs.type == TYPE_DOCK_DIVIDER) {
+ // For the docked divider, we calculate the stable insets like a full-screen window
+ // so it can use it to calculate the snap positions.
+ mStableInsets.set(Math.max(mStableFrame.left - mDisplayFrame.left, 0),
+ Math.max(mStableFrame.top - mDisplayFrame.top, 0),
+ Math.max(mDisplayFrame.right - mStableFrame.right, 0),
+ Math.max(mDisplayFrame.bottom - mStableFrame.bottom, 0));
+ } else {
+ mStableInsets.set(Math.max(mStableFrame.left - frame.left, 0),
+ Math.max(mStableFrame.top - frame.top, 0),
+ Math.max(frame.right - mStableFrame.right, 0),
+ Math.max(frame.bottom - mStableFrame.bottom, 0));
+ }
+
+ if (!mInsetFrame.isEmpty()) {
+ mContentFrame.set(mFrame);
+ mContentFrame.top += mContentInsets.top;
+ mContentFrame.bottom += mContentInsets.bottom;
+ mContentFrame.left += mContentInsets.left;
+ mContentFrame.right += mContentInsets.right;
+ mVisibleFrame.set(mFrame);
+ mVisibleFrame.top += mVisibleInsets.top;
+ mVisibleFrame.bottom += mVisibleInsets.bottom;
+ mVisibleFrame.left += mVisibleInsets.left;
+ mVisibleFrame.right += mVisibleInsets.right;
+ mStableFrame.set(mFrame);
+ mStableFrame.top += mStableInsets.top;
+ mStableFrame.bottom += mStableInsets.bottom;
+ mStableFrame.left += mStableInsets.left;
+ mStableFrame.right += mStableInsets.right;
+ }
mCompatFrame.set(mFrame);
if (mEnforceSizeCompat) {
// If there is a size compatibility scale being applied to the
@@ -1950,8 +1999,8 @@
// isDragResizing() or isDragResizeChanged() is true.
boolean resizing = isDragResizing() || isDragResizeChanged();
final Rect backDropFrame = (inFreeformWorkspace() || !resizing) ? frame : mTmpRect;
- mClient.resized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets,
- outsets, reportDraw, newConfig, backDropFrame);
+ mClient.resized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets,
+ reportDraw, newConfig, backDropFrame);
}
public void registerFocusObserver(IWindowFocusObserver observer) {
@@ -1998,7 +2047,13 @@
if (task.isDragResizing()) {
return true;
}
- return mDisplayContent.mDividerControllerLocked.isResizing() &&
+
+ // If the bounds are currently frozen, it means that the layout size that the app sees
+ // and the bounds we clip this window to might be different. In order to avoid holes, we
+ // simulate that we are still resizing so the app fills the hole with the resizing
+ // background.
+ return (mDisplayContent.mDividerControllerLocked.isResizing()
+ || mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) &&
!task.inFreeformWorkspace() && !task.isFullscreen();
}
@@ -2297,10 +2352,15 @@
}
void setReplacing(boolean animate) {
- if ((mAttrs.privateFlags & PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH) == 0) {
- mWillReplaceWindow = true;
- mReplacingWindow = null;
- mAnimateReplacingWindow = animate;
+ if ((mAttrs.privateFlags & PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH) != 0
+ || mAttrs.type == TYPE_APPLICATION_STARTING) {
+ // We don't set replacing on starting windows since they are added by window manager and
+ // not the client so won't be replaced by the client.
+ return;
}
+
+ mWillReplaceWindow = true;
+ mReplacingWindow = null;
+ mAnimateReplacingWindow = animate;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 381db56..83ab190 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -37,6 +37,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
import static com.android.server.wm.WindowManagerService.localLOGV;
+import static com.android.server.wm.WindowState.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.WindowState.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN;
@@ -1063,7 +1064,16 @@
final int top = w.mYOffset + w.mFrame.top;
// Initialize the decor rect to the entire frame.
- mSystemDecorRect.set(0, 0, width, height);
+ if (w.isDragResizing() && w.getResizeMode() == DRAG_RESIZE_MODE_DOCKED_DIVIDER) {
+
+ // If we are resizing with the divider, the task bounds might be smaller than the
+ // stack bounds. The system decor is used to clip to the task bounds, which we don't
+ // want in this case in order to avoid holes.
+ final DisplayInfo displayInfo = w.getDisplayContent().getDisplayInfo();
+ mSystemDecorRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
+ } else {
+ mSystemDecorRect.set(0, 0, width, height);
+ }
// If a freeform window is animating from a position where it would be cutoff, it would be
// cutoff during the animation. We don't want that, so for the duration of the animation
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index b784e0b..01ee18b 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -543,6 +543,11 @@
}
@Override
- public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+ public void registerDockedStackListener(IDockedStackListener listener) throws RemoteException {
+ }
+
+ @Override
+ public void setResizeDimLayer(boolean visible, int targetStackId, float alpha)
+ throws RemoteException {
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 683c4aa..c8e3d03 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -183,7 +183,7 @@
*/
private static LayoutLog sCurrentLog = sDefaultLog;
- private static final int LAST_SUPPORTED_FEATURE = Features.CHOREOGRAPHER;
+ private static final int LAST_SUPPORTED_FEATURE = Features.THEME_PREVIEW_NAVIGATION_BAR;
@Override
public int getApiLevel() {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
index 9c89bfe2..dfbc69b 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
@@ -19,9 +19,6 @@
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.resources.Density;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.LinearLayout;
@@ -41,29 +38,18 @@
private static final int WIDTH_DEFAULT = 36;
private static final int WIDTH_SW360 = 40;
private static final int WIDTH_SW600 = 48;
- private static final String LAYOUT_XML = "/bars/navigation_bar.xml";
+ protected static final String LAYOUT_XML = "/bars/navigation_bar.xml";
private static final String LAYOUT_600DP_XML = "/bars/navigation_bar600dp.xml";
-
- /**
- * Constructor to be used when creating the {@link NavigationBar} as a regular control.
- * This is currently used by the theme editor.
- */
- @SuppressWarnings("unused")
- public NavigationBar(Context context, AttributeSet attrs) {
- this((BridgeContext) context,
- Density.getEnum(((BridgeContext) context).getMetrics().densityDpi),
- LinearLayout.HORIZONTAL, // In this mode, it doesn't need to be render vertically
- ((BridgeContext) context).getConfiguration().getLayoutDirection() ==
- View.LAYOUT_DIRECTION_RTL,
- (context.getApplicationInfo().flags & ApplicationInfo.FLAG_SUPPORTS_RTL) != 0,
- 0);
+ public NavigationBar(BridgeContext context, Density density, int orientation, boolean isRtl,
+ boolean rtlEnabled, int simulatedPlatformVersion) {
+ this(context, density, orientation, isRtl, rtlEnabled, simulatedPlatformVersion,
+ getShortestWidth(context)>= 600 ? LAYOUT_600DP_XML : LAYOUT_XML);
}
- public NavigationBar(BridgeContext context, Density density, int orientation, boolean isRtl,
- boolean rtlEnabled, int simulatedPlatformVersion) {
- super(context, orientation, getShortestWidth(context)>= 600 ? LAYOUT_600DP_XML : LAYOUT_XML,
- "navigation_bar.xml", simulatedPlatformVersion);
+ protected NavigationBar(BridgeContext context, Density density, int orientation, boolean isRtl,
+ boolean rtlEnabled, int simulatedPlatformVersion, String layoutPath) {
+ super(context, orientation, layoutPath, "navigation_bar.xml", simulatedPlatformVersion);
int color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
setBackgroundColor(color == 0 ? 0xFF000000 : color);
@@ -117,7 +103,7 @@
view.setLayoutParams(layoutParams);
}
- private static int getSidePadding(float sw) {
+ protected int getSidePadding(float sw) {
if (sw >= 400) {
return PADDING_WIDTH_SW400;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ThemePreviewNavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ThemePreviewNavigationBar.java
new file mode 100644
index 0000000..0435280
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ThemePreviewNavigationBar.java
@@ -0,0 +1,58 @@
+/*
+ * 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.layoutlib.bridge.bars;
+
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.resources.Density;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+/**
+ * Navigation Bar for the Theme Editor preview.
+ *
+ * For small bars, it is identical to {@link NavigationBar}.
+ * But wide bars from {@link NavigationBar} are too wide for the Theme Editor preview.
+ * To solve that problem, {@link ThemePreviewNavigationBar} use the layout for small bars,
+ * and have no padding on the sides. That way, they have a similar look as the true ones,
+ * and they fit in the Theme Editor preview.
+ */
+public class ThemePreviewNavigationBar extends NavigationBar {
+ private static final int PADDING_WIDTH_SW600 = 0;
+
+ @SuppressWarnings("unused")
+ public ThemePreviewNavigationBar(Context context, AttributeSet attrs) {
+ super((BridgeContext) context,
+ Density.getEnum(((BridgeContext) context).getMetrics().densityDpi),
+ LinearLayout.HORIZONTAL, // In this mode, it doesn't need to be render vertically
+ ((BridgeContext) context).getConfiguration().getLayoutDirection() ==
+ View.LAYOUT_DIRECTION_RTL,
+ (context.getApplicationInfo().flags & ApplicationInfo.FLAG_SUPPORTS_RTL) != 0,
+ 0, LAYOUT_XML);
+ }
+
+ @Override
+ protected int getSidePadding(float sw) {
+ if (sw >= 600) {
+ return PADDING_WIDTH_SW600;
+ }
+ return super.getSidePadding(sw);
+ }
+}