summaryrefslogtreecommitdiffstats
path: root/quickstep
diff options
context:
space:
mode:
authorTony Wickham <twickham@google.com>2017-12-14 18:38:25 -0800
committerTony Wickham <twickham@google.com>2018-01-02 15:44:34 -0800
commit2fae2a0e9c337d217a63980f19eaae198720f86a (patch)
tree77aad50d2b4a238f7a8a238f31abf46dd94b59ed /quickstep
parent9328a51271dbcf122021dcf5e40a6cc41a0cb90f (diff)
downloadandroid_packages_apps_Trebuchet-2fae2a0e9c337d217a63980f19eaae198720f86a.tar.gz
android_packages_apps_Trebuchet-2fae2a0e9c337d217a63980f19eaae198720f86a.tar.bz2
android_packages_apps_Trebuchet-2fae2a0e9c337d217a63980f19eaae198720f86a.zip
Add system shortcuts when long pressing recent icon
We add a floating view for the menu that aligns with the task icon. If available, the following shortcuts are present: - Widgets - App info - Install (for instant apps) It is designed to be straightforward to add to this list. Bug: 70294936 Change-Id: I56c1098353d09fc564e0e92e59e4fcf692e486ba
Diffstat (limited to 'quickstep')
-rw-r--r--quickstep/res/layout/task_menu.xml36
-rw-r--r--quickstep/res/values/dimens.xml1
-rw-r--r--quickstep/src/com/android/quickstep/TaskMenuView.java223
-rw-r--r--quickstep/src/com/android/quickstep/TaskSystemShortcut.java92
-rw-r--r--quickstep/src/com/android/quickstep/TaskUtils.java42
-rw-r--r--quickstep/src/com/android/quickstep/TaskView.java2
6 files changed, 396 insertions, 0 deletions
diff --git a/quickstep/res/layout/task_menu.xml b/quickstep/res/layout/task_menu.xml
new file mode 100644
index 000000000..6e3fb4fbd
--- /dev/null
+++ b/quickstep/res/layout/task_menu.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 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.
+-->
+<com.android.quickstep.TaskMenuView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/bg_popup_item_width"
+ android:layout_height="wrap_content"
+ android:visibility="invisible"
+ android:elevation="@dimen/deep_shortcuts_elevation"
+ android:orientation="vertical"
+ android:background="?attr/popupColorPrimary"
+ android:divider="@drawable/all_apps_divider"
+ android:showDividers="middle"
+ android:animateLayoutChanges="true">
+ <TextView
+ android:id="@+id/task_icon_and_name"
+ android:layout_width="match_parent"
+ android:layout_height="112dp"
+ android:textSize="14sp"
+ android:paddingTop="18dp"
+ android:drawablePadding="8dp"
+ android:gravity="center_horizontal"/>
+</com.android.quickstep.TaskMenuView> \ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index a5716ea69..587261d82 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -18,6 +18,7 @@
<dimen name="task_thumbnail_top_margin">24dp</dimen>
<dimen name="task_thumbnail_icon_size">48dp</dimen>
+ <dimen name="task_menu_background_radius">12dp</dimen>
<dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
<dimen name="quickstep_fling_min_velocity">250dp</dimen>
diff --git a/quickstep/src/com/android/quickstep/TaskMenuView.java b/quickstep/src/com/android/quickstep/TaskMenuView.java
new file mode 100644
index 000000000..70542c296
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskMenuView.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2018 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.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Outline;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.TextView;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.shortcuts.DeepShortcutView;
+import com.android.systemui.shared.recents.model.Task;
+
+/**
+ * Contains options for a recent task when long-pressing its icon.
+ */
+public class TaskMenuView extends AbstractFloatingView {
+
+ private static final Rect sTempRect = new Rect();
+
+ /** Note that these will be shown in order from top to bottom, if available for the task. */
+ private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[] {
+ new TaskSystemShortcut.Widgets(),
+ new TaskSystemShortcut.AppInfo(),
+ new TaskSystemShortcut.Install()
+ };
+
+ private static final long OPEN_CLOSE_DURATION = 220;
+
+ private Launcher mLauncher;
+ private TextView mTaskIconAndName;
+ private AnimatorSet mOpenCloseAnimator;
+
+ public TaskMenuView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ mLauncher = Launcher.getLauncher(context);
+ setClipToOutline(true);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ float r = getResources().getDimensionPixelSize(R.dimen.task_menu_background_radius);
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), r);
+ }
+ });
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mTaskIconAndName = findViewById(R.id.task_icon_and_name);
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ DragLayer dl = mLauncher.getDragLayer();
+ if (!dl.isEventOverView(this, ev)) {
+ // TODO: log this once we have a new container type for it?
+ close(true);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ if (animate) {
+ animateClose();
+ } else {
+ closeComplete();
+ }
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ // TODO
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_TASK_MENU) != 0;
+ }
+
+ public static boolean showForTask(TaskView taskView) {
+ Launcher launcher = Launcher.getLauncher(taskView.getContext());
+ final TaskMenuView taskMenuView = (TaskMenuView) launcher.getLayoutInflater().inflate(
+ R.layout.task_menu, launcher.getDragLayer(), false);
+ return taskMenuView.populateAndShowForTask(taskView);
+ }
+
+ private boolean populateAndShowForTask(TaskView taskView) {
+ if (isAttachedToWindow()) {
+ return false;
+ }
+ mLauncher.getDragLayer().addView(this);
+ addMenuOptions(taskView.getTask());
+ orientAroundTaskView(taskView);
+ post(this::animateOpen);
+ return true;
+ }
+
+ private void addMenuOptions(Task task) {
+ Drawable icon = task.icon.getConstantState().newDrawable();
+ int iconSize = getResources().getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
+ icon.setBounds(0, 0, iconSize, iconSize);
+ mTaskIconAndName.setCompoundDrawables(null, icon, null, null);
+ mTaskIconAndName.setText(TaskUtils.getTitle(mLauncher, task));
+
+ LayoutInflater inflater = mLauncher.getLayoutInflater();
+ for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
+ OnClickListener onClickListener = menuOption.getOnClickListener(mLauncher, task);
+ if (onClickListener != null) {
+ DeepShortcutView menuOptionView = (DeepShortcutView) inflater.inflate(
+ R.layout.system_shortcut, this, false);
+ menuOptionView.getIconView().setBackgroundResource(menuOption.iconResId);
+ menuOptionView.getBubbleText().setText(menuOption.labelResId);
+ menuOptionView.setOnClickListener(onClickListener);
+ addView(menuOptionView);
+ }
+ }
+ }
+
+ private void orientAroundTaskView(TaskView taskView) {
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ mLauncher.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect);
+ Rect insets = mLauncher.getDragLayer().getInsets();
+ setX(sTempRect.left + (sTempRect.width() - getMeasuredWidth()) / 2 - insets.left);
+ setY(sTempRect.top - mTaskIconAndName.getPaddingTop() - insets.top);
+ }
+
+ private void animateOpen() {
+ animateOpenOrClosed(false);
+ mIsOpen = true;
+ }
+
+ private void animateClose() {
+ animateOpenOrClosed(true);
+ }
+
+ private void animateOpenOrClosed(boolean closing) {
+ if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
+ return;
+ }
+ mOpenCloseAnimator = LauncherAnimUtils.createAnimatorSet();
+ mOpenCloseAnimator.play(createOpenCloseOutlineProvider()
+ .createRevealAnimator(this, closing));
+ mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ setVisibility(VISIBLE);
+ }
+
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ if (closing) {
+ closeComplete();
+ }
+ }
+ });
+ mOpenCloseAnimator.play(ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1));
+ mOpenCloseAnimator.setDuration(OPEN_CLOSE_DURATION);
+ mOpenCloseAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+ mOpenCloseAnimator.start();
+ }
+
+ private void closeComplete() {
+ mIsOpen = false;
+ mLauncher.getDragLayer().removeView(this);
+ }
+
+ private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
+ int iconSize = getResources().getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
+ float fromRadius = iconSize / 2;
+ float toRadius = getResources().getDimensionPixelSize(
+ R.dimen.task_menu_background_radius);
+ Point iconCenter = new Point(getWidth() / 2, mTaskIconAndName.getPaddingTop() + iconSize / 2);
+ Rect fromRect = new Rect(iconCenter.x, iconCenter.y, iconCenter.x, iconCenter.y);
+ Rect toRect = new Rect(0, 0, getWidth(), getHeight());
+ return new RoundedRectRevealOutlineProvider(fromRadius, toRadius, fromRect, toRect) {
+ @Override
+ public boolean shouldRemoveElevationDuringAnimation() {
+ return true;
+ }
+ };
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
new file mode 100644
index 000000000..1ba7ce4e1
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 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.ComponentName;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.view.View;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.util.InstantAppResolver;
+import com.android.systemui.shared.recents.model.Task;
+
+/**
+ * Represents a system shortcut that can be shown for a recent task.
+ */
+public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut {
+
+ protected T mSystemShortcut;
+
+ protected TaskSystemShortcut(T systemShortcut) {
+ super(systemShortcut.iconResId, systemShortcut.labelResId);
+ mSystemShortcut = systemShortcut;
+ }
+
+ @Override
+ public View.OnClickListener getOnClickListener(Launcher launcher, ItemInfo itemInfo) {
+ return null;
+ }
+
+ public View.OnClickListener getOnClickListener(final Launcher launcher, final Task task) {
+ ShortcutInfo dummyInfo = new ShortcutInfo();
+ dummyInfo.intent = new Intent();
+ ComponentName component = task.getTopComponent();
+ dummyInfo.intent.setComponent(component);
+ dummyInfo.user = UserHandle.getUserHandleForUid(task.key.userId);
+ dummyInfo.title = TaskUtils.getTitle(launcher, task);
+
+ return getOnClickListenerForTask(launcher, task, dummyInfo);
+ }
+
+ protected View.OnClickListener getOnClickListenerForTask(final Launcher launcher,
+ final Task task, final ItemInfo dummyInfo) {
+ return mSystemShortcut.getOnClickListener(launcher, dummyInfo);
+ }
+
+
+ public static class Widgets extends TaskSystemShortcut<SystemShortcut.Widgets> {
+ public Widgets() {
+ super(new SystemShortcut.Widgets());
+ }
+ }
+
+ public static class AppInfo extends TaskSystemShortcut<SystemShortcut.AppInfo> {
+ public AppInfo() {
+ super(new SystemShortcut.AppInfo());
+ }
+ }
+
+ public static class Install extends TaskSystemShortcut<SystemShortcut.Install> {
+ public Install() {
+ super(new SystemShortcut.Install());
+ }
+
+ @Override
+ protected View.OnClickListener getOnClickListenerForTask(Launcher launcher, Task task,
+ ItemInfo itemInfo) {
+ if (InstantAppResolver.newInstance(launcher).isInstantApp(launcher,
+ task.getTopComponent().getPackageName())) {
+ return mSystemShortcut.createOnClickListener(launcher, itemInfo);
+ }
+ return null;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
new file mode 100644
index 000000000..a95e7c171
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.pm.PackageManager;
+import android.util.Log;
+
+import com.android.launcher3.Launcher;
+import com.android.systemui.shared.recents.model.Task;
+
+/**
+ * Contains helpful methods for retrieving data from {@link Task}s.
+ * TODO: remove this once we switch to getting the icon and label from IconCache.
+ */
+public class TaskUtils {
+ private static final String TAG = "TaskUtils";
+
+ public static CharSequence getTitle(Launcher launcher, Task task) {
+ PackageManager pm = launcher.getPackageManager();
+ try {
+ return pm.getPackageInfo(task.getTopComponent().getPackageName(), 0)
+ .applicationInfo.loadLabel(pm);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Failed to get title for task " + task, e);
+ }
+ return "";
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
index 6b37adae6..94d85ee6a 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -208,12 +208,14 @@ public class TaskView extends FrameLayout implements TaskCallbacks, SwipeDetecto
public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
mSnapshotView.setThumbnail(thumbnailData);
mIconView.setImageDrawable(task.icon);
+ mIconView.setOnLongClickListener(icon -> TaskMenuView.showForTask(this));
}
@Override
public void onTaskDataUnloaded() {
mSnapshotView.setThumbnail(null);
mIconView.setImageDrawable(null);
+ mIconView.setOnLongClickListener(null);
}
@Override