Merge "Makes all ArrowPopups AccessibilityTargets." into ub-launcher3-master
diff --git a/Android.bp b/Android.bp
index cb695df..e132854 100644
--- a/Android.bp
+++ b/Android.bp
@@ -47,3 +47,14 @@
},
static_libs: ["libprotobuf-java-lite"],
}
+
+java_library {
+ name: "LauncherPluginLib",
+
+ static_libs: ["PluginCoreLib"],
+
+ srcs: ["src_plugins/**/*.java"],
+
+ sdk_version: "current",
+ min_sdk_version: "28",
+}
diff --git a/Android.mk b/Android.mk
index c066a12..9cfcf17 100644
--- a/Android.mk
+++ b/Android.mk
@@ -17,24 +17,6 @@
LOCAL_PATH := $(call my-dir)
#
-# Build rule for plugin lib (needed to write a plugin).
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT2_ONLY := true
-LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES:= PluginCoreLib
-
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src_plugins)
-
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 28
-LOCAL_MODULE := LauncherPluginLib
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-#
# Build rule for Launcher3 dependencies lib.
#
include $(CLEAR_VARS)
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_log.proto b/protos/launcher_log.proto
index ec1d55b..d08d851 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -151,6 +151,10 @@
DISMISS_PREDICTION = 21;
HYBRID_HOTSEAT_ACCEPTED = 22;
HYBRID_HOTSEAT_CANCELED = 23;
+ OVERVIEW_ACTIONS_SHARE_BUTTON = 24;
+ OVERVIEW_ACTIONS_SCREENSHOT_BUTTON = 25;
+ OVERVIEW_ACTIONS_SELECT_BUTTON = 26;
+ SELECT_MODE_CLOSE_BUTTON = 27;
}
enum TipType {
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/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 7ce4d74..a06a2dd 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -47,10 +47,7 @@
</intent-filter>
</service>
- <!-- STOPSHIP: Change exported to false once all the integration is complete.
- It is set to true so that the activity can be started from command line -->
<activity android:name="com.android.quickstep.RecentsActivity"
- android:exported="true"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
diff --git a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
index 7f1425b..a572cad 100644
--- a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
+++ b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
@@ -14,12 +14,17 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.views.LauncherRecentsView
+<com.android.launcher3.InsettableFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:theme="@style/HomeScreenElementTheme"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:accessibilityPaneTitle="@string/accessibility_recent_apps"
- android:visibility="invisible" />
\ No newline at end of file
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <com.android.quickstep.views.LauncherRecentsView
+ android:id="@+id/overview_panel_recents"
+ android:theme="@style/HomeScreenElementTheme"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:accessibilityPaneTitle="@string/accessibility_recent_apps"
+ android:visibility="invisible" />
+</com.android.launcher3.InsettableFrameLayout>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index a1c8378..7895bac 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -37,6 +37,7 @@
import android.graphics.Rect;
import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
@@ -47,6 +48,7 @@
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.SysUINavigationMode;
@@ -143,6 +145,10 @@
public void onStateTransitionEnd(Launcher launcher) {
launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
DiscoveryBounce.showForOverviewIfNeeded(launcher);
+ RecentsView recentsView = launcher.getOverviewPanel();
+ AccessibilityManagerCompat.sendCustomAccessibilityEvent(
+ recentsView.getPageAt(recentsView.getCurrentPage()),
+ AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
}
@Override
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..5a9c2fe 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,23 +60,23 @@
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.
private final RectF mSourceWindowClipInsets = new RectF();
- // 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();
@@ -146,7 +146,6 @@
Math.max(scaledTargetRect.top, 0),
Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
- mSourceWindowClipInsetsForLiveTile.set(mSourceWindowClipInsets);
mSourceRect.set(scaledTargetRect);
}
@@ -156,25 +155,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 +185,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 +209,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 +231,33 @@
}
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);
+ mCurrentClipRectF.left = mSourceWindowClipInsets.left * progress;
+ mCurrentClipRectF.top = mSourceWindowClipInsets.top * progress;
+ mCurrentClipRectF.right =
+ mSourceStackBounds.width() - (mSourceWindowClipInsets.right * progress);
+ mCurrentClipRectF.bottom =
+ mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress);
}
public RectF getCurrentRectWithInsets() {
- mTmpMatrix.mapRect(mCurrentRectWithInsets, mClipRectF);
+ mTmpMatrix.mapRect(mCurrentRectWithInsets, mCurrentClipRectF);
return mCurrentRectWithInsets;
}
@@ -384,75 +389,152 @@
}
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 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;
+ 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;
}
- public TransformParams setForLiveTile(boolean forLiveTile) {
- this.forLiveTile = 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 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..3e106aa 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,8 +200,8 @@
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (tv.isRunningTask()) {
mTransformParams.setProgress(1 - progress)
- .setSyncTransactionApplier(mSyncTransactionApplier)
- .setForLiveTile(true);
+ .setCurrentRect(null)
+ .setSyncTransactionApplier(mSyncTransactionApplier);
mAppWindowAnimationHelper.applyTransform(mTransformParams);
} else {
redrawLiveTile(true);
@@ -267,7 +267,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/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index ef1698e..fe78a84 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -92,6 +92,7 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.anim.SpringObjectAnimator;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -972,6 +973,10 @@
TaskView runningTask = getRunningTaskView();
if (runningTask != null) {
runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha);
+ if (!isHidden) {
+ AccessibilityManagerCompat.sendCustomAccessibilityEvent(runningTask,
+ AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 15503b8..07d2381 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -194,7 +194,7 @@
if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(this)) {
// Overview is above all other launcher elements, including qsb, so move it to the top.
- getOverviewPanel().bringToFront();
+ getOverviewPanelContainer().bringToFront();
}
}
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index d8b10b6..7d52571 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.util;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_LSQ_VELOCITY_PROVIDER;
+
import android.content.Context;
import android.content.res.Resources;
import android.view.MotionEvent;
@@ -85,7 +87,8 @@
mForcePauseTimeout = new Alarm();
mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
mMakePauseHarderToTrigger = makePauseHarderToTrigger;
- mVelocityProvider = new LinearVelocityProvider(axis);
+ mVelocityProvider = ENABLE_LSQ_VELOCITY_PROVIDER.get()
+ ? new LSqVelocityProvider(axis) : new LinearVelocityProvider(axis);
}
/**
@@ -106,8 +109,6 @@
/**
* Computes velocity and acceleration to determine whether the motion is paused.
* @param ev The motion being tracked.
- *
- * TODO: Use historical positions as well, e.g. {@link MotionEvent#getHistoricalY(int, int)}.
*/
public void addPosition(MotionEvent ev) {
addPosition(ev, 0);
@@ -248,4 +249,137 @@
mPreviousPosition = null;
}
}
+
+ /**
+ * Java implementation of {@link android.view.VelocityTracker} using the Least Square (deg 2)
+ * algorithm.
+ */
+ private static class LSqVelocityProvider implements VelocityProvider {
+
+ // Maximum age of a motion event to be considered when calculating the velocity.
+ private static final long HORIZON_MS = 100;
+ // Number of samples to keep.
+ private static final int HISTORY_SIZE = 20;
+
+ // Position history are stored in a circular array
+ private final float[] mHistoricTimes = new float[HISTORY_SIZE];
+ private final float[] mHistoricPos = new float[HISTORY_SIZE];
+ private int mHistoryCount = 0;
+ private int mHistoryStart = 0;
+
+ private final int mAxis;
+
+ LSqVelocityProvider(int axis) {
+ mAxis = axis;
+ }
+
+ @Override
+ public void clear() {
+ mHistoryCount = mHistoryStart = 0;
+ }
+
+ private void addPositionAndTime(float eventTime, float eventPosition) {
+ mHistoricTimes[mHistoryStart] = eventTime;
+ mHistoricPos[mHistoryStart] = eventPosition;
+ mHistoryStart++;
+ if (mHistoryStart >= HISTORY_SIZE) {
+ mHistoryStart = 0;
+ }
+ mHistoryCount = Math.min(HISTORY_SIZE, mHistoryCount + 1);
+ }
+
+ @Override
+ public Float addMotionEvent(MotionEvent ev, int pointer) {
+ // Add all historic points
+ int historyCount = ev.getHistorySize();
+ for (int i = 0; i < historyCount; i++) {
+ addPositionAndTime(
+ ev.getHistoricalEventTime(i), ev.getHistoricalAxisValue(mAxis, pointer, i));
+ }
+
+ // Start index for the last position (about to be added)
+ int eventStartIndex = mHistoryStart;
+ addPositionAndTime(ev.getEventTime(), ev.getAxisValue(mAxis, pointer));
+ return solveUnweightedLeastSquaresDeg2(eventStartIndex);
+ }
+
+ /**
+ * Solves the instantaneous velocity.
+ * Based on solveUnweightedLeastSquaresDeg2 in VelocityTracker.cpp
+ */
+ private Float solveUnweightedLeastSquaresDeg2(final int pointPos) {
+ final float eventTime = mHistoricTimes[pointPos];
+
+ float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
+ int count = 0;
+ for (int i = 0; i < mHistoryCount; i++) {
+ int index = pointPos - i;
+ if (index < 0) {
+ index += HISTORY_SIZE;
+ }
+
+ float time = mHistoricTimes[index];
+ float age = eventTime - time;
+ if (age > HORIZON_MS) {
+ break;
+ }
+ count++;
+ float xi = -age;
+
+ float yi = mHistoricPos[index];
+ float xi2 = xi * xi;
+ float xi3 = xi2 * xi;
+ float xi4 = xi3 * xi;
+ float xiyi = xi * yi;
+ float xi2yi = xi2 * yi;
+
+ sxi += xi;
+ sxi2 += xi2;
+ sxiyi += xiyi;
+ sxi2yi += xi2yi;
+ syi += yi;
+ sxi3 += xi3;
+ sxi4 += xi4;
+ }
+
+ if (count < 3) {
+ // Too few samples
+ if (count == 2) {
+ int endPos = pointPos - 1;
+ if (endPos < 0) {
+ endPos += HISTORY_SIZE;
+ }
+ float denominator = eventTime - mHistoricTimes[endPos];
+ if (denominator != 0) {
+ return (eventTime - mHistoricPos[endPos]) / denominator;
+
+ }
+ }
+ return null;
+ }
+
+ float Sxx = sxi2 - sxi * sxi / count;
+ float Sxy = sxiyi - sxi * syi / count;
+ float Sxx2 = sxi3 - sxi * sxi2 / count;
+ float Sx2y = sxi2yi - sxi2 * syi / count;
+ float Sx2x2 = sxi4 - sxi2 * sxi2 / count;
+
+ float denominator = Sxx * Sx2x2 - Sxx2 * Sxx2;
+ if (denominator == 0) {
+ // division by 0 when computing velocity
+ return null;
+ }
+ // Compute a
+ // float numerator = Sx2y*Sxx - Sxy*Sxx2;
+
+ // Compute b
+ float numerator = Sxy * Sx2x2 - Sx2y * Sxx2;
+ float b = numerator / denominator;
+
+ // Compute c
+ // float c = syi/count - b * sxi/count - a * sxi2/count;
+
+ return b;
+ }
+ }
}
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index cca899b..6c66897 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -44,9 +44,8 @@
layout="@layout/hotseat" />
<include
- android:id="@+id/overview_panel"
- layout="@layout/overview_panel"
- android:visibility="gone" />
+ android:id="@+id/overview_panel_container"
+ layout="@layout/overview_panel"/>
<!-- Keep these behind the workspace so that they are not visible when
we go into AllApps -->
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index bdd5d23..7fff711 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -14,7 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<Space
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/overview_panel_recents"
android:layout_width="0dp"
- android:layout_height="0dp" />
\ No newline at end of file
+ android:layout_height="0dp"
+ android:visibility="gone" />
\ No newline at end of file
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 3fc8de2..33f5a95 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -80,6 +80,7 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.OvershootInterpolator;
+import android.widget.FrameLayout;
import android.widget.Toast;
import androidx.annotation.Nullable;
@@ -273,6 +274,7 @@
// UI and state for the overview panel
private View mOverviewPanel;
+ private FrameLayout mOverviewPanelContainer;
@Thunk
boolean mWorkspaceLoading = true;
@@ -1143,7 +1145,8 @@
mFocusHandler = mDragLayer.getFocusIndicatorHelper();
mWorkspace = mDragLayer.findViewById(R.id.workspace);
mWorkspace.initParentViews(mDragLayer);
- mOverviewPanel = findViewById(R.id.overview_panel);
+ mOverviewPanel = findViewById(R.id.overview_panel_recents);
+ mOverviewPanelContainer = findViewById(R.id.overview_panel_container);
mHotseat = findViewById(R.id.hotseat);
mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -1386,6 +1389,10 @@
return (T) mOverviewPanel;
}
+ public FrameLayout getOverviewPanelContainer() {
+ return mOverviewPanelContainer;
+ }
+
public DropTargetBar getDropTargetBar() {
return mDropTargetBar;
}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 4c6e1f3..01893e9 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -374,6 +374,8 @@
protected void onPageEndTransition() {
mWasInOverscroll = false;
AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
+ AccessibilityManagerCompat.sendCustomAccessibilityEvent(getPageAt(mCurrentPage),
+ AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
}
protected int getUnboundedScrollX() {
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 28579c1..db4bef0 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -18,11 +18,14 @@
import android.content.Context;
import android.os.Bundle;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.Utilities;
import com.android.launcher3.testing.TestProtocol;
@@ -37,11 +40,13 @@
return isAccessibilityEnabled(context);
}
- public static void sendCustomAccessibilityEvent(View target, int type, String text) {
+ public static void sendCustomAccessibilityEvent(View target, int type, @Nullable String text) {
if (isObservedEventType(target.getContext(), type)) {
AccessibilityEvent event = AccessibilityEvent.obtain(type);
target.onInitializeAccessibilityEvent(event);
- event.getText().add(text);
+ if (!TextUtils.isEmpty(text)) {
+ event.getText().add(text);
+ }
getManager(target.getContext()).sendAccessibilityEvent(event);
}
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 6413044..3d8a9d7 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -135,6 +135,10 @@
"ENABLE_UNIVERSAL_SMARTSPACE", false,
"Replace Smartspace with a version rendered by System UI.");
+ public static final BooleanFlag ENABLE_LSQ_VELOCITY_PROVIDER = getDebugFlag(
+ "ENABLE_LSQ_VELOCITY_PROVIDER", false,
+ "Use Least Square algorithm for motion pause detection.");
+
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index c72509e..e33d89f 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -33,6 +33,7 @@
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
+import static java.util.Optional.ofNullable;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -95,6 +96,7 @@
import com.android.launcher3.userevent.LauncherLogProto.LauncherEvent;
import com.android.launcher3.userevent.LauncherLogProto.Target;
import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ClipPathView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -322,12 +324,11 @@
post(() -> {
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
if (isEmpty(mFolderName.getText())) {
- FolderNameInfo[] nameInfos =
- (FolderNameInfo[]) mInfo.suggestedFolderNames.getParcelableArrayExtra(
- FolderInfo.EXTRA_FOLDER_SUGGESTIONS);
- if (nameInfos != null) {
- showLabelSuggestion(nameInfos, false);
- }
+ ofNullable(mInfo)
+ .map(info -> info.suggestedFolderNames)
+ .map(folderNames -> (FolderNameInfo[]) folderNames
+ .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
+ .ifPresent(nameInfos -> showLabelSuggestion(nameInfos, false));
}
}
mFolderName.setHint("");
@@ -345,7 +346,7 @@
}
mInfo.title = newTitle;
- mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, mFolderName.isEnteredCompose(),
+ mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, getAcceptedSuggestionIndex() < 0,
mLauncher.getModelWriter());
mFolderIcon.onTitleChanged(newTitle);
mLauncher.getModelWriter().updateItemInDatabase(mInfo);
@@ -426,7 +427,7 @@
mInfo = info;
ArrayList<WorkspaceItemInfo> children = info.contents;
Collections.sort(children, ITEM_POS_COMPARATOR);
- updateItemLocationsInDatabaseBatch();
+ updateItemLocationsInDatabaseBatch(true);
DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
if (lp == null) {
@@ -436,19 +437,15 @@
}
mItemsInvalidated = true;
mInfo.addListener(this);
+ mPreviousLabel = mInfo.title.toString();
+ mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
if (!isEmpty(mInfo.title)) {
mFolderName.setText(mInfo.title);
- mPreviousLabel = mInfo.title.toString();
- mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
mFolderName.setHint(null);
} else {
mFolderName.setText("");
- if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
- mFolderName.setHint("");
- } else {
- mFolderName.setHint(R.string.folder_hint_text);
- }
+ mFolderName.setHint(R.string.folder_hint_text);
}
// In case any children didn't come across during loading, clean up the folder accordingly
mFolderIcon.post(() -> {
@@ -464,8 +461,6 @@
*/
public void showSuggestedTitle(FolderNameInfo[] nameInfos) {
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
- mInfo.suggestedFolderNames = new Intent().putExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS,
- nameInfos);
if (isEmpty(mFolderName.getText().toString())
&& !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME)) {
showLabelSuggestion(nameInfos, true);
@@ -985,7 +980,7 @@
// Reordering may have occured, and we need to save the new item locations. We do this once
// at the end to prevent unnecessary database operations.
- updateItemLocationsInDatabaseBatch();
+ updateItemLocationsInDatabaseBatch(false);
// Use the item count to check for multi-page as the folder UI may not have
// been refreshed yet.
if (getItemCount() <= mContent.itemsPerPage()) {
@@ -995,7 +990,7 @@
}
}
- private void updateItemLocationsInDatabaseBatch() {
+ private void updateItemLocationsInDatabaseBatch(boolean isBind) {
FolderGridOrganizer verifier = new FolderGridOrganizer(
mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo);
@@ -1011,6 +1006,18 @@
if (!items.isEmpty()) {
mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
}
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && !isBind) {
+ Executors.MODEL_EXECUTOR.post(() -> {
+ FolderNameInfo[] nameInfos =
+ new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+ FolderNameProvider fnp = FolderNameProvider.newInstance(getContext());
+ fnp.getSuggestedFolderName(
+ getContext(), mInfo.contents, nameInfos);
+ mInfo.suggestedFolderNames = new Intent().putExtra(
+ FolderInfo.EXTRA_FOLDER_SUGGESTIONS,
+ nameInfos);
+ });
+ }
}
public void notifyDrop() {
@@ -1315,7 +1322,7 @@
// We only need to update the locations if it doesn't get handled in
// #onDropCompleted.
if (d.dragSource != this) {
- updateItemLocationsInDatabaseBatch();
+ updateItemLocationsInDatabaseBatch(false);
}
}
@@ -1356,7 +1363,7 @@
verifier.updateRankAndPos(item, rank);
mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX,
item.cellY);
- updateItemLocationsInDatabaseBatch();
+ updateItemLocationsInDatabaseBatch(false);
if (mContent.areViewsBound()) {
mContent.createAndAddViewForRank(item, rank);
@@ -1647,26 +1654,8 @@
checkNotNull(mFolderName.getText().toString(),
"Expected valid folder label, but found null");
- Optional<String[]> suggestedLabels = Optional.ofNullable(
- (FolderNameInfo[]) mInfo.suggestedFolderNames
- .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
- .map(folderNameInfoArray ->
- stream(folderNameInfoArray)
- .filter(Objects::nonNull)
- .map(FolderNameInfo::getLabel)
- .map(CharSequence::toString)
- .toArray(String[]::new));
-
-
- int accepted_suggestion_index = suggestedLabels
- .map(folderNameInfoArray ->
- IntStream.range(0, folderNameInfoArray.length)
- .filter(index -> newLabel.equalsIgnoreCase(
- folderNameInfoArray[index]))
- .findFirst()
- .orElse(-1)
- ).orElse(-1);
-
+ Optional<String[]> suggestedLabels = getSuggestedLabels();
+ int accepted_suggestion_index = getAcceptedSuggestionIndex();
boolean hasValidPrimary = suggestedLabels
.map(labels -> labels.length > 0 && !isEmpty(labels[0]))
.orElse(false);
@@ -1695,6 +1684,39 @@
: Target.ToFolderLabelState.valueOf("TO_CUSTOM" + suggestionsSuffix);
}
+ private Optional<String[]> getSuggestedLabels() {
+ return ofNullable(mInfo)
+ .map(info -> info.suggestedFolderNames)
+ .map(
+ folderNames ->
+ (FolderNameInfo[])
+ folderNames.getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
+ .map(
+ folderNameInfoArray ->
+ stream(folderNameInfoArray)
+ .filter(Objects::nonNull)
+ .map(FolderNameInfo::getLabel)
+ .filter(Objects::nonNull)
+ .map(CharSequence::toString)
+ .toArray(String[]::new));
+ }
+
+ private int getAcceptedSuggestionIndex() {
+ String newLabel =
+ checkNotNull(mFolderName.getText().toString(),
+ "Expected valid folder label, but found null");
+
+ return getSuggestedLabels()
+ .map(suggestionsArray ->
+ IntStream.range(0, suggestionsArray.length)
+ .filter(index -> newLabel.equalsIgnoreCase(
+ suggestionsArray[index]))
+ .findFirst()
+ .orElse(-1)
+ ).orElse(-1);
+
+ }
+
private Target.Builder newEditTextTargetBuilder() {
return Target.newBuilder().setType(Target.Type.ITEM).setItemType(ItemType.EDITTEXT);
diff --git a/src/com/android/launcher3/folder/FolderNameEditText.java b/src/com/android/launcher3/folder/FolderNameEditText.java
index 7e11b18..edf2c70 100644
--- a/src/com/android/launcher3/folder/FolderNameEditText.java
+++ b/src/com/android/launcher3/folder/FolderNameEditText.java
@@ -102,13 +102,6 @@
mEnteredCompose = value;
}
- protected boolean isEnteredCompose() {
- if (DEBUG) {
- Log.d(TAG, "isEnteredCompose " + mEnteredCompose);
- }
- return mEnteredCompose;
- }
-
private class FolderNameEditTextInputConnection extends InputConnectionWrapper {
FolderNameEditTextInputConnection(InputConnection target, boolean mutable) {
diff --git a/src/com/android/launcher3/folder/FolderNameInfo.java b/src/com/android/launcher3/folder/FolderNameInfo.java
index ecbe46c..1287219 100644
--- a/src/com/android/launcher3/folder/FolderNameInfo.java
+++ b/src/com/android/launcher3/folder/FolderNameInfo.java
@@ -84,6 +84,6 @@
@Override
@NonNull
public String toString() {
- return mLabel.toString() + ":" + mScore;
+ return String.format("%s:%.2f", mLabel, mScore);
}
}
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/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 199d13f..afa3f6d 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -147,7 +147,7 @@
fillIntentInfo(event.srcTarget[0], intent, userHandle);
}
ItemInfo info = (ItemInfo) v.getTag();
- if (Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
+ if (info != null && Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
FileLog.d(TAG, "appLaunch: packageName:" + info.getTargetComponent().getPackageName()
+ ",isWorkApp:" + (info.user != null && !Process.myUserHandle().equals(
userHandle)) + ",launchLocation:" + info.container);
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 5aa0090..6fe6739 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -73,11 +73,10 @@
mDevice.pressHome();
waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
-
waitForLauncherCondition("Personal tab is missing",
- launcher -> launcher.getAppsView().isPersonalTabVisible());
+ launcher -> launcher.getAppsView().isPersonalTabVisible(), 60000);
waitForLauncherCondition("Work tab is missing",
- launcher -> launcher.getAppsView().isWorkTabVisible());
+ launcher -> launcher.getAppsView().isWorkTabVisible(), 60000);
}
@Test
@@ -89,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..6775521 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");
@@ -159,7 +145,7 @@
private static final String WORKSPACE_RES_ID = "workspace";
private static final String APPS_RES_ID = "apps_view";
- private static final String OVERVIEW_RES_ID = "overview_panel";
+ private static final String OVERVIEW_RES_ID = "overview_panel_recents";
private static final String WIDGETS_RES_ID = "widgets_list_view";
private static final String CONTEXT_MENU_RES_ID = "deep_shortcuts_container";
public static final int WAIT_TIME_MS = 10000;
@@ -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;
+ }
+ }
+}