diff --git a/Android.mk b/Android.mk index 7a8ac69b25..9d92a9d2e0 100644 --- a/Android.mk +++ b/Android.mk @@ -320,7 +320,7 @@ LOCAL_RESOURCE_DIR := \ 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/res/layout/icon_recents_root_view.xml b/go/quickstep/res/layout/icon_recents_root_view.xml index 82d58909e5..6c5095000b 100644 --- a/go/quickstep/res/layout/icon_recents_root_view.xml +++ b/go/quickstep/res/layout/icon_recents_root_view.xml @@ -18,13 +18,11 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:gravity="center"> - - + + android:scrollbars="none"/> \ No newline at end of file diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java index afc8e47464..47e0e614f3 100644 --- a/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java +++ b/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java @@ -19,14 +19,12 @@ package com.android.launcher3.uioverrides; import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS; import static com.android.launcher3.anim.Interpolators.DEACCEL_2; -import android.graphics.Rect; - import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.userevent.nano.LauncherLogProto; -import com.android.quickstep.RecentsModel; +import com.android.quickstep.views.IconRecentsView; /** * Definition for overview state @@ -49,6 +47,12 @@ public class OverviewState extends LauncherState { return new float[] {1f, 0f}; } + @Override + public void onStateEnabled(Launcher launcher) { + IconRecentsView recentsView = launcher.getOverviewPanel(); + recentsView.onBeginTransitionToOverview(); + } + @Override public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) { return new PageAlphaProvider(DEACCEL_2) { diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java index 12f5360c22..d7cba39e36 100644 --- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java +++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java @@ -35,7 +35,8 @@ import java.util.ArrayList; * Provides recents-related {@link UiFactory} logic and classes. */ public abstract class RecentsUiFactory { - + + public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true; // Scale recents takes before animating in private static final float RECENTS_PREPARE_SCALE = 1.33f; 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 0000000000..57cd60a992 --- /dev/null +++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java @@ -0,0 +1,63 @@ +/* + * 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 { + + private static final int MAX_TASKS_TO_DISPLAY = 6; + private static final String TAG = "TaskAdapter"; + private final TaskListLoader mLoader; + + public TaskAdapter(@NonNull TaskListLoader loader) { + mLoader = loader; + } + + @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) { + ArrayList tasks = mLoader.getCurrentTaskList(); + if (position >= tasks.size()) { + // Task list has updated. + return; + } + holder.bindTask(tasks.get(position)); + } + + @Override + public int getItemCount() { + return Math.min(mLoader.getCurrentTaskList().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 0000000000..1ea6d7610f --- /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/TaskListLoader.java b/go/quickstep/src/com/android/quickstep/TaskListLoader.java new file mode 100644 index 0000000000..9f359b4c1b --- /dev/null +++ b/go/quickstep/src/com/android/quickstep/TaskListLoader.java @@ -0,0 +1,118 @@ +/* + * 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.content.Context; + +import androidx.annotation.Nullable; + +import com.android.systemui.shared.recents.model.Task; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +/** + * This class is responsible for maintaining the list of tasks and the task content. The list must + * be updated explicitly with {@link #loadTaskList} whenever the list needs to be + * up-to-date. + */ +public final class TaskListLoader { + + private final RecentsModel mRecentsModel; + + private ArrayList mTaskList = new ArrayList<>(); + private int mTaskListChangeId; + + public TaskListLoader(Context context) { + mRecentsModel = RecentsModel.INSTANCE.get(context); + } + + /** + * Returns the current task list as of the last completed load (see + * {@link #loadTaskList}). This list of tasks is guaranteed to always have all its task + * content loaded. + * + * @return the current list of tasks w/ all content loaded + */ + public ArrayList getCurrentTaskList() { + return mTaskList; + } + + /** + * Fetches the most recent tasks and updates the task list asynchronously. In addition it + * loads the content for each task (icon and label). The callback and task list being updated + * only occur when all task content is fully loaded and up-to-date. + * + * @param onTasksLoadedCallback callback for when the tasks are fully loaded. Done on the UI + * thread + */ + public void loadTaskList(@Nullable Consumer> onTasksLoadedCallback) { + if (mRecentsModel.isTaskListValid(mTaskListChangeId)) { + // Current task list is already up to date. No need to update. + if (onTasksLoadedCallback != null) { + onTasksLoadedCallback.accept(mTaskList); + } + return; + } + // TODO: Look into error checking / more robust handling for when things go wrong. + mTaskListChangeId = mRecentsModel.getTasks(tasks -> { + // Reverse tasks to put most recent at the bottom of the view + Collections.reverse(tasks); + // Load task content + loadTaskContents(tasks, () -> { + mTaskList = tasks; + if (onTasksLoadedCallback != null) { + onTasksLoadedCallback.accept(mTaskList); + } + }); + }); + } + + /** + * Loads task content for a list of tasks, including the label and the icon. Uses the list of + * tasks since the last load as a cache for loaded content. + * + * @param tasksToLoad list of tasks that need to load their content + * @param onLoadedCallback runnable to run after all tasks have loaded their content + */ + private void loadTaskContents(ArrayList tasksToLoad, + @Nullable Runnable onLoadedCallback) { + AtomicInteger loadRequestsCount = new AtomicInteger(0); + for (Task task : tasksToLoad) { + int index = mTaskList.indexOf(task); + if (index >= 0) { + // If we've already loaded the task and have its content then just copy it over. + Task loadedTask = mTaskList.get(index); + task.titleDescription = loadedTask.titleDescription; + task.icon = loadedTask.icon; + } else { + // Otherwise, load the content in the background. + loadRequestsCount.getAndIncrement(); + mRecentsModel.getIconCache().updateIconInBackground(task, loadedTask -> { + if (loadRequestsCount.decrementAndGet() == 0 && onLoadedCallback != null) { + onLoadedCallback.run(); + } + }); + } + } + if (loadRequestsCount.get() == 0 && onLoadedCallback != null) { + onLoadedCallback.run(); + } + } +} diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index e4741e9d50..afb05409ca 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -15,14 +15,24 @@ */ package com.android.quickstep.views; +import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL; + import android.content.Context; import android.util.AttributeSet; import android.util.FloatProperty; import android.view.ViewDebug; import android.widget.FrameLayout; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.launcher3.R; +import com.android.quickstep.TaskAdapter; +import com.android.quickstep.TaskListLoader; + /** - * 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 +55,11 @@ public final class IconRecentsView extends FrameLayout { @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,10 +73,44 @@ public final class IconRecentsView extends FrameLayout { * is top aligned and 0.5 is centered vertically. */ @ViewDebug.ExportedProperty(category = "launcher") + + private final Context mContext; + private float mTranslationYFactor; + private TaskAdapter mTaskAdapter; + private RecyclerView mTaskRecyclerView; + private TaskListLoader mTaskLoader; public IconRecentsView(Context context, AttributeSet attrs) { super(context, attrs); + mContext = context; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mTaskLoader = new TaskListLoader(mContext); + mTaskAdapter = new TaskAdapter(mTaskLoader); + mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view); + mTaskRecyclerView.setAdapter(mTaskAdapter); + mTaskRecyclerView.setLayoutManager( + new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */)); + } + + /** + * Logic for when we know we are going to overview/recents and will be putting up the recents + * view. This should be used to prepare recents (e.g. load any task data, etc.) before it + * becomes visible. + * + * TODO: Hook this up for fallback recents activity as well + */ + public void onBeginTransitionToOverview() { + // Load any task changes + mTaskLoader.loadTaskList(tasks -> { + // TODO: Put up some loading UI while task content is loading. May have to do something + // smarter when animating from app to overview. + mTaskAdapter.notifyDataSetChanged(); + }); } public void setTranslationYFactor(float translationFactor) { diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java index 76abe8d1ba..96b0a9e251 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java @@ -48,7 +48,7 @@ public class BaseIconFactory implements AutoCloseable { private ShadowGenerator mShadowGenerator; private Drawable mWrapperIcon; - private int mWrapperBackgroundColor; + private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND; protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) { mContext = context.getApplicationContext(); diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml index 542a235ada..62d0500160 100644 --- a/quickstep/AndroidManifest.xml +++ b/quickstep/AndroidManifest.xml @@ -38,7 +38,8 @@ + android:permission="android.permission.STATUS_BAR_SERVICE" + android:directBootAware="true" > diff --git a/quickstep/recents_ui_overrides/res/layout/hint.xml b/quickstep/recents_ui_overrides/res/layout/hint.xml deleted file mode 100644 index 7e2d6af81a..0000000000 --- a/quickstep/recents_ui_overrides/res/layout/hint.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/quickstep/recents_ui_overrides/res/layout/hint_container.xml b/quickstep/recents_ui_overrides/res/layout/hint_container.xml index 336f63e3fb..f8723fc81b 100644 --- a/quickstep/recents_ui_overrides/res/layout/hint_container.xml +++ b/quickstep/recents_ui_overrides/res/layout/hint_container.xml @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. --> - \ No newline at end of file diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java index 027fd910c0..81ff0c7cad 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java @@ -44,6 +44,7 @@ import java.util.ArrayList; */ public abstract class RecentsUiFactory { + public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false; private static final AsyncCommand SET_SHELF_HEIGHT_CMD = (visible, height) -> WindowManagerWrapper.getInstance().setShelfHeight(visible != 0, height); @@ -61,7 +62,6 @@ public abstract class RecentsUiFactory { if (swipeUpToHome) { list.add(new FlingAndHoldTouchController(launcher)); - list.add(new OverviewToAllAppsTouchController(launcher)); } else { if (launcher.getDeviceProfile().isVerticalBarLayout()) { list.add(new OverviewToAllAppsTouchController(launcher)); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java new file mode 100644 index 0000000000..109a4c5f79 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java @@ -0,0 +1,200 @@ +/* + * 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 static android.view.MotionEvent.ACTION_CANCEL; +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_MOVE; +import static android.view.MotionEvent.ACTION_POINTER_UP; +import static android.view.MotionEvent.ACTION_UP; + +import android.content.Context; +import android.graphics.PointF; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; +import android.view.Display; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.ViewConfiguration; +import android.view.WindowManager; +import com.android.systemui.shared.recents.ISystemUiProxy; +import com.android.systemui.shared.system.NavigationBarCompat; +import com.android.systemui.shared.system.WindowManagerWrapper; +import com.android.launcher3.R; + +/** + * Touch consumer for handling events to launch assistant from launcher + */ +public class AssistantTouchConsumer implements InputConsumer { + private static final String TAG = "AssistantTouchConsumer"; + + private final PointF mDownPos = new PointF(); + private final PointF mLastPos = new PointF(); + private int mActivePointerId = -1; + + private final int mDisplayRotation; + private final Rect mStableInsets = new Rect(); + + private final float mDragSlop; + private final float mTouchSlop; + private final float mThreshold; + + private float mStartDisplacement; + private boolean mPassedDragSlop; + private boolean mPassedTouchSlop; + private long mPassedTouchSlopTime; + private boolean mLaunchedAssistant; + private float mLastProgress; + + private final ISystemUiProxy mSysUiProxy; + + public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy) { + mSysUiProxy = systemUiProxy; + + mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx(); + mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx(); + mThreshold = context.getResources().getDimension(R.dimen.gestures_assistant_threshold); + + Display display = context.getSystemService(WindowManager.class).getDefaultDisplay(); + mDisplayRotation = display.getRotation(); + WindowManagerWrapper.getInstance().getStableInsets(mStableInsets); + } + + @Override + public int getType() { + return TYPE_ASSISTANT; + } + + @Override + public void onMotionEvent(MotionEvent ev) { + // TODO add logging + switch (ev.getActionMasked()) { + case ACTION_DOWN: { + mActivePointerId = ev.getPointerId(0); + mDownPos.set(ev.getX(), ev.getY()); + mLastPos.set(mDownPos); + mLastProgress = -1; + break; + } + case ACTION_POINTER_UP: { + int ptrIdx = ev.getActionIndex(); + int ptrId = ev.getPointerId(ptrIdx); + if (ptrId == mActivePointerId) { + final int newPointerIdx = ptrIdx == 0 ? 1 : 0; + mDownPos.set( + ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x), + ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y)); + mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx)); + mActivePointerId = ev.getPointerId(newPointerIdx); + } + break; + } + case ACTION_MOVE: { + int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == -1) { + break; + } + mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); + float displacement = getDisplacement(ev); + + if (!mPassedDragSlop) { + // Normal gesture, ensure we pass the drag slop before we start tracking + // the gesture + if (Math.abs(displacement) > mDragSlop) { + mPassedDragSlop = true; + mStartDisplacement = displacement; + mPassedTouchSlopTime = SystemClock.uptimeMillis(); + } + } + + if (!mPassedTouchSlop) { + if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) >= + mTouchSlop) { + mPassedTouchSlop = true; + if (!mPassedDragSlop) { + mPassedDragSlop = true; + mStartDisplacement = displacement; + mPassedTouchSlopTime = SystemClock.uptimeMillis(); + } + } + } + + if (mPassedDragSlop) { + // Move + float distance = mStartDisplacement - displacement; + if (distance >= 0) { + onAssistantProgress(distance / mThreshold); + } + } + break; + } + case ACTION_CANCEL: + break; + case ACTION_UP: { + if (ev.getEventTime() - mPassedTouchSlopTime < ViewConfiguration.getTapTimeout()) { + onAssistantProgress(1); + } + + break; + } + } + } + + private void onAssistantProgress(float progress) { + if (mLastProgress == progress) { + return; + } + try { + mSysUiProxy.onAssistantProgress(Math.max(0, Math.min(1, progress))); + if (progress >= 1 && !mLaunchedAssistant) { + mSysUiProxy.startAssistant(new Bundle()); + mLaunchedAssistant = true; + } + mLastProgress = progress; + } catch (RemoteException e) { + Log.w(TAG, "Failed to notify SysUI to start/send assistant progress: " + progress, e); + } + } + + private boolean isNavBarOnRight() { + return mDisplayRotation == Surface.ROTATION_90 && mStableInsets.right > 0; + } + + private boolean isNavBarOnLeft() { + return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0; + } + + private float getDisplacement(MotionEvent ev) { + float eventX = ev.getX(); + float eventY = ev.getY(); + float displacement = eventY - mDownPos.y; + if (isNavBarOnRight()) { + displacement = eventX - mDownPos.x; + } else if (isNavBarOnLeft()) { + displacement = mDownPos.x - eventX; + } + return displacement; + } + + static boolean withinTouchRegion(Context context, float x) { + return x > context.getResources().getDisplayMetrics().widthPixels + - context.getResources().getDimension(R.dimen.gestures_assistant_width); + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java new file mode 100644 index 0000000000..d919a3e711 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java @@ -0,0 +1,47 @@ +/* + * 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.content.Context; +import android.content.Intent; +import android.view.MotionEvent; + +/** + * A dummy input consumer used when the device is still locked, e.g. from secure camera. + */ +public class DeviceLockedInputConsumer implements InputConsumer { + + private final Context mContext; + + public DeviceLockedInputConsumer(Context context) { + mContext = context; + } + + @Override + public int getType() { + return TYPE_DEVICE_LOCKED; + } + + @Override + public void onMotionEvent(MotionEvent ev) { + // For now, just start the home intent so user is prompted to unlock the device. + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mContext.startActivity(new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java index 8dfb9abdda..ad9fe78f85 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java @@ -23,7 +23,16 @@ import android.view.MotionEvent; @TargetApi(Build.VERSION_CODES.O) public interface InputConsumer { - InputConsumer NO_OP = new InputConsumer() { }; + + int TYPE_NO_OP = 0; + int TYPE_OVERVIEW = 1; + int TYPE_OTHER_ACTIVITY = 2; + int TYPE_ASSISTANT = 3; + int TYPE_DEVICE_LOCKED = 4; + + InputConsumer NO_OP = () -> TYPE_NO_OP; + + int getType(); default boolean isActive() { return false; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java index ffd3b4b06e..9650a5316a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -100,17 +100,19 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe public HomeAnimationFactory prepareHomeUI(Launcher activity) { final DeviceProfile dp = activity.getDeviceProfile(); final RecentsView recentsView = activity.getOverviewPanel(); - final ComponentName component = recentsView.getRunningTaskView().getTask().key - .sourceComponent; - - final View workspaceView = activity.getWorkspace().getFirstMatchForAppClose(component); - final FloatingIconView floatingView = workspaceView == null ? null - : new FloatingIconView(activity); - final Rect iconLocation = new Rect(); - if (floatingView != null) { - floatingView.matchPositionOf(activity, workspaceView, true /* hideOriginal */, - iconLocation); + final TaskView runningTaskView = recentsView.getRunningTaskView(); + final View workspaceView; + if (runningTaskView != null) { + ComponentName component = runningTaskView.getTask().key.sourceComponent; + workspaceView = activity.getWorkspace().getFirstMatchForAppClose(component); + } else { + workspaceView = null; } + final Rect iconLocation = new Rect(); + final FloatingIconView floatingView = workspaceView == null ? null + : FloatingIconView.getFloatingIconView(activity, workspaceView, + true /* hideOriginal */, false /* useDrawableAsIs */, + activity.getDeviceProfile().getAspectRatioWithInsets(), iconLocation, null); return new HomeAnimationFactory() { @Nullable diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java index 76c1c9af47..e3afb92dbf 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java @@ -36,6 +36,8 @@ import android.content.Intent; import android.graphics.PointF; import android.graphics.Rect; import android.os.Build; +import android.os.Handler; +import android.os.Looper; import android.view.Display; import android.view.MotionEvent; import android.view.Surface; @@ -105,6 +107,12 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC // TODO: Start displacement should have both x and y private float mStartDisplacement; + private Handler mMainThreadHandler; + private Runnable mCancelRecentsAnimationRunnable = () -> { + ActivityManagerWrapper.getInstance().cancelRecentsAnimation( + true /* restoreHomeStackPosition */); + }; + public OtherActivityInputConsumer(Context base, RunningTaskInfo runningTaskInfo, RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl, boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks, @@ -113,6 +121,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC SwipeSharedState swipeSharedState) { super(base); + mMainThreadHandler = new Handler(Looper.getMainLooper()); mRunningTask = runningTaskInfo; mRecentsModel = recentsModel; mHomeIntent = homeIntent; @@ -138,6 +147,11 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mPassedTouchSlop = mPassedDragSlop = mSwipeSharedState.getActiveListener() != null; } + @Override + public int getType() { + return TYPE_OTHER_ACTIVITY; + } + @Override public void onMotionEvent(MotionEvent ev) { if (mVelocityTracker == null) { @@ -216,7 +230,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mTouchSlop) { mPassedTouchSlop = true; - TOUCH_INTERACTION_LOG.startQuickStep(); + TOUCH_INTERACTION_LOG.addLog("startQuickstep"); if (mIsDeferredDownTarget) { // Deferred gesture, start the animation and gesture tracking once // we pass the actual touch slop @@ -279,7 +293,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC } private void startTouchTrackingForWindowAnimation(long touchTimeMs) { - TOUCH_INTERACTION_LOG.startRecentsAnimation(); + TOUCH_INTERACTION_LOG.addLog("startRecentsAnimation"); RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener(); final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler( @@ -328,10 +342,12 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC onConsumerAboutToBeSwitched(); onInteractionGestureFinished(); - // Also clean up in case the system has handled the UP and canceled the animation before - // we had a chance to start the recents animation. In such a case, we will not receive - ActivityManagerWrapper.getInstance().cancelRecentsAnimation( - true /* restoreHomeStackPosition */); + // Cancel the recents animation if SysUI happens to handle UP before we have a chance + // to start the recents animation. In addition, workaround for b/126336729 by delaying + // the cancel of the animation for a period, in case SysUI is slow to handle UP and we + // handle DOWN & UP and move the home stack before SysUI can start the activity + mMainThreadHandler.removeCallbacks(mCancelRecentsAnimationRunnable); + mMainThreadHandler.postDelayed(mCancelRecentsAnimationRunnable, 100); } mVelocityTracker.recycle(); mVelocityTracker = null; @@ -341,6 +357,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC @Override public void onConsumerAboutToBeSwitched() { Preconditions.assertUIThread(); + mMainThreadHandler.removeCallbacks(mCancelRecentsAnimationRunnable); if (mInteractionHandler != null) { // The consumer is being switched while we are active. Set up the shared state to be // used by the next animation diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java index 28c4db46a3..d7898b4f8c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java @@ -59,6 +59,11 @@ public class OverviewInputConsumer mStartingInActivityBounds = startingInActivityBounds; } + @Override + public int getType() { + return TYPE_OVERVIEW; + } + @Override public void onMotionEvent(MotionEvent ev) { if (mInvalidated) { @@ -120,7 +125,7 @@ public class OverviewInputConsumer OverviewCallbacks.get(mActivity).closeAllWindows(); ActivityManagerWrapper.getInstance() .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); - TOUCH_INTERACTION_LOG.startQuickStep(); + TOUCH_INTERACTION_LOG.addLog("startQuickstep"); } mTrackingStarted = true; @@ -152,4 +157,4 @@ public class OverviewInputConsumer } return new OverviewInputConsumer(activity, startingInActivityBounds); } -} \ No newline at end of file +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionLog.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionLog.java deleted file mode 100644 index 4b660d4cf8..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionLog.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2017 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.MotionEvent; -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.LinkedList; - -/** - * Keeps track of debugging logs for a particular quickstep gesture. - */ -public class TouchInteractionLog { - - // The number of gestures to log - private static final int MAX_NUM_LOG_GESTURES = 5; - - private final Calendar mCalendar = Calendar.getInstance(); - private final SimpleDateFormat mDateFormat = new SimpleDateFormat("MMM dd - kk:mm:ss:SSS"); - private final LinkedList> mGestureLogs = new LinkedList<>(); - - public void prepareForNewGesture() { - mGestureLogs.add(new ArrayList<>()); - while (mGestureLogs.size() > MAX_NUM_LOG_GESTURES) { - mGestureLogs.pop(); - } - getCurrentLog().add("[" + mDateFormat.format(mCalendar.getTime()) + "]"); - } - - public void setInputConsumer(InputConsumer consumer) { - getCurrentLog().add("tc=" + consumer.getClass().getSimpleName()); - } - - public void addMotionEvent(MotionEvent event) { - getCurrentLog().add("ev=" + event.getActionMasked()); - } - - public void startQuickStep() { - getCurrentLog().add("qstStart"); - } - - public void startRecentsAnimation() { - getCurrentLog().add("raStart"); - } - - public void startRecentsAnimationCallback(int numTargets) { - getCurrentLog().add("raStartCb=" + numTargets); - } - - public void cancelRecentsAnimation() { - getCurrentLog().add("raCancel"); - } - - public void finishRecentsAnimation(boolean toHome) { - getCurrentLog().add("raFinish=" + toHome); - } - - public void dump(PrintWriter pw) { - pw.println("TouchInteractionLog {"); - for (ArrayList gesture : mGestureLogs) { - pw.print(" "); - for (String log : gesture) { - pw.print(log + " "); - } - pw.println(); - } - pw.println("}"); - } - - private ArrayList getCurrentLog() { - return mGestureLogs.getLast(); - } -} 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 ddf3ad584a..a3c3ff9655 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -23,13 +23,18 @@ import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYS import android.annotation.TargetApi; import android.app.ActivityManager.RunningTaskInfo; +import android.app.KeyguardManager; import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.graphics.Region; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.Looper; +import android.os.Process; import android.util.Log; import android.util.Pair; import android.view.Choreographer; @@ -37,6 +42,10 @@ import android.view.InputEvent; import android.view.MotionEvent; import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.Utilities; +import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.logging.EventLogArray; import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.UiThreadHelper; import com.android.systemui.shared.recents.IOverviewProxy; @@ -49,6 +58,8 @@ import com.android.systemui.shared.system.InputConsumerController; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; /** * Service connected by system-UI for handling touch interaction. @@ -60,7 +71,8 @@ public class TouchInteractionService extends Service { public static final LooperExecutor BACKGROUND_EXECUTOR = new LooperExecutor(UiThreadHelper.getBackgroundLooper()); - public static final TouchInteractionLog TOUCH_INTERACTION_LOG = new TouchInteractionLog(); + public static final EventLogArray TOUCH_INTERACTION_LOG = + new EventLogArray("touch_interaction_log", 40); public static final int EDGE_NAV_BAR = 1 << 8; @@ -75,8 +87,10 @@ public class TouchInteractionService extends Service { public void onInitialize(Bundle bundle) { mISystemUiProxy = ISystemUiProxy.Stub .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY)); - mRecentsModel.setSystemUiProxy(mISystemUiProxy); - mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy); + runWhenUserUnlocked(() -> { + mRecentsModel.setSystemUiProxy(mISystemUiProxy); + mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy); + }); disposeEventHandlers(); mInputEventReceiver = InputChannelCompat.fromBundle(bundle, KEY_EXTRA_INPUT_CHANNEL, @@ -128,8 +142,10 @@ public class TouchInteractionService extends Service { public void onBind(ISystemUiProxy iSystemUiProxy) { mISystemUiProxy = iSystemUiProxy; - mRecentsModel.setSystemUiProxy(mISystemUiProxy); - mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy); + runWhenUserUnlocked(() -> { + mRecentsModel.setSystemUiProxy(mISystemUiProxy); + mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy); + }); // On Bind is received before onInitialize which will dispose these handlers disposeEventHandlers(); @@ -138,7 +154,6 @@ public class TouchInteractionService extends Service { TouchInteractionService.this::onInputEvent); mDeprecatedDispatcher = pair.first; mInputEventReceiver = pair.second; - } }; @@ -148,6 +163,7 @@ public class TouchInteractionService extends Service { return sConnected; } + private KeyguardManager mKM; private ActivityManagerWrapper mAM; private RecentsModel mRecentsModel; private ISystemUiProxy mISystemUiProxy; @@ -159,6 +175,17 @@ public class TouchInteractionService extends Service { private InputConsumerController mInputConsumer; private SwipeSharedState mSwipeSharedState; + private boolean mIsUserUnlocked; + private List mOnUserUnlockedCallbacks; + private BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { + initWhenUserUnlocked(); + } + } + }; + private InputConsumer mConsumer = InputConsumer.NO_OP; private Choreographer mMainChoreographer; @@ -170,10 +197,29 @@ public class TouchInteractionService extends Service { @Override public void onCreate() { super.onCreate(); + + // Initialize anything here that is needed in direct boot mode. + // Everything else should be initialized in initWhenUserUnlocked() below. + mKM = getSystemService(KeyguardManager.class); + mMainChoreographer = Choreographer.getInstance(); + mOnUserUnlockedCallbacks = new ArrayList<>(); + + if (UserManagerCompat.getInstance(this).isUserUnlocked(Process.myUserHandle())) { + initWhenUserUnlocked(); + } else { + mIsUserUnlocked = false; + registerReceiver(mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED)); + } + + sConnected = true; + } + + private void initWhenUserUnlocked() { + mIsUserUnlocked = true; + mAM = ActivityManagerWrapper.getInstance(); mRecentsModel = RecentsModel.INSTANCE.get(this); mOverviewComponentObserver = new OverviewComponentObserver(this); - mMainChoreographer = Choreographer.getInstance(); mOverviewCommandHelper = new OverviewCommandHelper(this, mOverviewComponentObserver); mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this); @@ -183,18 +229,34 @@ public class TouchInteractionService extends Service { mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer(); mInputConsumer.registerInputConsumer(); - sConnected = true; + for (Runnable callback : mOnUserUnlockedCallbacks) { + callback.run(); + } + mOnUserUnlockedCallbacks.clear(); // Temporarily disable model preload // new ModelPreload().start(this); + + Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver); + } + + private void runWhenUserUnlocked(Runnable callback) { + if (mIsUserUnlocked) { + callback.run(); + } else { + mOnUserUnlockedCallbacks.add(callback); + } } @Override public void onDestroy() { - mInputConsumer.unregisterInputConsumer(); - mOverviewComponentObserver.onDestroy(); + if (mIsUserUnlocked) { + mInputConsumer.unregisterInputConsumer(); + mOverviewComponentObserver.onDestroy(); + } disposeEventHandlers(); sConnected = false; + Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver); super.onDestroy(); } @@ -221,19 +283,24 @@ public class TouchInteractionService extends Service { return; } MotionEvent event = (MotionEvent) ev; + TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked()); if (event.getAction() == ACTION_DOWN) { - TOUCH_INTERACTION_LOG.prepareForNewGesture(); boolean useSharedState = mConsumer.isActive(); mConsumer.onConsumerAboutToBeSwitched(); mConsumer = newConsumer(useSharedState, event); - TOUCH_INTERACTION_LOG.setInputConsumer(mConsumer); + TOUCH_INTERACTION_LOG.addLog("setInputConsumer", mConsumer.getType()); } - TOUCH_INTERACTION_LOG.addMotionEvent(event); - mConsumer.onMotionEvent(event); } private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) { + // TODO: this makes a binder call every touch down. we should move to a listener pattern. + if (mKM.isDeviceLocked()) { + // This handles apps launched in direct boot mode (e.g. dialer) as well as apps launched + // while device is locked even after exiting direct boot mode (e.g. camera). + return new DeviceLockedInputConsumer(this); + } + RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0); if (!useSharedState) { mSwipeSharedState.clearAllState(); @@ -241,6 +308,10 @@ public class TouchInteractionService extends Service { if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) { return InputConsumer.NO_OP; + } else if (mOverviewInteractionState.isSwipeUpGestureEnabled() + && FeatureFlags.ENABLE_ASSISTANT_GESTURE.get() + && AssistantTouchConsumer.withinTouchRegion(this, event.getX())) { + return new AssistantTouchConsumer(this, mRecentsModel.getSystemUiProxy()); } else if (mSwipeSharedState.goingToLauncher || mOverviewComponentObserver.getActivityControlHelper().isResumed()) { return OverviewInputConsumer.newInstance( @@ -271,6 +342,6 @@ public class TouchInteractionService extends Service { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - TOUCH_INTERACTION_LOG.dump(pw); + TOUCH_INTERACTION_LOG.dump("", pw); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java index 4d0136e0d5..7dc58a58c3 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -671,7 +671,7 @@ public class WindowTransformSwipeHandler initTransitionEndpoints(dp); mRecentsAnimationWrapper.setController(targetSet); - TOUCH_INTERACTION_LOG.startRecentsAnimationCallback(targetSet.apps.length); + TOUCH_INTERACTION_LOG.addLog("startRecentsAnimationCallback", targetSet.apps.length); setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED); mPassedOverviewThreshold = false; @@ -682,7 +682,7 @@ public class WindowTransformSwipeHandler mRecentsAnimationWrapper.setController(null); mActivityInitListener.unregister(); setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED); - TOUCH_INTERACTION_LOG.cancelRecentsAnimation(); + TOUCH_INTERACTION_LOG.addLog("cancelRecentsAnimation"); } @UiThread @@ -972,27 +972,36 @@ public class WindowTransformSwipeHandler final RectF currentRect = new RectF(); final View floatingView = homeAnimationFactory.getFloatingView(); + final boolean isFloatingIconView = floatingView instanceof FloatingIconView; + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - if (floatingView instanceof FloatingIconView) { + if (isFloatingIconView) { anim.addListener((FloatingIconView) floatingView); } + + // 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 ? 0.75f : 1f; anim.addUpdateListener(animation -> { float progress = animation.getAnimatedFraction(); - float interpolatedProgress = Interpolators.ACCEL_2.getInterpolation(progress); + float interpolatedProgress = Interpolators.ACCEL_1_5.getInterpolation(progress); // Initially go towards original target (task view in recents), // but accelerate towards the final target. // TODO: This is technically not correct. Instead, motion should continue at // the released velocity but accelerate towards the target. targetRect.set(rectFEvaluator.evaluate(interpolatedProgress, originalTarget, finalTarget)); - currentRect.set(rectFEvaluator.evaluate(progress, startRect, targetRect)); - float alpha = 1 - interpolatedProgress; - mTransformParams.setCurrentRectAndTargetAlpha(currentRect, alpha) + currentRect.set(rectFEvaluator.evaluate(interpolatedProgress, startRect, targetRect)); + + float iconAlpha = Utilities.mapToRange(interpolatedProgress, 0, + windowAlphaThreshold, 0f, 1f, Interpolators.LINEAR); + mTransformParams.setCurrentRectAndTargetAlpha(currentRect, 1f - iconAlpha) .setSyncTransactionApplier(mSyncTransactionApplier); mClipAnimationHelper.applyTransform(targetSet, mTransformParams); - if (floatingView instanceof FloatingIconView) { - ((FloatingIconView) floatingView).update(currentRect, 1f - alpha); + if (isFloatingIconView) { + ((FloatingIconView) floatingView).update(currentRect, iconAlpha, progress, + windowAlphaThreshold); } }); anim.addListener(new AnimationSuccessListener() { @@ -1016,7 +1025,7 @@ public class WindowTransformSwipeHandler @UiThread private void resumeLastTask() { mRecentsAnimationWrapper.finish(false /* toRecents */, null); - TOUCH_INTERACTION_LOG.finishRecentsAnimation(false); + TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false); } @UiThread @@ -1034,7 +1043,7 @@ public class WindowTransformSwipeHandler mMainThreadHandler); }); } - TOUCH_INTERACTION_LOG.finishRecentsAnimation(false); + TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false); doLogGesture(NEW_TASK); } @@ -1143,7 +1152,7 @@ public class WindowTransformSwipeHandler () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); } } - TOUCH_INTERACTION_LOG.finishRecentsAnimation(true); + TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true); } private void finishCurrentTransitionToHome() { @@ -1151,7 +1160,7 @@ public class WindowTransformSwipeHandler mRecentsAnimationWrapper.finish(true /* toRecents */, () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); } - TOUCH_INTERACTION_LOG.finishRecentsAnimation(true); + TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true); doLogGesture(HOME); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/ChipsContainer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/ChipsContainer.java new file mode 100644 index 0000000000..8fc89f2538 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/ChipsContainer.java @@ -0,0 +1,72 @@ +/* + * 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.hints; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.FloatProperty; +import android.view.View; +import android.widget.FrameLayout; + +public class ChipsContainer extends FrameLayout { + + private static final String TAG = "ChipsContainer"; + + public static final FloatProperty HINT_VISIBILITY = + new FloatProperty("hint_visibility") { + @Override + public void setValue(ChipsContainer chipsContainer, float v) { + chipsContainer.setHintVisibility(v); + } + + @Override + public Float get(ChipsContainer chipsContainer) { + return chipsContainer.mHintVisibility; + } + }; + + private float mHintVisibility; + + public ChipsContainer(Context context) { + super(context); + } + + public ChipsContainer(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ChipsContainer(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public ChipsContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public void setView(View v) { + removeAllViews(); + addView(v); + } + + public void setHintVisibility(float v) { + if (v == 1) { + setVisibility(VISIBLE); + } else { + setVisibility(GONE); + } + mHintVisibility = v; + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/HintsContainer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/HintsContainer.java deleted file mode 100644 index 22b121760e..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/HintsContainer.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * 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.hints; - -import static com.android.quickstep.hints.UiHintListenerConstants.HINTS_KEY; -import static com.android.quickstep.hints.UiHintListenerConstants.ON_HINTS_RETURNED_CODE; -import static com.android.quickstep.hints.UiInterfaceConstants.REQUEST_HINTS_CODE; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; -import android.util.AttributeSet; -import android.util.FloatProperty; -import android.util.Log; -import android.view.LayoutInflater; -import android.widget.LinearLayout; - -import androidx.annotation.Nullable; - -import com.android.launcher3.R; - -import java.util.ArrayList; - -public class HintsContainer extends LinearLayout { - - private static final String TAG = "HintsView"; - - public static final FloatProperty HINT_VISIBILITY = - new FloatProperty("hint_visibility") { - @Override - public void setValue(HintsContainer hintsContainer, float v) { - hintsContainer.setHintVisibility(v); - } - - @Override - public Float get(HintsContainer hintsContainer) { - return hintsContainer.mHintVisibility; - } - }; - - private static Intent mServiceIntent = - new Intent("com.android.systemui.action.UI_PULL_INTERFACE") - .setClassName( - "com.android.systemui.navbarhint", - "com.android.systemui.navbarhint.service.HintService"); - - @Nullable - private Messenger mHintServiceInterface; - private UiHintListener mUiHintListener; - private boolean mBound = false; - private float mHintVisibility; - - private final ServiceConnection mServiceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - mHintServiceInterface = new Messenger(iBinder); - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - mHintServiceInterface = null; - attemptBinding(); - } - - @Override - public void onBindingDied(ComponentName componentName) { - mHintServiceInterface = null; - attemptBinding(); - } - }; - - public HintsContainer(Context context) { - super(context); - } - - public HintsContainer(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public HintsContainer(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public HintsContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (mUiHintListener == null) { - mUiHintListener = new UiHintListener(this); - } - if (!mBound) { - attemptBinding(); - } - } - - @Override - protected void onDetachedFromWindow() { - if (mBound) { - getContext().unbindService(mServiceConnection); - mBound = false; - } - super.onDetachedFromWindow(); - } - - public void setHintVisibility(float v) { - if (v == 1) { - getHints(); - setVisibility(VISIBLE); - } else { - setVisibility(GONE); - } - mHintVisibility = v; - } - - private void attemptBinding() { - if (mBound) { - getContext().unbindService(mServiceConnection); - mBound = false; - } - boolean success = getContext().bindService(mServiceIntent, - mServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT); - if (success) { - mBound = true; - } else { - Log.w(TAG, "Binding to hint supplier failed"); - } - } - - private void sendOnHintTap(Bundle hint) { - if (mHintServiceInterface != null) { - Message msg = Message.obtain(null, UiInterfaceConstants.ON_HINT_TAP_CODE); - Bundle data = new Bundle(); - data.putString(UiInterfaceConstants.HINT_ID_KEY, HintUtil.getId(hint)); - data.putInt(UiInterfaceConstants.WIDTH_PX_KEY, getWidth()); - data.putInt(UiInterfaceConstants.HEIGHT_PX_KEY, getHeight()); - data.putInt(UiInterfaceConstants.HINT_SPACE_WIDTH_PX_KEY, 0); - data.putInt(UiInterfaceConstants.HINT_SPACE_HEIGHT_PX_KEY, 0); - msg.setData(data); - try { - mHintServiceInterface.send(msg); - } catch (RemoteException e) { - Log.e(TAG, "Failed to send hint tap", e); - } - } - } - - private void getHints() { - if (mHintServiceInterface != null) { - try { - Message m = Message.obtain(null, REQUEST_HINTS_CODE); - m.replyTo = new Messenger(mUiHintListener); - mHintServiceInterface.send(m); - } catch (RemoteException e) { - Log.e(TAG, "Failed to send message", e); - } - } - } - - private static class UiHintListener extends Handler { - private HintsContainer mView; - - UiHintListener(HintsContainer v) { - mView = v; - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case ON_HINTS_RETURNED_CODE: - handleHints(msg); - break; - default: - Log.e(TAG, "UiPullInterface got unrecognized code: " + msg.what); - break; - } - } - - private void handleHints(Message msg) { - Bundle bundle = msg.getData(); - ArrayList hints = bundle.getParcelableArrayList(HINTS_KEY); - - if (hints != null) { - mView.removeAllViews(); - - for (Bundle hint : hints) { - HintView h = (HintView) LayoutInflater.from(mView.getContext()).inflate( - R.layout.hint, mView, false); - h.setHint(hint); - h.setOnClickListener((v) -> mView.sendOnHintTap(hint)); - mView.addView(h); - } - } - } - } -} 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 fcba5b844c..fe5a675113 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 @@ public class ClipAnimationHelper { 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/DigitalWellBeingToast.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java index 5fe92d5b63..0aa1beb60d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java @@ -50,6 +50,7 @@ import java.util.Locale; public final class DigitalWellBeingToast extends LinearLayout { static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS); + static final int MINUTE_MS = 60000; public interface InitializeCallback { void call(float saturation, String contentDescription); @@ -185,7 +186,11 @@ public final class DigitalWellBeingToast extends LinearLayout { /* forceFormatWidth= */ false); } - private String getShorterReadableDuration(Duration duration) { + private String getRoundedUpToMinuteReadableDuration(long remainingTime) { + final Duration duration = Duration.ofMillis( + remainingTime > MINUTE_MS ? + (remainingTime + MINUTE_MS - 1) / MINUTE_MS * MINUTE_MS : + remainingTime); return getReadableDuration( duration, FormatWidth.NARROW, R.string.shorter_duration_less_than_one_minute); } @@ -196,7 +201,7 @@ public final class DigitalWellBeingToast extends LinearLayout { resources.getString(R.string.app_in_grayscale) : resources.getString( R.string.time_left_for_app, - getShorterReadableDuration(Duration.ofMillis(remainingTime))); + getRoundedUpToMinuteReadableDuration(remainingTime)); } public void openAppUsageSettings() { 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 88fe2ee8a5..97bce5eb98 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 @@ -42,9 +42,10 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.util.PendingAnimation; +import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.views.ScrimView; import com.android.quickstep.OverviewInteractionState; -import com.android.quickstep.hints.HintsContainer; +import com.android.quickstep.hints.ChipsContainer; import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.util.ClipAnimationHelper.TransformParams; import com.android.quickstep.util.LayoutUtils; @@ -77,7 +78,7 @@ public class LauncherRecentsView extends RecentsView { private float mTranslationYFactor; private final TransformParams mTransformParams = new TransformParams(); - private HintsContainer mHintsContainer; + private ChipsContainer mChipsContainer; public LauncherRecentsView(Context context) { this(context, null); @@ -111,8 +112,9 @@ public class LauncherRecentsView extends RecentsView { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - mHintsContainer = mActivity.findViewById(R.id.hints); - mHintsContainer.setPadding(0, 0, 0, mActivity.getDeviceProfile().chipHintBottomMarginPx); + mChipsContainer = mActivity.findViewById(R.id.hints); + BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) mChipsContainer.getLayoutParams(); + params.bottomMargin = mActivity.getDeviceProfile().chipHintBottomMarginPx; } public void setTranslationYFactor(float translationFactor) { @@ -131,11 +133,15 @@ public class LauncherRecentsView extends RecentsView { } public void setHintVisibility(float v) { - if (mHintsContainer != null && ENABLE_HINTS_IN_OVERVIEW.get()) { - mHintsContainer.setHintVisibility(v); + if (mChipsContainer != null && ENABLE_HINTS_IN_OVERVIEW.get()) { + mChipsContainer.setHintVisibility(v); } } + public ChipsContainer getChipsContainer() { + return mChipsContainer; + } + @Override public void draw(Canvas canvas) { maybeDrawEmptyMessage(canvas); @@ -192,7 +198,7 @@ public class LauncherRecentsView extends RecentsView { if (ENABLE_HINTS_IN_OVERVIEW.get()) { anim.anim.play(ObjectAnimator.ofFloat( - mHintsContainer, HintsContainer.HINT_VISIBILITY, 0)); + mChipsContainer, ChipsContainer.HINT_VISIBILITY, 0)); } return anim; @@ -206,10 +212,10 @@ public class LauncherRecentsView extends RecentsView { if (ENABLE_HINTS_IN_OVERVIEW.get()) { anim.anim.play(ObjectAnimator.ofFloat( - mHintsContainer, HintsContainer.HINT_VISIBILITY, 0)); + mChipsContainer, ChipsContainer.HINT_VISIBILITY, 0)); anim.addEndListener(onEndListener -> { if (!onEndListener.isSuccess) { - mHintsContainer.setHintVisibility(1); + mChipsContainer.setHintVisibility(1); } }); } 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 8faf95d5b7..3e0e8ae583 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.launcher3.util.ViewPool; 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 @@ import java.util.function.Consumer; @TargetApi(Build.VERSION_CODES.P) public abstract class RecentsView extends PagedView implements Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback, - InvariantDeviceProfile.OnIDPChangeListener { + InvariantDeviceProfile.OnIDPChangeListener, TaskThumbnailChangeListener { private static final String TAG = RecentsView.class.getSimpleName(); @@ -170,14 +171,6 @@ public abstract class RecentsView extends PagedView impl * TODO: Call reloadIdNeeded in onTaskStackChanged. */ 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) { @@ -262,7 +255,6 @@ public abstract class RecentsView extends PagedView impl private boolean mOverviewStateEnabled; private boolean mHandleTaskStackChanges; - private Runnable mNextPageSwitchRunnable; private boolean mSwipeDownShouldLaunchApp; private boolean mTouchDownToStartHome; private final int mTouchSlop; @@ -340,6 +332,19 @@ public abstract class RecentsView extends PagedView impl 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 @@ public abstract class RecentsView extends PagedView impl mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener); ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this); + RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); mIdp.addOnChangeListener(this); } @@ -382,6 +388,7 @@ public abstract class RecentsView extends PagedView impl mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener); ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener); mSyncTransactionApplier = null; + RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this); mIdp.removeOnChangeListener(this); } @@ -421,17 +428,9 @@ public abstract class RecentsView extends PagedView impl 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); } @@ -774,7 +773,7 @@ public abstract class RecentsView extends PagedView impl setCurrentTask(runningTaskId); } - public TaskView getRunningTaskView() { + public @Nullable TaskView getRunningTaskView() { return getTaskView(mRunningTaskId); } 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 bf817f8e47..e5b1a7988b 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 @@ -18,6 +18,7 @@ package com.android.quickstep.views; import static android.widget.Toast.LENGTH_SHORT; import static com.android.launcher3.BaseActivity.fromContext; +import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; @@ -28,7 +29,6 @@ import android.animation.ObjectAnimator; 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; @@ -91,7 +91,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { public static final long SCALE_ICON_DURATION = 120; private static final long DIM_ANIM_DURATION = 700; - private static final long TASK_LAUNCH_ANIM_DURATION = 200; public static final Property ZOOM_SCALE = new FloatProperty("zoomScale") { @@ -214,6 +213,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { * Updates this task view to the given {@param task}. */ public void bind(Task task) { + cancelPendingLoadTasks(); mTask = task; mSnapshotView.bind(task); } @@ -236,10 +236,10 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { public AnimatorPlaybackController createLaunchAnimationForRunningTask() { final PendingAnimation pendingAnimation = - getRecentsView().createTaskLauncherAnimation(this, TASK_LAUNCH_ANIM_DURATION); - pendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN); + getRecentsView().createTaskLauncherAnimation(this, RECENTS_LAUNCH_DURATION); + pendingAnimation.anim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR); AnimatorPlaybackController currentAnimation = AnimatorPlaybackController - .wrap(pendingAnimation.anim, TASK_LAUNCH_ANIM_DURATION, null); + .wrap(pendingAnimation.anim, RECENTS_LAUNCH_DURATION, null); currentAnimation.setEndAction(() -> { pendingAnimation.finish(true, Touch.SWIPE); launchTask(false); @@ -304,15 +304,15 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { 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); @@ -324,17 +324,22 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { }); }); } 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/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 2626481c3d..f5e5dd32cb 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -39,7 +39,6 @@ 0.285dp 0.5dp 48dp - 48dp 50dp @@ -65,4 +64,8 @@ 16dp 24dp + + + 70dp + 200dp diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java index ab5f479c97..f8b167b48c 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java @@ -403,9 +403,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans private void playIconAnimators(AnimatorSet appOpenAnimator, View v, Rect windowTargetBounds, boolean toggleVisibility) { final boolean isBubbleTextView = v instanceof BubbleTextView; - if (mFloatingView == null) { - mFloatingView = new FloatingIconView(mLauncher); - } else { + if (mFloatingView != null) { mFloatingView.setTranslationX(0); mFloatingView.setTranslationY(0); mFloatingView.setScaleX(1); @@ -414,7 +412,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans mFloatingView.setBackground(null); } Rect rect = new Rect(); - mFloatingView.matchPositionOf(mLauncher, v, toggleVisibility, rect); + mFloatingView = FloatingIconView.getFloatingIconView(mLauncher, v, toggleVisibility, + true /* useDrawableAsIs */, -1 /* aspectRatio */, rect, mFloatingView); int viewLocationStart = mIsRtl ? windowTargetBounds.width() - rect.right : rect.left; LayoutParams lp = (LayoutParams) mFloatingView.getLayoutParams(); @@ -615,11 +614,6 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans WindowManagerWrapper.ACTIVITY_TYPE_STANDARD, new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(false /* fromUnlock */), CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */)); - - definition.addRemoteAnimation( - WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER, - new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(true /* fromUnlock */), - CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */)); new ActivityCompat(mLauncher).registerRemoteAnimations(definition); } } diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java index 411e593ddd..a0ab30141a 100644 --- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java +++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java @@ -175,7 +175,7 @@ public class OverviewInteractionState { } } - public void notifySwipeUpSettingChanged(boolean swipeUpEnabled) { + private void notifySwipeUpSettingChanged(boolean swipeUpEnabled) { mUiHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED); mUiHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED, swipeUpEnabled ? 1 : 0, 0). sendToTarget(); diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java index 00b3e90938..e15a3f1ca1 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -60,6 +60,7 @@ public class RecentTasksList extends TaskStackChangeListener { mBgThreadExecutor = BackgroundExecutor.get(); mKeyguardManager = new KeyguardManagerCompat(context); mChangeId = 1; + ActivityManagerWrapper.getInstance().registerTaskStackListener(this); } /** @@ -97,6 +98,7 @@ public class RecentTasksList extends TaskStackChangeListener { if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) { // The list is up to date, callback with the same list mMainThreadExecutor.execute(resultCallback); + return requestLoadId; } // Kick off task loading in the background diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index f3e1545e0b..81a22a190a 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 class RecentsModel extends TaskStackChangeListener { public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(c -> new RecentsModel(c)); + private final List mThumbnailChangeListeners = new ArrayList<>(); private final Context mContext; - private final MainThreadExecutor mMainThreadExecutor; private ISystemUiProxy mSystemUiProxy; @@ -68,9 +64,6 @@ public class RecentsModel extends TaskStackChangeListener { private RecentsModel(Context context) { mContext = context; - - mMainThreadExecutor = new MainThreadExecutor(); - HandlerThread loaderThread = new HandlerThread("TaskThumbnailIconCache", Process.THREAD_PRIORITY_BACKGROUND); loaderThread.start(); @@ -168,6 +161,18 @@ public class RecentsModel extends TaskStackChangeListener { }); } + @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 @@ public class RecentsModel extends TaskStackChangeListener { + ": ", 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/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java index 612c00e1ff..07af9b3858 100644 --- a/quickstep/src/com/android/quickstep/TaskIconCache.java +++ b/quickstep/src/com/android/quickstep/TaskIconCache.java @@ -15,6 +15,8 @@ */ package com.android.quickstep; +import static com.android.launcher3.uioverrides.RecentsUiFactory.GO_LOW_RAM_RECENTS_ENABLED; + import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; @@ -24,14 +26,17 @@ import android.os.Handler; import android.os.Looper; import android.util.LruCache; import android.view.accessibility.AccessibilityManager; + import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.icons.cache.HandlerRunnable; +import com.android.launcher3.uioverrides.RecentsUiFactory; import com.android.launcher3.util.Preconditions; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.TaskKeyLruCache; import com.android.systemui.shared.system.ActivityManagerWrapper; + import java.util.function.Consumer; /** @@ -125,8 +130,9 @@ public class TaskIconCache { return label; } - // Skip loading content descriptions if accessibility is not enabled - if (!mAccessibilityManager.isEnabled()) { + // Skip loading content descriptions if accessibility is disabled unless low RAM recents + // is enabled. + if (!GO_LOW_RAM_RECENTS_ENABLED && !mAccessibilityManager.isEnabled()) { return ""; } diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java index 7a216ed2f5..d05196bc84 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.Utilities; 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 @@ public class TaskThumbnailCache { private final MainThreadExecutor mMainThreadExecutor; private final int mCacheSize; - private final TaskKeyLruCache mCache; + private final ThumbnailCache mCache; private final HighResLoadingState mHighResLoadingState; public static class HighResLoadingState { @@ -98,7 +99,7 @@ public class TaskThumbnailCache { Resources res = context.getResources(); mCacheSize = res.getInteger(R.integer.recentsThumbnailCacheSize); - mCache = new TaskKeyLruCache<>(mCacheSize); + mCache = new ThumbnailCache(mCacheSize); } /** @@ -106,13 +107,20 @@ public class TaskThumbnailCache { */ 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 @@ public class TaskThumbnailCache { * @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 callback) { + public ThumbnailLoadRequest updateThumbnailInBackground( + Task task, Consumer 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 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 @@ public class TaskThumbnailCache { @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 @@ public class TaskThumbnailCache { this.reducedResolution = reducedResolution; } } + + private static class ThumbnailCache extends TaskKeyLruCache { + + 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); + } + } + } } diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java index 21d814454b..8a117c8592 100644 --- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java +++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java @@ -40,7 +40,6 @@ public class MotionPauseDetector { private final float mSpeedSomewhatFast; private final float mSpeedFast; private final float mMinDisplacementForPause; - private final float mMaxOrthogonalDisplacementForPause; private final Alarm mForcePauseTimeout; private Long mPreviousTime = null; @@ -62,8 +61,6 @@ public class MotionPauseDetector { mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast); mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast); mMinDisplacementForPause = res.getDimension(R.dimen.motion_pause_detector_min_displacement); - mMaxOrthogonalDisplacementForPause = res.getDimension( - R.dimen.motion_pause_detector_max_orthogonal_displacement); mForcePauseTimeout = new Alarm(); mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */)); } @@ -77,7 +74,6 @@ public class MotionPauseDetector { if (mOnMotionPauseListener != null) { mOnMotionPauseListener.onMotionPauseChanged(mIsPaused); } - mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT); } /** @@ -94,6 +90,7 @@ public class MotionPauseDetector { if (mFirstOrthogonalPosition == null) { mFirstOrthogonalPosition = orthogonalPosition; } + mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT); long time = SystemClock.uptimeMillis(); if (mPreviousTime != null && mPreviousPosition != null) { long changeInTime = Math.max(1, time - mPreviousTime); @@ -108,7 +105,6 @@ public class MotionPauseDetector { } mPreviousTime = time; mPreviousPosition = position; - mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT); } private void checkMotionPaused(float velocity, float prevVelocity, @@ -135,9 +131,11 @@ public class MotionPauseDetector { } } boolean passedMinDisplacement = totalDisplacement.primary >= mMinDisplacementForPause; - boolean passedMaxOrthogonalDisplacement = - totalDisplacement.orthogonal >= mMaxOrthogonalDisplacementForPause; - isPaused &= passedMinDisplacement && !passedMaxOrthogonalDisplacement; + boolean isDisplacementOrthogonal = totalDisplacement.orthogonal > totalDisplacement.primary; + if (!passedMinDisplacement || isDisplacementOrthogonal) { + mForcePauseTimeout.cancelAlarm(); + isPaused = false; + } updatePaused(isPaused); } diff --git a/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java b/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java index 72740909d0..c5975a9590 100644 --- a/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java +++ b/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java @@ -19,6 +19,11 @@ package com.android.quickstep; import static com.android.quickstep.QuickStepOnOffRule.Mode.BOTH; import static com.android.quickstep.QuickStepOnOffRule.Mode.OFF; import static com.android.quickstep.QuickStepOnOffRule.Mode.ON; +import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME; + +import static org.junit.Assert.assertTrue; + +import android.provider.Settings; import androidx.test.InstrumentationRegistry; @@ -69,38 +74,52 @@ public class QuickStepOnOffRule implements TestRule { @Override public void evaluate() throws Throwable { try { - if (mode == ON || mode == BOTH) { - evaluateWithQuickstepOn(); - } - if (mode == OFF || mode == BOTH) { - evaluateWithQuickstepOff(); + if (SwipeUpSetting.isSwipeUpSettingAvailable()) { + if (mode == ON || mode == BOTH) { + evaluateWithQuickstepOn(); + } + if (mode == OFF || mode == BOTH) { + evaluateWithQuickstepOff(); + } + } else { + // Execute without changing the setting, if the requested mode is + // compatible. + final boolean swipeUpEnabledDefaultValue = + SwipeUpSetting.isSwipeUpEnabledDefaultValue(); + if (mode == BOTH || + mode == ON && swipeUpEnabledDefaultValue || + mode == OFF && !swipeUpEnabledDefaultValue) { + evaluateWithoutChangingSetting(base); + } } } finally { - overrideSwipeUpEnabled(null); + setSwipeUpSetting(null); + } } - private void evaluateWithQuickstepOff() throws Throwable { - overrideSwipeUpEnabled(false); + public void setSwipeUpSetting(String value) { + assertTrue("Couldn't change Quickstep mode", + Settings.Secure.putString( + InstrumentationRegistry.getInstrumentation().getTargetContext(). + getContentResolver(), + SWIPE_UP_SETTING_NAME, + value)); + } + + public void evaluateWithoutChangingSetting(Statement base) throws Throwable { base.evaluate(); } + private void evaluateWithQuickstepOff() throws Throwable { + setSwipeUpSetting("0"); + evaluateWithoutChangingSetting(base); + } + private void evaluateWithQuickstepOn() throws Throwable { - overrideSwipeUpEnabled(true); + setSwipeUpSetting("1"); base.evaluate(); } - - private void overrideSwipeUpEnabled(Boolean swipeUpEnabledOverride) - throws Throwable { - mLauncher.overrideSwipeUpEnabled(swipeUpEnabledOverride); - mMainThreadExecutor.execute(() -> OverviewInteractionState.INSTANCE.get( - InstrumentationRegistry.getInstrumentation().getTargetContext()). - notifySwipeUpSettingChanged(mLauncher.isSwipeUpEnabled())); - // TODO(b/124236673): avoid using sleep(). - mLauncher.getDevice().waitForIdle(); - Thread.sleep(2000); - mLauncher.getDevice().waitForIdle(); - } }; } else { return base; diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml index 91c37054ba..c9cea80624 100644 --- a/res/layout/launcher.xml +++ b/res/layout/launcher.xml @@ -38,6 +38,15 @@ android:theme="@style/HomeScreenElementTheme" launcher:pageIndicator="@+id/page_indicator" /> + + + - - - diff --git a/res/layout/snackbar.xml b/res/layout/snackbar.xml index bca3308426..b818943861 100644 --- a/res/layout/snackbar.xml +++ b/res/layout/snackbar.xml @@ -29,7 +29,7 @@ android:ellipsize="end" android:textSize="@dimen/snackbar_max_text_size" android:textColor="?android:attr/textColorPrimary" - android:theme="@style/TextTitle"/> + style="@style/TextTitle"/> \ No newline at end of file diff --git a/res/values/styles.xml b/res/values/styles.xml index f0955b3a52..252cae1461 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -126,7 +126,7 @@ - - +