Merge "Fixes NPE when creating folder with null suggestedFolderNames." into ub-launcher3-master
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index 7690b9d..3b3dc01 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -16,6 +16,7 @@
package com.android.launcher3.model;
+import android.content.ComponentName;
import android.content.Context;
import android.os.UserHandle;
@@ -68,4 +69,9 @@
public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user,
LauncherAppState app) {
}
+
+ public WidgetItem getWidgetProviderInfoByProviderName(
+ ComponentName providerName) {
+ return null;
+ }
}
\ No newline at end of file
diff --git a/protos/launcher_trace.proto b/protos/launcher_trace.proto
new file mode 100644
index 0000000..c6f3543
--- /dev/null
+++ b/protos/launcher_trace.proto
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+syntax = "proto2";
+
+package com.android.launcher3.tracing;
+
+option java_multiple_files = true;
+
+message LauncherTraceProto {
+
+ optional TouchInteractionServiceProto touch_interaction_service = 1;
+}
+
+message TouchInteractionServiceProto {
+
+ optional bool service_connected = 1;
+}
diff --git a/protos/launcher_trace_file.proto b/protos/launcher_trace_file.proto
new file mode 100644
index 0000000..6ce182a
--- /dev/null
+++ b/protos/launcher_trace_file.proto
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+syntax = "proto2";
+
+import "launcher_trace.proto";
+
+package com.android.launcher3.tracing;
+
+option java_multiple_files = true;
+
+/* represents a file full of launcher trace entries.
+ Encoded, it should start with 0x9 0x4C 0x4E 0x43 0x48 0x52 0x54 0x52 0x43 (.LNCHRTRC), such
+ that they can be easily identified. */
+message LauncherTraceFileProto {
+
+ /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
+ (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
+ constants into .proto files. */
+ enum MagicNumber {
+ INVALID = 0;
+ MAGIC_NUMBER_L = 0x48434E4C; /* LNCH (little-endian ASCII) */
+ MAGIC_NUMBER_H = 0x43525452; /* RTRC (little-endian ASCII) */
+ }
+
+ optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */
+ repeated LauncherTraceEntryProto entry = 2;
+}
+
+/* one launcher trace entry. */
+message LauncherTraceEntryProto {
+ /* required: elapsed realtime in nanos since boot of when this entry was logged */
+ optional fixed64 elapsed_realtime_nanos = 1;
+
+ optional LauncherTraceProto launcher = 3;
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index 7ff8969..3601af2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -428,6 +428,10 @@
// rounding at the end of the animation.
float startRadius = mAppWindowAnimationHelper.getCurrentCornerRadius();
float endRadius = startRect.width() / 6f;
+
+ float startTransformProgress = mTransformParams.getProgress();
+ float endTransformProgress = 1;
+
// We want the window alpha to be 0 once this threshold is met, so that the
// FolderIconView can be seen morphing into the icon shape.
final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
@@ -437,8 +441,10 @@
public void onUpdate(RectF currentRect, float progress) {
homeAnim.setPlayFraction(progress);
- mTransformParams.setProgress(progress)
- .setCurrentRectAndTargetAlpha(currentRect, getWindowAlpha(progress));
+ mTransformParams.setProgress(
+ Utilities.mapRange(progress, startTransformProgress, endTransformProgress))
+ .setCurrentRect(currentRect)
+ .setTargetAlpha(getWindowAlpha(progress));
if (isFloatingIconView) {
mTransformParams.setCornerRadius(endRadius * progress + startRadius
* (1f - progress));
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index bfe5738..8d73591 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -163,7 +163,7 @@
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
List<SurfaceParams> surfaceParamsList = new ArrayList<>();
// Append the surface transform params for the app that's being opened.
- Collections.addAll(surfaceParamsList, inOutHelper.getSurfaceParams(params));
+ Collections.addAll(surfaceParamsList, inOutHelper.computeSurfaceParams(params));
AppWindowAnimationHelper liveTileAnimationHelper =
v.getRecentsView().getClipAnimationHelper();
@@ -173,14 +173,14 @@
v.getRecentsView().getLiveTileParams(true /* mightNeedToRefill */);
if (liveTileParams != null) {
SurfaceParams[] liveTileSurfaceParams =
- liveTileAnimationHelper.getSurfaceParams(liveTileParams);
+ liveTileAnimationHelper.computeSurfaceParams(liveTileParams);
if (liveTileSurfaceParams != null) {
Collections.addAll(surfaceParamsList, liveTileSurfaceParams);
}
}
}
// Apply surface transform using the surface params list.
- AppWindowAnimationHelper.applySurfaceParams(params.syncTransactionApplier,
+ AppWindowAnimationHelper.applySurfaceParams(params.getSyncTransactionApplier(),
surfaceParamsList.toArray(new SurfaceParams[surfaceParamsList.size()]));
// Get the task bounds for the app that's being opened after surface transform
// update.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index a597458..28e8fb6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -28,6 +28,7 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT;
import android.annotation.TargetApi;
@@ -63,6 +64,8 @@
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.tracing.nano.LauncherTraceProto;
+import com.android.launcher3.tracing.nano.TouchInteractionServiceProto;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
@@ -76,6 +79,7 @@
import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AssistantUtilities;
+import com.android.quickstep.util.ProtoTracer;
import com.android.systemui.plugins.OverscrollPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.recents.IOverviewProxy;
@@ -85,6 +89,7 @@
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.RecentsAnimationListener;
+import com.android.systemui.shared.tracing.ProtoTraceable;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -113,7 +118,8 @@
* Service connected by system-UI for handling touch interaction.
*/
@TargetApi(Build.VERSION_CODES.Q)
-public class TouchInteractionService extends Service implements PluginListener<OverscrollPlugin> {
+public class TouchInteractionService extends Service implements PluginListener<OverscrollPlugin>,
+ ProtoTraceable<LauncherTraceProto> {
private static final String TAG = "TouchInteractionService";
@@ -275,6 +281,7 @@
mDeviceState = new RecentsAnimationDeviceState(this);
mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
+ ProtoTracer.INSTANCE.get(this).add(this);
sConnected = true;
}
@@ -381,6 +388,13 @@
SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(
mDeviceState.getSystemUiStateFlags());
mOverviewComponentObserver.onSystemUiStateChanged();
+
+ // Update the tracing state
+ if ((mDeviceState.getSystemUiStateFlags() & SYSUI_STATE_TRACING_ENABLED) != 0) {
+ ProtoTracer.INSTANCE.get(TouchInteractionService.this).start();
+ } else {
+ ProtoTracer.INSTANCE.get(TouchInteractionService.this).stop();
+ }
}
}
@@ -403,6 +417,8 @@
disposeEventHandlers();
mDeviceState.destroy();
SystemUiProxy.INSTANCE.get(this).setProxy(null);
+ ProtoTracer.INSTANCE.get(TouchInteractionService.this).stop();
+ ProtoTracer.INSTANCE.get(this).remove(this);
sConnected = false;
super.onDestroy();
@@ -723,6 +739,9 @@
pw.println(" resumed=" + resumed);
pw.println(" mConsumer=" + mConsumer.getName());
ActiveGestureLog.INSTANCE.dump("", pw);
+ pw.println("ProtoTrace:");
+ pw.println(" file="
+ + ProtoTracer.INSTANCE.get(TouchInteractionService.this).getTraceFile());
}
}
@@ -781,4 +800,13 @@
public void onPluginDisconnected(OverscrollPlugin overscrollPlugin) {
mOverscrollPlugin = null;
}
+
+ @Override
+ public void writeToProto(LauncherTraceProto proto) {
+ if (proto.touchInteractionService == null) {
+ proto.touchInteractionService = new TouchInteractionServiceProto();
+ }
+ proto.touchInteractionService.serviceConnected = true;
+ proto.touchInteractionService.serviceConnected = true;
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
index 9e29238..b71fede 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -60,10 +60,12 @@
private final Rect mSourceStackBounds = new Rect();
// The insets of the source app
private final Rect mSourceInsets = new Rect();
- // The source app bounds with the source insets applied, in the source app window coordinates
+ // The source app bounds with the source insets applied, in the device coordinates
private final RectF mSourceRect = new RectF();
- // The bounds of the task view in launcher window coordinates
+ // The bounds of the task view in device coordinates
private final RectF mTargetRect = new RectF();
+ // The bounds of the app window (between mSourceRect and mTargetRect) in device coordinates
+ private final RectF mCurrentRect = new RectF();
// The insets to be used for clipping the app window, which can be larger than mSourceInsets
// if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In
// app window coordinates.
@@ -71,12 +73,13 @@
// The insets to be used for clipping the app window. For live tile, we don't transform the clip
// relative to the target rect.
private final RectF mSourceWindowClipInsetsForLiveTile = new RectF();
+ // The clip rect in source app window coordinates. The app window surface will only be drawn
+ // within these bounds. This clip rect starts at the full mSourceStackBounds, and insets by
+ // mSourceWindowClipInsets as the transform progress goes to 1.
+ private final RectF mCurrentClipRectF = new RectF();
// The bounds of launcher (not including insets) in device coordinates
public final Rect mHomeStackBounds = new Rect();
-
- // The clip rect in source app window coordinates
- private final RectF mClipRectF = new RectF();
private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
private final Matrix mTmpMatrix = new Matrix();
private final Rect mTmpRect = new Rect();
@@ -156,25 +159,29 @@
}
public RectF applyTransform(TransformParams params) {
- SurfaceParams[] surfaceParams = getSurfaceParams(params);
+ SurfaceParams[] surfaceParams = computeSurfaceParams(params);
if (surfaceParams == null) {
return null;
}
- applySurfaceParams(params.syncTransactionApplier, surfaceParams);
- return params.currentRect;
+ applySurfaceParams(params.mSyncTransactionApplier, surfaceParams);
+ return mCurrentRect;
}
- public SurfaceParams[] getSurfaceParams(TransformParams params) {
- if (params.targetSet == null) {
+ /**
+ * Updates this AppWindowAnimationHelper's state based on the given TransformParams, and returns
+ * the SurfaceParams to apply via {@link SyncRtSurfaceTransactionApplierCompat#applyParams}.
+ */
+ public SurfaceParams[] computeSurfaceParams(TransformParams params) {
+ if (params.mTargetSet == null) {
return null;
}
- float progress = Utilities.boundToRange(params.progress, 0, 1);
+ float progress = Utilities.boundToRange(params.mProgress, 0, 1);
updateCurrentRect(params);
- SurfaceParams[] surfaceParams = new SurfaceParams[params.targetSet.unfilteredApps.length];
- for (int i = 0; i < params.targetSet.unfilteredApps.length; i++) {
- RemoteAnimationTargetCompat app = params.targetSet.unfilteredApps[i];
+ SurfaceParams[] surfaceParams = new SurfaceParams[params.mTargetSet.unfilteredApps.length];
+ for (int i = 0; i < params.mTargetSet.unfilteredApps.length; i++) {
+ RemoteAnimationTargetCompat app = params.mTargetSet.unfilteredApps[i];
mTmpMatrix.setTranslate(app.position.x, app.position.y);
Rect crop = mTmpRect;
crop.set(app.sourceContainerBounds);
@@ -182,17 +189,17 @@
float alpha;
int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
float cornerRadius = 0f;
- float scale = Math.max(params.currentRect.width(), mTargetRect.width()) / crop.width();
- if (app.mode == params.targetSet.targetMode) {
- alpha = mTaskAlphaCallback.getAlpha(app, params.targetAlpha);
+ float scale = Math.max(mCurrentRect.width(), mTargetRect.width()) / crop.width();
+ if (app.mode == params.mTargetSet.targetMode) {
+ alpha = mTaskAlphaCallback.getAlpha(app, params.mTargetAlpha);
if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
- mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL);
+ mTmpMatrix.setRectToRect(mSourceRect, mCurrentRect, ScaleToFit.FILL);
mTmpMatrix.postTranslate(app.position.x, app.position.y);
- mClipRectF.roundOut(crop);
+ mCurrentClipRectF.roundOut(crop);
if (mSupportsRoundedCornersOnWindows) {
- if (params.cornerRadius > -1) {
- cornerRadius = params.cornerRadius;
- scale = params.currentRect.width() / crop.width();
+ if (params.mCornerRadius > -1) {
+ cornerRadius = params.mCornerRadius;
+ scale = mCurrentRect.width() / crop.width();
} else {
float windowCornerRadius = mUseRoundedCornersOnWindows
? mWindowCornerRadius : 0;
@@ -206,14 +213,14 @@
&& app.isNotInRecents) {
alpha = 1 - Interpolators.DEACCEL_2_5.getInterpolation(progress);
}
- } else if (params.targetSet.hasRecents) {
+ } else if (params.mTargetSet.hasRecents) {
// If home has a different target then recents, reverse anim the
// home target.
- alpha = 1 - (progress * params.targetAlpha);
+ alpha = 1 - (progress * params.mTargetAlpha);
}
} else {
alpha = mBaseAlphaCallback.getAlpha(app, progress);
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.launcherOnTop) {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.mLauncherOnTop) {
crop = null;
layer = Integer.MAX_VALUE;
}
@@ -228,31 +235,35 @@
}
public RectF updateCurrentRect(TransformParams params) {
- float progress = params.progress;
- if (params.currentRect == null) {
- RectF currentRect;
+ if (params.mCurrentRect != null) {
+ mCurrentRect.set(params.mCurrentRect);
+ } else {
mTmpRectF.set(mTargetRect);
- Utilities.scaleRectFAboutCenter(mTmpRectF, params.offsetScale);
- currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF);
- currentRect.offset(params.offsetX, 0);
-
- // Don't clip past progress > 1.
- progress = Math.min(1, progress);
- final RectF sourceWindowClipInsets = params.forLiveTile
- ? mSourceWindowClipInsetsForLiveTile : mSourceWindowClipInsets;
- mClipRectF.left = sourceWindowClipInsets.left * progress;
- mClipRectF.top = sourceWindowClipInsets.top * progress;
- mClipRectF.right =
- mSourceStackBounds.width() - (sourceWindowClipInsets.right * progress);
- mClipRectF.bottom =
- mSourceStackBounds.height() - (sourceWindowClipInsets.bottom * progress);
- params.setCurrentRectAndTargetAlpha(currentRect, 1);
+ Utilities.scaleRectFAboutCenter(mTmpRectF, params.mOffsetScale);
+ mCurrentRect.set(mRectFEvaluator.evaluate(params.mProgress, mSourceRect, mTmpRectF));
+ mCurrentRect.offset(params.mOffsetX, 0);
}
- return params.currentRect;
+
+ updateClipRect(params);
+
+ return mCurrentRect;
+ }
+
+ private void updateClipRect(TransformParams params) {
+ // Don't clip past progress > 1.
+ float progress = Math.min(1, params.mProgress);
+ final RectF sourceWindowClipInsets = params.mForLiveTile
+ ? mSourceWindowClipInsetsForLiveTile : mSourceWindowClipInsets;
+ mCurrentClipRectF.left = sourceWindowClipInsets.left * progress;
+ mCurrentClipRectF.top = sourceWindowClipInsets.top * progress;
+ mCurrentClipRectF.right =
+ mSourceStackBounds.width() - (sourceWindowClipInsets.right * progress);
+ mCurrentClipRectF.bottom =
+ mSourceStackBounds.height() - (sourceWindowClipInsets.bottom * progress);
}
public RectF getCurrentRectWithInsets() {
- mTmpMatrix.mapRect(mCurrentRectWithInsets, mClipRectF);
+ mTmpMatrix.mapRect(mCurrentRectWithInsets, mCurrentClipRectF);
return mCurrentRectWithInsets;
}
@@ -384,75 +395,168 @@
}
public static class TransformParams {
- float progress;
- public float offsetX;
- public float offsetScale;
- public @Nullable RectF currentRect;
- float targetAlpha;
- boolean forLiveTile;
- float cornerRadius;
- boolean launcherOnTop;
-
- public RemoteAnimationTargets targetSet;
- public SyncRtSurfaceTransactionApplierCompat syncTransactionApplier;
+ private float mProgress;
+ private float mOffsetX;
+ private float mOffsetScale;
+ private @Nullable RectF mCurrentRect;
+ private float mTargetAlpha;
+ private boolean mForLiveTile;
+ private float mCornerRadius;
+ private boolean mLauncherOnTop;
+ private RemoteAnimationTargets mTargetSet;
+ private SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
public TransformParams() {
- progress = 0;
- offsetX = 0;
- offsetScale = 1;
- currentRect = null;
- targetAlpha = 0;
- forLiveTile = false;
- cornerRadius = -1;
- launcherOnTop = false;
+ mProgress = 0;
+ mOffsetX = 0;
+ mOffsetScale = 1;
+ mCurrentRect = null;
+ mTargetAlpha = 1;
+ mForLiveTile = false;
+ mCornerRadius = -1;
+ mLauncherOnTop = false;
}
+ /**
+ * Sets the progress of the transformation, where 0 is the source and 1 is the target. We
+ * automatically adjust properties such as currentRect and cornerRadius based on this
+ * progress, unless they are manually overridden by setting them on this TransformParams.
+ */
public TransformParams setProgress(float progress) {
- this.progress = progress;
- this.currentRect = null;
+ mProgress = progress;
return this;
}
+ /**
+ * Sets the corner radius of the transformed window, in pixels. If unspecified (-1), we
+ * simply interpolate between the window's corner radius to the task view's corner radius,
+ * based on {@link #mProgress}.
+ */
public TransformParams setCornerRadius(float cornerRadius) {
- this.cornerRadius = cornerRadius;
+ mCornerRadius = cornerRadius;
return this;
}
- public TransformParams setCurrentRectAndTargetAlpha(RectF currentRect, float targetAlpha) {
- this.currentRect = currentRect;
- this.targetAlpha = targetAlpha;
+ /**
+ * Sets the current rect to show the transformed window, in device coordinates. This gives
+ * the caller manual control of where to show the window. If unspecified (null), we
+ * interpolate between {@link AppWindowAnimationHelper#mSourceRect} and
+ * {@link AppWindowAnimationHelper#mTargetRect}, based on {@link #mProgress}.
+ */
+ public TransformParams setCurrentRect(RectF currentRect) {
+ mCurrentRect = currentRect;
return this;
}
+ /**
+ * Specifies the alpha of the transformed window. Default is 1.
+ */
+ public TransformParams setTargetAlpha(float targetAlpha) {
+ mTargetAlpha = targetAlpha;
+ return this;
+ }
+
+ /**
+ * If {@link #mCurrentRect} is null (i.e. {@link #setCurrentRect(RectF)} hasn't overridden
+ * the default), then offset the current rect by this amount after computing the rect based
+ * on {@link #mProgress}.
+ */
public TransformParams setOffsetX(float offsetX) {
- this.offsetX = offsetX;
+ mOffsetX = offsetX;
return this;
}
+ /**
+ * If {@link #mCurrentRect} is null (i.e. {@link #setCurrentRect(RectF)} hasn't overridden
+ * the default), then scale the current rect by this amount after computing the rect based
+ * on {@link #mProgress}.
+ */
public TransformParams setOffsetScale(float offsetScale) {
- this.offsetScale = offsetScale;
+ mOffsetScale = offsetScale;
return this;
}
+ /**
+ * Specifies whether we should clip the source window based on
+ * {@link AppWindowAnimationHelper#mSourceWindowClipInsetsForLiveTile} rather than
+ * {@link AppWindowAnimationHelper#mSourceWindowClipInsets} as {@link #mProgress} goes to 1.
+ */
public TransformParams setForLiveTile(boolean forLiveTile) {
- this.forLiveTile = forLiveTile;
+ mForLiveTile = forLiveTile;
return this;
}
+ /**
+ * If true, sets the crop = null and layer = Integer.MAX_VALUE for targets that don't match
+ * {@link #mTargetSet}.targetMode. (Currently only does this when live tiles are enabled.)
+ */
public TransformParams setLauncherOnTop(boolean launcherOnTop) {
- this.launcherOnTop = launcherOnTop;
+ mLauncherOnTop = launcherOnTop;
return this;
}
+ /**
+ * Specifies the set of RemoteAnimationTargetCompats that are included in the transformation
+ * that these TransformParams help compute. These TransformParams generally only apply to
+ * the targetSet.apps which match the targetSet.targetMode (e.g. the MODE_CLOSING app when
+ * swiping to home).
+ */
public TransformParams setTargetSet(RemoteAnimationTargets targetSet) {
- this.targetSet = targetSet;
+ mTargetSet = targetSet;
return this;
}
+ /**
+ * Sets the SyncRtSurfaceTransactionApplierCompat that will apply the SurfaceParams that
+ * are computed based on these TransformParams.
+ */
public TransformParams setSyncTransactionApplier(
SyncRtSurfaceTransactionApplierCompat applier) {
- this.syncTransactionApplier = applier;
+ mSyncTransactionApplier = applier;
return this;
}
+
+ // Pubic getters so outside packages can read the values.
+
+ public float getProgress() {
+ return mProgress;
+ }
+
+ public float getOffsetX() {
+ return mOffsetX;
+ }
+
+ public float getOffsetScale() {
+ return mOffsetScale;
+ }
+
+ @Nullable
+ public RectF getCurrentRect() {
+ return mCurrentRect;
+ }
+
+ public float getTargetAlpha() {
+ return mTargetAlpha;
+ }
+
+ public boolean isForLiveTile() {
+ return mForLiveTile;
+ }
+
+ public float getCornerRadius() {
+ return mCornerRadius;
+ }
+
+ public boolean isLauncherOnTop() {
+ return mLauncherOnTop;
+ }
+
+ public RemoteAnimationTargets getTargetSet() {
+ return mTargetSet;
+ }
+
+ public SyncRtSurfaceTransactionApplierCompat getSyncTransactionApplier() {
+ return mSyncTransactionApplier;
+ }
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ProtoTracer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ProtoTracer.java
new file mode 100644
index 0000000..190763a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ProtoTracer.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 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.quickstep.util;
+
+import static com.android.launcher3.tracing.nano.LauncherTraceFileProto.MagicNumber.MAGIC_NUMBER_H;
+import static com.android.launcher3.tracing.nano.LauncherTraceFileProto.MagicNumber.MAGIC_NUMBER_L;
+
+import android.content.Context;
+import android.os.SystemClock;
+
+import com.android.launcher3.tracing.nano.LauncherTraceProto;
+import com.android.launcher3.tracing.nano.LauncherTraceEntryProto;
+import com.android.launcher3.tracing.nano.LauncherTraceFileProto;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.systemui.shared.tracing.FrameProtoTracer;
+import com.android.systemui.shared.tracing.FrameProtoTracer.ProtoTraceParams;
+import com.android.systemui.shared.tracing.ProtoTraceable;
+import com.google.protobuf.nano.MessageNano;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Queue;
+
+
+/**
+ * Controller for coordinating winscope proto tracing.
+ */
+public class ProtoTracer implements ProtoTraceParams<MessageNano,
+ LauncherTraceFileProto, LauncherTraceEntryProto, LauncherTraceProto> {
+
+ public static final MainThreadInitializedObject<ProtoTracer> INSTANCE =
+ new MainThreadInitializedObject<>(ProtoTracer::new);
+
+ private static final String TAG = "ProtoTracer";
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ private final Context mContext;
+ private final FrameProtoTracer<MessageNano,
+ LauncherTraceFileProto, LauncherTraceEntryProto, LauncherTraceProto> mProtoTracer;
+
+ public ProtoTracer(Context context) {
+ mContext = context;
+ mProtoTracer = new FrameProtoTracer<>(this);
+ }
+
+ @Override
+ public File getTraceFile() {
+ return new File(mContext.getFilesDir(), "launcher_trace.pb");
+ }
+
+ @Override
+ public LauncherTraceFileProto getEncapsulatingTraceProto() {
+ return new LauncherTraceFileProto();
+ }
+
+ @Override
+ public LauncherTraceEntryProto updateBufferProto(LauncherTraceEntryProto reuseObj,
+ ArrayList<ProtoTraceable<LauncherTraceProto>> traceables) {
+ LauncherTraceEntryProto proto = reuseObj != null
+ ? reuseObj
+ : new LauncherTraceEntryProto();
+ proto.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
+ proto.launcher = proto.launcher != null ? proto.launcher : new LauncherTraceProto();
+ for (ProtoTraceable t : traceables) {
+ t.writeToProto(proto.launcher);
+ }
+ return proto;
+ }
+
+ @Override
+ public byte[] serializeEncapsulatingProto(LauncherTraceFileProto encapsulatingProto,
+ Queue<LauncherTraceEntryProto> buffer) {
+ encapsulatingProto.magicNumber = MAGIC_NUMBER_VALUE;
+ encapsulatingProto.entry = buffer.toArray(new LauncherTraceEntryProto[0]);
+ return MessageNano.toByteArray(encapsulatingProto);
+ }
+
+ @Override
+ public byte[] getProtoBytes(MessageNano proto) {
+ return MessageNano.toByteArray(proto);
+ }
+
+ @Override
+ public int getProtoSize(MessageNano proto) {
+ return proto.getCachedSize();
+ }
+
+ public void start() {
+ mProtoTracer.start();
+ }
+
+ public void stop() {
+ mProtoTracer.stop();
+ }
+
+ public void add(ProtoTraceable<LauncherTraceProto> traceable) {
+ mProtoTracer.add(traceable);
+ }
+
+ public void remove(ProtoTraceable<LauncherTraceProto> traceable) {
+ mProtoTracer.remove(traceable);
+ }
+
+ public void scheduleFrameUpdate() {
+ mProtoTracer.scheduleFrameUpdate();
+ }
+
+ public void update() {
+ mProtoTracer.update();
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 1bbb3f5..d705cc0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -200,6 +200,7 @@
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (tv.isRunningTask()) {
mTransformParams.setProgress(1 - progress)
+ .setCurrentRect(null)
.setSyncTransactionApplier(mSyncTransactionApplier)
.setForLiveTile(true);
mAppWindowAnimationHelper.applyTransform(mTransformParams);
@@ -267,7 +268,8 @@
}
mTempRectF.set(mTempRect);
mTransformParams.setProgress(1f)
- .setCurrentRectAndTargetAlpha(mTempRectF, taskView.getAlpha())
+ .setCurrentRect(mTempRectF)
+ .setTargetAlpha(taskView.getAlpha())
.setSyncTransactionApplier(mSyncTransactionApplier)
.setTargetSet(mRecentsAnimationTargets)
.setLauncherOnTop(true);
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index def76e8..a429af2 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -25,6 +25,7 @@
import android.annotation.TargetApi;
import android.app.Fragment;
+import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
@@ -55,6 +56,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -72,6 +74,8 @@
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.LoaderResults;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
@@ -248,6 +252,16 @@
addInScreenFromBind(folderIcon, info);
}
+ private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) {
+ WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName(
+ info.providerName);
+ AppWidgetHostView view = new AppWidgetHostView(mContext);
+ view.setAppWidget(-1, widgetItem.widgetInfo);
+ view.updateAppWidget(null);
+ view.setTag(info);
+ addInScreenFromBind(view, info);
+ }
+
private void dispatchVisibilityAggregated(View view, boolean isVisible) {
// Similar to View.dispatchVisibilityAggregated implementation.
final boolean thisVisible = view.getVisibility() == VISIBLE;
@@ -272,9 +286,9 @@
mContext).getModel();
final WorkspaceItemsInfoFetcher fetcher = new WorkspaceItemsInfoFetcher();
launcherModel.enqueueModelUpdateTask(fetcher);
- ArrayList<ItemInfo> workspaceItems;
+ WorkspaceResult workspaceResult;
try {
- workspaceItems = fetcher.mTask.get(5, TimeUnit.SECONDS);
+ workspaceResult = fetcher.mTask.get(5, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
Log.d(TAG, "Error fetching workspace items info", e);
return;
@@ -284,9 +298,14 @@
// items
ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
+ ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
+ ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
- filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceItems,
- currentWorkspaceItems, otherWorkspaceItems);
+ filterCurrentWorkspaceItems(0 /* currentScreenId */,
+ workspaceResult.mWorkspaceItems, currentWorkspaceItems,
+ otherWorkspaceItems);
+ filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceResult.mAppWidgets,
+ currentAppWidgets, otherAppWidgets);
sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
for (ItemInfo itemInfo : currentWorkspaceItems) {
@@ -303,6 +322,17 @@
break;
}
}
+ for (ItemInfo itemInfo : currentAppWidgets) {
+ switch (itemInfo.itemType) {
+ case Favorites.ITEM_TYPE_APPWIDGET:
+ case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+ inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
+ workspaceResult.mWidgetsModel);
+ break;
+ default:
+ break;
+ }
+ }
} else {
// Add hotseat icons
for (int i = 0; i < mIdp.numHotseatIcons; i++) {
@@ -349,10 +379,10 @@
}
}
- private static class WorkspaceItemsInfoFetcher implements Callable<ArrayList<ItemInfo>>,
+ private static class WorkspaceItemsInfoFetcher implements Callable<WorkspaceResult>,
LauncherModel.ModelUpdateTask {
- private final FutureTask<ArrayList<ItemInfo>> mTask = new FutureTask<>(this);
+ private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
private LauncherAppState mApp;
private LauncherModel mModel;
@@ -374,14 +404,16 @@
}
@Override
- public ArrayList<ItemInfo> call() throws Exception {
+ public WorkspaceResult call() throws Exception {
if (!mModel.isModelLoaded()) {
Log.d(TAG, "Workspace not loaded, loading now");
mModel.startLoaderForResults(
new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0]));
- return new ArrayList<>();
+ return null;
}
- return mBgDataModel.workspaceItems;
+
+ return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
+ mBgDataModel.widgetsModel);
}
}
@@ -389,4 +421,17 @@
view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
view.layout(0, 0, width, height);
}
+
+ private static class WorkspaceResult {
+ private final ArrayList<ItemInfo> mWorkspaceItems;
+ private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
+ private final WidgetsModel mWidgetsModel;
+
+ private WorkspaceResult(ArrayList<ItemInfo> workspaceItems,
+ ArrayList<LauncherAppWidgetInfo> appWidgets, WidgetsModel widgetsModel) {
+ mWorkspaceItems = workspaceItems;
+ mAppWidgets = appWidgets;
+ mWidgetsModel = widgetsModel;
+ }
+ }
}
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index 67f07b1..a3fdf8d 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -10,6 +10,7 @@
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.IOUtils;
import java.io.BufferedReader;
@@ -42,7 +43,7 @@
private static Handler sHandler = null;
private static File sLogsDirectory = null;
- public static final int LOG_DAYS = 4;
+ public static final int LOG_DAYS = FeatureFlags.ENABLE_HYBRID_HOTSEAT.get() ? 10 : 4;
public static void setDir(File logsDir) {
if (ENABLED) {
diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java
index 3ef48cd..2fc064c 100644
--- a/src/com/android/launcher3/model/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/PackageItemInfo.java
@@ -19,6 +19,8 @@
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherSettings;
+import java.util.Objects;
+
/**
* Represents a {@link Package} in the widget tray section.
*/
@@ -48,4 +50,17 @@
public PackageItemInfo clone() {
return new PackageItemInfo(this);
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PackageItemInfo that = (PackageItemInfo) o;
+ return Objects.equals(packageName, that.packageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(packageName);
+ }
}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 282867a..ae32692 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -6,6 +6,7 @@
import static com.android.launcher3.pm.ShortcutConfigActivityInfo.queryList;
import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Process;
@@ -243,4 +244,16 @@
}
}
}
+
+ public WidgetItem getWidgetProviderInfoByProviderName(
+ ComponentName providerName) {
+ ArrayList<WidgetItem> widgetsList = mWidgetsList.get(
+ new PackageItemInfo(providerName.getPackageName()));
+ for (WidgetItem item : widgetsList) {
+ if (item.componentName.equals(providerName)) {
+ return item;
+ }
+ }
+ return null;
+ }
}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index 810c91a..6fe6739 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -88,7 +88,7 @@
executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
waitForState("Launcher internal state didn't switch to All Apps", () -> ALL_APPS);
getOnceNotNull("Apps view did not bind",
- launcher -> launcher.getAppsView().getWorkFooterContainer());
+ launcher -> launcher.getAppsView().getWorkFooterContainer(), 60000);
UserManager userManager = getFromLauncher(l -> l.getSystemService(UserManager.class));
assertEquals(2, userManager.getUserProfiles().size());
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index c5fbd7c..b3b887d 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -69,22 +69,16 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Date;
import java.util.Deque;
-import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -97,16 +91,8 @@
private static final String TAG = "Tapl";
private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
private static final int GESTURE_STEP_MS = 16;
- private static final SimpleDateFormat DATE_TIME_FORMAT =
- new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static long START_TIME = System.currentTimeMillis();
- static final Pattern EVENT_LOG_ENTRY = Pattern.compile(
- "(?<time>[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] "
- + "[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9][0-9][0-9])"
- + ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<sequence>[a-zA-Z]+) / "
- + "(?<event>.*)");
-
private static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
private static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL");
@@ -176,11 +162,10 @@
private Consumer<ContainerType> mOnSettledStateAction;
- // Map from an event sequence name to an ordered list of expected events in that sequence.
- // Not null when we are collecting expected events to compare with actual ones.
- private Map<String, List<Pattern>> mExpectedEvents;
+ private static LogEventChecker sEventChecker;
+ // True if there is an gesture in progress that needs event verification.
+ private static boolean sCheckingEvents;
- private Date mStartRecordingTime;
private boolean mCheckEventsForSuccessfulGestures = false;
private static Pattern getTouchEventPattern(String prefix, String action) {
@@ -434,10 +419,12 @@
log("Hierarchy dump for: " + message);
dumpViewHierarchy();
- final String eventMismatch = getEventMismatchMessage(false);
-
- if (eventMismatch != null) {
- message = message + ", having produced " + eventMismatch;
+ if (sCheckingEvents) {
+ sCheckingEvents = false;
+ final String eventMismatch = sEventChecker.verify(0);
+ if (eventMismatch != null) {
+ message = message + ", having produced " + eventMismatch;
+ }
}
Assert.fail(formatSystemHealthMessage(message));
@@ -1245,204 +1232,34 @@
return tasks;
}
- // Returns actual events retrieved from logcat. The return value's key set is the set of all
- // sequence names that actually had at least one event, and the values are lists of events in
- // the given sequence, in the order they were recorded.
- private Map<String, List<String>> getEvents() {
- final Map<String, List<String>> events = new HashMap<>();
- try {
- // Logcat may skip events after the specified time. Querying for events starting 1 min
- // earlier.
- final Date startTime = new Date(mStartRecordingTime.getTime() - 60000);
- final String logcatCommand = "logcat -d -v year --pid=" + getPid() + " -t "
- + DATE_TIME_FORMAT.format(startTime).replaceAll(" ", "")
- + " -s " + TestProtocol.TAPL_EVENTS_TAG;
- log("Events query command: " + logcatCommand);
- final String logcatEvents = mDevice.executeShellCommand(logcatCommand);
- final Matcher matcher = EVENT_LOG_ENTRY.matcher(logcatEvents);
- while (matcher.find()) {
- // Skip events before recording start time.
- if (DATE_TIME_FORMAT.parse(matcher.group("time"))
- .compareTo(mStartRecordingTime) < 0) {
- continue;
- }
-
- eventsListForSequence(matcher.group("sequence"), events).add(
- matcher.group("event"));
- }
- return events;
- } catch (IOException e) {
- throw new RuntimeException(e);
- } catch (ParseException e) {
- throw new AssertionError(e);
- }
- }
-
- // Returns an event list for a given sequence, adding it to the map as needed.
- private static <T> List<T> eventsListForSequence(
- String sequenceName, Map<String, List<T>> events) {
- List<T> eventSequence = events.get(sequenceName);
- if (eventSequence == null) {
- eventSequence = new ArrayList<>();
- events.put(sequenceName, eventSequence);
- }
- return eventSequence;
- }
-
- private void startRecordingEvents() {
- Assert.assertTrue("Already recording events", mExpectedEvents == null);
- mExpectedEvents = new HashMap<>();
- mStartRecordingTime = new Date();
- log("startRecordingEvents: " + DATE_TIME_FORMAT.format(mStartRecordingTime));
- }
-
- private void stopRecordingEvents() {
- mExpectedEvents = null;
- mStartRecordingTime = null;
- }
-
public Closable eventsCheck() {
+ Assert.assertTrue("Nested event checking", !sCheckingEvents);
if ("com.android.launcher3".equals(getLauncherPackageName())) {
// Not checking specific Launcher3 event sequences.
return () -> {
};
}
-
- // Entering events check block.
- startRecordingEvents();
+ sCheckingEvents = true;
+ if (sEventChecker == null) sEventChecker = new LogEventChecker();
+ sEventChecker.start();
return () -> {
- // Leaving events check block.
- if (mExpectedEvents == null) {
- return; // There was a failure. Noo need to report another one.
- }
-
- if (!mCheckEventsForSuccessfulGestures) {
- stopRecordingEvents();
- return;
- }
-
- final String message = getEventMismatchMessage(true);
- if (message != null) {
- Assert.fail(formatSystemHealthMessage(
- "http://go/tapl : successful gesture produced " + message));
+ if (sCheckingEvents) {
+ sCheckingEvents = false;
+ if (mCheckEventsForSuccessfulGestures) {
+ final String message = sEventChecker.verify(WAIT_TIME_MS);
+ if (message != null) {
+ Assert.fail(formatSystemHealthMessage(
+ "http://go/tapl : successful gesture produced " + message));
+ }
+ } else {
+ sEventChecker.finishNoWait();
+ }
}
};
}
void expectEvent(String sequence, Pattern expected) {
- if (mExpectedEvents != null) {
- eventsListForSequence(sequence, mExpectedEvents).add(expected);
- }
- }
-
- // Returns non-null error message if the actual events in logcat don't match expected events.
- // If we are not checking events, returns null.
- private String getEventMismatchMessage(boolean waitForExpectedCount) {
- if (mExpectedEvents == null) return null;
-
- try {
- Map<String, List<String>> actual = getEvents();
-
- if (waitForExpectedCount) {
- // Wait until Launcher generates the expected number of events.
- final long endTime = SystemClock.uptimeMillis() + WAIT_TIME_MS;
- while (SystemClock.uptimeMillis() < endTime
- && !receivedEnoughEvents(actual)) {
- SystemClock.sleep(100);
- actual = getEvents();
- }
- }
-
- return getEventMismatchErrorMessage(actual);
- } finally {
- stopRecordingEvents();
- }
- }
-
- // Returns whether there is a sufficient number of events in the logcat to match the expected
- // events.
- private boolean receivedEnoughEvents(Map<String, List<String>> actual) {
- for (Map.Entry<String, List<Pattern>> expectedNamedSequence : mExpectedEvents.entrySet()) {
- final List<String> actualEventSequence = actual.get(expectedNamedSequence.getKey());
- if (actualEventSequence == null
- || actualEventSequence.size() < expectedNamedSequence.getValue().size()) {
- return false;
- }
- }
- return true;
- }
-
- // If the list of actual events matches the list of expected events, returns -1, otherwise
- // the position of the mismatch.
- private static int getMismatchPosition(List<Pattern> expected, List<String> actual) {
- for (int i = 0; i < expected.size(); ++i) {
- if (i >= actual.size()
- || !expected.get(i).matcher(actual.get(i)).find()) {
- return i;
- }
- }
-
- if (actual.size() > expected.size()) return expected.size();
-
- return -1;
- }
-
- // Returns non-null error message if the actual events passed as a param don't match expected
- // events.
- private String getEventMismatchErrorMessage(Map<String, List<String>> actualEvents) {
- final StringBuilder sb = new StringBuilder();
-
- // Check that all expected even sequences match the actual data.
- for (Map.Entry<String, List<Pattern>> expectedNamedSequence : mExpectedEvents.entrySet()) {
- List<String> actualEventSequence = actualEvents.get(expectedNamedSequence.getKey());
- if (actualEventSequence == null) actualEventSequence = new ArrayList<>();
- final int mismatchPosition = getMismatchPosition(
- expectedNamedSequence.getValue(), actualEventSequence);
- if (mismatchPosition != -1) {
- formatSequenceWithMismatch(
- sb,
- expectedNamedSequence.getKey(),
- expectedNamedSequence.getValue(),
- actualEventSequence,
- mismatchPosition);
- }
- }
-
- // Check for unexpected event sequences in the actual data.
- for (Map.Entry<String, List<String>> actualNamedSequence : actualEvents.entrySet()) {
- if (!mExpectedEvents.containsKey(actualNamedSequence.getKey())) {
- formatSequenceWithMismatch(
- sb,
- actualNamedSequence.getKey(),
- new ArrayList<>(),
- actualNamedSequence.getValue(),
- 0);
- }
- }
-
- return sb.length() != 0 ? "mismatching events: " + sb.toString() : null;
- }
-
- private static void formatSequenceWithMismatch(
- StringBuilder sb,
- String sequenceName,
- List<Pattern> expected,
- List<String> actualEvents,
- int mismatchPosition) {
- sb.append("\n>> Sequence " + sequenceName);
- sb.append("\n Expected:");
- formatEventListWithMismatch(sb, expected, mismatchPosition);
- sb.append("\n Actual:");
- formatEventListWithMismatch(sb, actualEvents, mismatchPosition);
- }
-
- private static void formatEventListWithMismatch(StringBuilder sb, List events, int position) {
- for (int i = 0; i < events.size(); ++i) {
- sb.append("\n | ");
- sb.append(i == position ? "---> " : " ");
- sb.append(events.get(i).toString());
- }
- if (position == events.size()) sb.append("\n | ---> (end)");
+ if (sCheckingEvents) sEventChecker.expectPattern(sequence, expected);
}
}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
new file mode 100644
index 0000000..0fc88ee
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2020 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.launcher3.tapl;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.util.Log;
+
+import com.android.launcher3.testing.TestProtocol;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility class to read log on a background thread.
+ */
+public class LogEventChecker {
+
+ private static final Pattern EVENT_LOG_ENTRY = Pattern.compile(
+ ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<sequence>[a-zA-Z]+) / (?<event>.*)");
+
+ private static final String START_PREFIX = "START_READER ";
+ private static final String FINISH_PREFIX = "FINISH_READER ";
+
+ private volatile CountDownLatch mFinished;
+
+ // Map from an event sequence name to an ordered list of expected events in that sequence.
+ private final ListMap<Pattern> mExpectedEvents = new ListMap<>();
+
+ private final ListMap<String> mEvents = new ListMap<>();
+ private final Semaphore mEventsCounter = new Semaphore(0);
+
+ private volatile String mStartCommand;
+ private volatile String mFinishCommand;
+
+ LogEventChecker() {
+ final Thread thread = new Thread(this::onRun, "log-reader-thread");
+ thread.setPriority(Thread.NORM_PRIORITY);
+ thread.start();
+ }
+
+ void start() {
+ if (mFinished != null) {
+ try {
+ mFinished.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ mFinished = null;
+ }
+ mEvents.clear();
+ mExpectedEvents.clear();
+ mEventsCounter.drainPermits();
+ final String id = UUID.randomUUID().toString();
+ mStartCommand = START_PREFIX + id;
+ mFinishCommand = FINISH_PREFIX + id;
+ Log.d(TestProtocol.TAPL_EVENTS_TAG, mStartCommand);
+ }
+
+ private void onRun() {
+ try {
+ // Note that we use Runtime.exec to start the log reading process instead of running
+ // it via UIAutomation, so that we can directly access the "Process" object and
+ // ensure that the instrumentation is not stuck forever.
+ final String cmd = "logcat -s " + TestProtocol.TAPL_EVENTS_TAG;
+
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+ Runtime.getRuntime().exec(cmd).getInputStream()))) {
+ for (;;) {
+ // Skip everything before the next start command.
+ for (;;) {
+ final String event = reader.readLine();
+ if (event.contains(TestProtocol.TAPL_EVENTS_TAG)
+ && event.contains(mStartCommand)) {
+ break;
+ }
+ }
+
+ // Store all actual events until the finish command.
+ for (;;) {
+ final String event = reader.readLine();
+ if (event.contains(TestProtocol.TAPL_EVENTS_TAG)) {
+ if (event.contains(mFinishCommand)) {
+ mFinished.countDown();
+ break;
+ } else {
+ final Matcher matcher = EVENT_LOG_ENTRY.matcher(event);
+ if (matcher.find()) {
+ mEvents.add(matcher.group("sequence"), matcher.group("event"));
+ mEventsCounter.release();
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ void expectPattern(String sequence, Pattern pattern) {
+ mExpectedEvents.add(sequence, pattern);
+ }
+
+ private void finishSync(long waitForExpectedCountMs) {
+ try {
+ // Wait until Launcher generates the expected number of events.
+ int expectedCount = mExpectedEvents.entrySet()
+ .stream().mapToInt(e -> e.getValue().size()).sum();
+ mEventsCounter.tryAcquire(expectedCount, waitForExpectedCountMs, MILLISECONDS);
+ finishNoWait();
+ mFinished.await();
+ mFinished = null;
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ void finishNoWait() {
+ mFinished = new CountDownLatch(1);
+ Log.d(TestProtocol.TAPL_EVENTS_TAG, mFinishCommand);
+ }
+
+ String verify(long waitForExpectedCountMs) {
+ finishSync(waitForExpectedCountMs);
+
+ final StringBuilder sb = new StringBuilder();
+ for (Map.Entry<String, List<Pattern>> expectedEvents : mExpectedEvents.entrySet()) {
+ String sequence = expectedEvents.getKey();
+
+ List<String> actual = new ArrayList<>(mEvents.getNonNull(sequence));
+ final int mismatchPosition = getMismatchPosition(expectedEvents.getValue(), actual);
+ if (mismatchPosition != -1) {
+ formatSequenceWithMismatch(
+ sb,
+ sequence,
+ expectedEvents.getValue(),
+ actual,
+ mismatchPosition);
+ }
+ }
+ // Check for unexpected event sequences in the actual data.
+ for (String actualNamedSequence : mEvents.keySet()) {
+ if (!mExpectedEvents.containsKey(actualNamedSequence)) {
+ formatSequenceWithMismatch(
+ sb,
+ actualNamedSequence,
+ new ArrayList<>(),
+ mEvents.get(actualNamedSequence),
+ 0);
+ }
+ }
+
+ return sb.length() != 0 ? "mismatching events: " + sb.toString() : null;
+ }
+
+ // If the list of actual events matches the list of expected events, returns -1, otherwise
+ // the position of the mismatch.
+ private static int getMismatchPosition(List<Pattern> expected, List<String> actual) {
+ for (int i = 0; i < expected.size(); ++i) {
+ if (i >= actual.size()
+ || !expected.get(i).matcher(actual.get(i)).find()) {
+ return i;
+ }
+ }
+
+ if (actual.size() > expected.size()) return expected.size();
+
+ return -1;
+ }
+
+ private static void formatSequenceWithMismatch(
+ StringBuilder sb,
+ String sequenceName,
+ List<Pattern> expected,
+ List<String> actualEvents,
+ int mismatchPosition) {
+ sb.append("\n>> Sequence " + sequenceName);
+ sb.append("\n Expected:");
+ formatEventListWithMismatch(sb, expected, mismatchPosition);
+ sb.append("\n Actual:");
+ formatEventListWithMismatch(sb, actualEvents, mismatchPosition);
+ }
+
+ private static void formatEventListWithMismatch(StringBuilder sb, List events, int position) {
+ for (int i = 0; i < events.size(); ++i) {
+ sb.append("\n | ");
+ sb.append(i == position ? "---> " : " ");
+ sb.append(events.get(i).toString());
+ }
+ if (position == events.size()) sb.append("\n | ---> (end)");
+ }
+
+ private static class ListMap<T> extends HashMap<String, List<T>> {
+
+ void add(String key, T value) {
+ getNonNull(key).add(value);
+ }
+
+ List<T> getNonNull(String key) {
+ List<T> list = get(key);
+ if (list == null) {
+ list = new ArrayList<>();
+ put(key, list);
+ }
+ return list;
+ }
+ }
+}