Merge "Make ENABLE_TASK_STABILIZER a toggleable via global settings" into ub-launcher3-master
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 43ae9b8..8f43192 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -42,14 +42,8 @@
android:importantForAccessibility="noHideDescendants"
android:background="#800000FF"
android:layout_gravity="bottom"
+ android:gravity="center"
+ android:textColor="@android:color/white"
android:visibility="gone"
- >
- <TextView
- android:id="@+id/remaining_time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:textColor="@android:color/white"
/>
- </com.android.quickstep.views.DigitalWellBeingToast>
</com.android.quickstep.views.TaskView>
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index cb214af..cc49d46 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -16,16 +16,13 @@
package com.android.quickstep;
-import android.content.Context;
import android.graphics.Matrix;
import android.view.View;
-import androidx.annotation.AnyThread;
-
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
-import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
@@ -34,11 +31,12 @@
import java.util.ArrayList;
import java.util.List;
+import androidx.annotation.AnyThread;
+
/**
* Factory class to create and add an overlays on the TaskView
*/
public class TaskOverlayFactory implements ResourceBasedOverride {
- private static TaskOverlayFactory sInstance;
/** Note that these will be shown in order from top to bottom, if available for the task. */
private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[]{
@@ -49,14 +47,9 @@
new TaskSystemShortcut.Freeform()
};
- public static TaskOverlayFactory get(Context context) {
- Preconditions.assertUIThread();
- if (sInstance == null) {
- sInstance = Overrides.getObject(TaskOverlayFactory.class,
- context.getApplicationContext(), R.string.task_overlay_factory_class);
- }
- return sInstance;
- }
+ public static final MainThreadInitializedObject<TaskOverlayFactory> INSTANCE =
+ new MainThreadInitializedObject<>(c -> Overrides.getObject(TaskOverlayFactory.class,
+ c, R.string.task_overlay_factory_class));
@AnyThread
public boolean needAssist() {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 7a6b135..8b6867f 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -203,7 +203,7 @@
mEventQueue = new MotionEventQueue(mMainThreadChoreographer, TouchConsumer.NO_OP);
mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
mOverviewCallbacks = OverviewCallbacks.get(this);
- mTaskOverlayFactory = TaskOverlayFactory.get(this);
+ mTaskOverlayFactory = TaskOverlayFactory.INSTANCE.get(this);
mTouchInteractionLog = new TouchInteractionLog();
mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
mInputConsumer.registerInputConsumer();
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index a92295e..b34d2bf 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -23,7 +23,6 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
-import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.launcher3.Launcher;
@@ -32,7 +31,8 @@
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.systemui.shared.recents.model.Task;
-public final class DigitalWellBeingToast extends LinearLayout {
+public final class DigitalWellBeingToast extends TextView {
+
public interface InitializeCallback {
void call(float saturation, String contentDescription);
}
@@ -56,12 +56,11 @@
final long appRemainingTimeMs = -1;
final boolean isGroupLimit = true;
post(() -> {
- final TextView remainingTimeText = findViewById(R.id.remaining_time);
if (appUsageLimitTimeMs < 0) {
setVisibility(GONE);
} else {
setVisibility(VISIBLE);
- remainingTimeText.setText(getText(appRemainingTimeMs, isGroupLimit));
+ setText(getText(appRemainingTimeMs, isGroupLimit));
}
callback.call(
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 4cfd8cb..5cbae65 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -81,6 +81,7 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.ViewPool;
import com.android.quickstep.OverviewCallbacks;
import com.android.quickstep.QuickScrubController;
import com.android.quickstep.RecentsAnimationWrapper;
@@ -156,6 +157,8 @@
private final InvariantDeviceProfile mIdp;
+ private final ViewPool<TaskView> mTaskViewPool;
+
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
*/
@@ -304,6 +307,9 @@
.inflate(R.layout.overview_clear_all_button, this, false);
mClearAllButton.setOnClickListener(this::dismissAllTasks);
+ mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
+ 10 /* initial size */);
+
mIsRtl = !Utilities.isRtl(getResources());
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
mTaskTopMargin = getResources()
@@ -384,6 +390,7 @@
mHasVisibleTaskData.delete(task.key.id);
taskView.onTaskListVisibilityChanged(false /* visible */);
}
+ mTaskViewPool.recycle(taskView);
}
}
@@ -487,10 +494,6 @@
int oldChildCount = getChildCount();
- // Ensure there are as many views as there are tasks in the stack (adding and trimming as
- // necessary)
- final LayoutInflater inflater = LayoutInflater.from(getContext());
-
// Unload existing visible task data
unloadVisibleTaskData();
@@ -503,7 +506,7 @@
removeView(mClearAllButton);
}
for (int i = getChildCount(); i < requiredTaskCount; i++) {
- addView(inflater.inflate(R.layout.task, this, false));
+ addView(mTaskViewPool.getView());
}
while (getChildCount() > requiredTaskCount) {
removeView(getChildAt(getChildCount() - 1));
@@ -754,8 +757,7 @@
public void showTask(int runningTaskId) {
if (getChildCount() == 0) {
// Add an empty view for now until the task plan is loaded and applied
- final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
- .inflate(R.layout.task, this, false);
+ final TaskView taskView = mTaskViewPool.getView();
addView(taskView);
addView(mClearAllButton);
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 0194b0b..8169d73 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -107,7 +107,7 @@
public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mCornerRadius = getResources().getDimension(R.dimen.task_corner_radius);
- mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
+ mOverlay = TaskOverlayFactory.INSTANCE.get(context).createOverlay(this);
mPaint.setFilterBitmap(true);
mBackgroundPaint.setColor(Color.WHITE);
mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 2946dfd..ad63c24 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -52,6 +52,7 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.PendingAnimation;
+import com.android.launcher3.util.ViewPool.Reusable;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskOverlayFactory;
@@ -70,7 +71,7 @@
/**
* A task in the Recents view.
*/
-public class TaskView extends FrameLayout implements PageCallbacks {
+public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
private static final String TAG = TaskView.class.getSimpleName();
@@ -399,13 +400,17 @@
setIconAndDimTransitionProgress(iconScale, invert);
}
- public void resetVisualProperties() {
+ private void resetViewTransforms() {
setZoomScale(1);
setTranslationX(0f);
setTranslationY(0f);
setTranslationZ(0);
setAlpha(1f);
setIconScaleAndDim(1);
+ }
+
+ public void resetVisualProperties() {
+ resetViewTransforms();
if (!getRecentsView().getQuickScrubController().isQuickSwitch()) {
// Reset full screen progress unless we are doing back to back quick switch.
setFullscreenProgress(0);
@@ -413,6 +418,12 @@
}
@Override
+ public void onRecycle() {
+ resetViewTransforms();
+ setFullscreenProgress(0);
+ }
+
+ @Override
public void onPageScroll(ScrollState scrollState) {
float curveInterpolation =
CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 75d3425..7eb4015 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -36,6 +36,7 @@
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.PendingAppWidgetHostView;
import java.nio.ByteBuffer;
@@ -131,9 +132,15 @@
width = (int) (mView.getWidth() * scale);
height = (int) (mView.getHeight() * scale);
- // Use software renderer for widgets as we know that they already work
- return BitmapRenderer.createSoftwareBitmap(width + blurSizeOutline,
- height + blurSizeOutline, (c) -> drawDragView(c, scale));
+ if (mView instanceof PendingAppWidgetHostView) {
+ // Use hardware renderer as the icon for the pending app widget may be a hw bitmap
+ return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
+ height + blurSizeOutline, (c) -> drawDragView(c, scale));
+ } else {
+ // Use software renderer for widgets as we know that they already work
+ return BitmapRenderer.createSoftwareBitmap(width + blurSizeOutline,
+ height + blurSizeOutline, (c) -> drawDragView(c, scale));
+ }
}
return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java
new file mode 100644
index 0000000..8af048d
--- /dev/null
+++ b/src/com/android/launcher3/util/ViewPool.java
@@ -0,0 +1,115 @@
+/*
+ * 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.launcher3.util;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.launcher3.util.ViewPool.Reusable;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
+/**
+ * Utility class to maintain a pool of reusable views.
+ * During initialization, views are inflated on the background thread.
+ */
+public class ViewPool<T extends View & Reusable> {
+
+ private final Object[] mPool;
+
+ private final LayoutInflater mInflater;
+ private final ViewGroup mParent;
+ private final int mLayoutId;
+
+ private int mCurrentSize = 0;
+
+ public ViewPool(Context context, @Nullable ViewGroup parent,
+ int layoutId, int maxSize, int initialSize) {
+ mLayoutId = layoutId;
+ mParent = parent;
+ mInflater = LayoutInflater.from(context);
+ mPool = new Object[maxSize];
+
+ if (initialSize > 0) {
+ initPool(initialSize);
+ }
+ }
+
+ @UiThread
+ private void initPool(int initialSize) {
+ Preconditions.assertUIThread();
+ Handler handler = new Handler();
+
+ // Inflate views on a non looper thread. This allows us to catch errors like calling
+ // "new Handler()" in constructor easily.
+ new Thread(() -> {
+ for (int i = 0; i < initialSize; i++) {
+ T view = inflateNewView();
+ handler.post(() -> addToPool(view));
+ }
+ }).start();
+ }
+
+ @UiThread
+ public void recycle(T view) {
+ Preconditions.assertUIThread();
+ view.onRecycle();
+ addToPool(view);
+ }
+
+ @UiThread
+ private void addToPool(T view) {
+ Preconditions.assertUIThread();
+ if (mCurrentSize >= mPool.length) {
+ // pool is full
+ return;
+ }
+
+ mPool[mCurrentSize] = view;
+ mCurrentSize++;
+ }
+
+ @UiThread
+ public T getView() {
+ Preconditions.assertUIThread();
+ if (mCurrentSize > 0) {
+ mCurrentSize--;
+ return (T) mPool[mCurrentSize];
+ }
+ return inflateNewView();
+ }
+
+ @AnyThread
+ private T inflateNewView() {
+ return (T) mInflater.inflate(mLayoutId, mParent, false);
+ }
+
+ /**
+ * Interface to indicate that a view is reusable
+ */
+ public interface Reusable {
+
+ /**
+ * Called when a view is recycled / added back to the pool
+ */
+ void onRecycle();
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 7582d53..3ffd30c 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -32,7 +32,7 @@
}
static BySelector getAppIconSelector(String appName, LauncherInstrumentation launcher) {
- return By.clazz(TextView.class).text(appName).pkg(launcher.mLauncherPackageName);
+ return By.clazz(TextView.class).text(appName).pkg(launcher.getLauncherPackageName());
}
/**
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 7070555..bb399d5 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -89,7 +89,6 @@
private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null);
private final UiDevice mDevice;
- final String mLauncherPackageName;
private final boolean mSwipeUpEnabled;
private Boolean mSwipeUpEnabledOverride = null;
private final Instrumentation mInstrumentation;
@@ -101,7 +100,6 @@
public LauncherInstrumentation(Instrumentation instrumentation) {
mInstrumentation = instrumentation;
mDevice = UiDevice.getInstance(instrumentation);
- mLauncherPackageName = mDevice.getLauncherPackageName();
final boolean swipeUpEnabledDefault =
!SwipeUpSetting.isSwipeUpSettingAvailable() ||
SwipeUpSetting.isSwipeUpEnabledDefaultValue();
@@ -382,13 +380,16 @@
UiObject2 waitForLauncherObject(String resName) {
final BySelector selector = getLauncherObjectSelector(resName);
final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
- assertNotNull("Can't find a launcher object; selector: " + selector + ", current launcher: "
- + mDevice.getLauncherPackageName(), object);
+ assertNotNull("Can't find a launcher object; selector: " + selector, object);
return object;
}
BySelector getLauncherObjectSelector(String resName) {
- return By.res(mLauncherPackageName, resName);
+ return By.res(getLauncherPackageName(), resName);
+ }
+
+ String getLauncherPackageName() {
+ return mDevice.getLauncherPackageName();
}
@NonNull