Merge "Slowing down dismiss gesture to reduce flakiness" into ub-launcher3-master
diff --git a/Android.mk b/Android.mk
index 628b632..74b96d4 100644
--- a/Android.mk
+++ b/Android.mk
@@ -319,7 +319,7 @@
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
LOCAL_PROGUARD_ENABLED := full
-LOCAL_PACKAGE_NAME := Launcher3QuickStepGoIconRecents
+LOCAL_PACKAGE_NAME := Launcher3GoIconRecents
LOCAL_PRIVILEGED_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
index 9a18b45..abb9242 100644
--- a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -20,7 +20,6 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.content.ComponentName;
import android.graphics.Rect;
import androidx.annotation.Nullable;
@@ -30,7 +29,6 @@
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.views.IconRecentsView;
-import com.android.quickstep.views.RecentsView;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
@@ -43,7 +41,7 @@
public final class FallbackActivityControllerHelper extends
GoActivityControlHelper<RecentsActivity> {
- public FallbackActivityControllerHelper(ComponentName homeComponent) { }
+ public FallbackActivityControllerHelper() { }
@Override
public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
@@ -98,7 +96,7 @@
@Nullable
@Override
- public RecentsView getVisibleRecentsView() {
+ public IconRecentsView getVisibleRecentsView() {
RecentsActivity activity = getCreatedActivity();
if (activity != null && activity.hasWindowFocus()) {
return activity.getOverviewPanel();
diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
new file mode 100644
index 0000000..77c3f33
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+
+import com.android.systemui.shared.recents.model.Task;
+
+import java.util.ArrayList;
+
+/**
+ * Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the
+ * appropriate {@link Task} from the recents task list.
+ */
+public final class TaskAdapter extends Adapter<TaskHolder> {
+
+ private static final int MAX_TASKS_TO_DISPLAY = 6;
+ private static final String TAG = "TaskAdapter";
+ private final ArrayList<Task> mTaskList;
+
+ public TaskAdapter(@NonNull ArrayList<Task> taskList) {
+ mTaskList = taskList;
+ }
+
+ @Override
+ public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ // TODO: Swap in an actual task view here (view w/ icon, label, etc.)
+ TextView stubView = new TextView(parent.getContext());
+ return new TaskHolder(stubView);
+ }
+
+ @Override
+ public void onBindViewHolder(TaskHolder holder, int position) {
+ holder.bindTask(mTaskList.get(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return Math.min(mTaskList.size(), MAX_TASKS_TO_DISPLAY);
+ }
+}
diff --git a/go/quickstep/src/com/android/quickstep/TaskHolder.java b/go/quickstep/src/com/android/quickstep/TaskHolder.java
new file mode 100644
index 0000000..1ea6d76
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/TaskHolder.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+import com.android.systemui.shared.recents.model.Task;
+
+/**
+ * A recycler view holder that holds the task view and binds {@link Task} content (app title, icon,
+ * etc.) to the view.
+ */
+final class TaskHolder extends ViewHolder {
+
+ // TODO: Implement the actual task view to be held.
+ // For now, we just use a simple text view.
+ private final TextView mStubView;
+
+ public TaskHolder(TextView stubView) {
+ super(stubView);
+ mStubView = stubView;
+ }
+
+ /**
+ * Bind task content to the view. This includes the task icon and title as well as binding
+ * input handlers such as which task to launch/remove.
+ *
+ * @param task the task to bind to the view this
+ */
+ public void bindTask(Task task) {
+ mStubView.setText("Stub task view: " + task.titleDescription);
+ }
+}
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index e4741e9..00415fe 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -21,8 +21,14 @@
import android.view.ViewDebug;
import android.widget.FrameLayout;
+import com.android.quickstep.TaskAdapter;
+import com.android.systemui.shared.recents.model.Task;
+
+import java.util.ArrayList;
+
/**
- * Root view for the icon recents view.
+ * Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code
+ * base.
*/
public final class IconRecentsView extends FrameLayout {
@@ -45,6 +51,11 @@
@Override
public void setValue(IconRecentsView view, float v) {
ALPHA.set(view, v);
+ if (view.getVisibility() != VISIBLE && v > 0) {
+ view.setVisibility(VISIBLE);
+ } else if (view.getVisibility() != GONE && v == 0){
+ view.setVisibility(GONE);
+ }
}
@Override
@@ -58,12 +69,24 @@
* is top aligned and 0.5 is centered vertically.
*/
@ViewDebug.ExportedProperty(category = "launcher")
+
+ // TODO: Write a recents task list observer that creates/updates tasks and signals task adapter.
+ private static final ArrayList<Task> DUMMY_TASK_LIST = new ArrayList<>();
+
private float mTranslationYFactor;
+ private TaskAdapter mTaskAdapter;
public IconRecentsView(Context context, AttributeSet attrs) {
super(context, attrs);
}
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mTaskAdapter = new TaskAdapter(DUMMY_TASK_LIST);
+ // TODO: Hook task adapter up to recycler view.
+ }
+
public void setTranslationYFactor(float translationFactor) {
mTranslationYFactor = translationFactor;
setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
index 6d374c6..4450b4b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -185,6 +185,7 @@
if (mSupportsRoundedCornersOnWindows) {
cornerRadius = Utilities.mapRange(params.progress, mWindowCornerRadius,
mTaskCornerRadius);
+ mCurrentCornerRadius = cornerRadius;
}
}
alpha = mTaskAlphaCallback.apply(app, params.targetAlpha);
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 8faf95d..a7bf2c3 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
@@ -90,6 +90,7 @@
import com.android.quickstep.OverviewCallbacks;
import com.android.quickstep.RecentsAnimationWrapper;
import com.android.quickstep.RecentsModel;
+import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.util.ClipAnimationHelper;
@@ -113,7 +114,7 @@
@TargetApi(Build.VERSION_CODES.P)
public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable,
TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
- InvariantDeviceProfile.OnIDPChangeListener {
+ InvariantDeviceProfile.OnIDPChangeListener, TaskThumbnailChangeListener {
private static final String TAG = RecentsView.class.getSimpleName();
@@ -171,14 +172,6 @@
*/
private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
- public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
- if (!mHandleTaskStackChanges) {
- return;
- }
- updateThumbnail(taskId, snapshot);
- }
-
- @Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
if (!mHandleTaskStackChanges) {
return;
@@ -262,7 +255,6 @@
private boolean mOverviewStateEnabled;
private boolean mHandleTaskStackChanges;
- private Runnable mNextPageSwitchRunnable;
private boolean mSwipeDownShouldLaunchApp;
private boolean mTouchDownToStartHome;
private final int mTouchSlop;
@@ -340,6 +332,19 @@
return mIsRtl;
}
+ @Override
+ public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
+ if (mHandleTaskStackChanges) {
+ TaskView taskView = getTaskView(taskId);
+ if (taskView != null) {
+ Task task = taskView.getTask();
+ taskView.getThumbnail().setThumbnail(task, thumbnailData);
+ return task;
+ }
+ }
+ return null;
+ }
+
public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
TaskView taskView = getTaskView(taskId);
if (taskView != null) {
@@ -371,6 +376,7 @@
mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this);
+ RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
mIdp.addOnChangeListener(this);
}
@@ -382,6 +388,7 @@
mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
mSyncTransactionApplier = null;
+ RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
mIdp.removeOnChangeListener(this);
}
@@ -421,17 +428,9 @@
updateTaskStackListenerState();
}
- public void setNextPageSwitchRunnable(Runnable r) {
- mNextPageSwitchRunnable = r;
- }
-
@Override
protected void onPageEndTransition() {
super.onPageEndTransition();
- if (mNextPageSwitchRunnable != null) {
- mNextPageSwitchRunnable.run();
- mNextPageSwitchRunnable = null;
- }
if (getNextPage() > 0) {
setSwipeDownShouldLaunchApp(true);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 9422141..fb58c24 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -29,7 +29,6 @@
import android.animation.TimeInterpolator;
import android.app.ActivityOptions;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Outline;
import android.graphics.drawable.Drawable;
@@ -215,6 +214,7 @@
* Updates this task view to the given {@param task}.
*/
public void bind(Task task) {
+ cancelPendingLoadTasks();
mTask = task;
mSnapshotView.bind(task);
}
@@ -305,15 +305,15 @@
if (mTask == null) {
return;
}
+ cancelPendingLoadTasks();
if (visible) {
// These calls are no-ops if the data is already loaded, try and load the high
// resolution thumbnail if the state permits
RecentsModel model = RecentsModel.INSTANCE.get(getContext());
TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
TaskIconCache iconCache = model.getIconCache();
- mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(mTask,
- !thumbnailCache.getHighResLoadingState().isEnabled() /* reducedResolution */,
- (task) -> mSnapshotView.setThumbnail(task, task.thumbnail));
+ mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(
+ mTask, thumbnail -> mSnapshotView.setThumbnail(mTask, thumbnail));
mIconLoadRequest = iconCache.updateIconInBackground(mTask,
(task) -> {
setIcon(task.icon);
@@ -325,17 +325,22 @@
});
});
} else {
- if (mThumbnailLoadRequest != null) {
- mThumbnailLoadRequest.cancel();
- }
- if (mIconLoadRequest != null) {
- mIconLoadRequest.cancel();
- }
mSnapshotView.setThumbnail(null, null);
setIcon(null);
}
}
+ private void cancelPendingLoadTasks() {
+ if (mThumbnailLoadRequest != null) {
+ mThumbnailLoadRequest.cancel();
+ mThumbnailLoadRequest = null;
+ }
+ if (mIconLoadRequest != null) {
+ mIconLoadRequest.cancel();
+ mIconLoadRequest = null;
+ }
+ }
+
private boolean showTaskMenu() {
getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
mMenuView = TaskMenuView.showForTask(this);
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index f3e1545..81a22a1 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -15,33 +15,29 @@
*/
package com.android.quickstep;
+import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.os.Build;
-import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
-import android.util.SparseArray;
-import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.Preconditions;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import java.util.ArrayList;
+import java.util.List;
import java.util.function.Consumer;
-import androidx.annotation.WorkerThread;
-
-import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
-
/**
* Singleton class to load and manage recents model.
*/
@@ -54,8 +50,8 @@
public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
new MainThreadInitializedObject<>(c -> new RecentsModel(c));
+ private final List<TaskThumbnailChangeListener> mThumbnailChangeListeners = new ArrayList<>();
private final Context mContext;
- private final MainThreadExecutor mMainThreadExecutor;
private ISystemUiProxy mSystemUiProxy;
@@ -68,9 +64,6 @@
private RecentsModel(Context context) {
mContext = context;
-
- mMainThreadExecutor = new MainThreadExecutor();
-
HandlerThread loaderThread = new HandlerThread("TaskThumbnailIconCache",
Process.THREAD_PRIORITY_BACKGROUND);
loaderThread.start();
@@ -168,6 +161,18 @@
});
}
+ @Override
+ public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
+ mThumbnailCache.updateTaskSnapShot(taskId, snapshot);
+
+ for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) {
+ Task task = mThumbnailChangeListeners.get(i).onTaskThumbnailChanged(taskId, snapshot);
+ if (task != null) {
+ task.thumbnail = snapshot;
+ }
+ }
+ }
+
public void setSystemUiProxy(ISystemUiProxy systemUiProxy) {
mSystemUiProxy = systemUiProxy;
}
@@ -239,4 +244,17 @@
+ ": ", e);
}
}
+
+ public void addThumbnailChangeListener(TaskThumbnailChangeListener listener) {
+ mThumbnailChangeListeners.add(listener);
+ }
+
+ public void removeThumbnailChangeListener(TaskThumbnailChangeListener listener) {
+ mThumbnailChangeListeners.remove(listener);
+ }
+
+ public interface TaskThumbnailChangeListener {
+
+ Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index 7a216ed..d05196b 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -26,6 +26,7 @@
import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.util.Preconditions;
import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -38,7 +39,7 @@
private final MainThreadExecutor mMainThreadExecutor;
private final int mCacheSize;
- private final TaskKeyLruCache<ThumbnailData> mCache;
+ private final ThumbnailCache mCache;
private final HighResLoadingState mHighResLoadingState;
public static class HighResLoadingState {
@@ -98,7 +99,7 @@
Resources res = context.getResources();
mCacheSize = res.getInteger(R.integer.recentsThumbnailCacheSize);
- mCache = new TaskKeyLruCache<>(mCacheSize);
+ mCache = new ThumbnailCache(mCacheSize);
}
/**
@@ -106,13 +107,20 @@
*/
public void updateThumbnailInCache(Task task) {
Preconditions.assertUIThread();
-
// Fetch the thumbnail for this task and put it in the cache
- updateThumbnailInBackground(task, true /* reducedResolution */, (t) -> {
- mCache.put(task.key, t.thumbnail);
- });
+ if (task.thumbnail == null) {
+ updateThumbnailInBackground(task.key, true /* reducedResolution */,
+ t -> task.thumbnail = t);
+ }
}
+ /**
+ * Synchronously updates the thumbnail in the cache if it is already there.
+ */
+ public void updateTaskSnapShot(int taskId, ThumbnailData thumbnail) {
+ Preconditions.assertUIThread();
+ mCache.updateIfAlreadyInCache(taskId, thumbnail);
+ }
/**
* Asynchronously fetches the icon and other task data for the given {@param task}.
@@ -120,22 +128,33 @@
* @param callback The callback to receive the task after its data has been populated.
* @return A cancelable handle to the request
*/
- public ThumbnailLoadRequest updateThumbnailInBackground(Task task, boolean reducedResolution,
- Consumer<Task> callback) {
+ public ThumbnailLoadRequest updateThumbnailInBackground(
+ Task task, Consumer<ThumbnailData> callback) {
Preconditions.assertUIThread();
+ boolean reducedResolution = !mHighResLoadingState.isEnabled();
if (task.thumbnail != null && (!task.thumbnail.reducedResolution || reducedResolution)) {
// Nothing to load, the thumbnail is already high-resolution or matches what the
// request, so just callback
- callback.accept(task);
+ callback.accept(task.thumbnail);
return null;
}
- ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(task.key);
+
+ return updateThumbnailInBackground(task.key, !mHighResLoadingState.isEnabled(), t -> {
+ task.thumbnail = t;
+ callback.accept(t);
+ });
+ }
+
+ private ThumbnailLoadRequest updateThumbnailInBackground(TaskKey key, boolean reducedResolution,
+ Consumer<ThumbnailData> callback) {
+ Preconditions.assertUIThread();
+
+ ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);
if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || reducedResolution)) {
// Already cached, lets use that thumbnail
- task.thumbnail = cachedThumbnail;
- callback.accept(task);
+ callback.accept(cachedThumbnail);
return null;
}
@@ -144,14 +163,14 @@
@Override
public void run() {
ThumbnailData thumbnail = ActivityManagerWrapper.getInstance().getTaskThumbnail(
- task.key.id, reducedResolution);
+ key.id, reducedResolution);
if (isCanceled()) {
// We don't call back to the provided callback in this case
return;
}
mMainThreadExecutor.execute(() -> {
- task.thumbnail = thumbnail;
- callback.accept(task);
+ mCache.put(key, thumbnail);
+ callback.accept(thumbnail);
onEnd();
});
}
@@ -196,4 +215,21 @@
this.reducedResolution = reducedResolution;
}
}
+
+ private static class ThumbnailCache extends TaskKeyLruCache<ThumbnailData> {
+
+ public ThumbnailCache(int cacheSize) {
+ super(cacheSize);
+ }
+
+ /**
+ * Updates the cache entry if it is already present in the cache
+ */
+ public void updateIfAlreadyInCache(int taskId, ThumbnailData thumbnailData) {
+ ThumbnailData oldData = getCacheEntry(taskId);
+ if (oldData != null) {
+ putCacheEntry(taskId, thumbnailData);
+ }
+ }
+ }
}