diff options
author | Tony Wickham <twickham@google.com> | 2017-12-14 18:38:25 -0800 |
---|---|---|
committer | Tony Wickham <twickham@google.com> | 2018-01-02 15:44:34 -0800 |
commit | 2fae2a0e9c337d217a63980f19eaae198720f86a (patch) | |
tree | 77aad50d2b4a238f7a8a238f31abf46dd94b59ed | |
parent | 9328a51271dbcf122021dcf5e40a6cc41a0cb90f (diff) | |
download | android_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
-rw-r--r-- | quickstep/res/layout/task_menu.xml | 36 | ||||
-rw-r--r-- | quickstep/res/values/dimens.xml | 1 | ||||
-rw-r--r-- | quickstep/src/com/android/quickstep/TaskMenuView.java | 223 | ||||
-rw-r--r-- | quickstep/src/com/android/quickstep/TaskSystemShortcut.java | 92 | ||||
-rw-r--r-- | quickstep/src/com/android/quickstep/TaskUtils.java | 42 | ||||
-rw-r--r-- | quickstep/src/com/android/quickstep/TaskView.java | 2 | ||||
-rw-r--r-- | src/com/android/launcher3/AbstractFloatingView.java | 6 | ||||
-rw-r--r-- | src/com/android/launcher3/popup/SystemShortcut.java | 23 | ||||
-rw-r--r-- | src/com/android/launcher3/util/InstantAppResolver.java | 14 |
9 files changed, 426 insertions, 13 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 diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index da464c007..9a6be0b2a 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -43,7 +43,8 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch TYPE_WIDGET_RESIZE_FRAME, TYPE_WIDGETS_FULL_SHEET, TYPE_QUICKSTEP_PREVIEW, - TYPE_ON_BOARD_POPUP + TYPE_ON_BOARD_POPUP, + TYPE_TASK_MENU }) @Retention(RetentionPolicy.SOURCE) public @interface FloatingViewType {} @@ -54,10 +55,11 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch public static final int TYPE_WIDGETS_FULL_SHEET = 1 << 4; public static final int TYPE_QUICKSTEP_PREVIEW = 1 << 5; public static final int TYPE_ON_BOARD_POPUP = 1 << 6; + public static final int TYPE_TASK_MENU = 1 << 7; public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET - | TYPE_QUICKSTEP_PREVIEW | TYPE_ON_BOARD_POPUP; + | TYPE_QUICKSTEP_PREVIEW | TYPE_ON_BOARD_POPUP | TYPE_TASK_MENU; // Type of popups which should be kept open during launcher rebind public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java index c398aaa28..42aa12b51 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -1,5 +1,8 @@ package com.android.launcher3.popup; +import static com.android.launcher3.userevent.nano.LauncherLogProto.Action; +import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; + import android.content.Intent; import android.graphics.Rect; import android.os.Bundle; @@ -19,9 +22,6 @@ import com.android.launcher3.widget.WidgetsBottomSheet; import java.util.List; -import static com.android.launcher3.userevent.nano.LauncherLogProto.Action; -import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; - /** * Represents a system shortcut for a given app. The shortcut should have a static label and * icon, and an onClickListener that depends on the item that the shortcut services. @@ -110,14 +110,15 @@ public abstract class SystemShortcut extends ItemInfo { if (!enabled) { return null; } - return new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = PackageManagerHelper.getMarketIntent(itemInfo - .getTargetComponent().getPackageName()); - launcher.startActivitySafely(view, intent, itemInfo); - AbstractFloatingView.closeAllOpenViews(launcher); - } + return createOnClickListener(launcher, itemInfo); + } + + public View.OnClickListener createOnClickListener(Launcher launcher, ItemInfo itemInfo) { + return view -> { + Intent intent = PackageManagerHelper.getMarketIntent(itemInfo + .getTargetComponent().getPackageName()); + launcher.startActivitySafely(view, intent, itemInfo); + AbstractFloatingView.closeAllOpenViews(launcher); }; } } diff --git a/src/com/android/launcher3/util/InstantAppResolver.java b/src/com/android/launcher3/util/InstantAppResolver.java index 99ce7ca90..601a5ab80 100644 --- a/src/com/android/launcher3/util/InstantAppResolver.java +++ b/src/com/android/launcher3/util/InstantAppResolver.java @@ -18,8 +18,11 @@ package com.android.launcher3.util; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.util.Log; import com.android.launcher3.AppInfo; +import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -44,6 +47,17 @@ public class InstantAppResolver { return false; } + public boolean isInstantApp(Launcher launcher, String packageName) { + PackageManager packageManager = launcher.getPackageManager(); + try { + return isInstantApp(packageManager.getPackageInfo(packageName, 0).applicationInfo); + } catch (PackageManager.NameNotFoundException e) { + Log.e("InstantAppResolver", "Failed to determine whether package is instant app " + + packageName, e); + } + return false; + } + public List<ApplicationInfo> getInstantApps() { return Collections.emptyList(); } |