Merge "Revert "Destroy docked divider surface when it's hidden.""
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index 26c7a1e..898b6cf 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -69,4 +69,16 @@
/** Remove a {@link UserRestrictionsListener}. */
public abstract void removeUserRestrictionsListener(UserRestrictionsListener listener);
+
+ /**
+ * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to update
+ * whether the device is managed by device owner.
+ */
+ public abstract void setDeviceManaged(boolean isManaged);
+
+ /**
+ * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to update
+ * whether the user is managed by profile owner.
+ */
+ public abstract void setUserManaged(int userId, boolean isManaged);
}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 4b63c36..1d4d572 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -413,6 +413,14 @@
public static final String POST_DIAL_DIGITS = "post_dial_digits";
/**
+ * Indicates that the entry will be copied from primary user to other users.
+ * <P>Type: INTEGER</P>
+ *
+ * @hide
+ */
+ public static final String ADD_FOR_ALL_USERS = "add_for_all_users";
+
+ /**
* If a successful call is made that is longer than this duration, update the phone number
* in the ContactsProvider with the normalized version of the number, based on the user's
* current country code.
@@ -444,7 +452,7 @@
int presentation, int callType, int features, PhoneAccountHandle accountHandle,
long start, int duration, Long dataUsage) {
return addCall(ci, context, number, "", presentation, callType, features, accountHandle,
- start, duration, dataUsage, false, false);
+ start, duration, dataUsage, false, null, false);
}
@@ -467,7 +475,9 @@
* the call.
* @param addForAllUsers If true, the call is added to the call log of all currently
* running users. The caller must have the MANAGE_USERS permission if this is true.
- *
+ * @param userToBeInsertedTo {@link UserHandle} of user that the call is going to be
+ * inserted to. null if it is inserted to the current user. The
+ * value is ignored if @{link addForAllUsers} is true.
* @result The URI of the call log entry belonging to the user that made or received this
* call.
* {@hide}
@@ -475,9 +485,10 @@
public static Uri addCall(CallerInfo ci, Context context, String number,
String postDialDigits, int presentation, int callType, int features,
PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage,
- boolean addForAllUsers) {
+ boolean addForAllUsers, UserHandle userToBeInsertedTo) {
return addCall(ci, context, number, postDialDigits, presentation, callType, features,
- accountHandle, start, duration, dataUsage, addForAllUsers, false);
+ accountHandle, start, duration, dataUsage, addForAllUsers, userToBeInsertedTo,
+ false);
}
/**
@@ -501,6 +512,9 @@
* the call.
* @param addForAllUsers If true, the call is added to the call log of all currently
* running users. The caller must have the MANAGE_USERS permission if this is true.
+ * @param userToBeInsertedTo {@link UserHandle} of user that the call is going to be
+ * inserted to. null if it is inserted to the current user. The
+ * value is ignored if @{link addForAllUsers} is true.
* @param is_read Flag to show if the missed call log has been read by the user or not.
* Used for call log restore of missed calls.
*
@@ -511,7 +525,7 @@
public static Uri addCall(CallerInfo ci, Context context, String number,
String postDialDigits, int presentation, int callType, int features,
PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage,
- boolean addForAllUsers, boolean is_read) {
+ boolean addForAllUsers, UserHandle userToBeInsertedTo, boolean is_read) {
final ContentResolver resolver = context.getContentResolver();
int numberPresentation = PRESENTATION_ALLOWED;
@@ -575,6 +589,7 @@
values.put(PHONE_ACCOUNT_ID, accountId);
values.put(PHONE_ACCOUNT_ADDRESS, accountAddress);
values.put(NEW, Integer.valueOf(1));
+ values.put(ADD_FOR_ALL_USERS, addForAllUsers ? 1 : 0);
if (callType == MISSED_TYPE) {
values.put(IS_READ, Integer.valueOf(is_read ? 1 : 0));
@@ -650,9 +665,13 @@
}
}
} else {
- result = addEntryAndRemoveExpiredEntries(context, CONTENT_URI, values);
+ Uri uri = CONTENT_URI;
+ if (userToBeInsertedTo != null) {
+ uri = ContentProvider
+ .maybeAddUserId(CONTENT_URI, userToBeInsertedTo.getIdentifier());
+ }
+ result = addEntryAndRemoveExpiredEntries(context, uri, values);
}
-
return result;
}
diff --git a/core/java/android/security/net/config/NetworkSecurityTrustManager.java b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
index 7f5b3ca..2b860fa 100644
--- a/core/java/android/security/net/config/NetworkSecurityTrustManager.java
+++ b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
@@ -65,7 +65,7 @@
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
- throw new CertificateException("Client authentication not supported");
+ mDelegate.checkClientTrusted(chain, authType);
}
@Override
@@ -149,6 +149,6 @@
@Override
public X509Certificate[] getAcceptedIssuers() {
- return new X509Certificate[0];
+ return mDelegate.getAcceptedIssuers();
}
}
diff --git a/core/java/android/security/net/config/RootTrustManager.java b/core/java/android/security/net/config/RootTrustManager.java
index b87bf1f..e307ad0 100644
--- a/core/java/android/security/net/config/RootTrustManager.java
+++ b/core/java/android/security/net/config/RootTrustManager.java
@@ -35,7 +35,6 @@
* @hide */
public class RootTrustManager implements X509TrustManager {
private final ApplicationConfig mConfig;
- private static final X509Certificate[] EMPTY_ISSUERS = new X509Certificate[0];
public RootTrustManager(ApplicationConfig config) {
if (config == null) {
@@ -47,7 +46,10 @@
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
- throw new CertificateException("Client authentication not supported");
+ // Use the default configuration for all client authentication. Domain specific configs are
+ // only for use in checking server trust not client trust.
+ NetworkSecurityConfig config = mConfig.getConfigForHostname("");
+ config.getTrustManager().checkClientTrusted(chain, authType);
}
@Override
@@ -84,6 +86,10 @@
@Override
public X509Certificate[] getAcceptedIssuers() {
- return EMPTY_ISSUERS;
+ // getAcceptedIssuers is meant to be used to determine which trust anchors the server will
+ // accept when verifying clients. Domain specific configs are only for use in checking
+ // server trust not client trust so use the default config.
+ NetworkSecurityConfig config = mConfig.getConfigForHostname("");
+ return config.getTrustManager().getAcceptedIssuers();
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 19a98f3..329d1b0 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -436,7 +436,8 @@
mCurId = res.id;
mBindSequence = res.sequence;
}
- startInputInner(null, 0, 0, 0);
+ startInputInner(InputMethodClient.START_INPUT_REASON_BOUND_TO_IMMS,
+ null, 0, 0, 0);
return;
}
case MSG_UNBIND: {
@@ -461,7 +462,9 @@
startInput = mActive;
}
if (startInput) {
- startInputInner(null, 0, 0, 0);
+ startInputInner(
+ InputMethodClient.START_INPUT_REASON_UNBOUND_FROM_IMMS, null, 0, 0,
+ 0);
}
return;
}
@@ -494,7 +497,10 @@
// In that case, we really should not call
// mServedInputConnection.finishComposingText.
if (checkFocusNoStartInput(mHasBeenInactive, false)) {
- startInputInner(null, 0, 0, 0);
+ final int reason = active ?
+ InputMethodClient.START_INPUT_REASON_ACTIVATED_BY_IMMS :
+ InputMethodClient.START_INPUT_REASON_DEACTIVATED_BY_IMMS;
+ startInputInner(reason, null, 0, 0, 0);
}
}
}
@@ -1118,18 +1124,23 @@
mServedConnecting = true;
}
-
- startInputInner(null, 0, 0, 0);
+
+ startInputInner(InputMethodClient.START_INPUT_REASON_APP_CALLED_RESTART_INPUT_API, null, 0,
+ 0, 0);
}
-
- boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode,
+
+ boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,
+ IBinder windowGainingFocus, int controlFlags, int softInputMode,
int windowFlags) {
final View view;
synchronized (mH) {
view = mServedView;
// Make sure we have a window token for the served view.
- if (DEBUG) Log.v(TAG, "Starting input: view=" + view);
+ if (DEBUG) {
+ Log.v(TAG, "Starting input: view=" + view +
+ " reason=" + InputMethodClient.getStartInputReason(startInputReason));
+ }
if (view == null) {
if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
return false;
@@ -1157,7 +1168,7 @@
vh.post(new Runnable() {
@Override
public void run() {
- startInputInner(null, 0, 0, 0);
+ startInputInner(startInputReason, null, 0, 0, 0);
}
});
return false;
@@ -1221,11 +1232,11 @@
+ Integer.toHexString(controlFlags));
InputBindResult res;
if (windowGainingFocus != null) {
- res = mService.windowGainedFocus(mClient, windowGainingFocus,
+ res = mService.windowGainedFocus(startInputReason, mClient, windowGainingFocus,
controlFlags, softInputMode, windowFlags,
tba, servedContext);
} else {
- res = mService.startInput(mClient,
+ res = mService.startInput(startInputReason, mClient,
servedContext, tba, controlFlags);
}
if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
@@ -1352,7 +1363,7 @@
*/
public void checkFocus() {
if (checkFocusNoStartInput(false, true)) {
- startInputInner(null, 0, 0, 0);
+ startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0);
}
}
@@ -1440,8 +1451,8 @@
// should be done in conjunction with telling the system service
// about the window gaining focus, to help make the transition
// smooth.
- if (startInputInner(rootView.getWindowToken(),
- controlFlags, softInputMode, windowFlags)) {
+ if (startInputInner(InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN,
+ rootView.getWindowToken(), controlFlags, softInputMode, windowFlags)) {
return;
}
}
@@ -1451,8 +1462,10 @@
synchronized (mH) {
try {
if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput");
- mService.windowGainedFocus(mClient, rootView.getWindowToken(),
- controlFlags, softInputMode, windowFlags, null, null);
+ mService.windowGainedFocus(
+ InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
+ rootView.getWindowToken(), controlFlags, softInputMode, windowFlags, null,
+ null);
} catch (RemoteException e) {
}
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 60c5e42..db3ecc6 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -45,9 +45,10 @@
void addClient(in IInputMethodClient client,
in IInputContext inputContext, int uid, int pid);
void removeClient(in IInputMethodClient client);
-
- InputBindResult startInput(in IInputMethodClient client,
- IInputContext inputContext, in EditorInfo attribute, int controlFlags);
+
+ InputBindResult startInput(/* @InputMethodClient.StartInputReason */ int startInputReason,
+ in IInputMethodClient client, IInputContext inputContext, in EditorInfo attribute,
+ int controlFlags);
void finishInput(in IInputMethodClient client);
boolean showSoftInput(in IInputMethodClient client, int flags,
in ResultReceiver resultReceiver);
@@ -55,9 +56,11 @@
in ResultReceiver resultReceiver);
// Report that a window has gained focus. If 'attribute' is non-null,
// this will also do a startInput.
- InputBindResult windowGainedFocus(in IInputMethodClient client, in IBinder windowToken,
- int controlFlags, int softInputMode, int windowFlags,
- in EditorInfo attribute, IInputContext inputContext);
+ InputBindResult windowGainedFocus(
+ /* @InputMethodClient.StartInputReason */ int startInputReason,
+ in IInputMethodClient client, in IBinder windowToken, int controlFlags,
+ int softInputMode, int windowFlags, in EditorInfo attribute,
+ IInputContext inputContext);
void showInputMethodPickerFromClient(in IInputMethodClient client,
int auxiliarySubtypeMode);
diff --git a/core/java/com/android/internal/view/InputMethodClient.java b/core/java/com/android/internal/view/InputMethodClient.java
index a035343..c27e9aa 100644
--- a/core/java/com/android/internal/view/InputMethodClient.java
+++ b/core/java/com/android/internal/view/InputMethodClient.java
@@ -23,6 +23,49 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
public final class InputMethodClient {
+ public static final int START_INPUT_REASON_UNSPECIFIED = 0;
+ public static final int START_INPUT_REASON_WINDOW_FOCUS_GAIN = 1;
+ public static final int START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY = 2;
+ public static final int START_INPUT_REASON_APP_CALLED_RESTART_INPUT_API = 3;
+ public static final int START_INPUT_REASON_CHECK_FOCUS = 4;
+ public static final int START_INPUT_REASON_BOUND_TO_IMMS = 5;
+ public static final int START_INPUT_REASON_UNBOUND_FROM_IMMS = 6;
+ public static final int START_INPUT_REASON_ACTIVATED_BY_IMMS = 7;
+ public static final int START_INPUT_REASON_DEACTIVATED_BY_IMMS = 8;
+
+ @Retention(SOURCE)
+ @IntDef({START_INPUT_REASON_UNSPECIFIED, START_INPUT_REASON_WINDOW_FOCUS_GAIN,
+ START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY,
+ START_INPUT_REASON_APP_CALLED_RESTART_INPUT_API, START_INPUT_REASON_CHECK_FOCUS,
+ START_INPUT_REASON_BOUND_TO_IMMS, START_INPUT_REASON_ACTIVATED_BY_IMMS,
+ START_INPUT_REASON_DEACTIVATED_BY_IMMS})
+ public @interface StartInputReason {}
+
+ public static String getStartInputReason(@StartInputReason final int reason) {
+ switch (reason) {
+ case START_INPUT_REASON_UNSPECIFIED:
+ return "UNSPECIFIED";
+ case START_INPUT_REASON_WINDOW_FOCUS_GAIN:
+ return "WINDOW_FOCUS_GAIN";
+ case START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY:
+ return "WINDOW_FOCUS_GAIN_REPORT_ONLY";
+ case START_INPUT_REASON_APP_CALLED_RESTART_INPUT_API:
+ return "APP_CALLED_RESTART_INPUT_API";
+ case START_INPUT_REASON_CHECK_FOCUS:
+ return "CHECK_FOCUS";
+ case START_INPUT_REASON_BOUND_TO_IMMS:
+ return "BOUND_TO_IMMS";
+ case START_INPUT_REASON_UNBOUND_FROM_IMMS:
+ return "UNBOUND_FROM_IMMS";
+ case START_INPUT_REASON_ACTIVATED_BY_IMMS:
+ return "ACTIVATED_BY_IMMS";
+ case START_INPUT_REASON_DEACTIVATED_BY_IMMS:
+ return "DEACTIVATED_BY_IMMS";
+ default:
+ return "Unknown=" + reason;
+ }
+ }
+
public static final int UNBIND_REASON_UNSPECIFIED = 0;
public static final int UNBIND_REASON_SWITCH_CLIENT = 1;
public static final int UNBIND_REASON_SWITCH_IME = 2;
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
index 042dc83..fdebf37 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
@@ -28,6 +28,7 @@
import libcore.util.EmptyArray;
+import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
@@ -386,7 +387,38 @@
@Override
protected final int engineUpdate(ByteBuffer input, ByteBuffer output)
throws ShortBufferException {
- return super.engineUpdate(input, output);
+ if (input == null) {
+ throw new NullPointerException("input == null");
+ }
+ if (output == null) {
+ throw new NullPointerException("output == null");
+ }
+
+ int inputSize = input.remaining();
+ byte[] outputArray;
+ if (input.hasArray()) {
+ outputArray =
+ engineUpdate(
+ input.array(), input.arrayOffset() + input.position(), inputSize);
+ input.position(input.position() + inputSize);
+ } else {
+ byte[] inputArray = new byte[inputSize];
+ input.get(inputArray);
+ outputArray = engineUpdate(inputArray, 0, inputSize);
+ }
+
+ int outputSize = (outputArray != null) ? outputArray.length : 0;
+ if (outputSize > 0) {
+ int outputBufferAvailable = output.remaining();
+ try {
+ output.put(outputArray);
+ } catch (BufferOverflowException e) {
+ throw new ShortBufferException(
+ "Output buffer too small. Produced: " + outputSize + ", available: "
+ + outputBufferAvailable);
+ }
+ }
+ return outputSize;
}
@Override
@@ -512,7 +544,38 @@
@Override
protected final int engineDoFinal(ByteBuffer input, ByteBuffer output)
throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
- return super.engineDoFinal(input, output);
+ if (input == null) {
+ throw new NullPointerException("input == null");
+ }
+ if (output == null) {
+ throw new NullPointerException("output == null");
+ }
+
+ int inputSize = input.remaining();
+ byte[] outputArray;
+ if (input.hasArray()) {
+ outputArray =
+ engineDoFinal(
+ input.array(), input.arrayOffset() + input.position(), inputSize);
+ input.position(input.position() + inputSize);
+ } else {
+ byte[] inputArray = new byte[inputSize];
+ input.get(inputArray);
+ outputArray = engineDoFinal(inputArray, 0, inputSize);
+ }
+
+ int outputSize = (outputArray != null) ? outputArray.length : 0;
+ if (outputSize > 0) {
+ int outputBufferAvailable = output.remaining();
+ try {
+ output.put(outputArray);
+ } catch (BufferOverflowException e) {
+ throw new ShortBufferException(
+ "Output buffer too small. Produced: " + outputSize + ", available: "
+ + outputBufferAvailable);
+ }
+ }
+ return outputSize;
}
@Override
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index cc68fb2..fc4916c 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -101,6 +101,7 @@
ifeq (true, $(HWUI_NEW_OPS))
hwui_src_files += \
+ BakedOpDispatcher.cpp \
BakedOpRenderer.cpp \
OpReorderer.cpp \
RecordingCanvas.cpp
@@ -275,7 +276,9 @@
LOCAL_MODULE_STEM_32 := hwuimicro
LOCAL_MODULE_STEM_64 := hwuimicro64
LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries)
-LOCAL_CFLAGS := $(hwui_cflags)
+LOCAL_CFLAGS := \
+ $(hwui_cflags) \
+ -DHWUI_NULL_GPU
LOCAL_C_INCLUDES += bionic/benchmarks/
LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static_null_gpu
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
new file mode 100644
index 0000000..b56b1e4
--- /dev/null
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -0,0 +1,264 @@
+/*
+ * 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 "BakedOpDispatcher.h"
+
+#include "BakedOpRenderer.h"
+#include "Caches.h"
+#include "Glop.h"
+#include "GlopBuilder.h"
+#include "renderstate/OffscreenBufferPool.h"
+#include "renderstate/RenderState.h"
+#include "utils/GLUtils.h"
+#include "VertexBuffer.h"
+
+#include <algorithm>
+#include <math.h>
+
+namespace android {
+namespace uirenderer {
+
+void BakedOpDispatcher::onRenderNodeOp(BakedOpRenderer&, const RenderNodeOp&, const BakedOpState&) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) {
+ renderer.caches().textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
+ Texture* texture = renderer.getTexture(op.bitmap);
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
+ const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
+ ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedUnitQuad(texture->uvMapper)
+ .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height))
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("todo");
+}
+
+void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) {
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshUnitQuad()
+ .setFillPaint(*op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRect(op.unmappedBounds)
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+namespace VertexBufferRenderFlags {
+ enum {
+ Offset = 0x1,
+ ShadowInterp = 0x2,
+ };
+}
+
+static void renderVertexBuffer(BakedOpRenderer& renderer, const BakedOpState& state,
+ const VertexBuffer& vertexBuffer, float translateX, float translateY,
+ SkPaint& paint, int vertexBufferRenderFlags) {
+ if (CC_LIKELY(vertexBuffer.getVertexCount())) {
+ bool shadowInterp = vertexBufferRenderFlags & VertexBufferRenderFlags::ShadowInterp;
+ const int transformFlags = TransformFlags::OffsetByFudgeFactor;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshVertexBuffer(vertexBuffer, shadowInterp)
+ .setFillPaint(paint, state.alpha)
+ .setTransform(state.computedState.transform, transformFlags)
+ .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds())
+ .build();
+ renderer.renderGlop(state, glop);
+ }
+}
+
+static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, float casterAlpha,
+ const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer) {
+ SkPaint paint;
+ paint.setAntiAlias(true); // want to use AlphaVertex
+
+ // The caller has made sure casterAlpha > 0.
+ uint8_t ambientShadowAlpha = renderer.getLightInfo().ambientShadowAlpha;
+ if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) {
+ ambientShadowAlpha = Properties::overrideAmbientShadowStrength;
+ }
+ if (ambientShadowVertexBuffer && ambientShadowAlpha > 0) {
+ paint.setAlpha((uint8_t)(casterAlpha * ambientShadowAlpha));
+ renderVertexBuffer(renderer, state, *ambientShadowVertexBuffer, 0, 0,
+ paint, VertexBufferRenderFlags::ShadowInterp);
+ }
+
+ uint8_t spotShadowAlpha = renderer.getLightInfo().spotShadowAlpha;
+ if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) {
+ spotShadowAlpha = Properties::overrideSpotShadowStrength;
+ }
+ if (spotShadowVertexBuffer && spotShadowAlpha > 0) {
+ paint.setAlpha((uint8_t)(casterAlpha * spotShadowAlpha));
+ renderVertexBuffer(renderer, state, *spotShadowVertexBuffer, 0, 0,
+ paint, VertexBufferRenderFlags::ShadowInterp);
+ }
+}
+
+void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) {
+ TessellationCache::vertexBuffer_pair_t buffers;
+ renderer.caches().tessellationCache.getShadowBuffers(&state.computedState.transform,
+ op.localClipRect, op.casterAlpha >= 1.0f, op.casterPath,
+ &op.shadowMatrixXY, &op.shadowMatrixZ,
+ op.lightCenter, renderer.getLightInfo().lightRadius,
+ buffers);
+
+ renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
+}
+
+void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, const BakedOpState& state) {
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4)
+ .setFillPaint(*op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewOffsetRect(0, 0, op.unmappedBounds)
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
+ const TextOp& op, const BakedOpState& state) {
+ renderer.caches().textureState().activateTexture(0);
+
+ PaintUtils::TextShadow textShadow;
+ if (!PaintUtils::getTextShadow(op.paint, &textShadow)) {
+ LOG_ALWAYS_FATAL("failed to query shadow attributes");
+ }
+
+ renderer.caches().dropShadowCache.setFontRenderer(fontRenderer);
+ ShadowTexture* texture = renderer.caches().dropShadowCache.get(
+ op.paint, (const char*) op.glyphs,
+ op.glyphCount, textShadow.radius, op.positions);
+ // If the drop shadow exceeds the max texture size or couldn't be
+ // allocated, skip drawing
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
+ const float sx = op.x - texture->left + textShadow.dx;
+ const float sy = op.y - texture->top + textShadow.dy;
+
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedUnitQuad(nullptr)
+ .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) {
+ FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
+
+ if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ renderTextShadow(renderer, fontRenderer, op, state);
+ }
+
+ float x = op.x;
+ float y = op.y;
+ const Matrix4& transform = state.computedState.transform;
+ const bool pureTranslate = transform.isPureTranslate();
+ if (CC_LIKELY(pureTranslate)) {
+ x = floorf(x + transform.getTranslateX() + 0.5f);
+ y = floorf(y + transform.getTranslateY() + 0.5f);
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ fontRenderer.setTextureFiltering(false);
+ } else if (CC_UNLIKELY(transform.isPerspective())) {
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ fontRenderer.setTextureFiltering(true);
+ } else {
+ // We only pass a partial transform to the font renderer. That partial
+ // matrix defines how glyphs are rasterized. Typically we want glyphs
+ // to be rasterized at their final size on screen, which means the partial
+ // matrix needs to take the scale factor into account.
+ // When a partial matrix is used to transform glyphs during rasterization,
+ // the mesh is generated with the inverse transform (in the case of scale,
+ // the mesh is generated at 1.0 / scale for instance.) This allows us to
+ // apply the full transform matrix at draw time in the vertex shader.
+ // Applying the full matrix in the shader is the easiest way to handle
+ // rotation and perspective and allows us to always generated quads in the
+ // font renderer which greatly simplifies the code, clipping in particular.
+ float sx, sy;
+ transform.decomposeScale(sx, sy);
+ fontRenderer.setFont(op.paint, SkMatrix::MakeScale(
+ roundf(std::max(1.0f, sx)),
+ roundf(std::max(1.0f, sy))));
+ fontRenderer.setTextureFiltering(true);
+ }
+
+ // TODO: Implement better clipping for scaled/rotated text
+ const Rect* clip = !pureTranslate ? nullptr : &state.computedState.clipRect;
+ Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
+
+ int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
+ SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
+ TextDrawFunctor functor(&renderer, &state, x, y, pureTranslate, alpha, mode, op.paint);
+
+ bool hasActiveLayer = false; // TODO
+ fontRenderer.renderPosText(op.paint, clip, (const char*) op.glyphs, op.glyphCount, x, y,
+ op.positions, hasActiveLayer ? &layerBounds : nullptr, &functor, true); // TODO: merging
+}
+
+void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
+ OffscreenBuffer* buffer = *op.layerHandle;
+
+ // TODO: extend this to handle HW layers & paint properties which
+ // reside in node.properties().layerProperties()
+ float layerAlpha = op.alpha * state.alpha;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedIndexedVbo(buffer->vbo, buffer->elementCount)
+ .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top,
+ Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
+ .build();
+ renderer.renderGlop(state, glop);
+
+ if (op.destroy) {
+ renderer.renderState().layerPool().putOrDelete(buffer);
+ }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/BakedOpDispatcher.h b/libs/hwui/BakedOpDispatcher.h
new file mode 100644
index 0000000..caf14bf
--- /dev/null
+++ b/libs/hwui/BakedOpDispatcher.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HWUI_BAKED_OP_DISPATCHER_H
+#define ANDROID_HWUI_BAKED_OP_DISPATCHER_H
+
+#include "BakedOpState.h"
+#include "RecordedOp.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Provides all "onBitmapOp(...)" style static methods for every op type, which convert the
+ * RecordedOps and their state to Glops, and renders them with the provided BakedOpRenderer.
+ *
+ * This dispatcher is separate from the renderer so that the dispatcher / renderer interaction is
+ * minimal through public BakedOpRenderer APIs.
+ */
+class BakedOpDispatcher {
+public:
+ // Declares all "onBitmapOp(...)" style methods for every op type
+#define DISPATCH_METHOD(Type) \
+ static void on##Type(BakedOpRenderer& renderer, const Type& op, const BakedOpState& state);
+ MAP_OPS(DISPATCH_METHOD);
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_BAKED_OP_DISPATCHER_H
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index d13d7ef..6cdc320 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -25,15 +25,10 @@
#include "VertexBuffer.h"
#include <algorithm>
-#include <math.h>
namespace android {
namespace uirenderer {
-////////////////////////////////////////////////////////////////////////////////
-// BakedOpRenderer
-////////////////////////////////////////////////////////////////////////////////
-
OffscreenBuffer* BakedOpRenderer::startTemporaryLayer(uint32_t width, uint32_t height) {
LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
@@ -151,238 +146,5 @@
if (!mRenderTarget.frameBufferId) mHasDrawn = true;
}
-////////////////////////////////////////////////////////////////////////////////
-// static BakedOpDispatcher methods
-////////////////////////////////////////////////////////////////////////////////
-
-void BakedOpDispatcher::onRenderNodeOp(BakedOpRenderer&, const RenderNodeOp&, const BakedOpState&) {
- LOG_ALWAYS_FATAL("unsupported operation");
-}
-
-void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) {
- LOG_ALWAYS_FATAL("unsupported operation");
-}
-
-void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) {
- LOG_ALWAYS_FATAL("unsupported operation");
-}
-
-void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) {
- renderer.caches().textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
- Texture* texture = renderer.getTexture(op.bitmap);
- if (!texture) return;
- const AutoTexture autoCleanup(texture);
-
- const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
- ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
- Glop glop;
- GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
- .setRoundRectClipState(state.roundRectClipState)
- .setMeshTexturedUnitQuad(texture->uvMapper)
- .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
- .setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height))
- .build();
- renderer.renderGlop(state, glop);
-}
-
-void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) {
- LOG_ALWAYS_FATAL("todo");
-}
-
-void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) {
- Glop glop;
- GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
- .setRoundRectClipState(state.roundRectClipState)
- .setMeshUnitQuad()
- .setFillPaint(*op.paint, state.alpha)
- .setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewMapUnitToRect(op.unmappedBounds)
- .build();
- renderer.renderGlop(state, glop);
-}
-
-namespace VertexBufferRenderFlags {
- enum {
- Offset = 0x1,
- ShadowInterp = 0x2,
- };
-}
-
-static void renderVertexBuffer(BakedOpRenderer& renderer, const BakedOpState& state,
- const VertexBuffer& vertexBuffer, float translateX, float translateY,
- SkPaint& paint, int vertexBufferRenderFlags) {
- if (CC_LIKELY(vertexBuffer.getVertexCount())) {
- bool shadowInterp = vertexBufferRenderFlags & VertexBufferRenderFlags::ShadowInterp;
- const int transformFlags = TransformFlags::OffsetByFudgeFactor;
- Glop glop;
- GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
- .setRoundRectClipState(state.roundRectClipState)
- .setMeshVertexBuffer(vertexBuffer, shadowInterp)
- .setFillPaint(paint, state.alpha)
- .setTransform(state.computedState.transform, transformFlags)
- .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds())
- .build();
- renderer.renderGlop(state, glop);
- }
-}
-
-static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, float casterAlpha,
- const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer) {
- SkPaint paint;
- paint.setAntiAlias(true); // want to use AlphaVertex
-
- // The caller has made sure casterAlpha > 0.
- uint8_t ambientShadowAlpha = renderer.getLightInfo().ambientShadowAlpha;
- if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) {
- ambientShadowAlpha = Properties::overrideAmbientShadowStrength;
- }
- if (ambientShadowVertexBuffer && ambientShadowAlpha > 0) {
- paint.setAlpha((uint8_t)(casterAlpha * ambientShadowAlpha));
- renderVertexBuffer(renderer, state, *ambientShadowVertexBuffer, 0, 0,
- paint, VertexBufferRenderFlags::ShadowInterp);
- }
-
- uint8_t spotShadowAlpha = renderer.getLightInfo().spotShadowAlpha;
- if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) {
- spotShadowAlpha = Properties::overrideSpotShadowStrength;
- }
- if (spotShadowVertexBuffer && spotShadowAlpha > 0) {
- paint.setAlpha((uint8_t)(casterAlpha * spotShadowAlpha));
- renderVertexBuffer(renderer, state, *spotShadowVertexBuffer, 0, 0,
- paint, VertexBufferRenderFlags::ShadowInterp);
- }
-}
-
-void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) {
- TessellationCache::vertexBuffer_pair_t buffers;
- renderer.caches().tessellationCache.getShadowBuffers(&state.computedState.transform,
- op.localClipRect, op.casterAlpha >= 1.0f, op.casterPath,
- &op.shadowMatrixXY, &op.shadowMatrixZ,
- op.lightCenter, renderer.getLightInfo().lightRadius,
- buffers);
-
- renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
-}
-
-void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, const BakedOpState& state) {
- Glop glop;
- GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
- .setRoundRectClipState(state.roundRectClipState)
- .setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4)
- .setFillPaint(*op.paint, state.alpha)
- .setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewOffsetRect(0, 0, op.unmappedBounds)
- .build();
- renderer.renderGlop(state, glop);
-}
-
-static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
- const TextOp& op, const BakedOpState& state) {
- renderer.caches().textureState().activateTexture(0);
-
- PaintUtils::TextShadow textShadow;
- if (!PaintUtils::getTextShadow(op.paint, &textShadow)) {
- LOG_ALWAYS_FATAL("failed to query shadow attributes");
- }
-
- renderer.caches().dropShadowCache.setFontRenderer(fontRenderer);
- ShadowTexture* texture = renderer.caches().dropShadowCache.get(
- op.paint, (const char*) op.glyphs,
- op.glyphCount, textShadow.radius, op.positions);
- // If the drop shadow exceeds the max texture size or couldn't be
- // allocated, skip drawing
- if (!texture) return;
- const AutoTexture autoCleanup(texture);
-
- const float sx = op.x - texture->left + textShadow.dx;
- const float sy = op.y - texture->top + textShadow.dy;
-
- Glop glop;
- GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
- .setRoundRectClipState(state.roundRectClipState)
- .setMeshTexturedUnitQuad(nullptr)
- .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha)
- .setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
- .build();
- renderer.renderGlop(state, glop);
-}
-
-void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) {
- FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
-
- if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
- fontRenderer.setFont(op.paint, SkMatrix::I());
- renderTextShadow(renderer, fontRenderer, op, state);
- }
-
- float x = op.x;
- float y = op.y;
- const Matrix4& transform = state.computedState.transform;
- const bool pureTranslate = transform.isPureTranslate();
- if (CC_LIKELY(pureTranslate)) {
- x = floorf(x + transform.getTranslateX() + 0.5f);
- y = floorf(y + transform.getTranslateY() + 0.5f);
- fontRenderer.setFont(op.paint, SkMatrix::I());
- fontRenderer.setTextureFiltering(false);
- } else if (CC_UNLIKELY(transform.isPerspective())) {
- fontRenderer.setFont(op.paint, SkMatrix::I());
- fontRenderer.setTextureFiltering(true);
- } else {
- // We only pass a partial transform to the font renderer. That partial
- // matrix defines how glyphs are rasterized. Typically we want glyphs
- // to be rasterized at their final size on screen, which means the partial
- // matrix needs to take the scale factor into account.
- // When a partial matrix is used to transform glyphs during rasterization,
- // the mesh is generated with the inverse transform (in the case of scale,
- // the mesh is generated at 1.0 / scale for instance.) This allows us to
- // apply the full transform matrix at draw time in the vertex shader.
- // Applying the full matrix in the shader is the easiest way to handle
- // rotation and perspective and allows us to always generated quads in the
- // font renderer which greatly simplifies the code, clipping in particular.
- float sx, sy;
- transform.decomposeScale(sx, sy);
- fontRenderer.setFont(op.paint, SkMatrix::MakeScale(
- roundf(std::max(1.0f, sx)),
- roundf(std::max(1.0f, sy))));
- fontRenderer.setTextureFiltering(true);
- }
-
- // TODO: Implement better clipping for scaled/rotated text
- const Rect* clip = !pureTranslate ? nullptr : &state.computedState.clipRect;
- Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
-
- int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
- SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
- TextDrawFunctor functor(&renderer, &state, x, y, pureTranslate, alpha, mode, op.paint);
-
- bool hasActiveLayer = false; // TODO
- fontRenderer.renderPosText(op.paint, clip, (const char*) op.glyphs, op.glyphCount, x, y,
- op.positions, hasActiveLayer ? &layerBounds : nullptr, &functor, true); // TODO: merging
-}
-
-void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
- OffscreenBuffer* buffer = *op.layerHandle;
-
- // TODO: extend this to handle HW layers & paint properties which
- // reside in node.properties().layerProperties()
- float layerAlpha = op.alpha * state.alpha;
- Glop glop;
- GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
- .setRoundRectClipState(state.roundRectClipState)
- .setMeshTexturedIndexedVbo(buffer->vbo, buffer->elementCount)
- .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap)
- .setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top,
- Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
- .build();
- renderer.renderGlop(state, glop);
-
- if (op.destroy) {
- renderer.renderState().layerPool().putOrDelete(buffer);
- }
-}
-
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index 29f9a6f..62d1838 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -91,21 +91,6 @@
const LightInfo mLightInfo;
};
-/**
- * Provides all "onBitmapOp(...)" style static methods for every op type, which convert the
- * RecordedOps and their state to Glops, and renders them with the provided BakedOpRenderer.
- *
- * This dispatcher is separate from the renderer so that the dispatcher / renderer interaction is
- * minimal through public BakedOpRenderer APIs.
- */
-class BakedOpDispatcher {
-public:
- // Declares all "onBitmapOp(...)" style methods for every op type
-#define DISPATCH_METHOD(Type) \
- static void on##Type(BakedOpRenderer& renderer, const Type& op, const BakedOpState& state);
- MAP_OPS(DISPATCH_METHOD);
-};
-
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 00c4e2d..60cc7ba 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -132,7 +132,7 @@
DisplayList();
~DisplayList();
- // index of DisplayListOp restore, after which projected descendents should be drawn
+ // index of DisplayListOp restore, after which projected descendants should be drawn
int projectionReceiveIndex;
const LsaVector<Chunk>& getChunks() const { return chunks; }
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index bf98f79..72fc100 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -55,6 +55,7 @@
class DeferredLayerUpdater;
class DisplayListOp;
class DrawOp;
+class DrawRenderNodeOp;
class RenderNode;
class StateOp;
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 977b53c..bd11d0a 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1386,19 +1386,19 @@
: DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), nullptr)
, renderNode(renderNode)
, mRecordedWithPotentialStencilClip(!clipIsSimple || !transformFromParent.isSimple())
- , mTransformFromParent(transformFromParent)
- , mSkipInOrderDraw(false) {}
+ , localMatrix(transformFromParent)
+ , skipInOrderDraw(false) {}
virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
bool useQuickReject) override {
- if (renderNode->isRenderable() && !mSkipInOrderDraw) {
+ if (renderNode->isRenderable() && !skipInOrderDraw) {
renderNode->defer(deferStruct, level + 1);
}
}
virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level,
bool useQuickReject) override {
- if (renderNode->isRenderable() && !mSkipInOrderDraw) {
+ if (renderNode->isRenderable() && !skipInOrderDraw) {
renderNode->replay(replayStruct, level + 1);
}
}
@@ -1439,7 +1439,7 @@
/**
* Records transform vs parent, used for computing total transform without rerunning DL contents
*/
- const mat4 mTransformFromParent;
+ const mat4 localMatrix;
/**
* Holds the transformation between the projection surface ViewGroup and this RenderNode
@@ -1449,8 +1449,8 @@
*
* Note: doesn't include transformation within the RenderNode, or its properties.
*/
- mat4 mTransformFromCompositingAncestor;
- bool mSkipInOrderDraw;
+ mat4 transformFromCompositingAncestor;
+ bool skipInOrderDraw;
};
/**
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 5f33cae..47654f4 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -29,8 +29,9 @@
#if HWUI_NEW_OPS
-#include "BakedOpState.h"
+#include "BakedOpDispatcher.h"
#include "BakedOpRenderer.h"
+#include "BakedOpState.h"
#else
#include "OpenGLRenderer.h"
#endif
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 5e954ae..9cbd9c2d 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -330,6 +330,7 @@
for (int i = layers.entries().size() - 1; i >= 0; i--) {
RenderNode* layerNode = layers.entries()[i].renderNode;
const Rect& layerDamage = layers.entries()[i].damage;
+ layerNode->computeOrdering();
// map current light center into RenderNode's coordinate space
Vector3 lightCenter = mCanvasState.currentSnapshot()->getRelativeLightCenter();
@@ -339,7 +340,7 @@
layerDamage, lightCenter, nullptr, layerNode);
if (layerNode->getDisplayList()) {
- deferDisplayList(*(layerNode->getDisplayList()));
+ deferNodeOps(*layerNode);
}
restoreForLayer();
}
@@ -347,6 +348,7 @@
// Defer Fbo0
for (const sp<RenderNode>& node : nodes) {
if (node->nothingToDraw()) continue;
+ node->computeOrdering();
int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
deferNodePropsAndOps(*node);
@@ -354,20 +356,6 @@
}
}
-OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
- const Vector3& lightCenter)
- : mCanvasState(*this) {
- ATRACE_NAME("prepare drawing commands");
- // Prepare to defer Fbo0
- mLayerReorderers.emplace_back(viewportWidth, viewportHeight,
- Rect(viewportWidth, viewportHeight));
- mLayerStack.push_back(0);
- mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
- 0, 0, viewportWidth, viewportHeight, lightCenter);
-
- deferDisplayList(displayList);
-}
-
void OpReorderer::onViewportInitialized() {}
void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
@@ -462,10 +450,10 @@
Matrix4::identity(),
saveLayerBounds,
&saveLayerPaint));
- deferDisplayList(*(node.getDisplayList()));
+ deferNodeOps(node);
onEndLayerOp(*new (mAllocator) EndLayerOp());
} else {
- deferDisplayList(*(node.getDisplayList()));
+ deferNodeOps(node);
}
}
}
@@ -610,18 +598,53 @@
}
}
+void OpReorderer::deferProjectedChildren(const RenderNode& renderNode) {
+ const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
+ int count = mCanvasState.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+
+ // can't be null, since DL=null node rejection happens before deferNodePropsAndOps
+ const DisplayList& displayList = *(renderNode.getDisplayList());
+
+ const RecordedOp* op = (displayList.getOps()[displayList.projectionReceiveIndex]);
+ const RenderNodeOp* backgroundOp = static_cast<const RenderNodeOp*>(op);
+ const RenderProperties& backgroundProps = backgroundOp->renderNode->properties();
+
+ // Transform renderer to match background we're projecting onto
+ // (by offsetting canvas by translationX/Y of background rendernode, since only those are set)
+ mCanvasState.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
+
+ // If the projection receiver has an outline, we mask projected content to it
+ // (which we know, apriori, are all tessellated paths)
+ mCanvasState.setProjectionPathMask(mAllocator, projectionReceiverOutline);
+
+ // draw projected nodes
+ for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) {
+ RenderNodeOp* childOp = renderNode.mProjectedNodes[i];
+
+ int restoreTo = mCanvasState.save(SkCanvas::kMatrix_SaveFlag);
+ mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
+ deferRenderNodeOp(*childOp);
+ mCanvasState.restoreToCount(restoreTo);
+ }
+
+ mCanvasState.restoreToCount(count);
+}
+
/**
* Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
*
- * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. E.g. a
- * BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
+ * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas.
+ * E.g. a BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
*/
#define OP_RECEIVER(Type) \
[](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
-void OpReorderer::deferDisplayList(const DisplayList& displayList) {
+void OpReorderer::deferNodeOps(const RenderNode& renderNode) {
static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = {
MAP_OPS(OP_RECEIVER)
};
+
+ // can't be null, since DL=null node rejection happens before deferNodePropsAndOps
+ const DisplayList& displayList = *(renderNode.getDisplayList());
for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes;
buildZSortedChildList(&zTranslatedNodes, displayList, chunk);
@@ -630,6 +653,12 @@
for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
const RecordedOp* op = displayList.getOps()[opIndex];
receivers[op->opId](*this, *op);
+
+ if (CC_UNLIKELY(!renderNode.mProjectedNodes.empty()
+ && displayList.projectionReceiveIndex >= 0
+ && static_cast<int>(opIndex) == displayList.projectionReceiveIndex)) {
+ deferProjectedChildren(renderNode);
+ }
}
defer3dChildren(ChildrenSelectMode::Positive, zTranslatedNodes);
}
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 976f413..00df8b0 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -124,9 +124,6 @@
uint32_t viewportWidth, uint32_t viewportHeight,
const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter);
- OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
- const Vector3& lightCenter);
-
virtual ~OpReorderer() {}
/**
@@ -202,16 +199,18 @@
return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
}
- // should always be surrounded by a save/restore pair
+ // should always be surrounded by a save/restore pair, and not called if DisplayList is null
void deferNodePropsAndOps(RenderNode& node);
- void deferShadow(const RenderNodeOp& casterOp);
-
- void deferDisplayList(const DisplayList& displayList);
-
template <typename V>
void defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes);
+ void deferShadow(const RenderNodeOp& casterOp);
+
+ void deferProjectedChildren(const RenderNode& renderNode);
+
+ void deferNodeOps(const RenderNode& renderNode);
+
void deferRenderNodeOp(const RenderNodeOp& op);
void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index 127dca5..b4a201e 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -97,7 +97,17 @@
RenderNodeOp(BASE_PARAMS_PAINTLESS, RenderNode* renderNode)
: SUPER_PAINTLESS(RenderNodeOp)
, renderNode(renderNode) {}
- RenderNode * renderNode; // not const, since drawing modifies it (somehow...)
+ RenderNode * renderNode; // not const, since drawing modifies it
+
+ /**
+ * Holds the transformation between the projection surface ViewGroup and this RenderNode
+ * drawing instance. Represents any translations / transformations done within the drawing of
+ * the compositing ancestor ViewGroup's draw, before the draw of the View represented by this
+ * DisplayList draw instance.
+ *
+ * Note: doesn't include transformation within the RenderNode, or its properties.
+ */
+ Matrix4 transformFromCompositingAncestor;
bool skipInOrderDraw = false;
};
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 736cc9e..6d0e9e0 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -205,10 +205,6 @@
return dstBuffer;
}
- inline char* refText(const char* text, size_t byteLength) {
- return (char*) refBuffer<uint8_t>((uint8_t*)text, byteLength);
- }
-
inline const SkPath* refPath(const SkPath* path) {
if (!path) return nullptr;
@@ -220,13 +216,8 @@
}
/**
- * Returns a RenderThread-safe, const copy of the SkPaint parameter passed in (with deduping
- * based on paint generation ID)
- *
- * Note that this forces Left_Align, since drawText glyph rendering expects left alignment,
- * since alignment offsetting has been done at a higher level. This is done to essentially all
- * copied paints, since the deduping can mean a paint is shared by drawText commands and other
- * types (which wouldn't care about alignment).
+ * Returns a RenderThread-safe, const copy of the SkPaint parameter passed in
+ * (with deduping based on paint hash / equality check)
*/
inline const SkPaint* refPaint(const SkPaint* paint) {
if (!paint) return nullptr;
@@ -246,11 +237,8 @@
// In the unlikely event that 2 unique paints have the same hash we do a
// object equality check to ensure we don't erroneously dedup them.
if (cachedPaint == nullptr || *cachedPaint != *paint) {
- SkPaint* copy = new SkPaint(*paint);
- copy->setTextAlign(SkPaint::kLeft_Align);
-
- cachedPaint = copy;
- mDisplayList->paints.emplace_back(copy);
+ cachedPaint = new SkPaint(*paint);
+ mDisplayList->paints.emplace_back(cachedPaint);
// replaceValueFor() performs an add if the entry doesn't exist
mPaintMap.replaceValueFor(key, cachedPaint);
refBitmapsInShader(cachedPaint->getShader());
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 3f24f44..ae690fd 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -487,7 +487,7 @@
info.damageAccumulator->pushTransform(&op->localMatrix);
bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip;
#else
- info.damageAccumulator->pushTransform(&op->mTransformFromParent);
+ info.damageAccumulator->pushTransform(&op->localMatrix);
bool childFunctorsNeedLayer = functorsNeedLayer
// Recorded with non-rect clip, or canvas-rotated by parent
|| op->mRecordedWithPotentialStencilClip;
@@ -658,7 +658,6 @@
* which are flagged to not draw in the standard draw loop.
*/
void RenderNode::computeOrdering() {
-#if !HWUI_NEW_OPS
ATRACE_CALL();
mProjectedNodes.clear();
@@ -666,43 +665,41 @@
// transform properties are applied correctly to top level children
if (mDisplayList == nullptr) return;
for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) {
- DrawRenderNodeOp* childOp = mDisplayList->getChildren()[i];
+ renderNodeOp_t* childOp = mDisplayList->getChildren()[i];
childOp->renderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity());
}
-#endif
}
void RenderNode::computeOrderingImpl(
- DrawRenderNodeOp* opState,
- std::vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
+ renderNodeOp_t* opState,
+ std::vector<renderNodeOp_t*>* compositedChildrenOfProjectionSurface,
const mat4* transformFromProjectionSurface) {
-#if !HWUI_NEW_OPS
mProjectedNodes.clear();
if (mDisplayList == nullptr || mDisplayList->isEmpty()) return;
// TODO: should avoid this calculation in most cases
// TODO: just calculate single matrix, down to all leaf composited elements
Matrix4 localTransformFromProjectionSurface(*transformFromProjectionSurface);
- localTransformFromProjectionSurface.multiply(opState->mTransformFromParent);
+ localTransformFromProjectionSurface.multiply(opState->localMatrix);
if (properties().getProjectBackwards()) {
// composited projectee, flag for out of order draw, save matrix, and store in proj surface
- opState->mSkipInOrderDraw = true;
- opState->mTransformFromCompositingAncestor = localTransformFromProjectionSurface;
+ opState->skipInOrderDraw = true;
+ opState->transformFromCompositingAncestor = localTransformFromProjectionSurface;
compositedChildrenOfProjectionSurface->push_back(opState);
} else {
// standard in order draw
- opState->mSkipInOrderDraw = false;
+ opState->skipInOrderDraw = false;
}
if (mDisplayList->getChildren().size() > 0) {
const bool isProjectionReceiver = mDisplayList->projectionReceiveIndex >= 0;
bool haveAppliedPropertiesToProjection = false;
for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) {
- DrawRenderNodeOp* childOp = mDisplayList->getChildren()[i];
+ renderNodeOp_t* childOp = mDisplayList->getChildren()[i];
RenderNode* child = childOp->renderNode;
- std::vector<DrawRenderNodeOp*>* projectionChildren = nullptr;
+ std::vector<renderNodeOp_t*>* projectionChildren = nullptr;
const mat4* projectionTransform = nullptr;
if (isProjectionReceiver && !child->properties().getProjectBackwards()) {
// if receiving projections, collect projecting descendant
@@ -723,7 +720,6 @@
child->computeOrderingImpl(childOp, projectionChildren, projectionTransform);
}
}
-#endif
}
class DeferOperationHandler {
@@ -793,10 +789,10 @@
if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
zTranslatedNodes.push_back(ZDrawRenderNodeOpPair(childZ, childOp));
- childOp->mSkipInOrderDraw = true;
+ childOp->skipInOrderDraw = true;
} else if (!child->properties().getProjectBackwards()) {
// regular, in order drawing DisplayList
- childOp->mSkipInOrderDraw = false;
+ childOp->skipInOrderDraw = false;
}
}
@@ -913,7 +909,7 @@
// attempt to render the shadow if the caster about to be drawn is its caster,
// OR if its caster's Z value is similar to the previous potential caster
if (shadowIndex == drawIndex || casterZ - lastCasterZ < SHADOW_DELTA) {
- caster->issueDrawShadowOperation(casterOp->mTransformFromParent, handler);
+ caster->issueDrawShadowOperation(casterOp->localMatrix, handler);
lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
shadowIndex++;
@@ -927,10 +923,10 @@
DrawRenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
- renderer.concatMatrix(childOp->mTransformFromParent);
- childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
+ renderer.concatMatrix(childOp->localMatrix);
+ childOp->skipInOrderDraw = false; // this is horrible, I'm so sorry everyone
handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
- childOp->mSkipInOrderDraw = true;
+ childOp->skipInOrderDraw = true;
renderer.restoreToCount(restoreTo);
drawIndex++;
@@ -967,14 +963,14 @@
// draw projected nodes
for (size_t i = 0; i < mProjectedNodes.size(); i++) {
- DrawRenderNodeOp* childOp = mProjectedNodes[i];
+ renderNodeOp_t* childOp = mProjectedNodes[i];
// matrix save, concat, and restore can be done safely without allocating operations
int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
- renderer.concatMatrix(childOp->mTransformFromCompositingAncestor);
- childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
+ renderer.concatMatrix(childOp->transformFromCompositingAncestor);
+ childOp->skipInOrderDraw = false; // this is horrible, I'm so sorry everyone
handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
- childOp->mSkipInOrderDraw = true;
+ childOp->skipInOrderDraw = true;
renderer.restoreToCount(restoreTo);
}
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 83d1b58..b6f50b1 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -51,20 +51,22 @@
class Rect;
class SkiaShader;
-
#if HWUI_NEW_OPS
class OffscreenBuffer;
+struct RenderNodeOp;
typedef OffscreenBuffer layer_t;
+typedef RenderNodeOp renderNodeOp_t;
#else
class Layer;
typedef Layer layer_t;
+typedef DrawRenderNodeOp renderNodeOp_t;
#endif
class ClipRectOp;
+class DrawRenderNodeOp;
class SaveLayerOp;
class SaveOp;
class RestoreToCountOp;
-class DrawRenderNodeOp;
class TreeInfo;
namespace proto {
@@ -85,6 +87,7 @@
*/
class RenderNode : public VirtualLightRefBase {
friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties
+friend class OpReorderer;
public:
enum DirtyPropertyMask {
GENERIC = 1 << 1,
@@ -221,8 +224,8 @@
PositiveZChildren
};
- void computeOrderingImpl(DrawRenderNodeOp* opState,
- std::vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
+ void computeOrderingImpl(renderNodeOp_t* opState,
+ std::vector<renderNodeOp_t*>* compositedChildrenOfProjectionSurface,
const mat4* transformFromProjectionSurface);
template <class T>
@@ -305,7 +308,7 @@
*/
// for projection surfaces, contains a list of all children items
- std::vector<DrawRenderNodeOp*> mProjectedNodes;
+ std::vector<renderNodeOp_t*> mProjectedNodes;
// How many references our parent(s) have to us. Typically this should alternate
// between 2 and 1 (when a staging push happens we inc first then dec)
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index eea0c7f..53b64c3 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -17,11 +17,14 @@
#include <benchmark/Benchmark.h>
#include "BakedOpState.h"
+#include "BakedOpDispatcher.h"
#include "BakedOpRenderer.h"
+#include "LayerUpdateQueue.h"
#include "OpReorderer.h"
#include "RecordedOp.h"
#include "RecordingCanvas.h"
#include "utils/TestUtils.h"
+#include "Vector.h"
#include "microbench/MicroBench.h"
#include <vector>
@@ -29,26 +32,38 @@
using namespace android;
using namespace android::uirenderer;
-auto sReorderingDisplayList = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
- SkPaint paint;
+const LayerUpdateQueue sEmptyLayerUpdateQueue;
+const Vector3 sLightCenter = {100, 100, 100};
- // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
- // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
- for (int i = 0; i < 30; i++) {
- canvas.translate(0, 10);
- canvas.drawRect(0, 0, 10, 10, paint);
- canvas.drawBitmap(bitmap, 5, 0, nullptr);
- }
- canvas.restore();
-});
+static std::vector<sp<RenderNode>> createTestNodeList() {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
+ SkPaint paint;
+
+ // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
+ // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
+ canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ for (int i = 0; i < 30; i++) {
+ canvas.translate(0, 10);
+ canvas.drawRect(0, 0, 10, 10, paint);
+ canvas.drawBitmap(bitmap, 5, 0, nullptr);
+ }
+ canvas.restore();
+ });
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+ std::vector<sp<RenderNode>> vec;
+ vec.emplace_back(node);
+ return vec;
+}
BENCHMARK_NO_ARG(BM_OpReorderer_defer);
void BM_OpReorderer_defer::Run(int iters) {
+ auto nodes = createTestNodeList();
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
- OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
+ nodes, sLightCenter);
MicroBench::DoNotOptimize(&reorderer);
}
StopBenchmarkTiming();
@@ -57,13 +72,16 @@
BENCHMARK_NO_ARG(BM_OpReorderer_deferAndRender);
void BM_OpReorderer_deferAndRender::Run(int iters) {
TestUtils::runOnRenderThread([this, iters](renderthread::RenderThread& thread) {
+ auto nodes = createTestNodeList();
+ BakedOpRenderer::LightInfo lightInfo = {50.0f, 128, 128 };
+
RenderState& renderState = thread.renderState();
Caches& caches = Caches::getInstance();
- BakedOpRenderer::LightInfo lightInfo = { 50.0f, 128, 128 };
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
- OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
+ nodes, sLightCenter);
BakedOpRenderer renderer(caches, renderState, true, lightInfo);
reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index c3cfc94..d36ce99 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -28,6 +28,7 @@
#include "renderthread/RenderThread.h"
#if HWUI_NEW_OPS
+#include "BakedOpDispatcher.h"
#include "BakedOpRenderer.h"
#endif
diff --git a/libs/hwui/tests/scenes/OvalAnimation.cpp b/libs/hwui/tests/scenes/OvalAnimation.cpp
index 919a53d..936aba1 100644
--- a/libs/hwui/tests/scenes/OvalAnimation.cpp
+++ b/libs/hwui/tests/scenes/OvalAnimation.cpp
@@ -29,17 +29,14 @@
sp<RenderNode> card;
void createContent(int width, int height, TestCanvas& canvas) override {
canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- canvas.insertReorderBarrier(true);
-
- card = TestUtils::createNode(0, 0, 200, 200, [](TestCanvas& canvas) {
+ card = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, TestCanvas& canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(0xFF000000);
canvas.drawOval(0, 0, 200, 200, paint);
});
-
canvas.drawRenderNode(card.get());
- canvas.insertReorderBarrier(false);
}
void doFrame(int frameNr) override {
diff --git a/libs/hwui/tests/scenes/PartialDamageAnimation.cpp b/libs/hwui/tests/scenes/PartialDamageAnimation.cpp
index 0fba4eb..c31ddd1 100644
--- a/libs/hwui/tests/scenes/PartialDamageAnimation.cpp
+++ b/libs/hwui/tests/scenes/PartialDamageAnimation.cpp
@@ -44,7 +44,7 @@
SkColor color = COLORS[static_cast<int>((y / dp(116))) % 4];
sp<RenderNode> card = TestUtils::createNode(x, y,
x + dp(100), y + dp(100),
- [color](TestCanvas& canvas) {
+ [color](RenderProperties& props, TestCanvas& canvas) {
canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
});
canvas.drawRenderNode(card.get());
diff --git a/libs/hwui/tests/scenes/RectGridAnimation.cpp b/libs/hwui/tests/scenes/RectGridAnimation.cpp
index 254f828..a1f04d6 100644
--- a/libs/hwui/tests/scenes/RectGridAnimation.cpp
+++ b/libs/hwui/tests/scenes/RectGridAnimation.cpp
@@ -34,7 +34,7 @@
canvas.insertReorderBarrier(true);
card = TestUtils::createNode(50, 50, 250, 250,
- [](TestCanvas& canvas) {
+ [](RenderProperties& props, TestCanvas& canvas) {
canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
SkRegion region;
diff --git a/libs/hwui/tests/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/scenes/SaveLayerAnimation.cpp
index c62dd19..c73e97b 100644
--- a/libs/hwui/tests/scenes/SaveLayerAnimation.cpp
+++ b/libs/hwui/tests/scenes/SaveLayerAnimation.cpp
@@ -32,7 +32,7 @@
canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
card = TestUtils::createNode(0, 0, 200, 200,
- [](TestCanvas& canvas) {
+ [](RenderProperties& props, TestCanvas& canvas) {
canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped
canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
diff --git a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
index 05fd08a..cc15cc6 100644
--- a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
+++ b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
@@ -31,7 +31,7 @@
// sync node properties, so properties() reflects correct width and height
static sp<RenderNode> createSyncedNode(uint32_t width, uint32_t height) {
- sp<RenderNode> node = TestUtils::createNode(0, 0, width, height);
+ sp<RenderNode> node = TestUtils::createNode(0, 0, width, height, nullptr);
TestUtils::syncHierarchyPropertiesAndDisplayList(node);
return node;
}
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index d76086c..2ce1d0a 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -111,25 +111,29 @@
}
};
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 100, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(25, 25);
canvas.drawRect(0, 0, 100, 200, SkPaint());
canvas.drawBitmap(bitmap, 10, 10, nullptr);
});
- OpReorderer reorderer(100, 200, *dl, sLightCenter);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
+ createSyncedNodeList(node), sLightCenter);
SimpleTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
}
TEST(OpReorderer, simpleRejection) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op); // intersection should be empty
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.restore();
});
- OpReorderer reorderer(200, 200, *dl, sLightCenter);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(node), sLightCenter);
FailRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -147,7 +151,8 @@
}
};
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
// Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
@@ -161,7 +166,8 @@
canvas.restore();
});
- OpReorderer reorderer(200, 200, *dl, sLightCenter);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(node), sLightCenter);
SimpleBatchingTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2 * LOOPS, renderer.getIndex())
@@ -179,16 +185,19 @@
EXPECT_TRUE(mIndex++ < LOOPS) << "Text should be beneath all strikethrough rects";
}
};
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 2000, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 200, 2000,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
SkPaint textPaint;
textPaint.setAntiAlias(true);
textPaint.setTextSize(20);
textPaint.setStrikeThruText(true);
+ textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
for (int i = 0; i < LOOPS; i++) {
TestUtils::drawTextToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
}
});
- OpReorderer reorderer(200, 2000, *dl, sLightCenter);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 2000), 200, 2000,
+ createSyncedNodeList(node), sLightCenter);
TextStrikethroughTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2 * LOOPS, renderer.getIndex())
@@ -214,14 +223,16 @@
}
};
- sp<RenderNode> child = TestUtils::createNode(10, 10, 110, 110, [](RecordingCanvas& canvas) {
+ auto child = TestUtils::createNode(10, 10, 110, 110,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
});
RenderNode* childPtr = child.get();
- sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
+ auto parent = TestUtils::createNode(0, 0, 200, 200,
+ [childPtr](RenderProperties& props, RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorDKGRAY);
canvas.drawRect(0, 0, 200, 200, paint);
@@ -249,7 +260,8 @@
}
};
- sp<RenderNode> node = TestUtils::createNode(0, 0, 200, 200, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
canvas.drawBitmap(bitmap, 0, 0, nullptr);
});
@@ -291,13 +303,14 @@
}
};
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
canvas.saveLayerAlpha(10, 10, 190, 190, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawRect(10, 10, 190, 190, SkPaint());
canvas.restore();
});
-
- OpReorderer reorderer(200, 200, *dl, sLightCenter);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(node), sLightCenter);
SaveLayerSimpleTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex());
@@ -354,7 +367,8 @@
}
};
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(800, 800, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 800, 800,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
canvas.saveLayerAlpha(0, 0, 800, 800, 128, SkCanvas::kClipToLayer_SaveFlag);
{
canvas.drawRect(0, 0, 800, 800, SkPaint());
@@ -367,14 +381,16 @@
canvas.restore();
});
- OpReorderer reorderer(800, 800, *dl, sLightCenter);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(800, 800), 800, 800,
+ createSyncedNodeList(node), sLightCenter);
SaveLayerNestedTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(10, renderer.getIndex());
}
TEST(OpReorderer, saveLayerContentRejection) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op);
canvas.saveLayerAlpha(200, 200, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag);
@@ -385,7 +401,8 @@
canvas.restore();
canvas.restore();
});
- OpReorderer reorderer(200, 200, *dl, sLightCenter);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(node), sLightCenter);
FailRenderer renderer;
// should see no ops, even within the layer, since the layer should be rejected
@@ -424,7 +441,7 @@
}
};
- sp<RenderNode> node = TestUtils::createNode(10, 10, 110, 110,
+ auto node = TestUtils::createNode(10, 10, 110, 110,
[](RenderProperties& props, RecordingCanvas& canvas) {
props.mutateLayerProperties().setType(LayerType::RenderLayer);
SkPaint paint;
@@ -562,7 +579,7 @@
}
static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) {
auto node = TestUtils::createNode(0, 0, 100, 100,
- [expectedDrawOrder](RecordingCanvas& canvas) {
+ [expectedDrawOrder](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedRect(&canvas, expectedDrawOrder);
});
node->mutateStagingProperties().setTranslationZ(z);
@@ -579,7 +596,7 @@
};
auto parent = TestUtils::createNode(0, 0, 100, 100,
- [](RecordingCanvas& canvas) {
+ [](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
drawOrderedRect(&canvas, 1);
canvas.insertReorderBarrier(true);
@@ -600,10 +617,93 @@
EXPECT_EQ(10, renderer.getIndex());
};
+TEST(OpReorderer, projectionReorder) {
+ static const int scrollX = 5;
+ static const int scrollY = 10;
+ class ProjectionReorderTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ const int index = mIndex++;
+
+ Matrix4 expectedMatrix;
+ switch (index) {
+ case 0:
+ EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
+ EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+ expectedMatrix.loadIdentity();
+ break;
+ case 1:
+ EXPECT_EQ(Rect(-10, -10, 60, 60), op.unmappedBounds);
+ EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
+ expectedMatrix.loadTranslate(50, 50, 0); // TODO: should scroll be respected here?
+ break;
+ case 2:
+ EXPECT_EQ(Rect(100, 50), op.unmappedBounds);
+ EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
+ expectedMatrix.loadTranslate(-scrollX, 50 - scrollY, 0);
+ break;
+ default:
+ ADD_FAILURE();
+ }
+ EXPECT_MATRIX_APPROX_EQ(expectedMatrix, state.computedState.transform);
+ }
+ };
+
+ /**
+ * Construct a tree of nodes, where the root (A) has a receiver background (B), and a child (C)
+ * with a projecting child (P) of its own. P would normally draw between B and C's "background"
+ * draw, but because it is projected backwards, it's drawn in between B and C.
+ *
+ * The parent is scrolled by scrollX/scrollY, but this does not affect the background
+ * (which isn't affected by scroll).
+ */
+ auto receiverBackground = TestUtils::createNode(0, 0, 100, 100,
+ [](RenderProperties& properties, RecordingCanvas& canvas) {
+ properties.setProjectionReceiver(true);
+ // scroll doesn't apply to background, so undone via translationX/Y
+ // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+ properties.setTranslationX(scrollX);
+ properties.setTranslationY(scrollY);
+
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+ auto projectingRipple = TestUtils::createNode(50, 0, 100, 50,
+ [](RenderProperties& properties, RecordingCanvas& canvas) {
+ properties.setProjectBackwards(true);
+ properties.setClipToBounds(false);
+ SkPaint paint;
+ paint.setColor(SK_ColorDKGRAY);
+ canvas.drawRect(-10, -10, 60, 60, paint);
+ });
+ auto child = TestUtils::createNode(0, 50, 100, 100,
+ [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorBLUE);
+ canvas.drawRect(0, 0, 100, 50, paint);
+ canvas.drawRenderNode(projectingRipple.get());
+ });
+ auto parent = TestUtils::createNode(0, 0, 100, 100,
+ [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
+ canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
+ canvas.drawRenderNode(receiverBackground.get());
+ canvas.drawRenderNode(child.get());
+ canvas.restore();
+ });
+
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
+ createSyncedNodeList(parent), sLightCenter);
+ ProjectionReorderTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(3, renderer.getIndex());
+}
+
// creates a 100x100 shadow casting node with provided translationZ
static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
return TestUtils::createNode(0, 0, 100, 100,
- [translationZ] (RenderProperties& properties, RecordingCanvas& canvas) {
+ [translationZ](RenderProperties& properties, RecordingCanvas& canvas) {
properties.setTranslationZ(translationZ);
properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f);
SkPaint paint;
@@ -630,8 +730,8 @@
}
};
- sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
- [] (RecordingCanvas& canvas) {
+ auto parent = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
canvas.insertReorderBarrier(true);
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
});
@@ -666,8 +766,8 @@
}
};
- sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
- [] (RecordingCanvas& canvas) {
+ auto parent = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
// save/restore outside of reorderBarrier, so they don't get moved out of place
canvas.translate(20, 10);
int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
@@ -706,7 +806,7 @@
}
};
- sp<RenderNode> parent = TestUtils::createNode(50, 60, 150, 160,
+ auto parent = TestUtils::createNode(50, 60, 150, 160,
[](RenderProperties& props, RecordingCanvas& canvas) {
props.mutateLayerProperties().setType(LayerType::RenderLayer);
canvas.insertReorderBarrier(true);
@@ -749,8 +849,8 @@
EXPECT_TRUE(index == 2 || index == 3);
}
};
- sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
- [] (RecordingCanvas& canvas) {
+ auto parent = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
canvas.insertReorderBarrier(true);
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get());
@@ -954,7 +1054,7 @@
SaveLayerAlphaData observedData;
testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
// Translate and rotate the view so that the only visible part is the top left corner of
- // the view. It will form an isoceles right triangle with a long side length of 200 at the
+ // the view. It will form an isosceles right triangle with a long side length of 200 at the
// bottom of the viewport.
properties.setTranslationX(100);
properties.setTranslationY(100);
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index c23d47e..81f0851 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -75,6 +75,7 @@
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
});
@@ -95,6 +96,7 @@
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
paint.setUnderlineText(i != 0);
@@ -126,6 +128,7 @@
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setTextAlign(SkPaint::kLeft_Align);
TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
paint.setTextAlign(SkPaint::kCenter_Align);
@@ -135,12 +138,17 @@
});
int count = 0;
- playbackOps(*dl, [&count](const RecordedOp& op) {
+ float lastX = FLT_MAX;
+ playbackOps(*dl, [&count, &lastX](const RecordedOp& op) {
count++;
ASSERT_EQ(RecordedOpId::TextOp, op.opId);
EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign())
<< "recorded drawText commands must force kLeft_Align on their paint";
- EXPECT_EQ(SkPaint::kGlyphID_TextEncoding, op.paint->getTextEncoding()); // verify TestUtils
+
+ // verify TestUtils alignment offsetting (TODO: move asserts to Canvas base class)
+ EXPECT_GT(lastX, ((const TextOp&)op).x)
+ << "x coordinate should reduce across each of the draw commands, from alignment";
+ lastX = ((const TextOp&)op).x;
});
ASSERT_EQ(3, count);
}
@@ -313,6 +321,49 @@
EXPECT_EQ(3, count);
}
+TEST(RecordingCanvas, drawRenderNode_projection) {
+ sp<RenderNode> background = TestUtils::createNode(50, 50, 150, 150,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+ {
+ background->mutateStagingProperties().setProjectionReceiver(false);
+
+ // NO RECEIVER PRESENT
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
+ [&background](RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ canvas.drawRenderNode(background.get());
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ });
+ EXPECT_EQ(-1, dl->projectionReceiveIndex)
+ << "no projection receiver should have been observed";
+ }
+ {
+ background->mutateStagingProperties().setProjectionReceiver(true);
+
+ // RECEIVER PRESENT
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
+ [&background](RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ canvas.drawRenderNode(background.get());
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ });
+
+ ASSERT_EQ(3u, dl->getOps().size()) << "Must be three ops";
+ auto op = dl->getOps()[1];
+ EXPECT_EQ(RecordedOpId::RenderNodeOp, op->opId);
+ EXPECT_EQ(1, dl->projectionReceiveIndex)
+ << "correct projection receiver not identified";
+
+ // verify the behavior works even though projection receiver hasn't been sync'd yet
+ EXPECT_TRUE(background->stagingProperties().isProjectionReceiver());
+ EXPECT_FALSE(background->properties().isProjectionReceiver());
+ }
+}
+
TEST(RecordingCanvas, insertReorderBarrier) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.drawRect(0, 0, 400, 400, SkPaint());
@@ -334,5 +385,39 @@
EXPECT_TRUE(chunks[1].reorderChildren);
}
+TEST(RecordingCanvas, refPaint) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ paint.setTextAlign(SkPaint::kLeft_Align);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&paint](RecordingCanvas& canvas) {
+ paint.setColor(SK_ColorBLUE);
+ // first three should use same paint
+ canvas.drawRect(0, 0, 200, 10, paint);
+ SkPaint paintCopy(paint);
+ canvas.drawRect(0, 10, 200, 20, paintCopy);
+ TestUtils::drawTextToCanvas(&canvas, "helloworld", paint, 50, 25);
+
+ // only here do we use different paint ptr
+ paint.setColor(SK_ColorRED);
+ canvas.drawRect(0, 20, 200, 30, paint);
+ });
+ auto ops = dl->getOps();
+ ASSERT_EQ(4u, ops.size());
+
+ // first three are the same
+ EXPECT_NE(nullptr, ops[0]->paint);
+ EXPECT_NE(&paint, ops[0]->paint);
+ EXPECT_EQ(ops[0]->paint, ops[1]->paint);
+ EXPECT_EQ(ops[0]->paint, ops[2]->paint);
+
+ // last is different, but still copied / non-null
+ EXPECT_NE(nullptr, ops[3]->paint);
+ EXPECT_NE(ops[0]->paint, ops[3]->paint);
+ EXPECT_NE(&paint, ops[3]->paint);
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/utils/TestUtils.cpp b/libs/hwui/utils/TestUtils.cpp
index dd6fc36..6cef852 100644
--- a/libs/hwui/utils/TestUtils.cpp
+++ b/libs/hwui/utils/TestUtils.cpp
@@ -37,44 +37,56 @@
}
void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text,
- const SkPaint& inPaint, float x, float y) {
- // copy to force TextEncoding (which JNI layer would have done)
- SkPaint paint(inPaint);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ const SkPaint& paint, float x, float y) {
+ // drawing text requires GlyphID TextEncoding (which JNI layer would have done)
+ LOG_ALWAYS_FATAL_IF(paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding,
+ "must use glyph encoding");
- SkMatrix identity;
- identity.setIdentity();
- SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
- SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &identity);
+ SkMatrix identity;
+ identity.setIdentity();
+ SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
+ SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &identity);
- float totalAdvance = 0;
- std::vector<glyph_t> glyphs;
- std::vector<float> positions;
- Rect bounds;
- while (*text != '\0') {
- SkUnichar unichar = SkUTF8_NextUnichar(&text);
- glyph_t glyph = autoCache.getCache()->unicharToGlyph(unichar);
- autoCache.getCache()->unicharToGlyph(unichar);
+ float totalAdvance = 0;
+ std::vector<glyph_t> glyphs;
+ std::vector<float> positions;
+ Rect bounds;
+ while (*text != '\0') {
+ SkUnichar unichar = SkUTF8_NextUnichar(&text);
+ glyph_t glyph = autoCache.getCache()->unicharToGlyph(unichar);
+ autoCache.getCache()->unicharToGlyph(unichar);
- // push glyph and its relative position
- glyphs.push_back(glyph);
- positions.push_back(totalAdvance);
- positions.push_back(0);
+ // push glyph and its relative position
+ glyphs.push_back(glyph);
+ positions.push_back(totalAdvance);
+ positions.push_back(0);
- // compute bounds
- SkGlyph skGlyph = autoCache.getCache()->getUnicharMetrics(unichar);
- Rect glyphBounds(skGlyph.fWidth, skGlyph.fHeight);
- glyphBounds.translate(totalAdvance + skGlyph.fLeft, skGlyph.fTop);
- bounds.unionWith(glyphBounds);
+ // compute bounds
+ SkGlyph skGlyph = autoCache.getCache()->getUnicharMetrics(unichar);
+ Rect glyphBounds(skGlyph.fWidth, skGlyph.fHeight);
+ glyphBounds.translate(totalAdvance + skGlyph.fLeft, skGlyph.fTop);
+ bounds.unionWith(glyphBounds);
- // advance next character
- SkScalar skWidth;
- paint.getTextWidths(&glyph, sizeof(glyph), &skWidth, NULL);
- totalAdvance += skWidth;
- }
- bounds.translate(x, y);
- canvas->drawText(glyphs.data(), positions.data(), glyphs.size(), paint, x, y,
- bounds.left, bounds.top, bounds.right, bounds.bottom, totalAdvance);
+ // advance next character
+ SkScalar skWidth;
+ paint.getTextWidths(&glyph, sizeof(glyph), &skWidth, NULL);
+ totalAdvance += skWidth;
+ }
+
+ // apply alignment via x parameter (which JNI layer would have done)
+ if (paint.getTextAlign() == SkPaint::kCenter_Align) {
+ x -= totalAdvance / 2;
+ } else if (paint.getTextAlign() == SkPaint::kRight_Align) {
+ x -= totalAdvance;
+ }
+
+ bounds.translate(x, y);
+
+ // Force left alignment, since alignment offset is already baked in
+ SkPaint alignPaintCopy(paint);
+ alignPaintCopy.setTextAlign(SkPaint::kLeft_Align);
+ canvas->drawText(glyphs.data(), positions.data(), glyphs.size(), alignPaintCopy, x, y,
+ bounds.left, bounds.top, bounds.right, bounds.bottom, totalAdvance);
}
} /* namespace uirenderer */
diff --git a/libs/hwui/utils/TestUtils.h b/libs/hwui/utils/TestUtils.h
index f9fa242..9c1c0b9 100644
--- a/libs/hwui/utils/TestUtils.h
+++ b/libs/hwui/utils/TestUtils.h
@@ -121,7 +121,7 @@
}
static sp<RenderNode> createNode(int left, int top, int right, int bottom,
- std::function<void(RenderProperties& props, TestCanvas& canvas)> setup = nullptr) {
+ std::function<void(RenderProperties& props, TestCanvas& canvas)> setup) {
#if HWUI_NULL_GPU
// if RenderNodes are being sync'd/used, device info will be needed, since
// DeviceInfo::maxTextureSize() affects layer property
@@ -140,22 +140,6 @@
return node;
}
- static sp<RenderNode> createNode(int left, int top, int right, int bottom,
- std::function<void(RenderProperties& props)> setup) {
- return createNode(left, top, right, bottom,
- [&setup](RenderProperties& props, TestCanvas& canvas) {
- setup(props);
- });
- }
-
- static sp<RenderNode> createNode(int left, int top, int right, int bottom,
- std::function<void(TestCanvas& canvas)> setup) {
- return createNode(left, top, right, bottom,
- [&setup](RenderProperties& props, TestCanvas& canvas) {
- setup(canvas);
- });
- }
-
static void recordNode(RenderNode& node,
std::function<void(TestCanvas&)> contentCallback) {
TestCanvas canvas(node.stagingProperties().getWidth(),
@@ -164,6 +148,13 @@
node.setStagingDisplayList(canvas.finishRecording());
}
+ /**
+ * Forces a sync of a tree of RenderNode, such that every descendant will have its staging
+ * properties and DisplayList moved to the render copies.
+ *
+ * Note: does not check dirtiness bits, so any non-staging DisplayLists will be discarded.
+ * For this reason, this should generally only be called once on a tree.
+ */
static void syncHierarchyPropertiesAndDisplayList(sp<RenderNode>& node) {
syncHierarchyPropertiesAndDisplayListImpl(node.get());
}
@@ -197,7 +188,7 @@
static SkColor interpolateColor(float fraction, SkColor start, SkColor end);
static void drawTextToCanvas(TestCanvas* canvas, const char* text,
- const SkPaint& inPaint, float x, float y);
+ const SkPaint& paint, float x, float y);
private:
static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 0cc2a67..ece34fb 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -6,7 +6,9 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-proto-files-under,src) $(call all-Iaidl-files-under, src) \
src/com/android/systemui/EventLogTags.logtags
-LOCAL_STATIC_JAVA_LIBRARIES := Keyguard
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ Keyguard \
+ android-support-v7-recyclerview
LOCAL_JAVA_LIBRARIES := telephony-common
LOCAL_PACKAGE_NAME := SystemUI
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index f2920e5..75e7959 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -21,11 +21,6 @@
public <init>(android.os.Bundle);
}
--keep class com.android.systemui.recents.views.TaskStackLayoutAlgorithm {
- public float getFocusState();
- public void setFocusState(float);
-}
-
-keep class com.android.systemui.recents.views.TaskView {
public int getDim();
public void setDim(int);
diff --git a/packages/SystemUI/res/layout/recents.xml b/packages/SystemUI/res/layout/recents.xml
index 064d225..2c010dd 100644
--- a/packages/SystemUI/res/layout/recents.xml
+++ b/packages/SystemUI/res/layout/recents.xml
@@ -39,6 +39,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
+ <!-- History View -->
+ <ViewStub android:id="@+id/history_view_stub"
+ android:layout="@layout/recents_history"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<!-- Nav Bar Scrim View -->
<ImageView
android:id="@+id/nav_bar_scrim"
diff --git a/packages/SystemUI/res/layout/recents_history.xml b/packages/SystemUI/res/layout/recents_history.xml
new file mode 100644
index 0000000..de70d30
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_history.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<com.android.systemui.recents.history.RecentsHistoryView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#99000000"
+ android:orientation="vertical">
+ <TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="14dp"
+ android:gravity="start"
+ android:text="@string/recents_history_label"
+ android:textSize="24sp"
+ android:textColor="#FFFFFF"
+ android:fontFamily="sans-serif-medium" />
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</com.android.systemui.recents.history.RecentsHistoryView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_history_button.xml b/packages/SystemUI/res/layout/recents_history_button.xml
new file mode 100644
index 0000000..471f518
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_history_button.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical"
+ android:text="@string/recents_show_history_button_label"
+ android:textSize="14sp"
+ android:textColor="#FFFFFF"
+ android:textAllCaps="true"
+ android:fontFamily="sans-serif-medium"
+ android:visibility="invisible" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_history_date.xml b/packages/SystemUI/res/layout/recents_history_date.xml
new file mode 100644
index 0000000..6d6a9ee
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_history_date.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:theme="@android:style/Theme.Material"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="12dp"
+ android:gravity="start"
+ android:textSize="14sp"
+ android:textColor="#009688"
+ android:textAllCaps="true"
+ android:fontFamily="sans-serif-medium"
+ android:background="?android:selectableItemBackground"
+ android:alpha="1" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_history_task.xml b/packages/SystemUI/res/layout/recents_history_task.xml
new file mode 100644
index 0000000..b9de156
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_history_task.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:theme="@android:style/Theme.Material"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:paddingLeft="32dp"
+ android:gravity="start|center_vertical"
+ android:textSize="14sp"
+ android:textColor="#FFFFFF"
+ android:fontFamily="sans-serif-medium"
+ android:background="?android:selectableItemBackground"
+ android:alpha="1" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d8193ab..902db26 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -177,6 +177,9 @@
<!-- The animation duration for scrolling the stack to a particular item. -->
<integer name="recents_animate_task_stack_scroll_duration">225</integer>
+ <!-- The animation duration for entering and exiting the history. -->
+ <integer name="recents_history_transition_duration">250</integer>
+
<!-- The minimum alpha for the dim applied to cards that go deeper into the stack. -->
<integer name="recents_max_task_stack_view_dim">96</integer>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e0c0f6a..04233ba 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -698,6 +698,10 @@
<string name="recents_search_bar_label">search</string>
<!-- Recents: Launch error string. [CHAR LIMIT=NONE] -->
<string name="recents_launch_error_message">Could not start <xliff:g id="app" example="Calendar">%s</xliff:g>.</string>
+ <!-- Recents: Show history string. [CHAR LIMIT=NONE] -->
+ <string name="recents_show_history_button_label">More</string>
+ <!-- Recents: The history of recents. [CHAR LIMIT=NONE] -->
+ <string name="recents_history_label">History</string>
<!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] -->
<string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string>
@@ -1157,21 +1161,26 @@
<!-- Option to use new paging layout in quick settings [CHAR LIMIT=60] -->
<string name="qs_paging" translatable="false">Use the new Quick Settings</string>
- <!-- Toggles paging recents via the recents button -->
+ <!-- Toggles paging recents via the recents button. DO NOT TRANSLATE -->
<string name="overview_page_on_toggle">Enable paging</string>
- <!-- Description for the toggle for fast-toggling recents via the recents button -->
+ <!-- Description for the toggle for fast-toggling recents via the recents button. DO NOT TRANSLATE -->
<string name="overview_page_on_toggle_desc">Enable paging via the Overview button</string>
- <!-- Toggles fast-toggling recents via the recents button -->
+ <!-- Toggles fast-toggling recents via the recents button. DO NOT TRANSLATE -->
<string name="overview_fast_toggle_via_button">Enable fast toggle</string>
- <!-- Description for the toggle for fast-toggling recents via the recents button -->
+ <!-- Description for the toggle for fast-toggling recents via the recents button. DO NOT TRANSLATE -->
<string name="overview_fast_toggle_via_button_desc">Enable launch timeout while paging</string>
- <!-- Toggles fullscreen screenshots -->
+ <!-- Toggles fullscreen screenshots. DO NOT TRANSLATE -->
<string name="overview_fullscreen_thumbnails">Enable fullscreen screenshots</string>
- <!-- Description for the toggle for fullscreen screenshots -->
+ <!-- Description for the toggle for fullscreen screenshots. DO NOT TRANSLATE -->
<string name="overview_fullscreen_thumbnails_desc">Enable fullscreen screenshots in Overview</string>
+ <!-- Toggle to show the history view in Overview. DO NOT TRANSLATE -->
+ <string name="overview_show_history">Show History</string>
+ <!-- Description for the toggle to show the history view in Overview. DO NOT TRANSLATE -->
+ <string name="overview_show_history_desc">Enables the history view to see more recent tasks</string>
+
<!-- Category in the System UI Tuner settings, where new/experimental
settings are -->
<string name="experimental">Experimental</string>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 4d07d5f..f398af3 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -102,6 +102,11 @@
android:title="@string/overview_fullscreen_thumbnails"
android:summary="@string/overview_fullscreen_thumbnails_desc" />
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="overview_show_history"
+ android:title="@string/overview_show_history"
+ android:summary="@string/overview_show_history_desc" />
+
</PreferenceScreen>
<SwitchPreference
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 9d98772..7933cc6 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -32,6 +32,7 @@
@StringDef({
Key.OVERVIEW_SEARCH_APP_WIDGET_ID,
Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE,
+ Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME,
Key.DEBUG_MODE_ENABLED,
Key.HOTSPOT_TILE_LAST_USED,
Key.COLOR_INVERSION_TILE_LAST_USED,
@@ -46,6 +47,7 @@
public @interface Key {
String OVERVIEW_SEARCH_APP_WIDGET_ID = "searchAppWidgetId";
String OVERVIEW_SEARCH_APP_WIDGET_PACKAGE = "searchAppWidgetPackage";
+ String OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME = "OverviewLastStackTaskActiveTime";
String DEBUG_MODE_ENABLED = "debugModeEnabled";
String HOTSPOT_TILE_LAST_USED = "HotspotTileLastUsed";
String COLOR_INVERSION_TILE_LAST_USED = "ColorInversionTileLastUsed";
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 16b1592..94c45a4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -46,10 +46,12 @@
import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
+import com.android.systemui.recents.events.activity.HideHistoryEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
+import com.android.systemui.recents.events.activity.ShowHistoryEvent;
import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
@@ -65,6 +67,7 @@
import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
+import com.android.systemui.recents.history.RecentsHistoryView;
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -99,6 +102,8 @@
private SystemBarScrimViews mScrimViews;
private ViewStub mEmptyViewStub;
private View mEmptyView;
+ private ViewStub mHistoryViewStub;
+ private RecentsHistoryView mHistoryView;
// Resize task debug
private RecentsResizeTaskDialog mResizeTaskDebugDialog;
@@ -196,14 +201,12 @@
TaskStack stack = plan.getTaskStack();
launchState.launchedWithNoRecentTasks = !plan.hasTasks();
- if (!launchState.launchedWithNoRecentTasks) {
- mRecentsView.setTaskStack(stack);
- }
+ mRecentsView.setTaskStack(stack);
// Mark the task that is the launch target
int launchTaskIndexInStack = 0;
if (launchState.launchedToTaskId != -1) {
- ArrayList<Task> tasks = stack.getTasks();
+ ArrayList<Task> tasks = stack.getStackTasks();
int taskCount = tasks.size();
for (int j = 0; j < taskCount; j++) {
Task t = tasks.get(j);
@@ -255,11 +258,23 @@
MetricsLogger.count(this, "overview_source_home", 1);
}
// Keep track of the total stack task count
- int taskCount = stack.getTaskCount();
+ int taskCount = stack.getStackTaskCount();
MetricsLogger.histogram(this, "overview_task_count", taskCount);
}
/**
+ * Dismisses the history view back into the stack view.
+ */
+ boolean dismissHistory() {
+ // Try and hide the history view first
+ if (mHistoryView != null && mHistoryView.isVisible()) {
+ EventBus.getDefault().send(new HideHistoryEvent(true /* animate */));
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Dismisses recents if we are already visible and the intent is to toggle the recents view.
*/
boolean dismissRecentsToFocusedTask() {
@@ -349,6 +364,7 @@
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
+ mHistoryViewStub = (ViewStub) findViewById(R.id.history_view_stub);
mScrimViews = new SystemBarScrimViews(this);
getWindow().getAttributes().privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
@@ -432,6 +448,9 @@
// Reset some states
mIgnoreAltTabRelease = false;
+ if (mHistoryView != null) {
+ EventBus.getDefault().send(new HideHistoryEvent(false /* animate */));
+ }
// Notify that recents is now hidden
SystemServicesProxy ssp = Recents.getSystemServices();
@@ -550,8 +569,9 @@
@Override
public void onBackPressed() {
- // Dismiss Recents to the focused Task or Home
- dismissRecentsToFocusedTaskOrHome();
+ if (!dismissHistory()) {
+ dismissRecentsToFocusedTaskOrHome();
+ }
}
/**** RecentsResizeTaskDialog ****/
@@ -566,7 +586,9 @@
/**** EventBus events ****/
public final void onBusEvent(ToggleRecentsEvent event) {
- dismissRecentsToFocusedTaskOrHome();
+ if (!dismissHistory()) {
+ dismissRecentsToFocusedTaskOrHome();
+ }
}
public final void onBusEvent(IterateRecentsEvent event) {
@@ -596,7 +618,21 @@
}
} else if (event.triggeredFromHomeKey) {
// Otherwise, dismiss Recents to Home
- dismissRecentsToHome(true /* animated */);
+ if (mHistoryView != null && mHistoryView.isVisible()) {
+ ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
+ t.increment();
+ t.addLastDecrementRunnable(new Runnable() {
+ @Override
+ public void run() {
+ dismissRecentsToHome(true /* animated */);
+ }
+ });
+ EventBus.getDefault().send(new HideHistoryEvent(true, t));
+ t.decrement();
+
+ } else {
+ dismissRecentsToHome(true /* animated */);
+ }
} else {
// Do nothing
}
@@ -726,6 +762,23 @@
mIgnoreAltTabRelease = true;
}
+ public final void onBusEvent(ShowHistoryEvent event) {
+ if (mHistoryView == null) {
+ mHistoryView = (RecentsHistoryView) mHistoryViewStub.inflate();
+ // Since this history view is inflated by a view stub after the insets have already
+ // been applied, we have to set them ourselves initial from the insets that were last
+ // provided.
+ mHistoryView.setSystemInsets(mRecentsView.getSystemInsets());
+ }
+ mHistoryView.show(mRecentsView.getTaskStack());
+ }
+
+ public final void onBusEvent(HideHistoryEvent event) {
+ if (mHistoryView != null) {
+ mHistoryView.hide(event.animate, event.postAnimationTrigger);
+ }
+ }
+
private void refreshSearchWidgetView() {
if (mSearchWidgetInfo != null) {
SystemServicesProxy ssp = Recents.getSystemServices();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index 67d7115..e8b8816 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -29,6 +29,7 @@
private static final String KEY_FAST_TOGGLE = "overview_fast_toggle";
private static final String KEY_PAGE_ON_TOGGLE = "overview_page_on_toggle";
private static final String KEY_FULLSCREEN_THUMBNAILS = "overview_fullscreen_thumbnails";
+ private static final String KEY_SHOW_HISTORY = "overview_show_history";
public static class Static {
// Enables debug drawing for the transition thumbnail
@@ -52,6 +53,7 @@
private boolean mFastToggleRecents;
private boolean mPageOnToggle;
private boolean mUseFullscreenThumbnails;
+ private boolean mShowHistory;
/**
* We read the prefs once when we start the activity, then update them as the tuner changes
@@ -61,7 +63,7 @@
// Register all our flags, this will also call onTuningChanged() for each key, which will
// initialize the current state of each flag
TunerService.get(context).addTunable(this, KEY_FAST_TOGGLE, KEY_PAGE_ON_TOGGLE,
- KEY_FULLSCREEN_THUMBNAILS);
+ KEY_FULLSCREEN_THUMBNAILS, KEY_SHOW_HISTORY);
}
/**
@@ -85,6 +87,13 @@
return mUseFullscreenThumbnails;
}
+ /**
+ * @return whether we should show the history
+ */
+ public boolean isHistoryEnabled() {
+ return mShowHistory;
+ }
+
@Override
public void onTuningChanged(String key, String newValue) {
switch (key) {
@@ -100,6 +109,10 @@
mUseFullscreenThumbnails = (newValue != null) &&
(Integer.parseInt(newValue) != 0);
break;
+ case KEY_SHOW_HISTORY:
+ mShowHistory = (newValue != null) &&
+ (Integer.parseInt(newValue) != 0);
+ break;
}
EventBus.getDefault().send(new DebugFlagsChangedEvent());
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index ee57863..0f82cce 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -381,7 +381,7 @@
sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
TaskStack stack = sInstanceLoadPlan.getTaskStack();
- if (stack.getTaskCount() > 0) {
+ if (stack.getStackTaskCount() > 0) {
// We try and draw the thumbnail transition bitmap in parallel before
// toggle/show recents is called
preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView);
@@ -415,7 +415,7 @@
TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
- if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
+ if (focusedStack == null || focusedStack.getStackTaskCount() == 0) return;
ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
// Return early if there is no running task
@@ -423,7 +423,7 @@
// Find the task in the recents list
boolean isTopTaskHome = SystemServicesProxy.isHomeStack(runningTask.stackId);
- ArrayList<Task> tasks = focusedStack.getTasks();
+ ArrayList<Task> tasks = focusedStack.getStackTasks();
Task toTask = null;
ActivityOptions launchOpts = null;
int taskCount = tasks.size();
@@ -467,7 +467,7 @@
TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
- if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
+ if (focusedStack == null || focusedStack.getStackTaskCount() == 0) return;
ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
// Return early if there is no running task (can't determine affiliated tasks in this case)
@@ -476,7 +476,7 @@
if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
// Find the task in the recents list
- ArrayList<Task> tasks = focusedStack.getTasks();
+ ArrayList<Task> tasks = focusedStack.getStackTasks();
Task toTask = null;
ActivityOptions launchOpts = null;
int taskCount = tasks.size();
@@ -685,10 +685,10 @@
if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
stackView.getScroller().setStackScrollToInitialState();
- ArrayList<Task> tasks = stack.getTasks();
+ ArrayList<Task> tasks = stack.getStackTasks();
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
- if (SystemServicesProxy.isFreeformStack(task.key.stackId)) {
+ if (task.isFreeformTask()) {
mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
stackView.getScroller().getStackScroll(), mTmpTransform, null);
Rect toTaskRect = new Rect();
@@ -741,7 +741,7 @@
TaskStackView stackView, int runningTaskId, Task runningTaskOut) {
// Find the running task in the TaskStack
Task task = null;
- ArrayList<Task> tasks = stack.getTasks();
+ ArrayList<Task> tasks = stack.getStackTasks();
if (runningTaskId != -1) {
// Otherwise, try and find the task with the
int taskCount = tasks.size();
@@ -827,7 +827,7 @@
return;
}
- boolean hasRecentTasks = stack.getTaskCount() > 0;
+ boolean hasRecentTasks = stack.getStackTaskCount() > 0;
boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
if (useThumbnailTransition) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryButtonEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryButtonEvent.java
new file mode 100644
index 0000000..6c767e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryButtonEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when the history view button should be hidden.
+ */
+public class HideHistoryButtonEvent extends EventBus.Event {
+ // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryEvent.java
new file mode 100644
index 0000000..34c35a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryEvent.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.misc.ReferenceCountedTrigger;
+
+/**
+ * This is sent when the history view will be closed.
+ */
+public class HideHistoryEvent extends EventBus.Event {
+
+ public final boolean animate;
+ public final ReferenceCountedTrigger postAnimationTrigger;
+
+ public HideHistoryEvent(boolean animate) {
+ this(animate, null);
+ }
+
+ public HideHistoryEvent(boolean animate, ReferenceCountedTrigger postAnimationTrigger) {
+ this.animate = animate;
+ this.postAnimationTrigger = postAnimationTrigger;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryButtonEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryButtonEvent.java
new file mode 100644
index 0000000..7042537
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryButtonEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when the history view button should be shown.
+ */
+public class ShowHistoryButtonEvent extends EventBus.Event {
+ // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryEvent.java
new file mode 100644
index 0000000..870119d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when the history view button is clicked.
+ */
+public class ShowHistoryEvent extends EventBus.Event {
+ // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
new file mode 100644
index 0000000..913d427
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.history;
+
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v7.widget.RecyclerView;
+import android.text.format.DateFormat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+
+/**
+ * An adapter for the list of recent tasks in the history view.
+ */
+public class RecentsHistoryAdapter extends RecyclerView.Adapter<RecentsHistoryAdapter.ViewHolder> {
+
+ private static final String TAG = "RecentsHistoryView";
+ private static final boolean DEBUG = false;
+
+ static final int DATE_ROW_VIEW_TYPE = 0;
+ static final int TASK_ROW_VIEW_TYPE = 1;
+
+ /**
+ * View holder implementation.
+ */
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ public View mContent;
+
+ public ViewHolder(View v) {
+ super(v);
+ mContent = v;
+ }
+ }
+
+ /**
+ * A single row of content.
+ */
+ private interface Row {
+ int getViewType();
+ }
+
+ /**
+ * A date row.
+ */
+ private static class DateRow implements Row {
+
+ public final String date;
+
+ public DateRow(String date) {
+ this.date = date;
+ }
+
+ @Override
+ public int getViewType() {
+ return RecentsHistoryAdapter.DATE_ROW_VIEW_TYPE;
+ }
+ }
+
+ /**
+ * A task row.
+ */
+ private static class TaskRow implements Row, View.OnClickListener {
+
+ public final String description;
+ private final int mTaskId;
+
+ public TaskRow(Task task) {
+ mTaskId = task.key.id;
+ description = task.activityLabel;
+ }
+
+ @Override
+ public void onClick(View v) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.startActivityFromRecents(v.getContext(), mTaskId, description,
+ ActivityOptions.makeBasic());
+ }
+
+ @Override
+ public int getViewType() {
+ return RecentsHistoryAdapter.TASK_ROW_VIEW_TYPE;
+ }
+ }
+
+ private LayoutInflater mLayoutInflater;
+ private final List<Row> mRows = new ArrayList<>();
+
+ public RecentsHistoryAdapter(Context context) {
+ mLayoutInflater = LayoutInflater.from(context);
+ }
+
+ /**
+ * Updates this adapter with the given tasks.
+ */
+ public void updateTasks(Context context, List<Task> tasks) {
+ final Locale l = context.getResources().getConfiguration().locale;
+ final String dateFormatStr = DateFormat.getBestDateTimePattern(l, "EEEEMMMMd");
+ final List<Task> tasksMostRecent = new ArrayList<>(tasks);
+ Collections.reverse(tasksMostRecent);
+ int prevDayKey = -1;
+ mRows.clear();
+ for (Task task : tasksMostRecent) {
+ if (task.isFreeformTask()) {
+ continue;
+ }
+
+ Calendar cal = Calendar.getInstance(l);
+ cal.setTimeInMillis(task.key.lastActiveTime);
+ int dayKey = Objects.hash(cal.get(Calendar.YEAR), cal.get(Calendar.DAY_OF_YEAR));
+ if (dayKey != prevDayKey) {
+ prevDayKey = dayKey;
+ mRows.add(new DateRow(DateFormat.format(dateFormatStr, cal).toString()));
+ }
+ mRows.add(new TaskRow(task));
+ }
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case DATE_ROW_VIEW_TYPE:
+ return new ViewHolder(mLayoutInflater.inflate(R.layout.recents_history_date, parent,
+ false));
+ case TASK_ROW_VIEW_TYPE:
+ return new ViewHolder(mLayoutInflater.inflate(R.layout.recents_history_task, parent,
+ false));
+ default:
+ return new ViewHolder(null);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ Row row = mRows.get(position);
+ int viewType = mRows.get(position).getViewType();
+ switch (viewType) {
+ case DATE_ROW_VIEW_TYPE: {
+ TextView tv = (TextView) holder.mContent;
+ tv.setText(((DateRow) row).date);
+ break;
+ }
+ case TASK_ROW_VIEW_TYPE: {
+ TextView tv = (TextView) holder.mContent;
+ TaskRow taskRow = (TaskRow) row;
+ tv.setText(taskRow.description);
+ tv.setOnClickListener(taskRow);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mRows.size();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mRows.get(position).getViewType();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
new file mode 100644
index 0000000..f48883f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.history;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.LinearLayout;
+
+import com.android.systemui.R;
+import com.android.systemui.recents.misc.ReferenceCountedTrigger;
+import com.android.systemui.recents.model.TaskStack;
+
+/**
+ * A list of the recent tasks that are not in the stack.
+ */
+public class RecentsHistoryView extends LinearLayout {
+
+ private static final String TAG = "RecentsHistoryView";
+ private static final boolean DEBUG = false;
+
+ private RecyclerView mRecyclerView;
+ private RecentsHistoryAdapter mAdapter;
+ private boolean mIsVisible;
+
+ private Interpolator mFastOutSlowInInterpolator;
+ private Interpolator mFastOutLinearInInterpolator;
+ private int mHistoryTransitionDuration;
+
+ public RecentsHistoryView(Context context) {
+ super(context);
+ }
+
+ public RecentsHistoryView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RecentsHistoryView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public RecentsHistoryView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ Resources res = context.getResources();
+ mAdapter = new RecentsHistoryAdapter(context);
+ mHistoryTransitionDuration = res.getInteger(R.integer.recents_history_transition_duration);
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ com.android.internal.R.interpolator.fast_out_slow_in);
+ mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
+ com.android.internal.R.interpolator.fast_out_linear_in);
+ }
+
+ /**
+ * Updates this history view with the recent tasks, and then shows it.
+ */
+ public void show(TaskStack stack) {
+ setVisibility(View.VISIBLE);
+ setAlpha(0f);
+ animate()
+ .alpha(1f)
+ .setDuration(mHistoryTransitionDuration)
+ .setInterpolator(mFastOutSlowInInterpolator)
+ .withLayer()
+ .start();
+
+ mAdapter.updateTasks(getContext(), stack.computeAllTasksList());
+ mIsVisible = true;
+ }
+
+ /**
+ * Hides this history view.
+ */
+ public void hide(boolean animate, final ReferenceCountedTrigger postAnimationTrigger) {
+ if (animate) {
+ animate()
+ .alpha(0f)
+ .setDuration(mHistoryTransitionDuration)
+ .setInterpolator(mFastOutLinearInInterpolator)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ setVisibility(View.INVISIBLE);
+ if (postAnimationTrigger != null) {
+ postAnimationTrigger.decrement();
+ }
+ }
+ })
+ .withLayer()
+ .start();
+ if (postAnimationTrigger != null) {
+ postAnimationTrigger.increment();
+ }
+ } else {
+ setAlpha(0f);
+ setVisibility(View.INVISIBLE);
+ }
+ mIsVisible = false;
+ }
+
+ /**
+ * Updates the system insets of this history view to the provided values.
+ */
+ public void setSystemInsets(Rect systemInsets) {
+ setPadding(systemInsets.left, systemInsets.top, systemInsets.right, systemInsets.bottom);
+ }
+
+ /**
+ * Returns whether this view is visible.
+ */
+ public boolean isVisible() {
+ return mIsVisible;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mRecyclerView = (RecyclerView) findViewById(R.id.list);
+ mRecyclerView.setAdapter(mAdapter);
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ setSystemInsets(insets.getSystemWindowInsets());
+ return insets;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 7b04493..5921d13 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -23,8 +23,10 @@
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.util.Log;
+import com.android.systemui.Prefs;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.misc.SystemServicesProxy;
import java.util.ArrayList;
@@ -45,7 +47,8 @@
private static String TAG = "RecentsTaskLoadPlan";
private static boolean DEBUG = false;
- private static int INVALID_TASK_ID = -1;
+ private static int MIN_NUM_TASKS = 5;
+ private static int SESSION_BEGIN_TIME = 60 /* s/min */ * 60 /* min/hr */ * 6 /* hrs */;
/** The set of conditions to load tasks. */
public static class Options {
@@ -99,6 +102,7 @@
public synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
if (DEBUG) Log.d(TAG, "preloadPlan");
+ RecentsDebugFlags debugFlags = Recents.getDebugFlags();
RecentsConfiguration config = Recents.getConfiguration();
SystemServicesProxy ssp = Recents.getSystemServices();
Resources res = mContext.getResources();
@@ -107,6 +111,10 @@
if (mRawTasks == null) {
preloadRawTasks(isTopTaskHome);
}
+
+ long lastStackActiveTime = Prefs.getLong(mContext,
+ Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, 0);
+ long newLastStackActiveTime = -1;
int taskCount = mRawTasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
@@ -115,13 +123,27 @@
Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
t.userId, t.firstActiveTime, t.lastActiveTime);
+ // This task is only shown in the stack if it statisfies the historical time or min
+ // number of tasks constraints. Freeform tasks are also always shown.
+ boolean isStackTask = true;
+ if (debugFlags.isHistoryEnabled()) {
+ boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
+ isStackTask = !isFreeformTask && (!isHistoricalTask(t) ||
+ (t.lastActiveTime >= lastStackActiveTime &&
+ i >= (taskCount - MIN_NUM_TASKS)));
+ if (isStackTask && newLastStackActiveTime < 0) {
+ newLastStackActiveTime = t.lastActiveTime;
+ }
+ }
+
// Load the label, icon, and color
String activityLabel = loader.getAndUpdateActivityLabel(taskKey, t.taskDescription,
ssp);
String contentDescription = loader.getAndUpdateContentDescription(taskKey,
activityLabel, ssp, res);
- Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, ssp,
- res, false);
+ Drawable activityIcon = isStackTask
+ ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, ssp, res, false)
+ : null;
int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
Bitmap icon = t.taskDescription != null
@@ -132,7 +154,7 @@
// Add the task to the stack
Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel,
contentDescription, activityIcon, activityColor, (i == (taskCount - 1)),
- config.lockToAppEnabled, icon, iconFilename, t.bounds);
+ config.lockToAppEnabled, !isStackTask, icon, iconFilename, t.bounds);
task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false);
if (DEBUG) {
Log.d(TAG, activityLabel + " bounds: " + t.bounds);
@@ -144,6 +166,10 @@
stackTasks.add(task);
}
}
+ if (debugFlags.isHistoryEnabled() && newLastStackActiveTime != -1) {
+ Prefs.putLong(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME,
+ newLastStackActiveTime);
+ }
// Initialize the stacks
ArrayList<Task> allTasks = new ArrayList<>();
@@ -168,7 +194,7 @@
Resources res = mContext.getResources();
// Iterate through each of the tasks and load them according to the load conditions.
- ArrayList<Task> tasks = mStack.getTasks();
+ ArrayList<Task> tasks = mStack.getStackTasks();
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
@@ -214,8 +240,15 @@
/** Returns whether there are any tasks in any stacks. */
public boolean hasTasks() {
if (mStack != null) {
- return mStack.getTaskCount() > 0;
+ return mStack.getStackTaskCount() > 0;
}
return false;
}
+
+ /**
+ * Returns whether this task is considered a task to be shown in the history.
+ */
+ private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) {
+ return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 67e18f3..60bedae 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -105,6 +105,7 @@
public Bitmap thumbnail;
public boolean lockToThisTask;
public boolean lockToTaskEnabled;
+ public boolean isHistorical;
public Bitmap icon;
public String iconFilename;
public Rect bounds;
@@ -117,8 +118,8 @@
public Task(TaskKey key, int taskAffiliation, int taskAffiliationColor,
String activityTitle, String contentDescription, Drawable activityIcon,
- int colorPrimary, boolean lockToThisTask, boolean lockToTaskEnabled, Bitmap icon,
- String iconFilename, Rect bounds) {
+ int colorPrimary, boolean lockToThisTask, boolean lockToTaskEnabled,
+ boolean isHistorical, Bitmap icon, String iconFilename, Rect bounds) {
boolean isInAffiliationGroup = (taskAffiliation != key.id);
boolean hasAffiliationGroupColor = isInAffiliationGroup && (taskAffiliationColor != 0);
this.key = key;
@@ -132,6 +133,7 @@
Color.WHITE) > 3f;
this.lockToThisTask = lockToTaskEnabled && lockToThisTask;
this.lockToTaskEnabled = lockToTaskEnabled;
+ this.isHistorical = isHistorical;
this.icon = icon;
this.iconFilename = iconFilename;
this.bounds = bounds;
@@ -149,6 +151,10 @@
this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
this.lockToThisTask = o.lockToThisTask;
this.lockToTaskEnabled = o.lockToTaskEnabled;
+ this.isHistorical = o.isHistorical;
+ this.icon = o.icon;
+ this.iconFilename = o.iconFilename;
+ this.bounds = o.bounds;
}
/** Set the callbacks */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 0970252..cd0a46f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -198,14 +198,14 @@
/** Task stack callbacks */
public interface TaskStackCallbacks {
- /* Notifies when a task has been added to the stack */
- void onStackTaskAdded(TaskStack stack, Task t);
/* Notifies when a task has been removed from the stack */
void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
Task newFrontMostTask);
}
-
+ /**
+ * The various possible dock states when dragging and dropping a task.
+ */
public enum DockState implements DropTarget {
NONE(-1, 96, null, null),
LEFT(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, 192,
@@ -287,10 +287,19 @@
}
}
+ // A comparator that sorts tasks by their last active time
+ private Comparator<Task> LAST_ACTIVE_TIME_COMPARATOR = new Comparator<Task>() {
+ @Override
+ public int compare(Task o1, Task o2) {
+ return Long.compare(o1.key.lastActiveTime, o2.key.lastActiveTime);
+ }
+ };
+
// The task offset to apply to a task id as a group affiliation
static final int IndividualTaskIdOffset = 1 << 16;
- FilteredTaskList mTaskList = new FilteredTaskList();
+ FilteredTaskList mStackTaskList = new FilteredTaskList();
+ FilteredTaskList mHistoryTaskList = new FilteredTaskList();
TaskStackCallbacks mCb;
ArrayList<TaskGrouping> mGroups = new ArrayList<>();
@@ -298,10 +307,16 @@
public TaskStack() {
// Ensure that we only show non-docked tasks
- mTaskList.setFilter(new TaskFilter() {
+ mStackTaskList.setFilter(new TaskFilter() {
@Override
public boolean acceptTask(Task t, int index) {
- return !SystemServicesProxy.isDockedStack(t.key.stackId);
+ return !t.isHistorical && !SystemServicesProxy.isDockedStack(t.key.stackId);
+ }
+ });
+ mHistoryTaskList.setFilter(new TaskFilter() {
+ @Override
+ public boolean acceptTask(Task t, int index) {
+ return t.isHistorical && !SystemServicesProxy.isDockedStack(t.key.stackId);
}
});
}
@@ -314,29 +329,22 @@
/** Resets this TaskStack. */
public void reset() {
mCb = null;
- mTaskList.reset();
+ mStackTaskList.reset();
+ mHistoryTaskList.reset();
mGroups.clear();
mAffinitiesGroups.clear();
}
- /** Adds a new task */
- public void addTask(Task t) {
- mTaskList.add(t);
- if (mCb != null) {
- mCb.onStackTaskAdded(this, t);
- }
- }
-
/**
* Moves the given task to either the front of the freeform workspace or the stack.
*/
public void moveTaskToStack(Task task, int newStackId) {
// Find the index to insert into
- ArrayList<Task> taskList = mTaskList.getTasks();
+ ArrayList<Task> taskList = mStackTaskList.getTasks();
int taskCount = taskList.size();
if (!task.isFreeformTask() && (newStackId == FREEFORM_WORKSPACE_STACK_ID)) {
// Insert freeform tasks at the front
- mTaskList.moveTaskToStack(task, taskCount, newStackId);
+ mStackTaskList.moveTaskToStack(task, taskCount, newStackId);
} else if (task.isFreeformTask() && (newStackId == FULLSCREEN_WORKSPACE_STACK_ID)) {
// Insert after the first stacked task
int insertIndex = 0;
@@ -346,14 +354,14 @@
break;
}
}
- mTaskList.moveTaskToStack(task, insertIndex, newStackId);
+ mStackTaskList.moveTaskToStack(task, insertIndex, newStackId);
}
}
/** Does the actual work associated with removing the task. */
- void removeTaskImpl(Task t) {
+ void removeTaskImpl(FilteredTaskList taskList, Task t) {
// Remove the task from the list
- mTaskList.remove(t);
+ taskList.remove(t);
// Remove it from the group as well, and if it is empty, remove the group
TaskGrouping group = t.group;
group.removeTask(t);
@@ -366,10 +374,10 @@
/** Removes a task */
public void removeTask(Task t) {
- if (mTaskList.contains(t)) {
- boolean wasFrontMostTask = (getFrontMostTask() == t);
- removeTaskImpl(t);
- Task newFrontMostTask = getFrontMostTask();
+ if (mStackTaskList.contains(t)) {
+ boolean wasFrontMostTask = (getStackFrontMostTask() == t);
+ removeTaskImpl(mStackTaskList, t);
+ Task newFrontMostTask = getStackFrontMostTask();
if (newFrontMostTask != null && newFrontMostTask.lockToTaskEnabled) {
newFrontMostTask.lockToThisTask = true;
}
@@ -377,61 +385,88 @@
// Notify that a task has been removed
mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask);
}
- }
- }
-
- /** Sets a few tasks in one go */
- public void setTasks(List<Task> tasks) {
- ArrayList<Task> taskList = mTaskList.getTasks();
- int taskCount = taskList.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- Task t = taskList.get(i);
- removeTaskImpl(t);
+ } else if (mHistoryTaskList.contains(t)) {
+ removeTaskImpl(mHistoryTaskList, t);
if (mCb != null) {
// Notify that a task has been removed
mCb.onStackTaskRemoved(this, t, false, null);
}
}
- mTaskList.set(tasks);
- for (Task t : tasks) {
- if (mCb != null) {
- mCb.onStackTaskAdded(this, t);
+ }
+
+ /**
+ * Sets a few tasks in one go, without calling any callbacks.
+ */
+ public void setTasks(List<Task> tasks) {
+ ArrayList<Task> stackTasks = new ArrayList<>();
+ ArrayList<Task> historyTasks = new ArrayList<>();
+ for (Task task : tasks) {
+ if (task.isHistorical) {
+ historyTasks.add(task);
+ } else {
+ stackTasks.add(task);
}
}
+ mStackTaskList.set(stackTasks);
+ mHistoryTaskList.set(historyTasks);
}
/** Gets the front task */
- public Task getFrontMostTask() {
- if (mTaskList.size() == 0) return null;
- return mTaskList.getTasks().get(mTaskList.size() - 1);
+ public Task getStackFrontMostTask() {
+ if (mStackTaskList.size() == 0) return null;
+ return mStackTaskList.getTasks().get(mStackTaskList.size() - 1);
}
/** Gets the task keys */
public ArrayList<Task.TaskKey> getTaskKeys() {
ArrayList<Task.TaskKey> taskKeys = new ArrayList<>();
- ArrayList<Task> tasks = mTaskList.getTasks();
+ ArrayList<Task> tasks = computeAllTasksList();
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
- taskKeys.add(tasks.get(i).key);
+ Task task = tasks.get(i);
+ taskKeys.add(task.key);
}
return taskKeys;
}
- /** Gets the tasks */
- public ArrayList<Task> getTasks() {
- return mTaskList.getTasks();
- }
-
- /** Gets the number of tasks */
- public int getTaskCount() {
- return mTaskList.size();
+ /**
+ * Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
+ */
+ public ArrayList<Task> getStackTasks() {
+ return mStackTaskList.getTasks();
}
/**
- * Returns the task in this stack which is the launch target.
+ * Returns the set of tasks that are inactive. These tasks will be presented in a separate
+ * history view.
+ */
+ public ArrayList<Task> getHistoricalTasks() {
+ return mHistoryTaskList.getTasks();
+ }
+
+ /**
+ * Computes a set of all the active and historical tasks ordered by their last active time.
+ */
+ public ArrayList<Task> computeAllTasksList() {
+ ArrayList<Task> tasks = new ArrayList<>();
+ tasks.addAll(mStackTaskList.getTasks());
+ tasks.addAll(mHistoryTaskList.getTasks());
+ Collections.sort(tasks, LAST_ACTIVE_TIME_COMPARATOR);
+ return tasks;
+ }
+
+ /**
+ * Returns the number of tasks in the active stack.
+ */
+ public int getStackTaskCount() {
+ return mStackTaskList.size();
+ }
+
+ /**
+ * Returns the task in stack tasks which is the launch target.
*/
public Task getLaunchTarget() {
- ArrayList<Task> tasks = mTaskList.getTasks();
+ ArrayList<Task> tasks = mStackTaskList.getTasks();
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
Task task = tasks.get(i);
@@ -443,16 +478,14 @@
}
/** Returns the index of this task in this current task stack */
- public int indexOfTask(Task t) {
- return mTaskList.indexOf(t);
+ public int indexOfStackTask(Task t) {
+ return mStackTaskList.indexOf(t);
}
/** Finds the task with the specified task id. */
public Task findTaskWithId(int taskId) {
- ArrayList<Task> tasks = mTaskList.getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
+ ArrayList<Task> tasks = computeAllTasksList();
+ for (Task task : tasks) {
if (task.key.id == taskId) {
return task;
}
@@ -460,21 +493,6 @@
return null;
}
- /**
- * Returns whether this stack has freeform tasks.
- */
- public boolean hasFreeformTasks() {
- ArrayList<Task> tasks = mTaskList.getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- if (task.isFreeformTask()) {
- return true;
- }
- }
- return false;
- }
-
/******** Grouping ********/
/** Adds a group to the set */
@@ -500,7 +518,7 @@
if (RecentsDebugFlags.Static.EnableSimulatedTaskGroups) {
HashMap<Task.TaskKey, Task> taskMap = new HashMap<Task.TaskKey, Task>();
// Sort all tasks by increasing firstActiveTime of the task
- ArrayList<Task> tasks = mTaskList.getTasks();
+ ArrayList<Task> tasks = mStackTaskList.getTasks();
Collections.sort(tasks, new Comparator<Task>() {
@Override
public int compare(Task task, Task task2) {
@@ -560,11 +578,11 @@
taskIndex++;
}
}
- mTaskList.set(tasks);
+ mStackTaskList.set(tasks);
} else {
// Create the task groups
HashMap<Task.TaskKey, Task> tasksMap = new HashMap<Task.TaskKey, Task>();
- ArrayList<Task> tasks = mTaskList.getTasks();
+ ArrayList<Task> tasks = mStackTaskList.getTasks();
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
Task t = tasks.get(i);
@@ -636,8 +654,12 @@
@Override
public String toString() {
- String str = "Tasks:\n";
- for (Task t : mTaskList.getTasks()) {
+ String str = "Stack Tasks:\n";
+ for (Task t : mStackTaskList.getTasks()) {
+ str += " " + t.toString() + "\n";
+ }
+ str += "Historical Tasks:\n";
+ for (Task t : mHistoryTaskList.getTasks()) {
str += " " + t.toString() + "\n";
}
return str;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
index 70370ec..945fdc1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -16,27 +16,53 @@
package com.android.systemui.recents.views;
+import android.animation.ObjectAnimator;
import android.graphics.Outline;
import android.graphics.Rect;
+import android.util.IntProperty;
+import android.util.Property;
import android.view.View;
import android.view.ViewOutlineProvider;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsConfiguration;
/* An outline provider that has a clip and outline that can be animated. */
public class AnimateableViewBounds extends ViewOutlineProvider {
- TaskView mSourceView;
+ View mSourceView;
Rect mClipRect = new Rect();
Rect mClipBounds = new Rect();
int mCornerRadius;
float mAlpha = 1f;
final float mMinAlpha = 0.25f;
- public AnimateableViewBounds(TaskView source, int cornerRadius) {
+ public static final Property<AnimateableViewBounds, Integer> CLIP_BOTTOM =
+ new IntProperty<AnimateableViewBounds>("clipBottom") {
+ @Override
+ public void setValue(AnimateableViewBounds object, int clip) {
+ object.setClipBottom(clip, false /* force */);
+ }
+
+ @Override
+ public Integer get(AnimateableViewBounds object) {
+ return object.getClipBottom();
+ }
+ };
+
+ public static final Property<AnimateableViewBounds, Integer> CLIP_RIGHT =
+ new IntProperty<AnimateableViewBounds>("clipRight") {
+ @Override
+ public void setValue(AnimateableViewBounds object, int clip) {
+ object.setClipRight(clip, false /* force */);
+ }
+
+ @Override
+ public Integer get(AnimateableViewBounds object) {
+ return object.getClipRight();
+ }
+ };
+
+ public AnimateableViewBounds(View source, int cornerRadius) {
mSourceView = source;
mCornerRadius = cornerRadius;
- setClipBottom(getClipBottom(), false /* force */);
}
@Override
@@ -56,18 +82,21 @@
}
}
+ /**
+ * Animates the bottom clip.
+ */
+ public void animateClipBottom(int bottom) {
+ ObjectAnimator animator = ObjectAnimator.ofInt(this, CLIP_BOTTOM, getClipBottom(), bottom);
+ animator.setDuration(150);
+ animator.start();
+ }
+
/** Sets the bottom clip. */
public void setClipBottom(int bottom, boolean force) {
if (bottom != mClipRect.bottom || force) {
mClipRect.bottom = bottom;
mSourceView.invalidateOutline();
updateClipBounds();
-
- RecentsConfiguration config = Recents.getConfiguration();
- if (!config.useHardwareLayers) {
- mSourceView.mThumbnailView.updateThumbnailVisibility(
- bottom - mSourceView.getPaddingBottom());
- }
}
}
@@ -76,6 +105,20 @@
return mClipRect.bottom;
}
+ /** Sets the right clip. */
+ public void setClipRight(int right, boolean force) {
+ if (right != mClipRect.right || force) {
+ mClipRect.right = right;
+ mSourceView.invalidateOutline();
+ updateClipBounds();
+ }
+ }
+
+ /** Returns the right clip. */
+ public int getClipRight() {
+ return mClipRect.right;
+ }
+
private void updateClipBounds() {
mClipBounds.set(mClipRect.left, mClipRect.top,
mSourceView.getWidth() - mClipRect.right,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
index 765f686..90d62c1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -110,7 +110,7 @@
transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = true;
- transformOut.p = 0;
+ transformOut.p = 1f;
if (DEBUG) {
Log.d(TAG, "getTransform: " + task.key + ", " + transformOut);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index d7eb099..736020f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -160,9 +160,9 @@
if (ssp.startActivityFromRecents(mContext, task.key.id, task.activityLabel, opts)) {
// Keep track of the index of the task launch
int taskIndexFromFront = 0;
- int taskIndex = stack.indexOfTask(task);
+ int taskIndex = stack.indexOfStackTask(task);
if (taskIndex > -1) {
- taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
+ taskIndexFromFront = stack.getStackTaskCount() - taskIndex - 1;
}
EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
} else {
@@ -275,7 +275,7 @@
// Otherwise, for freeform tasks, create a new animation spec for each task we have to
// launch
TaskStack stack = stackView.getStack();
- ArrayList<Task> tasks = stack.getTasks();
+ ArrayList<Task> tasks = stack.getStackTasks();
int taskCount = tasks.size();
for (int i = taskCount - 1; i >= 0; i--) {
Task t = tasks.get(i);
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 90456c0..0557f65 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.views;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -25,20 +26,26 @@
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.View;
import android.view.WindowInsets;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
-import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsAppWidgetHostView;
import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
+import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
+import com.android.systemui.recents.events.activity.HideHistoryButtonEvent;
+import com.android.systemui.recents.events.activity.HideHistoryEvent;
+import com.android.systemui.recents.events.activity.ShowHistoryButtonEvent;
+import com.android.systemui.recents.events.activity.ShowHistoryEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
@@ -63,11 +70,12 @@
private static final String TAG = "RecentsView";
private static final boolean DEBUG = false;
- LayoutInflater mInflater;
Handler mHandler;
+ TaskStack mStack;
TaskStackView mTaskStackView;
RecentsAppWidgetHostView mSearchBar;
+ View mHistoryButton;
boolean mAwaitingFirstLayout = true;
boolean mLastTaskLaunchedWasFreeform;
@@ -81,7 +89,9 @@
TaskStack.DockState.BOTTOM,
};
- Interpolator mFastOutSlowInInterpolator;
+ private Interpolator mFastOutSlowInInterpolator;
+ private Interpolator mFastOutLinearInInterpolator;
+ private int mHistoryTransitionDuration;
Rect mSystemInsets = new Rect();
@@ -99,18 +109,31 @@
public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ Resources res = context.getResources();
setWillNotDraw(false);
- mInflater = LayoutInflater.from(context);
mHandler = new Handler();
mTransitionHelper = new RecentsTransitionHelper(getContext(), mHandler);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.fast_out_slow_in);
+ mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
+ com.android.internal.R.interpolator.fast_out_linear_in);
+ mHistoryTransitionDuration = res.getInteger(R.integer.recents_history_transition_duration);
mTouchHandler = new RecentsViewTouchHandler(this);
+
+ LayoutInflater inflater = LayoutInflater.from(context);
+ mHistoryButton = inflater.inflate(R.layout.recents_history_button, this, false);
+ mHistoryButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ EventBus.getDefault().send(new ShowHistoryEvent());
+ }
+ });
}
/** Set/get the bsp root node */
public void setTaskStack(TaskStack stack) {
RecentsConfiguration config = Recents.getConfiguration();
+ mStack = stack;
if (config.getLaunchState().launchedReuseTaskStackViews) {
if (mTaskStackView != null) {
// If onRecentsHidden is not triggered, we need to the stack view again here
@@ -129,6 +152,11 @@
mTaskStackView.setCallbacks(this);
addView(mTaskStackView);
}
+ if (indexOfChild(mHistoryButton) == -1) {
+ addView(mHistoryButton);
+ } else {
+ mHistoryButton.bringToFront();
+ }
// Trigger a new layout
requestLayout();
@@ -141,13 +169,20 @@
return mLastTaskLaunchedWasFreeform;
}
+ /**
+ * Returns the currently set task stack.
+ */
+ public TaskStack getTaskStack() {
+ return mStack;
+ }
+
/** Gets the next task in the stack - or if the last - the top task */
public Task getNextTaskOrTopTask(Task taskToSearch) {
Task returnTask = null;
boolean found = false;
if (mTaskStackView != null) {
TaskStack stack = mTaskStackView.getStack();
- ArrayList<Task> taskList = stack.getTasks();
+ ArrayList<Task> taskList = stack.getStackTasks();
// Iterate the stack views and try and find the focused task
for (int j = taskList.size() - 1; j >= 0; --j) {
Task task = taskList.get(j);
@@ -219,10 +254,15 @@
}
ctx.postAnimationTrigger.decrement();
+ // Hide the history button
+ int taskViewExitToHomeDuration = getResources().getInteger(
+ R.integer.recents_task_exit_to_home_duration);
+ hideHistoryButton(taskViewExitToHomeDuration);
+
// If we are going home, cancel the previous task's window transition
EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
- // Notify of the exit animation
+ // Notify sof the exit animation
EventBus.getDefault().send(new DismissRecentsToHomeAnimationStarted());
}
@@ -253,6 +293,13 @@
}
}
+ /**
+ * Returns the last known system insets.
+ */
+ public Rect getSystemInsets() {
+ return mSystemInsets;
+ }
+
@Override
protected void onAttachedToWindow() {
EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
@@ -301,6 +348,13 @@
MeasureSpec.makeMeasureSpec(taskRect.height(), MeasureSpec.AT_MOST));
}
+ // Measure the history button with the full space above the stack, but width-constrained
+ // to the stack
+ Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
+ measureChild(mHistoryButton,
+ MeasureSpec.makeMeasureSpec(historyButtonRect.width(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(historyButtonRect.height(),
+ MeasureSpec.EXACTLY));
setMeasuredDimension(width, height);
}
@@ -330,6 +384,12 @@
top + mDragView.getMeasuredHeight());
}
+ // Layout the history button left-aligned with the stack, but offset from the top of the
+ // view
+ Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
+ mHistoryButton.layout(historyButtonRect.left, historyButtonRect.top,
+ historyButtonRect.right, historyButtonRect.bottom);
+
if (mAwaitingFirstLayout) {
mAwaitingFirstLayout = false;
@@ -346,7 +406,7 @@
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mSystemInsets.set(insets.getSystemWindowInsets());
requestLayout();
- return insets.consumeSystemWindowInsets();
+ return insets;
}
@Override
@@ -393,7 +453,7 @@
public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
final TaskStack stack, final Task task, final boolean lockToTask,
final Rect bounds, int destinationStack) {
- mLastTaskLaunchedWasFreeform = SystemServicesProxy.isFreeformStack(task.key.stackId);
+ mLastTaskLaunchedWasFreeform = task.isFreeformTask();
mTransitionHelper.launchTaskFromRecents(stack, task, stackView, tv, lockToTask, bounds,
destinationStack);
}
@@ -492,6 +552,67 @@
animate().translationY(0f);
}
+ public final void onBusEvent(ShowHistoryEvent event) {
+ // Hide the history button when the history view is shown
+ hideHistoryButton(mHistoryTransitionDuration);
+ }
+
+ public final void onBusEvent(HideHistoryEvent event) {
+ // Show the history button when the history view is hidden
+ showHistoryButton(mHistoryTransitionDuration);
+ }
+
+ public final void onBusEvent(ShowHistoryButtonEvent event) {
+ showHistoryButton(150);
+ }
+
+ public final void onBusEvent(HideHistoryButtonEvent event) {
+ hideHistoryButton(100);
+ }
+
+ public final void onBusEvent(DebugFlagsChangedEvent event) {
+ RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+ if (!debugFlags.isHistoryEnabled()) {
+ hideHistoryButton(100);
+ }
+ }
+
+ /**
+ * Shows the history button.
+ */
+ private void showHistoryButton(int duration) {
+ RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+ if (!debugFlags.isHistoryEnabled()) {
+ return;
+ }
+
+ mHistoryButton.setVisibility(View.VISIBLE);
+ mHistoryButton.animate()
+ .alpha(1f)
+ .setDuration(duration)
+ .setInterpolator(mFastOutSlowInInterpolator)
+ .withLayer()
+ .start();
+ }
+
+ /**
+ * Hides the history button.
+ */
+ private void hideHistoryButton(int duration) {
+ mHistoryButton.animate()
+ .alpha(0f)
+ .setDuration(duration)
+ .setInterpolator(mFastOutLinearInInterpolator)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mHistoryButton.setVisibility(View.INVISIBLE);
+ }
+ })
+ .withLayer()
+ .start();
+ }
+
/**
* Updates the dock region to match the specified dock state.
*/
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 5d091b1..6af2ada 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -162,6 +162,8 @@
public Rect mCurrentStackRect = new Rect();
// This is the current system insets
public Rect mSystemInsets = new Rect();
+ // This is the bounds of the history button above the stack rect
+ public Rect mHistoryButtonRect = new Rect();
// The visible ranges when the stack is focused and unfocused
private Range mUnfocusedRange;
@@ -170,7 +172,12 @@
// The offset from the top when scrolled to the top of the stack
private int mFocusedPeekHeight;
- // The offset from the bottom of the stack to the bottom of the bounds
+ // The offset from the top of the stack to the top of the bounds when the stack is scrolled to
+ // the end
+ private int mStackTopOffset;
+
+ // The offset from the bottom of the stack to the bottom of the bounds when the stack is
+ // scrolled to the front
private int mStackBottomOffset;
// The paths defining the motion of the tasks when the stack is focused and unfocused
@@ -211,6 +218,7 @@
FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
public TaskStackLayoutAlgorithm(Context context, TaskStackView stackView) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
Resources res = context.getResources();
mStackView = stackView;
@@ -276,6 +284,7 @@
// The freeform height is the visible height (not including system insets) - padding above
// freeform and below stack - gap between the freeform and stack
+ mStackTopOffset = mFocusedPeekHeight + heightPadding;
mStackBottomOffset = mSystemInsets.bottom + heightPadding;
int ffHeight = (taskStackBounds.height() - 2 * heightPadding - mStackBottomOffset) / 2;
mFreeformRect.set(taskStackBounds.left + widthPadding,
@@ -290,15 +299,20 @@
taskStackBounds.top + heightPadding,
taskStackBounds.right - widthPadding,
taskStackBounds.bottom);
+ mCurrentStackRect = ssp.hasFreeformWorkspaceSupport() ? mFreeformStackRect : mStackRect;
+ mHistoryButtonRect.set(mCurrentStackRect.left, mCurrentStackRect.top - heightPadding,
+ mCurrentStackRect.right, mCurrentStackRect.top + mFocusedPeekHeight);
// Anchor the task rect to the top-center of the non-freeform stack rect
float aspect = (float) (taskStackBounds.width() - mSystemInsets.left - mSystemInsets.right)
/ (taskStackBounds.height() - mSystemInsets.bottom);
int width = mStackRect.width();
- int height = debugFlags.isFullscreenThumbnailsEnabled() ? (int) (width / aspect) : width;
+ int minHeight = mCurrentStackRect.height() - mFocusedPeekHeight - mStackBottomOffset;
+ int height = debugFlags.isFullscreenThumbnailsEnabled()
+ ? (int) Math.min(width / aspect, minHeight)
+ : width;
mTaskRect.set(mStackRect.left, mStackRect.top,
mStackRect.left + width, mStackRect.top + height);
- mCurrentStackRect = ssp.hasFreeformWorkspaceSupport() ? mFreeformStackRect : mStackRect;
// Short circuit here if the stack rects haven't changed so we don't do all the work below
if (lastStackRect.equals(mCurrentStackRect)) {
@@ -333,7 +347,7 @@
mTaskIndexMap.clear();
// Return early if we have no tasks
- ArrayList<Task> tasks = stack.getTasks();
+ ArrayList<Task> tasks = stack.getStackTasks();
if (tasks.isEmpty()) {
mFrontMostTaskP = 0;
mMinScrollP = mMaxScrollP = 0;
@@ -573,7 +587,7 @@
(mCurrentStackRect.height() - mTaskRect.height()) / 2;
y = centerYOffset + getYForDeltaP(p, 0);
z = mMaxTranslationZ;
- relP = p;
+ relP = 1f;
} else {
// Otherwise, update the task to the stack layout
@@ -638,17 +652,13 @@
* Creates a new path for the focused curve.
*/
private Path constructFocusedCurve() {
- SystemServicesProxy ssp = Recents.getSystemServices();
int taskBarHeight = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_task_bar_height);
// Initialize the focused curve. This curve is a piecewise curve composed of several
// quadradic beziers that goes from (0,1) through (0.5, peek height offset),
// (0.667, next task offset), (0.833, bottom task offset), and (1,0).
- float peekHeightPct = 0f;
- if (!ssp.hasFreeformWorkspaceSupport()) {
- peekHeightPct = (float) mFocusedPeekHeight / mCurrentStackRect.height();
- }
+ float peekHeightPct = (float) mFocusedPeekHeight / mCurrentStackRect.height();
Path p = new Path();
p.moveTo(0f, 1f);
p.lineTo(0.5f, 1f - peekHeightPct);
@@ -662,8 +672,6 @@
* Creates a new path for the unfocused curve.
*/
private Path constructUnfocusedCurve() {
- SystemServicesProxy ssp = Recents.getSystemServices();
-
// Initialize the unfocused curve. This curve is a piecewise curve composed of two quadradic
// beziers that goes from (0,1) through (0.5, peek height offset) and ends at (1,0). This
// ensures that we match the range, at which 0.5 represents the stack scroll at the current
@@ -672,10 +680,7 @@
// there is a tangent at (0.5, peek height offset).
float cpoint1X = 0.4f;
float cpoint1Y = 1f;
- float peekHeightPct = 0f;
- if (!ssp.hasFreeformWorkspaceSupport()) {
- peekHeightPct = (float) mFocusedPeekHeight / mCurrentStackRect.height();
- }
+ float peekHeightPct = (float) mFocusedPeekHeight / mCurrentStackRect.height();
float slope = ((1f - peekHeightPct) - cpoint1Y) / (0.5f - cpoint1X);
float b = 1f - slope * cpoint1X;
float cpoint2X = 0.75f;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 67710bf..30efd5f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -42,8 +42,12 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
+import com.android.systemui.recents.events.activity.HideHistoryButtonEvent;
+import com.android.systemui.recents.events.activity.HideHistoryEvent;
import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
+import com.android.systemui.recents.events.activity.ShowHistoryButtonEvent;
+import com.android.systemui.recents.events.activity.ShowHistoryEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
@@ -82,6 +86,10 @@
private final static String TAG = "TaskStackView";
private final static boolean DEBUG = false;
+ // The thresholds at which to show/hide the history button.
+ private static final float SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
+ private static final float HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
+
/** The TaskView callbacks */
interface TaskStackViewCallbacks {
public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
@@ -404,7 +412,7 @@
boolean synchronizeStackViewsWithModel() {
if (mStackViewsDirty) {
// Get all the task transforms
- ArrayList<Task> tasks = mStack.getTasks();
+ ArrayList<Task> tasks = mStack.getStackTasks();
float stackScroll = mStackScroller.getStackScroll();
int[] visibleStackRange = mTmpVisibleRange;
boolean isValidVisibleStackRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
@@ -424,7 +432,7 @@
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
- int taskIndex = mStack.indexOfTask(task);
+ int taskIndex = mStack.indexOfStackTask(task);
if (task.isFreeformTask() ||
visibleStackRange[1] <= taskIndex && taskIndex <= visibleStackRange[0]) {
mTmpTaskViewMap.put(task, tv);
@@ -443,7 +451,7 @@
// Pick up all the freeform tasks
int firstVisStackIndex = isValidVisibleStackRange ? visibleStackRange[0] : 0;
- for (int i = mStack.getTaskCount() - 1; i >= firstVisStackIndex; i--) {
+ for (int i = mStack.getStackTaskCount() - 1; i >= firstVisStackIndex; i--) {
Task task = tasks.get(i);
if (!task.isFreeformTask()) {
continue;
@@ -468,12 +476,12 @@
// Reattach it in the right z order
detachViewFromParent(tv);
int insertIndex = -1;
- int taskIndex = mStack.indexOfTask(task);
+ int taskIndex = mStack.indexOfStackTask(task);
taskViews = getTaskViews();
taskViewCount = taskViews.size();
for (int j = 0; j < taskViewCount; j++) {
Task tvTask = taskViews.get(j).getTask();
- if (taskIndex < mStack.indexOfTask(tvTask)) {
+ if (taskIndex < mStack.indexOfStackTask(tvTask)) {
insertIndex = j;
break;
}
@@ -543,6 +551,8 @@
* Updates the clip for each of the task views from back to front.
*/
void clipTaskViews(boolean forceUpdate) {
+ RecentsConfiguration config = Recents.getConfiguration();
+
// Update the clip on each task child
List<TaskView> taskViews = getTaskViews();
TaskView tmpTv = null;
@@ -580,6 +590,9 @@
}
}
tv.getViewBounds().setClipBottom(clipBottom, forceUpdate);
+ if (!config.useHardwareLayers) {
+ tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom());
+ }
}
mStackViewsClipDirty = false;
}
@@ -625,14 +638,14 @@
private boolean setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated,
final boolean requestViewFocus) {
// Find the next task to focus
- int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
- Math.max(0, Math.min(mStack.getTaskCount() - 1, taskIndex)) : -1;
+ int newFocusedTaskIndex = mStack.getStackTaskCount() > 0 ?
+ Math.max(0, Math.min(mStack.getStackTaskCount() - 1, taskIndex)) : -1;
final Task newFocusedTask = (newFocusedTaskIndex != -1) ?
- mStack.getTasks().get(newFocusedTaskIndex) : null;
+ mStack.getStackTasks().get(newFocusedTaskIndex) : null;
// Reset the last focused task state if changed
if (mFocusedTaskIndex != -1) {
- Task focusedTask = mStack.getTasks().get(mFocusedTaskIndex);
+ Task focusedTask = mStack.getStackTasks().get(mFocusedTaskIndex);
if (focusedTask != newFocusedTask) {
resetFocusedTask();
}
@@ -713,14 +726,14 @@
int newIndex = -1;
if (mFocusedTaskIndex != -1) {
if (stackTasksOnly) {
- List<Task> tasks = mStack.getTasks();
+ List<Task> tasks = mStack.getStackTasks();
newIndex = mFocusedTaskIndex;
Task task = tasks.get(mFocusedTaskIndex);
if (task.isFreeformTask()) {
// Try and focus the front most stack task
TaskView tv = getFrontMostTaskView(stackTasksOnly);
if (tv != null) {
- newIndex = mStack.indexOfTask(tv.getTask());
+ newIndex = mStack.indexOfStackTask(tv.getTask());
}
} else {
// Try the next task if it is a stack task
@@ -735,14 +748,14 @@
} else {
// No restrictions, lets just move to the new task (looping forward/backwards if
// necessary)
- int taskCount = mStack.getTaskCount();
+ int taskCount = mStack.getStackTaskCount();
newIndex = (mFocusedTaskIndex + (forward ? -1 : 1) + taskCount) % taskCount;
}
} else {
// We don't have a focused task, so focus the first visible task view
TaskView tv = getFrontMostTaskView(stackTasksOnly);
if (tv != null) {
- newIndex = mStack.indexOfTask(tv.getTask());
+ newIndex = mStack.indexOfStackTask(tv.getTask());
}
}
if (newIndex != -1) {
@@ -760,7 +773,7 @@
*/
void resetFocusedTask() {
if (mFocusedTaskIndex != -1) {
- Task t = mStack.getTasks().get(mFocusedTaskIndex);
+ Task t = mStack.getStackTasks().get(mFocusedTaskIndex);
TaskView tv = getChildViewForTask(t);
if (tv != null) {
tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */);
@@ -774,7 +787,7 @@
*/
Task getFocusedTask() {
if (mFocusedTaskIndex != -1) {
- return mStack.getTasks().get(mFocusedTaskIndex);
+ return mStack.getStackTasks().get(mFocusedTaskIndex);
}
return null;
}
@@ -787,11 +800,11 @@
if (taskViewCount > 0) {
TaskView backMostTask = taskViews.get(0);
TaskView frontMostTask = taskViews.get(taskViewCount - 1);
- event.setFromIndex(mStack.indexOfTask(backMostTask.getTask()));
- event.setToIndex(mStack.indexOfTask(frontMostTask.getTask()));
+ event.setFromIndex(mStack.indexOfStackTask(backMostTask.getTask()));
+ event.setToIndex(mStack.indexOfStackTask(frontMostTask.getTask()));
event.setContentDescription(frontMostTask.getTask().activityLabel);
}
- event.setItemCount(mStack.getTaskCount());
+ event.setItemCount(mStack.getStackTaskCount());
event.setScrollY(mStackScroller.mScroller.getCurrY());
event.setMaxScrollY(mStackScroller.progressToScrollRange(mLayoutAlgorithm.mMaxScrollP));
}
@@ -806,7 +819,7 @@
if (mFocusedTaskIndex > 0) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
}
- if (mFocusedTaskIndex < mStack.getTaskCount() - 1) {
+ if (mFocusedTaskIndex < mStack.getStackTaskCount() - 1) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}
}
@@ -883,7 +896,7 @@
* updateLayoutForStack() is called first.
*/
public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
- return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
+ return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getStackTasks());
}
public void setTaskStackBounds(Rect taskStackBounds, Rect systemInsets) {
@@ -1011,12 +1024,17 @@
// until after the enter-animation
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
- int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
+ int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getStackTaskCount());
if (focusedTaskIndex != -1) {
setFocusedTask(focusedTaskIndex, false /* scrollToTask */, false /* animated */,
false /* requestViewFocus */);
}
+ // Update the history button visibility
+ if (mStackScroller.getStackScroll() < SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD) {
+ EventBus.getDefault().send(new ShowHistoryButtonEvent());
+ }
+
// Start dozing
mUIDozeTrigger.startDozing();
}
@@ -1030,7 +1048,7 @@
return;
}
- if (mStack.getTaskCount() > 0) {
+ if (mStack.getStackTaskCount() > 0) {
// Find the launch target task
Task launchTargetTask = mStack.getLaunchTarget();
List<TaskView> taskViews = getTaskViews();
@@ -1139,7 +1157,7 @@
* Launches the freeform tasks.
*/
public boolean launchFreeformTasks() {
- Task frontTask = mStack.getFrontMostTask();
+ Task frontTask = mStack.getStackFrontMostTask();
if (frontTask != null && frontTask.isFreeformTask()) {
onTaskViewClicked(getChildViewForTask(frontTask), frontTask, false);
return true;
@@ -1150,11 +1168,6 @@
/**** TaskStackCallbacks Implementation ****/
@Override
- public void onStackTaskAdded(TaskStack stack, Task t) {
- requestSynchronizeStackViewsWithModel();
- }
-
- @Override
public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
Task newFrontMostTask) {
if (!removedTask.isFreeformTask()) {
@@ -1169,9 +1182,9 @@
// front most task will be our anchor task)
Task anchorTask = null;
float prevAnchorTaskScroll = 0;
- boolean pullStackForward = stack.getTaskCount() > 0;
+ boolean pullStackForward = stack.getStackTaskCount() > 0;
if (pullStackForward) {
- anchorTask = mStack.getFrontMostTask();
+ anchorTask = mStack.getStackFrontMostTask();
prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
}
@@ -1224,8 +1237,8 @@
}
// If there are no remaining tasks, then just close recents
- if (mStack.getTaskCount() == 0) {
- boolean shouldFinishActivity = (mStack.getTaskCount() == 0);
+ if (mStack.getStackTaskCount() == 0) {
+ boolean shouldFinishActivity = (mStack.getStackTaskCount() == 0);
if (shouldFinishActivity) {
EventBus.getDefault().send(new AllTaskViewsDismissedEvent());
}
@@ -1275,13 +1288,13 @@
// Find the index where this task should be placed in the stack
int insertIndex = -1;
- int taskIndex = mStack.indexOfTask(task);
+ int taskIndex = mStack.indexOfStackTask(task);
if (taskIndex != -1) {
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
for (int i = 0; i < taskViewCount; i++) {
Task tvTask = taskViews.get(i).getTask();
- if (taskIndex < mStack.indexOfTask(tvTask)) {
+ if (taskIndex < mStack.indexOfStackTask(tvTask)) {
insertIndex = i;
break;
}
@@ -1333,10 +1346,18 @@
/**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
@Override
- public void onScrollChanged(float p) {
+ public void onScrollChanged(float prevScroll, float curScroll) {
mUIDozeTrigger.poke();
requestSynchronizeStackViewsWithModel();
postInvalidateOnAnimation();
+
+ if (prevScroll > SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD &&
+ curScroll <= SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD) {
+ EventBus.getDefault().send(new ShowHistoryButtonEvent());
+ } else if (prevScroll < HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD &&
+ curScroll >= HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD) {
+ EventBus.getDefault().send(new HideHistoryButtonEvent());
+ }
}
/**** EventBus Events ****/
@@ -1347,7 +1368,7 @@
event.packageName, event.userId);
// For other tasks, just remove them directly if they no longer exist
- ArrayList<Task> tasks = mStack.getTasks();
+ ArrayList<Task> tasks = mStack.getStackTasks();
for (int i = tasks.size() - 1; i >= 0; i--) {
final Task t = tasks.get(i);
if (removedComponents.contains(t.key.getComponent())) {
@@ -1382,7 +1403,7 @@
public final void onBusEvent(DismissFocusedTaskViewEvent event) {
if (mFocusedTaskIndex != -1) {
- Task t = mStack.getTasks().get(mFocusedTaskIndex);
+ Task t = mStack.getStackTasks().get(mFocusedTaskIndex);
TaskView tv = getChildViewForTask(t);
tv.dismissTask();
}
@@ -1496,15 +1517,30 @@
}
}
+ public final void onBusEvent(ShowHistoryEvent event) {
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
+ tv.animate().alpha(0f).setDuration(200).start();
+ }
+ }
+
+ public final void onBusEvent(HideHistoryEvent event) {
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
+ tv.animate().alpha(1f).setDuration(200).start();
+ }
+ }
+
/**
* Removes the task from the stack, and updates the focus to the next task in the stack if the
* removed TaskView was focused.
*/
private void removeTaskViewFromStack(TaskView tv) {
- SystemServicesProxy ssp = Recents.getSystemServices();
Task task = tv.getTask();
- int taskIndex = mStack.indexOfTask(task);
- boolean taskWasFocused = tv.isFocusedTask();
// Reset the previously focused task before it is removed from the stack
resetFocusedTask();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 62b640e..90b73fe 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -35,7 +35,7 @@
private static final boolean DEBUG = false;
public interface TaskStackViewScrollerCallbacks {
- void onScrollChanged(float p);
+ void onScrollChanged(float prevScroll, float curScroll);
}
Context mContext;
@@ -78,9 +78,10 @@
/** Sets the current stack scroll */
public void setStackScroll(float s) {
+ float prevStackScroll = mStackScrollP;
mStackScrollP = s;
if (mCb != null) {
- mCb.onScrollChanged(mStackScrollP);
+ mCb.onScrollChanged(prevStackScroll, mStackScrollP);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 523f84f..6db2eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -71,7 +71,7 @@
ObjectAnimator mTaskProgressAnimator;
float mMaxDimScale;
int mDimAlpha;
- AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(1f);
+ AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator(3f);
PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
Paint mDimLayerPaint = new Paint();
float mActionButtonTranslationZ;
@@ -265,15 +265,6 @@
toTransform.translationZ = 0;
}
- /**
- * When we are un/filtering, this method will setup the transform that we are animating from,
- * in order to show the task.
- */
- void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) {
- // Fade the view in
- fromTransform.alpha = 0f;
- }
-
/** Prepares this task view for the enter-recents animations. This is called earlier in the
* first layout because the actual animation into recents may take a long time. */
void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, boolean hideTask,
@@ -617,12 +608,11 @@
/** Compute the dim as a function of the scale of this view. */
int getDimFromTaskProgress() {
- // TODO: Temporarily disable the dim on the stack
- /*
- float dim = mMaxDimScale * mDimInterpolator.getInterpolation(1f - mTaskProgress);
+ float x = mTaskProgress < 0
+ ? 1f
+ : mDimInterpolator.getInterpolation(1f - mTaskProgress);
+ float dim = mMaxDimScale * x;
return (int) (dim * 255);
- */
- return 0;
}
/** Update the dim as a function of the scale of this view. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index bc50846..b3d263e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -45,6 +45,8 @@
*/
public class TaskViewThumbnail extends View {
+ private Task mTask;
+
// Drawing
int mCornerRadius;
float mDimAlpha;
@@ -52,6 +54,7 @@
Paint mDrawPaint = new Paint();
RectF mBitmapRect = new RectF();
RectF mLayoutRect = new RectF();
+ float mBitmapScale = 1f;
BitmapShader mBitmapShader;
LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0);
@@ -165,7 +168,16 @@
/** Updates the thumbnail shader's scale transform. */
void updateThumbnailScale() {
if (mBitmapShader != null) {
- mScaleMatrix.setRectToRect(mBitmapRect, mLayoutRect, Matrix.ScaleToFit.FILL);
+ if (mTask.isFreeformTask()) {
+ // For freeform tasks, we scale the bitmap rect to fit in the layout rect
+ mBitmapScale = Math.min(mLayoutRect.width() / mBitmapRect.width(),
+ mLayoutRect.height() / mBitmapRect.height());
+ } else {
+ // For stack tasks, we scale the bitmap to fit the width
+ mBitmapScale = Math.max(1f, mLayoutRect.width() / mBitmapRect.width());
+ }
+
+ mScaleMatrix.setScale(mBitmapScale, mBitmapScale);
mBitmapShader.setLocalMatrix(mScaleMatrix);
}
}
@@ -202,6 +214,7 @@
/** Binds the thumbnail view to the task */
void rebindToTask(Task t) {
+ mTask = t;
if (t.thumbnail != null) {
setThumbnail(t.thumbnail);
} else {
@@ -211,6 +224,7 @@
/** Unbinds the thumbnail view from the task */
void unbindFromTask() {
+ mTask = null;
setThumbnail(null);
}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index fdc2543..9cf64d3 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -37,7 +37,10 @@
LOCAL_PACKAGE_NAME := SystemUITests
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target Keyguard
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ mockito-target \
+ Keyguard \
+ android-support-v7-recyclerview
# sign this with platform cert, so this test is allowed to inject key events into
# UI it doesn't own. This is necessary to allow screenshots to be taken
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 5e4f2b2..78a4e35 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -1225,7 +1225,7 @@
void unbindCurrentClientLocked(
/* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
if (mCurClient != null) {
- if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = "
+ if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client="
+ mCurClient.client.asBinder());
if (mBoundToMethod) {
mBoundToMethod = false;
@@ -1290,8 +1290,10 @@
mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
}
- InputBindResult startInputLocked(IInputMethodClient client,
- IInputContext inputContext, EditorInfo attribute, int controlFlags) {
+ InputBindResult startInputLocked(
+ /* @InputMethodClient.StartInputReason */ final int startInputReason,
+ IInputMethodClient client, IInputContext inputContext, EditorInfo attribute,
+ int controlFlags) {
// If no method is currently selected, do nothing.
if (mCurMethodId == null) {
return mNoBinding;
@@ -1320,8 +1322,8 @@
return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
}
- InputBindResult startInputUncheckedLocked(@NonNull ClientState cs,
- IInputContext inputContext, @NonNull EditorInfo attribute, int controlFlags) {
+ InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
+ @NonNull EditorInfo attribute, int controlFlags) {
// If no method is currently selected, do nothing.
if (mCurMethodId == null) {
return mNoBinding;
@@ -1340,7 +1342,7 @@
// If the client is changing, we need to switch over to the new
// one.
unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_CLIENT);
- if (DEBUG) Slog.v(TAG, "switching to client: client = "
+ if (DEBUG) Slog.v(TAG, "switching to client: client="
+ cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
// If the screen is on, inform the new client it is active
@@ -1442,15 +1444,26 @@
}
@Override
- public InputBindResult startInput(IInputMethodClient client,
- IInputContext inputContext, EditorInfo attribute, int controlFlags) {
+ public InputBindResult startInput(
+ /* @InputMethodClient.StartInputReason */ final int startInputReason,
+ IInputMethodClient client, IInputContext inputContext, EditorInfo attribute,
+ int controlFlags) {
if (!calledFromValidUser()) {
return null;
}
synchronized (mMethodMap) {
+ if (DEBUG) {
+ Slog.v(TAG, "startInput: reason="
+ + InputMethodClient.getStartInputReason(startInputReason)
+ + " client = " + client.asBinder()
+ + " inputContext=" + inputContext
+ + " attribute=" + attribute
+ + " controlFlags=#" + Integer.toHexString(controlFlags));
+ }
final long ident = Binder.clearCallingIdentity();
try {
- return startInputLocked(client, inputContext, attribute, controlFlags);
+ return startInputLocked(startInputReason, client, inputContext, attribute,
+ controlFlags);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2151,17 +2164,21 @@
}
@Override
- public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
- int controlFlags, int softInputMode, int windowFlags,
- EditorInfo attribute, IInputContext inputContext) {
+ public InputBindResult windowGainedFocus(
+ /* @InputMethodClient.StartInputReason */ final int startInputReason,
+ IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
+ int windowFlags, EditorInfo attribute, IInputContext inputContext) {
// Needs to check the validity before clearing calling identity
final boolean calledFromValidUser = calledFromValidUser();
-
InputBindResult res = null;
long ident = Binder.clearCallingIdentity();
try {
synchronized (mMethodMap) {
- if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
+ if (DEBUG) Slog.v(TAG, "windowGainedFocus: reason="
+ + InputMethodClient.getStartInputReason(startInputReason)
+ + " client=" + client.asBinder()
+ + " inputContext=" + inputContext
+ + " attribute=" + attribute
+ " controlFlags=#" + Integer.toHexString(controlFlags)
+ " softInputMode=#" + Integer.toHexString(softInputMode)
+ " windowFlags=#" + Integer.toHexString(windowFlags));
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index cd29050..da2c8c5c 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -859,13 +859,11 @@
if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tTaking screenshot");
// When this flag is set, we currently take the fullscreen screenshot of the activity
- // but scaled inversely by the density. This gives us a "good-enough" fullscreen
- // thumbnail to use within SystemUI without using enormous amounts of memory on high
- // density devices.
+ // but scaled to half the size. This gives us a "good-enough" fullscreen thumbnail to
+ // use within SystemUI while keeping memory usage low.
if (mService.mTakeFullscreenScreenshots) {
- Context context = mService.mContext;
w = h = -1;
- scale = (1f / Math.max(1f, context.getResources().getDisplayMetrics().density));
+ scale = 0.5f;
}
return mWindowManager.screenshotApplications(who.appToken, Display.DEFAULT_DISPLAY,
w, h, scale);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ff829ff..424b902 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -24,7 +24,6 @@
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
-import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -216,14 +215,15 @@
private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
/**
- * User restrictions set by {@link DevicePolicyManager} that should be applied to all users,
- * including guests.
+ * User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
+ * that should be applied to all users, including guests.
*/
@GuardedBy("mRestrictionsLock")
private Bundle mDevicePolicyGlobalUserRestrictions;
/**
- * User restrictions set by {@link DevicePolicyManager} for each user.
+ * User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
+ * for each user.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mDevicePolicyLocalUserRestrictions = new SparseArray<>();
@@ -248,6 +248,12 @@
private final LocalService mLocalService;
+ @GuardedBy("mUsersLock")
+ private boolean mIsDeviceManaged;
+
+ @GuardedBy("mUsersLock")
+ private final SparseBooleanArray mIsUserManaged = new SparseBooleanArray();
+
@GuardedBy("mUserRestrictionsListeners")
private final ArrayList<UserRestrictionsListener> mUserRestrictionsListeners =
new ArrayList<>();
@@ -509,11 +515,9 @@
if (!userInfo.isAdmin()) {
return false;
}
+ // restricted profile can be created if there is no DO set and the admin user has no PO;
+ return !mIsDeviceManaged && !mIsUserManaged.get(userId);
}
- DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
- // restricted profile can be created if there is no DO set and the admin user has no PO
- return !dpm.isDeviceManaged() && dpm.getProfileOwnerAsUser(userId) == null;
}
/*
@@ -1596,11 +1600,10 @@
if (UserManager.isSplitSystemUser()
&& !isGuest && !isManagedProfile && getPrimaryUser() == null) {
flags |= UserInfo.FLAG_PRIMARY;
- DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
- mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- if (devicePolicyManager == null
- || !devicePolicyManager.isDeviceManaged()) {
- flags |= UserInfo.FLAG_ADMIN;
+ synchronized (mUsersLock) {
+ if (!mIsDeviceManaged) {
+ flags |= UserInfo.FLAG_ADMIN;
+ }
}
}
userId = getNextAvailableId();
@@ -1865,6 +1868,13 @@
// Remove this user from the list
synchronized (mUsersLock) {
mUsers.remove(userHandle);
+ mIsUserManaged.delete(userHandle);
+ }
+ synchronized (mRestrictionsLock) {
+ mBaseUserRestrictions.remove(userHandle);
+ mAppliedUserRestrictions.remove(userHandle);
+ mCachedEffectiveUserRestrictions.remove(userHandle);
+ mDevicePolicyLocalUserRestrictions.remove(userHandle);
}
// Remove user file
AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
@@ -2376,9 +2386,10 @@
if (user == null) {
continue;
}
+ final int userId = user.id;
pw.print(" "); pw.print(user);
pw.print(" serialNo="); pw.print(user.serialNumber);
- if (mRemovingUserIds.get(mUsers.keyAt(i))) {
+ if (mRemovingUserIds.get(userId)) {
pw.print(" <removing> ");
}
if (user.partial) {
@@ -2403,6 +2414,8 @@
sb.append(" ago");
pw.println(sb);
}
+ pw.print(" Has profile owner: ");
+ pw.println(mIsUserManaged.get(userId));
pw.println(" Restrictions:");
synchronized (mRestrictionsLock) {
UserRestrictionsUtils.dumpRestrictions(
@@ -2427,6 +2440,10 @@
synchronized (mGuestRestrictions) {
UserRestrictionsUtils.dumpRestrictions(pw, " ", mGuestRestrictions);
}
+ synchronized (mUsersLock) {
+ pw.println();
+ pw.println(" Device managed: " + mIsDeviceManaged);
+ }
}
}
@@ -2507,6 +2524,20 @@
mUserRestrictionsListeners.remove(listener);
}
}
+
+ @Override
+ public void setDeviceManaged(boolean isManaged) {
+ synchronized (mUsersLock) {
+ mIsDeviceManaged = isManaged;
+ }
+ }
+
+ @Override
+ public void setUserManaged(int userId, boolean isManaged) {
+ synchronized (mUsersLock) {
+ mIsUserManaged.put(userId, isManaged);
+ }
+ }
}
private class Shell extends ShellCommand {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f80a611..8199250 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1071,7 +1071,7 @@
}
Owners newOwners() {
- return new Owners(mContext);
+ return new Owners(mContext, getUserManager(), getUserManagerInternal());
}
UserManager getUserManager() {
@@ -2150,20 +2150,24 @@
}
private void ensureDeviceOwnerUserStarted() {
- if (mOwners.hasDeviceOwner()) {
- final int userId = mOwners.getDeviceOwnerUserId();
- if (VERBOSE_LOG) {
- Log.v(LOG_TAG, "Starting non-system DO user: " + userId);
+ final int userId;
+ synchronized (this) {
+ if (!mOwners.hasDeviceOwner()) {
+ return;
}
- if (userId != UserHandle.USER_SYSTEM) {
- try {
- mInjector.getIActivityManager().startUserInBackground(userId);
+ userId = mOwners.getDeviceOwnerUserId();
+ }
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG, "Starting non-system DO user: " + userId);
+ }
+ if (userId != UserHandle.USER_SYSTEM) {
+ try {
+ mInjector.getIActivityManager().startUserInBackground(userId);
- // STOPSHIP Prevent the DO user from being killed.
+ // STOPSHIP Prevent the DO user from being killed.
- } catch (RemoteException e) {
- Slog.w(LOG_TAG, "Exception starting user", e);
- }
+ } catch (RemoteException e) {
+ Slog.w(LOG_TAG, "Exception starting user", e);
}
}
}
@@ -4584,7 +4588,7 @@
+ " for device owner");
}
synchronized (this) {
- enforceCanSetDeviceOwner(userId);
+ enforceCanSetDeviceOwnerLocked(userId);
// Shutting down backup manager service permanently.
long ident = mInjector.binderClearCallingIdentity();
@@ -4751,7 +4755,7 @@
+ " not installed for userId:" + userHandle);
}
synchronized (this) {
- enforceCanSetProfileOwner(userHandle);
+ enforceCanSetProfileOwnerLocked(userHandle);
mOwners.setProfileOwner(who, ownerName, userHandle);
mOwners.writeProfileOwner(userHandle);
return true;
@@ -4953,7 +4957,7 @@
* - SYSTEM_UID
* - adb if there are not accounts.
*/
- private void enforceCanSetProfileOwner(int userHandle) {
+ private void enforceCanSetProfileOwnerLocked(int userHandle) {
UserInfo info = mUserManager.getUserInfo(userHandle);
if (info == null) {
// User doesn't exist.
@@ -4995,7 +4999,7 @@
* The device owner can only be set before the setup phase of the primary user has completed,
* except for adb if no accounts or additional users are present on the device.
*/
- private void enforceCanSetDeviceOwner(int userId) {
+ private void enforceCanSetDeviceOwnerLocked(int userId) {
if (mOwners.hasDeviceOwner()) {
throw new IllegalStateException("Trying to set the device owner, but device owner "
+ "is already set.");
@@ -6819,20 +6823,22 @@
public boolean isProvisioningAllowed(String action) {
final int callingUserId = mInjector.userHandleGetCallingUserId();
if (DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE.equals(action)) {
- if (mOwners.hasDeviceOwner()) {
- if (!mInjector.userManagerIsSplitSystemUser()) {
- // Only split-system-user systems support managed-profiles in combination with
- // device-owner.
- return false;
- }
- if (mOwners.getDeviceOwnerUserId() != UserHandle.USER_SYSTEM) {
- // Only system device-owner supports managed-profiles. Non-system device-owner
- // doesn't.
- return false;
- }
- if (callingUserId == UserHandle.USER_SYSTEM) {
- // Managed-profiles cannot be setup on the system user, only regular users.
- return false;
+ synchronized (this) {
+ if (mOwners.hasDeviceOwner()) {
+ if (!mInjector.userManagerIsSplitSystemUser()) {
+ // Only split-system-user systems support managed-profiles in combination with
+ // device-owner.
+ return false;
+ }
+ if (mOwners.getDeviceOwnerUserId() != UserHandle.USER_SYSTEM) {
+ // Only system device-owner supports managed-profiles. Non-system device-owner
+ // doesn't.
+ return false;
+ }
+ if (callingUserId == UserHandle.USER_SYSTEM) {
+ // Managed-profiles cannot be setup on the system user, only regular users.
+ return false;
+ }
}
}
if (getProfileOwner(callingUserId) != null) {
@@ -6877,8 +6883,10 @@
}
private boolean isDeviceOwnerProvisioningAllowed(int callingUserId) {
- if (mOwners.hasDeviceOwner()) {
- return false;
+ synchronized (this) {
+ if (mOwners.hasDeviceOwner()) {
+ return false;
+ }
}
if (getProfileOwner(callingUserId) != null) {
return false;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 435de7a..f7de0b3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -23,6 +23,7 @@
import android.os.Environment;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManagerInternal;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Log;
@@ -50,6 +51,9 @@
/**
* Stores and restores state for the Device and Profile owners. By definition there can be
* only one device owner, but there may be a profile owner for each user.
+ *
+ * <p>This class is not thread safe. (i.e. access to this class must always be synchronized
+ * in the caller side.)
*/
class Owners {
private static final String TAG = "DevicePolicyManagerService";
@@ -78,8 +82,8 @@
private static final String TAG_SYSTEM_UPDATE_POLICY = "system-update-policy";
- private final Context mContext;
private final UserManager mUserManager;
+ private final UserManagerInternal mUserManagerInternal;
// Internal state for the device owner package.
private OwnerInfo mDeviceOwner;
@@ -92,44 +96,48 @@
// Local system update policy controllable by device owner.
private SystemUpdatePolicy mSystemUpdatePolicy;
- public Owners(Context context) {
- mContext = context;
- mUserManager = context.getSystemService(UserManager.class);
+ public Owners(Context context, UserManager userManager,
+ UserManagerInternal userManagerInternal) {
+ mUserManager = userManager;
+ mUserManagerInternal = userManagerInternal;
}
/**
* Load configuration from the disk.
*/
void load() {
- synchronized (this) {
- // First, try to read from the legacy file.
- final File legacy = getLegacyConfigFileWithTestOverride();
+ // First, try to read from the legacy file.
+ final File legacy = getLegacyConfigFileWithTestOverride();
- if (readLegacyOwnerFile(legacy)) {
- if (DEBUG) {
- Log.d(TAG, "Legacy config file found.");
- }
+ final List<UserInfo> users = mUserManager.getUsers();
- // Legacy file exists, write to new files and remove the legacy one.
- writeDeviceOwner();
- for (int userId : getProfileOwnerKeys()) {
- writeProfileOwner(userId);
- }
- if (DEBUG) {
- Log.d(TAG, "Deleting legacy config file");
- }
- if (!legacy.delete()) {
- Slog.e(TAG, "Failed to remove the legacy setting file");
- }
- } else {
- // No legacy file, read from the new format files.
- new DeviceOwnerReadWriter().readFromFileLocked();
-
- final List<UserInfo> users = mUserManager.getUsers();
- for (UserInfo ui : users) {
- new ProfileOwnerReadWriter(ui.id).readFromFileLocked();
- }
+ if (readLegacyOwnerFile(legacy)) {
+ if (DEBUG) {
+ Log.d(TAG, "Legacy config file found.");
}
+
+ // Legacy file exists, write to new files and remove the legacy one.
+ writeDeviceOwner();
+ for (int userId : getProfileOwnerKeys()) {
+ writeProfileOwner(userId);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Deleting legacy config file");
+ }
+ if (!legacy.delete()) {
+ Slog.e(TAG, "Failed to remove the legacy setting file");
+ }
+ } else {
+ // No legacy file, read from the new format files.
+ new DeviceOwnerReadWriter().readFromFileLocked();
+
+ for (UserInfo ui : users) {
+ new ProfileOwnerReadWriter(ui.id).readFromFileLocked();
+ }
+ }
+ mUserManagerInternal.setDeviceManaged(hasDeviceOwner());
+ for (UserInfo ui : users) {
+ mUserManagerInternal.setUserManaged(ui.id, hasProfileOwner(ui.id));
}
if (hasDeviceOwner() && hasProfileOwner(getDeviceOwnerUserId())) {
Slog.w(TAG, String.format("User %d has both DO and PO, which is not supported",
@@ -169,21 +177,27 @@
boolean userRestrictionsMigrated) {
mDeviceOwner = new OwnerInfo(ownerName, admin, userRestrictionsMigrated);
mDeviceOwnerUserId = userId;
+
+ mUserManagerInternal.setDeviceManaged(true);
}
void clearDeviceOwner() {
mDeviceOwner = null;
mDeviceOwnerUserId = UserHandle.USER_NULL;
+
+ mUserManagerInternal.setDeviceManaged(false);
}
void setProfileOwner(ComponentName admin, String ownerName, int userId) {
// For a newly set PO, there's no need for migration.
mProfileOwners.put(userId, new OwnerInfo(ownerName, admin,
/* userRestrictionsMigrated =*/ true));
+ mUserManagerInternal.setUserManaged(userId, true);
}
void removeProfileOwner(int userId) {
mProfileOwners.remove(userId);
+ mUserManagerInternal.setUserManaged(userId, false);
}
ComponentName getProfileOwnerComponent(int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 56d6fc0..435b602 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -52,7 +52,7 @@
private final File mProfileOwnerBase;
public OwnersTestable(DpmMockContext context) {
- super(context);
+ super(context, context.userManager, context.userManagerInternal);
mLegacyFile = new File(context.dataDir, LEGACY_FILE);
mDeviceOwnerFile = new File(context.dataDir, DEVICE_OWNER_FILE);
mProfileOwnerBase = new File(context.dataDir, PROFILE_OWNER_FILE_BASE);
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
index 8899e53..01c3c50 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
@@ -184,8 +184,10 @@
}
@Override
- public InputBindResult startInput(IInputMethodClient client, IInputContext inputContext,
- EditorInfo attribute, int controlFlags) throws RemoteException {
+ public InputBindResult startInput(
+ /* @InputMethodClient.StartInputReason */ int startInputReason,
+ IInputMethodClient client, IInputContext inputContext, EditorInfo attribute,
+ int controlFlags) throws RemoteException {
// TODO Auto-generated method stub
return null;
}
@@ -226,9 +228,11 @@
}
@Override
- public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
- int controlFlags, int softInputMode, int windowFlags, EditorInfo attribute,
- IInputContext inputContext) throws RemoteException {
+ public InputBindResult windowGainedFocus(
+ /* @InputMethodClient.StartInputReason */ int startInputReason,
+ IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
+ int windowFlags, EditorInfo attribute, IInputContext inputContext)
+ throws RemoteException {
// TODO Auto-generated method stub
return null;
}