Long-press on an app to reveal its shortcuts.
- Add ShortcutsContainerListener to icons on workspace, folders, and all apps. This handles long-press and forwards following touches to the DeepShortcutsContainer that is created. - Drag over shortcut before lifting finger to launch it. - Shortcuts are rendered in pill-shaped DeepShortcutViews, which are inside DeepShortcutContainer on DragLayer. - The shortcut container orients above or below the icon, and left or right-aligns with it. Biases for above + left-align. - Long press a DeepShortcutPill to drag and pin it to the workspace. Bug: 28980830 Change-Id: I08658d13ae51fe53064644e8d8f7b42f150fdd7d
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/quantum_panel_bg_color" />
|
||||
<size android:height="48dp" />
|
||||
<corners android:radius="24dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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.launcher3.shortcuts.DeepShortcutView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/Icon.DeepShortcut"
|
||||
android:focusable="true"/>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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.launcher3.shortcuts.DeepShortcutsContainer
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/deep_shortcuts_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="@dimen/deep_shortcuts_elevation"
|
||||
android:orientation="vertical">
|
||||
|
||||
</com.android.launcher3.shortcuts.DeepShortcutsContainer>
|
||||
@@ -154,4 +154,14 @@
|
||||
<dimen name="pending_widget_min_padding">8dp</dimen>
|
||||
<dimen name="pending_widget_elevation">2dp</dimen>
|
||||
|
||||
<!-- Deep shortcuts -->
|
||||
<dimen name="deep_shortcuts_elevation">9dp</dimen>
|
||||
<dimen name="deep_shortcuts_width">180dp</dimen>
|
||||
<dimen name="deep_shortcuts_spacing">4dp</dimen>
|
||||
<dimen name="deep_shortcuts_drag_view_scale">6dp</dimen>
|
||||
<!-- an icon with shortcuts must be dragged this much distance away from the nearest edge
|
||||
of the shortcut container before the container is removed. -->
|
||||
<dimen name="deep_shortcuts_start_drag_threshold">35dp</dimen>
|
||||
<dimen name="deep_shortcut_icon_size">36dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
||||
+15
-1
@@ -70,6 +70,21 @@
|
||||
<item name="customShadows">false</item>
|
||||
</style>
|
||||
|
||||
<style name="Icon.DeepShortcut">
|
||||
<item name="android:background">@drawable/bg_white_pill</item>
|
||||
<item name="android:gravity">start|center_vertical</item>
|
||||
<item name="android:minWidth">@dimen/deep_shortcuts_width</item>
|
||||
<item name="android:maxWidth">@dimen/deep_shortcuts_width</item>
|
||||
<item name="android:elevation">@dimen/deep_shortcuts_elevation</item>
|
||||
<item name="android:paddingLeft">7dp</item>
|
||||
<item name="android:drawablePadding">12dp</item>
|
||||
<item name="android:textColor">@color/quantum_panel_text_color</item>
|
||||
<item name="android:shadowRadius">0</item>
|
||||
<item name="customShadows">false</item>
|
||||
<item name="layoutHorizontal">true</item>
|
||||
<item name="iconSizeOverride">@dimen/deep_shortcut_icon_size</item>
|
||||
</style>
|
||||
|
||||
<!-- Drop targets -->
|
||||
<style name="DropTargetButtonBase">
|
||||
<item name="android:drawablePadding">7.5dp</item>
|
||||
@@ -99,5 +114,4 @@
|
||||
<item name="indicatorSize">4dp</item>
|
||||
<item name="ringOutset">4dp</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -67,6 +67,7 @@ public class BubbleTextView extends TextView
|
||||
private final Launcher mLauncher;
|
||||
private Drawable mIcon;
|
||||
private final Drawable mBackground;
|
||||
private OnLongClickListener mOnLongClickListener;
|
||||
private final CheckLongPressHelper mLongPressHelper;
|
||||
private final HolographicOutlineHelper mOutlineHelper;
|
||||
private final StylusEventHelper mStylusEventHelper;
|
||||
@@ -270,6 +271,16 @@ public class BubbleTextView extends TextView
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnLongClickListener(OnLongClickListener l) {
|
||||
super.setOnLongClickListener(l);
|
||||
mOnLongClickListener = l;
|
||||
}
|
||||
|
||||
public OnLongClickListener getOnLongClickListener() {
|
||||
return mOnLongClickListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// Call the superclass onTouchEvent first, because sometimes it changes the state to
|
||||
|
||||
@@ -22,10 +22,12 @@ import com.android.launcher3.util.Thunk;
|
||||
|
||||
public class CheckLongPressHelper {
|
||||
|
||||
public static final int DEFAULT_LONG_PRESS_TIMEOUT = 300;
|
||||
|
||||
@Thunk View mView;
|
||||
@Thunk View.OnLongClickListener mListener;
|
||||
@Thunk boolean mHasPerformedLongPress;
|
||||
private int mLongPressTimeout = 300;
|
||||
private int mLongPressTimeout = DEFAULT_LONG_PRESS_TIMEOUT;
|
||||
private CheckForLongPress mPendingCheckForLongPress;
|
||||
|
||||
class CheckForLongPress implements Runnable {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.launcher3;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -137,6 +138,10 @@ public class ItemInfo {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ComponentName getTargetComponent() {
|
||||
return getIntent() == null ? null : getIntent().getComponent();
|
||||
}
|
||||
|
||||
public void writeToValues(ContentValues values) {
|
||||
values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
|
||||
values.put(LauncherSettings.Favorites.CONTAINER, container);
|
||||
|
||||
@@ -59,7 +59,6 @@ import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.StrictMode;
|
||||
@@ -113,11 +112,10 @@ import com.android.launcher3.folder.Folder;
|
||||
import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
|
||||
import com.android.launcher3.logging.FileLog;
|
||||
import com.android.launcher3.logging.LoggerUtils;
|
||||
import com.android.launcher3.logging.UserEventDispatcher;
|
||||
import com.android.launcher3.model.WidgetsModel;
|
||||
import com.android.launcher3.pageindicators.PageIndicator;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.MultiHashMap;
|
||||
import com.android.launcher3.util.PackageManagerHelper;
|
||||
@@ -136,7 +134,6 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Default launcher application.
|
||||
@@ -4062,6 +4059,15 @@ public class Launcher extends Activity
|
||||
if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
|
||||
}
|
||||
|
||||
public List<String> getShortcutIdsForItem(ItemInfo info) {
|
||||
if (!DeepShortcutManager.supportsShortcuts(info)) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
ComponentName component = info.getTargetComponent();
|
||||
List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user));
|
||||
return ids == null ? Collections.EMPTY_LIST : ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* A package was updated.
|
||||
*
|
||||
|
||||
@@ -47,7 +47,6 @@ import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.support.v4.os.BuildCompat;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextUtils;
|
||||
@@ -57,7 +56,9 @@ import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
import android.util.TypedValue;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.launcher3.compat.UserHandleCompat;
|
||||
@@ -68,8 +69,10 @@ import com.android.launcher3.util.IconNormalizer;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -425,6 +428,30 @@ public final class Utilities {
|
||||
localY < (v.getHeight() + slop);
|
||||
}
|
||||
|
||||
/** Translates MotionEvents from src's coordinate system to dst's. */
|
||||
public static void translateEventCoordinates(View src, View dst, MotionEvent dstEvent) {
|
||||
toGlobalMotionEvent(src, dstEvent);
|
||||
toLocalMotionEvent(dst, dstEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations
|
||||
* (scaleX, scaleY, etc).
|
||||
*/
|
||||
private static void toGlobalMotionEvent(View view, MotionEvent event) {
|
||||
view.getLocationOnScreen(sLoc0);
|
||||
event.offsetLocation(sLoc0[0], sLoc0[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulates View.toLocalMotionEvent(). This implementation does not handle transformations
|
||||
* (scaleX, scaleY, etc).
|
||||
*/
|
||||
private static void toLocalMotionEvent(View view, MotionEvent event) {
|
||||
view.getLocationOnScreen(sLoc0);
|
||||
event.offsetLocation(-sLoc0[0], -sLoc0[1]);
|
||||
}
|
||||
|
||||
public static int[] getCenterDeltaInScreenSpace(View v0, View v1, int[] delta) {
|
||||
v0.getLocationInWindow(sLoc0);
|
||||
v1.getLocationInWindow(sLoc1);
|
||||
@@ -819,6 +846,11 @@ public final class Utilities {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Returns whether the collection is null or empty. */
|
||||
public static boolean isEmpty(Collection c) {
|
||||
return c == null || c.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
|
||||
* This allows the badging to be done based on the action bitmap size rather than
|
||||
|
||||
@@ -77,7 +77,8 @@ import com.android.launcher3.dragndrop.SpringLoadedDragController;
|
||||
import com.android.launcher3.folder.Folder;
|
||||
import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.logging.UserEventDispatcher;
|
||||
import com.android.launcher3.pageindicators.PageIndicator;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
||||
import com.android.launcher3.shortcuts.ShortcutsContainerListener;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
|
||||
import com.android.launcher3.util.LongArrayMap;
|
||||
@@ -1094,6 +1095,10 @@ public class Workspace extends PagedView
|
||||
if (!(child instanceof Folder)) {
|
||||
child.setHapticFeedbackEnabled(false);
|
||||
child.setOnLongClickListener(mLongClickListener);
|
||||
if (child instanceof BubbleTextView && DeepShortcutManager.supportsShortcuts(info)) {
|
||||
// TODO: only add this listener if the item has shortcuts associated with it.
|
||||
child.setOnTouchListener(new ShortcutsContainerListener((BubbleTextView) child));
|
||||
}
|
||||
}
|
||||
if (child instanceof DropTarget) {
|
||||
mDragController.addDropTarget((DropTarget) child);
|
||||
|
||||
@@ -18,17 +18,13 @@ package com.android.launcher3.allapps;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Selection;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.method.TextKeyListener;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
@@ -45,14 +41,14 @@ import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.DragSource;
|
||||
import com.android.launcher3.DropTarget;
|
||||
import com.android.launcher3.ExtendedEditText;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.folder.Folder;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherTransitionable;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.folder.Folder;
|
||||
import com.android.launcher3.keyboard.FocusedItemDecorator;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
|
||||
@@ -133,8 +129,7 @@ final class SimpleSectionMergeAlgorithm implements AlphabeticalAppsList.MergeAlg
|
||||
* The all apps view container.
|
||||
*/
|
||||
public class AllAppsContainerView extends BaseContainerView implements DragSource,
|
||||
LauncherTransitionable, View.OnTouchListener, View.OnLongClickListener,
|
||||
AllAppsSearchBarController.Callbacks {
|
||||
LauncherTransitionable, View.OnLongClickListener, AllAppsSearchBarController.Callbacks {
|
||||
|
||||
private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
|
||||
private static final int MAX_NUM_MERGES_PHONE = 2;
|
||||
@@ -163,8 +158,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||
private int mRecyclerViewTopBottomPadding;
|
||||
// This coordinate is relative to this container view
|
||||
private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
|
||||
// This coordinate is relative to its parent
|
||||
private final Point mIconLastTouchPos = new Point();
|
||||
|
||||
public AllAppsContainerView(Context context) {
|
||||
this(context, null);
|
||||
@@ -181,7 +174,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||
mLauncher = Launcher.getLauncher(context);
|
||||
mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
|
||||
mApps = new AlphabeticalAppsList(context);
|
||||
mAdapter = new AllAppsGridAdapter(mLauncher, mApps, this, mLauncher, this);
|
||||
mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
|
||||
mApps.setAdapter(mAdapter);
|
||||
mLayoutManager = mAdapter.getLayoutManager();
|
||||
mItemDecoration = mAdapter.getItemDecoration();
|
||||
@@ -529,18 +522,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||
return handleTouchEvent(ev);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent ev) {
|
||||
switch (ev.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
// Return early if this is not initiated from a touch
|
||||
@@ -553,7 +534,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||
if (!mLauncher.isDraggingEnabled()) return false;
|
||||
|
||||
// Start the drag
|
||||
mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false);
|
||||
mLauncher.getWorkspace().beginDragShared(v, this, false);
|
||||
// Enter spring loaded mode
|
||||
mLauncher.enterSpringLoadedDragMode();
|
||||
|
||||
|
||||
@@ -17,17 +17,13 @@ package com.android.launcher3.allapps;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
|
||||
import android.support.v4.view.accessibility.AccessibilityEventCompat;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.Gravity;
|
||||
@@ -38,13 +34,14 @@ import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.launcher3.AppInfo;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
||||
import com.android.launcher3.shortcuts.ShortcutsContainerListener;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -331,7 +328,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
private final GridLayoutManager mGridLayoutMgr;
|
||||
private final GridSpanSizer mGridSizer;
|
||||
private final GridItemDecoration mItemDecoration;
|
||||
private final View.OnTouchListener mTouchListener;
|
||||
private final View.OnClickListener mIconClickListener;
|
||||
private final View.OnLongClickListener mIconLongClickListener;
|
||||
|
||||
@@ -357,8 +353,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
private Intent mMarketSearchIntent;
|
||||
|
||||
public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps,
|
||||
View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
|
||||
View.OnLongClickListener iconLongClickListener) {
|
||||
View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener) {
|
||||
Resources res = launcher.getResources();
|
||||
mLauncher = launcher;
|
||||
mApps = apps;
|
||||
@@ -368,7 +363,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
|
||||
mItemDecoration = new GridItemDecoration();
|
||||
mLayoutInflater = LayoutInflater.from(launcher);
|
||||
mTouchListener = touchListener;
|
||||
mIconClickListener = iconClickListener;
|
||||
mIconLongClickListener = iconLongClickListener;
|
||||
mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
|
||||
@@ -454,7 +448,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
|
||||
viewType == ICON_VIEW_TYPE ? R.layout.all_apps_icon :
|
||||
R.layout.all_apps_prediction_bar_icon, parent, false);
|
||||
icon.setOnTouchListener(mTouchListener);
|
||||
icon.setOnClickListener(mIconClickListener);
|
||||
icon.setOnLongClickListener(mIconLongClickListener);
|
||||
icon.setLongPressTimeout(ViewConfiguration.get(parent.getContext())
|
||||
@@ -490,6 +483,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
|
||||
BubbleTextView icon = (BubbleTextView) holder.mContent;
|
||||
icon.applyFromApplicationInfo(info);
|
||||
if (DeepShortcutManager.supportsShortcuts(info)) {
|
||||
// TODO: only add this listener if the item has shortcuts associated with it.
|
||||
icon.setOnTouchListener(new ShortcutsContainerListener(icon));
|
||||
}
|
||||
icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
|
||||
break;
|
||||
}
|
||||
@@ -497,6 +494,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
||||
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
|
||||
BubbleTextView icon = (BubbleTextView) holder.mContent;
|
||||
icon.applyFromApplicationInfo(info);
|
||||
if (DeepShortcutManager.supportsShortcuts(info)) {
|
||||
// TODO: only add this listener if the item has shortcuts associated with it.
|
||||
icon.setOnTouchListener(new ShortcutsContainerListener(icon));
|
||||
}
|
||||
icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import com.android.launcher3.ShortcutInfo;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
|
||||
@@ -247,9 +248,12 @@ public class DragController implements DragDriver.EventListener, TouchController
|
||||
|
||||
mDragObject = new DropTarget.DragObject();
|
||||
|
||||
final Resources res = mLauncher.getResources();
|
||||
final float scaleDps = FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ?
|
||||
res.getDimensionPixelSize(R.dimen.dragViewScale) : 0f;
|
||||
final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
|
||||
registrationY, 0, 0, b.getWidth(), b.getHeight(),
|
||||
initialDragViewScale);
|
||||
initialDragViewScale, scaleDps);
|
||||
|
||||
mDragObject.dragComplete = false;
|
||||
if (mIsAccessibleDrag) {
|
||||
@@ -284,6 +288,10 @@ public class DragController implements DragDriver.EventListener, TouchController
|
||||
return dragView;
|
||||
}
|
||||
|
||||
public Point getMotionDown() {
|
||||
return new Point(mMotionDownX, mMotionDownY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this from a drag source view like this:
|
||||
*
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
|
||||
package com.android.launcher3.dragndrop;
|
||||
|
||||
import com.android.launcher3.AnotherWindowDropTarget;
|
||||
import com.android.launcher3.DropTarget;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Canvas;
|
||||
@@ -30,6 +24,12 @@ import android.view.DragEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.AnotherWindowDropTarget;
|
||||
import com.android.launcher3.DropTarget;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
|
||||
/**
|
||||
* Base class for driving a drag/drop operation.
|
||||
*/
|
||||
|
||||
@@ -43,23 +43,22 @@ import android.widget.TextView;
|
||||
|
||||
import com.android.launcher3.AppWidgetResizeFrame;
|
||||
import com.android.launcher3.CellLayout;
|
||||
import com.android.launcher3.DropTargetBar;
|
||||
import com.android.launcher3.InsettableFrameLayout;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherAppWidgetHostView;
|
||||
import com.android.launcher3.PinchToOverviewListener;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.DropTargetBar;
|
||||
import com.android.launcher3.ShortcutAndWidgetContainer;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
|
||||
import com.android.launcher3.allapps.AllAppsTransitionController;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.folder.Folder;
|
||||
import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutsContainer;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
|
||||
@@ -193,22 +192,23 @@ public class DragLayer extends InsettableFrameLayout {
|
||||
}
|
||||
|
||||
public boolean isEventOverHotseat(MotionEvent ev) {
|
||||
getDescendantRectRelativeToSelf(mLauncher.getHotseat(), mHitRect);
|
||||
return mHitRect.contains((int) ev.getX(), (int) ev.getY());
|
||||
return isEventOverView(mLauncher.getHotseat(), ev);
|
||||
}
|
||||
|
||||
private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
|
||||
getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
|
||||
return mHitRect.contains((int) ev.getX(), (int) ev.getY());
|
||||
return isEventOverView(folder.getEditTextRegion(), ev);
|
||||
}
|
||||
|
||||
private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
|
||||
getDescendantRectRelativeToSelf(folder, mHitRect);
|
||||
return mHitRect.contains((int) ev.getX(), (int) ev.getY());
|
||||
return isEventOverView(folder, ev);
|
||||
}
|
||||
|
||||
private boolean isEventOverDropTargetBar(MotionEvent ev) {
|
||||
getDescendantRectRelativeToSelf(mLauncher.getDropTargetBar(), mHitRect);
|
||||
return isEventOverView(mLauncher.getDropTargetBar(), ev);
|
||||
}
|
||||
|
||||
private boolean isEventOverView(View view, MotionEvent ev) {
|
||||
getDescendantRectRelativeToSelf(view, mHitRect);
|
||||
return mHitRect.contains((int) ev.getX(), (int) ev.getY());
|
||||
}
|
||||
|
||||
@@ -251,6 +251,25 @@ public class DragLayer extends InsettableFrameLayout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the shortcuts container when touching outside of it.
|
||||
DeepShortcutsContainer deepShortcutsContainer = (DeepShortcutsContainer)
|
||||
findViewById(R.id.deep_shortcuts_container);
|
||||
if (deepShortcutsContainer != null) {
|
||||
if (!isEventOverView(deepShortcutsContainer, ev)) {
|
||||
if (isInAccessibleDrag()) {
|
||||
// Do not close the container if in drag and drop.
|
||||
if (!isEventOverDropTargetBar(ev)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
removeView(deepShortcutsContainer);
|
||||
// We let touches on the original icon go through so that users can launch
|
||||
// the app with one tap if they don't find a shortcut they want.
|
||||
return !isEventOverView(deepShortcutsContainer.getDeferredDragIcon(), ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -258,7 +277,6 @@ public class DragLayer extends InsettableFrameLayout {
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
int action = ev.getAction();
|
||||
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
if (handleTouchDown(ev, true)) {
|
||||
return true;
|
||||
@@ -275,6 +293,7 @@ public class DragLayer extends InsettableFrameLayout {
|
||||
mActiveController = mDragController;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onInterceptTouchEvent(ev)) {
|
||||
mActiveController = mAllAppsController;
|
||||
return true;
|
||||
@@ -395,7 +414,6 @@ public class DragLayer extends InsettableFrameLayout {
|
||||
int x = (int) ev.getX();
|
||||
int y = (int) ev.getY();
|
||||
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
if (handleTouchDown(ev, false)) {
|
||||
return true;
|
||||
@@ -526,6 +544,10 @@ public class DragLayer extends InsettableFrameLayout {
|
||||
return new LayoutParams(p);
|
||||
}
|
||||
|
||||
public void setController(TouchController controller) {
|
||||
mActiveController = controller;
|
||||
}
|
||||
|
||||
public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
|
||||
public int x, y;
|
||||
public boolean customPosition = false;
|
||||
|
||||
@@ -84,16 +84,13 @@ public class DragView extends View {
|
||||
* @param registrationY The y coordinate of the registration point.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
|
||||
int left, int top, int width, int height, final float initialScale) {
|
||||
public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY, int left,
|
||||
int top, int width, int height, final float initialScale, final float finalScaleDps) {
|
||||
super(launcher);
|
||||
mDragLayer = launcher.getDragLayer();
|
||||
mDragController = launcher.getDragController();
|
||||
|
||||
final Resources res = getResources();
|
||||
final float scaleDps = !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? 0f
|
||||
: res.getDimensionPixelSize(R.dimen.dragViewScale);
|
||||
final float scale = (width + scaleDps) / width;
|
||||
final float scale = (width + finalScaleDps) / width;
|
||||
|
||||
// Set the initial scale to avoid any jumps
|
||||
setScaleX(initialScale);
|
||||
@@ -349,12 +346,12 @@ public class DragView extends View {
|
||||
* @param touchX the x coordinate the user touched in DragLayer coordinates
|
||||
* @param touchY the y coordinate the user touched in DragLayer coordinates
|
||||
*/
|
||||
void move(int touchX, int touchY) {
|
||||
public void move(int touchX, int touchY) {
|
||||
setTranslationX(touchX - mRegistrationX);
|
||||
setTranslationY(touchY - mRegistrationY);
|
||||
}
|
||||
|
||||
void remove() {
|
||||
public void remove() {
|
||||
if (getParent() != null) {
|
||||
mDragLayer.removeView(DragView.this);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
@@ -295,7 +294,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
|
||||
return false;
|
||||
}
|
||||
|
||||
mLauncher.getWorkspace().beginDragShared(v, new Point(), this, accessible);
|
||||
mLauncher.getWorkspace().beginDragShared(v, this, accessible);
|
||||
|
||||
mCurrentDragInfo = item;
|
||||
mEmptyCellRank = item.rank;
|
||||
|
||||
@@ -46,6 +46,8 @@ import com.android.launcher3.Workspace.ItemOperator;
|
||||
import com.android.launcher3.dragndrop.DragController;
|
||||
import com.android.launcher3.pageindicators.PageIndicator;
|
||||
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
||||
import com.android.launcher3.shortcuts.ShortcutsContainerListener;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -234,6 +236,10 @@ public class FolderPagedView extends PagedView {
|
||||
textView.applyFromShortcutInfo(item, mIconCache);
|
||||
textView.setOnClickListener(mFolder);
|
||||
textView.setOnLongClickListener(mFolder);
|
||||
if (DeepShortcutManager.supportsShortcuts(item)) {
|
||||
// TODO: only add this listener if the item has shortcuts associated with it.
|
||||
textView.setOnTouchListener(new ShortcutsContainerListener(textView));
|
||||
}
|
||||
textView.setOnFocusChangeListener(mFocusIndicatorHelper);
|
||||
textView.setOnKeyListener(mKeyListener);
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.compat.UserHandleCompat;
|
||||
|
||||
@@ -54,6 +56,11 @@ public class DeepShortcutManager {
|
||||
mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
|
||||
}
|
||||
|
||||
public static boolean supportsShortcuts(ItemInfo info) {
|
||||
return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
|
||||
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
|
||||
}
|
||||
|
||||
public void onShortcutsChanged(List<ShortcutInfoCompat> shortcuts) {
|
||||
// mShortcutCache.removeShortcuts(shortcuts);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.shortcuts;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.R;
|
||||
|
||||
/**
|
||||
* A {@link BubbleTextView} that represents a deep shortcut within an app.
|
||||
*/
|
||||
public class DeepShortcutView extends BubbleTextView {
|
||||
|
||||
private static final float HOVER_SCALE = 1.1f;
|
||||
// The direction this view should translate when animating the hover state.
|
||||
// This allows hovered shortcuts to "push" other shortcuts away.
|
||||
@IntDef({DIRECTION_UP, DIRECTION_NONE, DIRECTION_DOWN})
|
||||
public @interface TranslationDirection {}
|
||||
|
||||
public static final int DIRECTION_UP = -1;
|
||||
public static final int DIRECTION_NONE = 0;
|
||||
public static final int DIRECTION_DOWN = 1;
|
||||
@TranslationDirection
|
||||
private int mTranslationDirection = DIRECTION_NONE;
|
||||
|
||||
private int mSpacing;
|
||||
private int mTop;
|
||||
private boolean mIsHoveringOver = false;
|
||||
|
||||
public DeepShortcutView(Context context) {
|
||||
this(context, null, 0);
|
||||
}
|
||||
|
||||
public DeepShortcutView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public DeepShortcutView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
mSpacing = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
|
||||
}
|
||||
|
||||
public int getSpacing() {
|
||||
return mSpacing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state of this view based on touches over the container before user lifts finger.
|
||||
*
|
||||
* @param containerContainsTouch whether the {@link DeepShortcutsContainer} this shortcut
|
||||
* is inside contains the current touch
|
||||
* @param isBelowHoveredShortcut whether a sibling shortcut before this one in the
|
||||
* view hierarchy is being hovered over
|
||||
* @param touchY the y coordinate of the touch, relative to the {@link DeepShortcutsContainer}
|
||||
* this shortcut is inside
|
||||
* @return whether this shortcut is being hovered over
|
||||
*/
|
||||
public boolean updateHoverState(boolean containerContainsTouch, boolean isBelowHoveredShortcut,
|
||||
float touchY) {
|
||||
if (!containerContainsTouch) {
|
||||
mIsHoveringOver = false;
|
||||
mTranslationDirection = DIRECTION_NONE;
|
||||
} else if (isBelowHoveredShortcut) {
|
||||
mIsHoveringOver = false;
|
||||
mTranslationDirection = DIRECTION_DOWN;
|
||||
} else {
|
||||
// Include space around the view when determining hover state to avoid gaps.
|
||||
mTop = (int) (getY() - getTranslationY());
|
||||
mIsHoveringOver = (touchY >= mTop - mSpacing / 2)
|
||||
&& (touchY < mTop + getHeight() + mSpacing / 2);
|
||||
mTranslationDirection = mIsHoveringOver ? DIRECTION_NONE : DIRECTION_UP;
|
||||
}
|
||||
animateHoverState();
|
||||
return mIsHoveringOver;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this shortcut is being hovered over, we scale it up. If another shortcut is being hovered
|
||||
* over, we translate this one away from it to account for its increased size.
|
||||
*
|
||||
* TODO: apply motion spec here
|
||||
*/
|
||||
private void animateHoverState() {
|
||||
float scale = mIsHoveringOver ? HOVER_SCALE : 1f;
|
||||
setScaleX(scale);
|
||||
setScaleY(scale);
|
||||
|
||||
float translation = (HOVER_SCALE - 1f) * getHeight();
|
||||
setTranslationY(translation * mTranslationDirection);
|
||||
}
|
||||
|
||||
public boolean isHoveringOver() {
|
||||
return mIsHoveringOver;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
package com.android.launcher3.shortcuts;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.DragSource;
|
||||
import com.android.launcher3.DropTarget;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LogDecelerateInterpolator;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.ShortcutInfo;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.compat.UserHandleCompat;
|
||||
import com.android.launcher3.dragndrop.DragLayer;
|
||||
import com.android.launcher3.dragndrop.DragView;
|
||||
import com.android.launcher3.logging.UserEventDispatcher;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.util.UiThreadCircularReveal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A container for shortcuts to deep links within apps.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
public class DeepShortcutsContainer extends LinearLayout implements View.OnClickListener,
|
||||
View.OnLongClickListener, View.OnTouchListener, DragSource,
|
||||
UserEventDispatcher.LaunchSourceProvider, TouchController {
|
||||
private static final String TAG = "ShortcutsContainer";
|
||||
|
||||
private Launcher mLauncher;
|
||||
private DeepShortcutManager mDeepShortcutsManager;
|
||||
private final int mDragDeadzone;
|
||||
private final int mStartDragThreshold;
|
||||
private BubbleTextView mDeferredDragIcon;
|
||||
private int mActivePointerId;
|
||||
private Point mTouchDown = null;
|
||||
private DragView mDragView;
|
||||
private float mLastX, mLastY;
|
||||
private float mDistanceDragged = 0;
|
||||
private final Rect mTempRect = new Rect();
|
||||
private final int[] mTempXY = new int[2];
|
||||
private Point mIconLastTouchPos = new Point();
|
||||
private boolean mIsLeftAligned;
|
||||
private boolean mIsAboveIcon;
|
||||
|
||||
public DeepShortcutsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mLauncher = (Launcher) context;
|
||||
mDeepShortcutsManager = LauncherAppState.getInstance().getShortcutManager();
|
||||
|
||||
mDragDeadzone = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
mStartDragThreshold = getResources().getDimensionPixelSize(
|
||||
R.dimen.deep_shortcuts_start_drag_threshold);
|
||||
}
|
||||
|
||||
public DeepShortcutsContainer(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public DeepShortcutsContainer(Context context) {
|
||||
this(context, null, 0);
|
||||
}
|
||||
|
||||
public void populateAndShow(final BubbleTextView originalIcon, final List<String> ids) {
|
||||
// Add dummy views first, and populate with real shortcut info when ready.
|
||||
for (int i = 0; i < ids.size(); i++) {
|
||||
final DeepShortcutView shortcut = (DeepShortcutView)
|
||||
mLauncher.getLayoutInflater().inflate(R.layout.deep_shortcut, this, false);
|
||||
if (i < ids.size() - 1) {
|
||||
((LayoutParams) shortcut.getLayoutParams()).bottomMargin = shortcut.getSpacing();
|
||||
}
|
||||
addView(shortcut);
|
||||
}
|
||||
|
||||
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
||||
animateOpen(originalIcon);
|
||||
|
||||
deferDrag(originalIcon);
|
||||
|
||||
// Load the shortcuts on a background thread and update the container as it animates.
|
||||
final ItemInfo originalInfo = (ItemInfo) originalIcon.getTag();
|
||||
final UserHandleCompat user = originalInfo.user;
|
||||
final ComponentName activity = originalInfo.getTargetComponent();
|
||||
new AsyncTask<Void, Void, List<ShortcutInfo>>() {
|
||||
public List<ShortcutInfo> doInBackground(Void ... args) {
|
||||
List<ShortcutInfoCompat> shortcuts = mDeepShortcutsManager
|
||||
.queryForAllAppShortcuts(activity, ids, user);
|
||||
List<ShortcutInfo> shortcutInfos = new ArrayList<>(shortcuts.size());
|
||||
for (ShortcutInfoCompat shortcut : shortcuts) {
|
||||
shortcutInfos.add(ShortcutInfo.fromDeepShortcutInfo(shortcut, mLauncher));
|
||||
}
|
||||
return shortcutInfos;
|
||||
}
|
||||
|
||||
// TODO: implement onProgressUpdate() to load shortcuts one at a time.
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<ShortcutInfo> shortcuts) {
|
||||
for (int i = 0; i < shortcuts.size(); i++) {
|
||||
DeepShortcutView iconAndText = (DeepShortcutView) getChildAt(i);
|
||||
ShortcutInfo launcherShortcutInfo = shortcuts.get(i);
|
||||
iconAndText.applyFromShortcutInfo(launcherShortcutInfo,
|
||||
LauncherAppState.getInstance().getIconCache());
|
||||
iconAndText.setOnClickListener(DeepShortcutsContainer.this);
|
||||
iconAndText.setOnLongClickListener(DeepShortcutsContainer.this);
|
||||
iconAndText.setOnTouchListener(DeepShortcutsContainer.this);
|
||||
int viewId = mLauncher.getViewIdForItem(originalInfo);
|
||||
iconAndText.setId(viewId);
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
// TODO: update this animation
|
||||
private void animateOpen(BubbleTextView originalIcon) {
|
||||
orientAboutIcon(originalIcon);
|
||||
|
||||
setVisibility(View.VISIBLE);
|
||||
int rx = (int) Math.max(Math.max(getMeasuredWidth() - getPivotX(), 0), getPivotX());
|
||||
int ry = (int) Math.max(Math.max(getMeasuredHeight() - getPivotY(), 0), getPivotY());
|
||||
float radius = (float) Math.hypot(rx, ry);
|
||||
Animator reveal = UiThreadCircularReveal.createCircularReveal(this, (int) getPivotX(),
|
||||
(int) getPivotY(), 0, radius);
|
||||
reveal.setDuration(getResources().getInteger(R.integer.config_materialFolderExpandDuration));
|
||||
reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
|
||||
reveal.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Orients this container above or below the given icon, aligning with the left or right.
|
||||
*
|
||||
* These are the preferred orientations, in order:
|
||||
* - Above and left-aligned
|
||||
* - Above and right-aligned
|
||||
* - Below and left-aligned
|
||||
* - Below and right-aligned
|
||||
*
|
||||
* So we always align left if there is enough horizontal space
|
||||
* and align above if there is enough vertical space.
|
||||
*
|
||||
* TODO: draw pointer based on orientation.
|
||||
*/
|
||||
private void orientAboutIcon(BubbleTextView icon) {
|
||||
int width = getMeasuredWidth();
|
||||
int height = getMeasuredHeight();
|
||||
|
||||
DragLayer dragLayer = mLauncher.getDragLayer();
|
||||
dragLayer.getDescendantRectRelativeToSelf(icon, mTempRect);
|
||||
// Align left and above by default.
|
||||
int x = mTempRect.left + icon.getPaddingLeft();
|
||||
int y = mTempRect.top - height;
|
||||
Rect insets = dragLayer.getInsets();
|
||||
|
||||
mIsLeftAligned = x + width < dragLayer.getRight() - insets.right;
|
||||
if (!mIsLeftAligned) {
|
||||
x = mTempRect.right - width - icon.getPaddingRight();
|
||||
}
|
||||
|
||||
mIsAboveIcon = mTempRect.top - height > dragLayer.getTop() + insets.top;
|
||||
if (!mIsAboveIcon) {
|
||||
y = mTempRect.bottom;
|
||||
}
|
||||
|
||||
setPivotX(width / 2);
|
||||
setPivotY(height / 2);
|
||||
|
||||
// Insets are added later, so subtract them now.
|
||||
y -= insets.top;
|
||||
|
||||
setX(x);
|
||||
setY(y);
|
||||
}
|
||||
|
||||
private void deferDrag(BubbleTextView originalIcon) {
|
||||
mDeferredDragIcon = originalIcon;
|
||||
showDragView(originalIcon);
|
||||
}
|
||||
|
||||
public BubbleTextView getDeferredDragIcon() {
|
||||
return mDeferredDragIcon;
|
||||
}
|
||||
|
||||
private void showDragView(BubbleTextView originalIcon) {
|
||||
// TODO: implement support for Drawable DragViews so we don't have to create a bitmap here.
|
||||
Bitmap b = Utilities.createIconBitmap(originalIcon.getIcon(), mLauncher);
|
||||
float scale = mLauncher.getDragLayer().getLocationInDragLayer(originalIcon, mTempXY);
|
||||
int dragLayerX = Math.round(mTempXY[0] - (b.getWidth() - scale * originalIcon.getWidth()) / 2);
|
||||
int dragLayerY = Math.round(mTempXY[1] - (b.getHeight() - scale * b.getHeight()) / 2
|
||||
- Workspace.DRAG_BITMAP_PADDING / 2) + originalIcon.getPaddingTop();
|
||||
int motionDownX = mLauncher.getDragController().getMotionDown().x;
|
||||
int motionDownY = mLauncher.getDragController().getMotionDown().y;
|
||||
final int registrationX = motionDownX - dragLayerX;
|
||||
final int registrationY = motionDownY - dragLayerY;
|
||||
|
||||
float scaleDps = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_drag_view_scale);
|
||||
mDragView = new DragView(mLauncher, b, registrationX, registrationY,
|
||||
0, 0, b.getWidth(), b.getHeight(), 1f, scaleDps);
|
||||
mLastX = mLastY = mDistanceDragged = 0;
|
||||
mDragView.show(motionDownX, motionDownY);
|
||||
}
|
||||
|
||||
public boolean onForwardedEvent(MotionEvent ev, int activePointerId, MotionEvent touchDownEvent) {
|
||||
mTouchDown = new Point((int) touchDownEvent.getX(), (int) touchDownEvent.getY());
|
||||
mActivePointerId = activePointerId;
|
||||
return dispatchTouchEvent(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (mDeferredDragIcon == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (activePointerIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
final float x = ev.getX(activePointerIndex);
|
||||
final float y = ev.getY(activePointerIndex);
|
||||
|
||||
|
||||
int action = ev.getAction();
|
||||
// The event was in this container's coordinate system before this,
|
||||
// but will be in DragLayer's coordinate system from now on.
|
||||
Utilities.translateEventCoordinates(this, mLauncher.getDragLayer(), ev);
|
||||
final int dragLayerX = (int) ev.getX();
|
||||
final int dragLayerY = (int) ev.getY();
|
||||
int childCount = getChildCount();
|
||||
if (action == MotionEvent.ACTION_MOVE) {
|
||||
if (mLastX != 0 || mLastY != 0) {
|
||||
mDistanceDragged += Math.hypot(mLastX - x, mLastY - y);
|
||||
}
|
||||
mLastX = x;
|
||||
mLastY = y;
|
||||
|
||||
boolean containerContainsTouch = x >= 0 && y >= 0 && x < getWidth() && y < getHeight();
|
||||
if (shouldStartDeferredDrag((int) x, (int) y, containerContainsTouch)) {
|
||||
mDeferredDragIcon.getParent().requestDisallowInterceptTouchEvent(false);
|
||||
mDeferredDragIcon.setVisibility(VISIBLE);
|
||||
mDeferredDragIcon.getOnLongClickListener().onLongClick(mDeferredDragIcon);
|
||||
mLauncher.getDragLayer().removeView(this);
|
||||
mLauncher.getDragController().onTouchEvent(ev);
|
||||
cleanupDeferredDrag();
|
||||
return true;
|
||||
} else {
|
||||
// Determine whether touch is over a shortcut.
|
||||
boolean hoveringOverShortcut = false;
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
DeepShortcutView shortcut = (DeepShortcutView) getChildAt(i);
|
||||
if (shortcut.updateHoverState(containerContainsTouch, hoveringOverShortcut, y)) {
|
||||
hoveringOverShortcut = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hoveringOverShortcut && mDistanceDragged > mDragDeadzone) {
|
||||
// After dragging further than a small deadzone,
|
||||
// have the drag view follow the user's finger.
|
||||
mDragView.setVisibility(VISIBLE);
|
||||
mDragView.move(dragLayerX, dragLayerY);
|
||||
mDeferredDragIcon.setVisibility(INVISIBLE);
|
||||
} else if (hoveringOverShortcut) {
|
||||
// Jump drag view back to original place on grid,
|
||||
// so user doesn't think they are still dragging.
|
||||
// TODO: can we improve this interaction? maybe with a ghost icon or similar?
|
||||
mDragView.setVisibility(INVISIBLE);
|
||||
mDeferredDragIcon.setVisibility(VISIBLE);
|
||||
}
|
||||
}
|
||||
} else if (action == MotionEvent.ACTION_UP) {
|
||||
mDeferredDragIcon.setVisibility(VISIBLE);
|
||||
cleanupDeferredDrag();
|
||||
// Launch a shortcut if user was hovering over it.
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
DeepShortcutView shortcut = (DeepShortcutView) getChildAt(i);
|
||||
if (shortcut.isHoveringOver()) {
|
||||
shortcut.performClick();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the deferred drag should be started based on touch coordinates
|
||||
* relative to the original icon and the shortcuts container.
|
||||
*
|
||||
* Current behavior:
|
||||
* - Compute distance from original touch down to closest container edge.
|
||||
* - Compute distance from latest touch (given x and y) and compare to original distance;
|
||||
* if the new distance is larger than a threshold, the deferred drag should start.
|
||||
* - Never defer the drag if this container contains the touch.
|
||||
*
|
||||
* @param x the x touch coordinate relative to this container
|
||||
* @param y the y touch coordinate relative to this container
|
||||
*/
|
||||
private boolean shouldStartDeferredDrag(int x, int y, boolean containerContainsTouch) {
|
||||
Point closestEdge = new Point(mTouchDown.x, mIsAboveIcon ? getMeasuredHeight() : 0);
|
||||
double distToEdge = Math.hypot(mTouchDown.x - closestEdge.x, mTouchDown.y - closestEdge.y);
|
||||
double newDistToEdge = Math.hypot(x - closestEdge.x, y - closestEdge.y);
|
||||
return !containerContainsTouch && (newDistToEdge - distToEdge > mStartDragThreshold);
|
||||
}
|
||||
|
||||
public void cleanupDeferredDrag() {
|
||||
if (mDragView != null) {
|
||||
mDragView.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent ev) {
|
||||
// Touched a shortcut, update where it was touched so we can drag from there on long click.
|
||||
switch (ev.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
// Clicked on a shortcut.
|
||||
mLauncher.onClick(view);
|
||||
((DragLayer) getParent()).removeView(this);
|
||||
}
|
||||
|
||||
public boolean onLongClick(View v) {
|
||||
// Return early if this is not initiated from a touch
|
||||
if (!v.isInTouchMode()) return false;
|
||||
// Return if global dragging is not enabled
|
||||
if (!mLauncher.isDraggingEnabled()) return false;
|
||||
|
||||
// Long clicked on a shortcut.
|
||||
// TODO remove this hack; it required because DragLayer isn't intercepting touch, so
|
||||
// the controller is not updated from what it was previously.
|
||||
mLauncher.getDragLayer().setController(mLauncher.getDragController());
|
||||
mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false);
|
||||
((DragLayer) getParent()).removeView(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFlingToDelete() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppInfoDropTarget() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsDeleteDropTarget() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getIntrinsicIconScaleFactor() {
|
||||
return (float) getResources().getDimensionPixelSize(R.dimen.deep_shortcut_icon_size)
|
||||
/ mLauncher.getDeviceProfile().iconSizePx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFlingToDeleteCompleted() {
|
||||
// Don't care; ignore.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
|
||||
boolean success) {
|
||||
if (!success) {
|
||||
d.dragView.remove();
|
||||
mLauncher.showWorkspace(true);
|
||||
mLauncher.getDropTargetBar().onDragEnd();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
|
||||
target.itemType = LauncherLogProto.SHORTCUT; // TODO: change to DYNAMIC_SHORTCUT
|
||||
target.gridX = info.cellX;
|
||||
target.gridY = info.cellY;
|
||||
target.pageIndex = 0;
|
||||
targetParent.containerType = LauncherLogProto.FOLDER; // TODO: change to DYNAMIC_SHORTCUTS
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,7 @@ public class ShortcutCache {
|
||||
for (ShortcutInfoCompat shortcut : shortcuts) {
|
||||
ShortcutKey key = ShortcutKey.fromInfo(shortcut);
|
||||
mCachedShortcuts.remove(key);
|
||||
mPinnedShortcuts.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
package com.android.launcher3.shortcuts;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.SystemClock;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.CheckLongPressHelper;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.dragndrop.DragLayer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link android.view.View.OnTouchListener} that creates a {@link DeepShortcutsContainer} and
|
||||
* forwards touch events to it. This listener should be put on any icon that supports shortcuts.
|
||||
*/
|
||||
public class ShortcutsContainerListener implements View.OnTouchListener,
|
||||
View.OnAttachStateChangeListener {
|
||||
|
||||
/** Scaled touch slop, used for detecting movement outside bounds. */
|
||||
private final float mScaledTouchSlop;
|
||||
|
||||
/** Timeout before disallowing intercept on the source's parent. */
|
||||
private final int mTapTimeout;
|
||||
|
||||
/** Timeout before accepting a long-press to start forwarding. */
|
||||
private final int mLongPressTimeout;
|
||||
|
||||
/** Source view from which events are forwarded. */
|
||||
private final BubbleTextView mSrcIcon;
|
||||
|
||||
/** Runnable used to prevent conflicts with scrolling parents. */
|
||||
private Runnable mDisallowIntercept;
|
||||
|
||||
/** Runnable used to trigger forwarding on long-press. */
|
||||
private Runnable mTriggerLongPress;
|
||||
|
||||
/** Whether this listener is currently forwarding touch events. */
|
||||
private boolean mForwarding;
|
||||
|
||||
/** The id of the first pointer down in the current event stream. */
|
||||
private int mActivePointerId;
|
||||
|
||||
private Launcher mLauncher;
|
||||
private DragLayer mDragLayer;
|
||||
private MotionEvent mTouchDownEvent;
|
||||
|
||||
public ShortcutsContainerListener(BubbleTextView icon) {
|
||||
mSrcIcon = icon;
|
||||
mScaledTouchSlop = ViewConfiguration.get(icon.getContext()).getScaledTouchSlop();
|
||||
mTapTimeout = ViewConfiguration.getTapTimeout();
|
||||
|
||||
mLongPressTimeout = CheckLongPressHelper.DEFAULT_LONG_PRESS_TIMEOUT;
|
||||
|
||||
icon.addOnAttachStateChangeListener(this);
|
||||
|
||||
mLauncher = Launcher.getLauncher(mSrcIcon.getContext());
|
||||
|
||||
mDragLayer = mLauncher.getDragLayer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (mLauncher.getShortcutIdsForItem((ItemInfo) v.getTag()).isEmpty()) {
|
||||
// There are no shortcuts associated with this item, so return to normal touch handling.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
mTouchDownEvent = MotionEvent.obtainNoHistory(event);
|
||||
}
|
||||
|
||||
final boolean wasForwarding = mForwarding;
|
||||
final boolean forwarding;
|
||||
if (wasForwarding) {
|
||||
forwarding = onTouchForwarded(event) || !onForwardingStopped();
|
||||
} else {
|
||||
forwarding = onTouchObserved(event) && onForwardingStarted();
|
||||
|
||||
if (forwarding) {
|
||||
// Make sure we cancel any ongoing source event stream.
|
||||
final long now = SystemClock.uptimeMillis();
|
||||
final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
|
||||
0.0f, 0.0f, 0);
|
||||
mSrcIcon.onTouchEvent(e);
|
||||
e.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
mForwarding = forwarding;
|
||||
return forwarding || wasForwarding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAttachedToWindow(View v) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View v) {
|
||||
mForwarding = false;
|
||||
mActivePointerId = MotionEvent.INVALID_POINTER_ID;
|
||||
|
||||
if (mDisallowIntercept != null) {
|
||||
mSrcIcon.removeCallbacks(mDisallowIntercept);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when forwarding would like to start.
|
||||
* <p>
|
||||
* This is when we populate the shortcuts container and add it to the DragLayer.
|
||||
*
|
||||
* @return true to start forwarding, false otherwise
|
||||
*/
|
||||
protected boolean onForwardingStarted() {
|
||||
List<String> ids = mLauncher.getShortcutIdsForItem((ItemInfo) mSrcIcon.getTag());
|
||||
if (!ids.isEmpty()) {
|
||||
// There are shortcuts associated with the app, so defer its drag.
|
||||
LayoutInflater layoutInflater = (LayoutInflater) mLauncher.getSystemService
|
||||
(Context.LAYOUT_INFLATER_SERVICE);
|
||||
final DeepShortcutsContainer deepShortcutsContainer = (DeepShortcutsContainer)
|
||||
layoutInflater.inflate(R.layout.deep_shortcuts_container, mDragLayer, false);
|
||||
deepShortcutsContainer.setVisibility(View.INVISIBLE);
|
||||
mDragLayer.addView(deepShortcutsContainer);
|
||||
deepShortcutsContainer.populateAndShow(mSrcIcon, ids);
|
||||
mSrcIcon.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
|
||||
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when forwarding would like to stop.
|
||||
*
|
||||
* @return true to stop forwarding, false otherwise
|
||||
*/
|
||||
protected boolean onForwardingStopped() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Observes motion events and determines when to start forwarding.
|
||||
*
|
||||
* @param srcEvent motion event in source view coordinates
|
||||
* @return true to start forwarding motion events, false otherwise
|
||||
*/
|
||||
private boolean onTouchObserved(MotionEvent srcEvent) {
|
||||
final View src = mSrcIcon;
|
||||
if (!src.isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int actionMasked = srcEvent.getActionMasked();
|
||||
switch (actionMasked) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mActivePointerId = srcEvent.getPointerId(0);
|
||||
|
||||
if (mDisallowIntercept == null) {
|
||||
mDisallowIntercept = new DisallowIntercept();
|
||||
}
|
||||
src.postDelayed(mDisallowIntercept, mTapTimeout);
|
||||
|
||||
if (mTriggerLongPress == null) {
|
||||
mTriggerLongPress = new TriggerLongPress();
|
||||
}
|
||||
src.postDelayed(mTriggerLongPress, mLongPressTimeout);
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
|
||||
if (activePointerIndex >= 0) {
|
||||
final float x = srcEvent.getX(activePointerIndex);
|
||||
final float y = srcEvent.getY(activePointerIndex);
|
||||
|
||||
// Has the pointer moved outside of the view?
|
||||
if (!Utilities.pointInView(src, x, y, mScaledTouchSlop)) {
|
||||
clearCallbacks();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
clearCallbacks();
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void clearCallbacks() {
|
||||
if (mTriggerLongPress != null) {
|
||||
mSrcIcon.removeCallbacks(mTriggerLongPress);
|
||||
}
|
||||
|
||||
if (mDisallowIntercept != null) {
|
||||
mSrcIcon.removeCallbacks(mDisallowIntercept);
|
||||
}
|
||||
}
|
||||
|
||||
private void onLongPress() {
|
||||
clearCallbacks();
|
||||
|
||||
final View src = mSrcIcon;
|
||||
if (!src.isEnabled() || mLauncher.getShortcutIdsForItem((ItemInfo) src.getTag()).isEmpty()) {
|
||||
// Ignore long-press if the view is disabled or doesn't have shortcuts.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!onForwardingStarted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't let the parent intercept our events.
|
||||
src.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
|
||||
// Make sure we cancel any ongoing source event stream.
|
||||
final long now = SystemClock.uptimeMillis();
|
||||
final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
|
||||
src.onTouchEvent(e);
|
||||
e.recycle();
|
||||
|
||||
mForwarding = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles forwarded motion events and determines when to stop
|
||||
* forwarding.
|
||||
*
|
||||
* @param srcEvent motion event in source view coordinates
|
||||
* @return true to continue forwarding motion events, false to cancel
|
||||
*/
|
||||
private boolean onTouchForwarded(MotionEvent srcEvent) {
|
||||
final View src = mSrcIcon;
|
||||
|
||||
final DeepShortcutsContainer dst = (DeepShortcutsContainer)
|
||||
mDragLayer.findViewById(R.id.deep_shortcuts_container);
|
||||
if (dst == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert event to destination-local coordinates.
|
||||
final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
|
||||
Utilities.translateEventCoordinates(src, dst, dstEvent);
|
||||
|
||||
// Convert touch down event to destination-local coordinates.
|
||||
// TODO: only create this once, or just store the x and y.
|
||||
final MotionEvent touchDownEvent = MotionEvent.obtainNoHistory(mTouchDownEvent);
|
||||
Utilities.translateEventCoordinates(src, dst, touchDownEvent);
|
||||
|
||||
// Forward converted event to destination view, then recycle it.
|
||||
// TODO: don't create objects in onForwardedEvent.
|
||||
final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId, touchDownEvent);
|
||||
dstEvent.recycle();
|
||||
touchDownEvent.recycle();
|
||||
|
||||
// Always cancel forwarding when the touch stream ends.
|
||||
final int action = srcEvent.getActionMasked();
|
||||
final boolean keepForwarding = action != MotionEvent.ACTION_UP
|
||||
&& action != MotionEvent.ACTION_CANCEL;
|
||||
|
||||
return handled && keepForwarding;
|
||||
}
|
||||
|
||||
private class DisallowIntercept implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
final ViewParent parent = mSrcIcon.getParent();
|
||||
parent.requestDisallowInterceptTouchEvent(true);
|
||||
}
|
||||
}
|
||||
|
||||
private class TriggerLongPress implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
onLongPress();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user