summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTony Wickham <twickham@google.com>2018-08-21 11:40:23 -0700
committerTony Wickham <twickham@google.com>2018-08-30 16:22:25 -0700
commit6a71a5bd7787a2dc4a585e9e6ea0a440d0458a54 (patch)
treeb3bdc0c63c2540f57042c0d09d82786ec8d1869c /src
parent1654c9e1c00cb41de392880adf7c3a5a558270b3 (diff)
downloadandroid_packages_apps_Trebuchet-6a71a5bd7787a2dc4a585e9e6ea0a440d0458a54.tar.gz
android_packages_apps_Trebuchet-6a71a5bd7787a2dc4a585e9e6ea0a440d0458a54.tar.bz2
android_packages_apps_Trebuchet-6a71a5bd7787a2dc4a585e9e6ea0a440d0458a54.zip
Add undo snackbar for deleting items
- Add methods to ModelWriter to prepareForUndoDelete, then enqueueDeleteRunnable, followed by commitDelete or abortDelete. - Add Snackbar floating view - Show Undo snackbar when dropping or flinging to delete target; if the undo action is clicked, we abort the delete, otherwise we commit it. Bug: 24238108 Change-Id: I9997235e1f8525cbb8b1fa2338099609e7358426
Diffstat (limited to 'src')
-rw-r--r--src/com/android/launcher3/AbstractFloatingView.java14
-rw-r--r--src/com/android/launcher3/BaseActivity.java1
-rw-r--r--src/com/android/launcher3/ButtonDropTarget.java4
-rw-r--r--src/com/android/launcher3/DeleteDropTarget.java22
-rw-r--r--src/com/android/launcher3/Launcher.java40
-rw-r--r--src/com/android/launcher3/LauncherModel.java7
-rw-r--r--src/com/android/launcher3/SecondaryDropTarget.java2
-rw-r--r--src/com/android/launcher3/Workspace.java5
-rw-r--r--src/com/android/launcher3/dragndrop/DragController.java2
-rw-r--r--src/com/android/launcher3/dragndrop/DragOptions.java2
-rw-r--r--src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java7
-rw-r--r--src/com/android/launcher3/folder/Folder.java2
-rw-r--r--src/com/android/launcher3/folder/FolderAnimationManager.java1
-rw-r--r--src/com/android/launcher3/model/LoaderResults.java2
-rw-r--r--src/com/android/launcher3/model/ModelWriter.java74
-rw-r--r--src/com/android/launcher3/touch/ItemLongClickListener.java2
-rw-r--r--src/com/android/launcher3/util/FlingAnimation.java7
-rw-r--r--src/com/android/launcher3/views/Snackbar.java151
18 files changed, 293 insertions, 52 deletions
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 1c07ea4a7..4fe60991c 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -19,7 +19,6 @@ package com.android.launcher3;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
-
import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
@@ -54,6 +53,7 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
TYPE_WIDGETS_FULL_SHEET,
TYPE_ON_BOARD_POPUP,
TYPE_DISCOVERY_BOUNCE,
+ TYPE_SNACKBAR,
TYPE_QUICKSTEP_PREVIEW,
TYPE_TASK_MENU,
@@ -68,23 +68,25 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
public static final int TYPE_WIDGETS_FULL_SHEET = 1 << 4;
public static final int TYPE_ON_BOARD_POPUP = 1 << 5;
public static final int TYPE_DISCOVERY_BOUNCE = 1 << 6;
+ public static final int TYPE_SNACKBAR = 1 << 7;
// Popups related to quickstep UI
- public static final int TYPE_QUICKSTEP_PREVIEW = 1 << 7;
- public static final int TYPE_TASK_MENU = 1 << 8;
- public static final int TYPE_OPTIONS_POPUP = 1 << 9;
+ public static final int TYPE_QUICKSTEP_PREVIEW = 1 << 8;
+ public static final int TYPE_TASK_MENU = 1 << 9;
+ public static final int TYPE_OPTIONS_POPUP = 1 << 10;
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_DISCOVERY_BOUNCE | TYPE_TASK_MENU
- | TYPE_OPTIONS_POPUP;
+ | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR;
// Type of popups which should be kept open during launcher rebind
public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
| TYPE_QUICKSTEP_PREVIEW | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE;
// Usually we show the back button when a floating view is open. Instead, hide for these types.
- public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE;
+ public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
+ | TYPE_SNACKBAR;
public static final int TYPE_ACCESSIBLE = TYPE_ALL
& ~TYPE_DISCOVERY_BOUNCE & ~TYPE_QUICKSTEP_PREVIEW;
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 2ec7e012c..7b565273f 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -17,7 +17,6 @@
package com.android.launcher3;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
-
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.app.Activity;
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index ed8c42d07..dd63ebce0 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -264,6 +264,10 @@ public abstract class ButtonDropTarget extends TextView
*/
@Override
public void onDrop(final DragObject d, final DragOptions options) {
+ if (options.isFlingToDelete) {
+ // FlingAnimation handles the animation and then calls completeDrop().
+ return;
+ }
final DragLayer dragLayer = mLauncher.getDragLayer();
final Rect from = new Rect();
dragLayer.getViewRectRelativeToSelf(d.dragView, from);
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 64a58fb8c..c80f96bb8 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -23,10 +23,11 @@ import android.view.View;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.folder.Folder;
import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.views.Snackbar;
public class DeleteDropTarget extends ButtonDropTarget {
@@ -81,13 +82,17 @@ public class DeleteDropTarget extends ButtonDropTarget {
*/
private void setTextBasedOnDragSource(ItemInfo item) {
if (!TextUtils.isEmpty(mText)) {
- mText = getResources().getString(item.id != ItemInfo.NO_ID
+ mText = getResources().getString(canRemove(item)
? R.string.remove_drop_target_label
: android.R.string.cancel);
requestLayout();
}
}
+ private boolean canRemove(ItemInfo item) {
+ return item.id != ItemInfo.NO_ID;
+ }
+
/**
* Set mControlType depending on the drag item.
*/
@@ -97,10 +102,21 @@ public class DeleteDropTarget extends ButtonDropTarget {
}
@Override
+ public void onDrop(DragObject d, DragOptions options) {
+ if (canRemove(d.dragInfo)) {
+ mLauncher.getModelWriter().prepareToUndoDelete();
+ }
+ super.onDrop(d, options);
+ }
+
+ @Override
public void completeDrop(DragObject d) {
ItemInfo item = d.dragInfo;
- if ((d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder)) {
+ if (canRemove(item)) {
onAccessibilityDrop(null, item);
+ ModelWriter modelWriter = mLauncher.getModelWriter();
+ Snackbar.show(mLauncher, R.string.item_removed, R.string.undo,
+ modelWriter::commitDelete, modelWriter::abortDelete);
}
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index f0ddd53ed..55074f813 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -19,7 +19,7 @@ package com.android.launcher3;
import static android.content.pm.ActivityInfo.CONFIG_LOCALE;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
-
+import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
@@ -48,7 +48,6 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Point;
-import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -1549,7 +1548,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
mWorkspace.removeWorkspaceItem(v);
if (deleteFromDb) {
- deleteWidgetInfo(widgetInfo);
+ getModelWriter().deleteWidgetInfo(widgetInfo, getAppWidgetHost());
}
} else {
return false;
@@ -1557,23 +1556,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
return true;
}
- /**
- * Deletes the widget info and the widget id.
- */
- private void deleteWidgetInfo(final LauncherAppWidgetInfo widgetInfo) {
- final LauncherAppWidgetHost appWidgetHost = getAppWidgetHost();
- if (appWidgetHost != null && !widgetInfo.isCustomWidget() && widgetInfo.isWidgetIdAllocated()) {
- // Deleting an app widget ID is a void call but writes to disk before returning
- // to the caller...
- new AsyncTask<Void, Void, Void>() {
- public Void doInBackground(Void ... args) {
- appWidgetHost.deleteAppWidgetId(widgetInfo.appWidgetId);
- return null;
- }
- }.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
- }
- getModelWriter().deleteItemFromDatabase(widgetInfo);
- }
+
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
@@ -1808,6 +1791,17 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
}
@Override
+ public void preAddApps() {
+ // If there's an undo snackbar, force it to complete to ensure empty screens are removed
+ // before trying to add new items.
+ mModelWriter.commitDelete();
+ AbstractFloatingView snackbar = AbstractFloatingView.getOpenView(this, TYPE_SNACKBAR);
+ if (snackbar != null) {
+ snackbar.post(() -> snackbar.close(true));
+ }
+ }
+
+ @Override
public void bindAppsAdded(ArrayList<Long> newScreens, ArrayList<ItemInfo> addNotAnimated,
ArrayList<ItemInfo> addAnimated) {
// Add the new screens
@@ -2040,7 +2034,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
// Verify that we own the widget
if (appWidgetInfo == null) {
FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId);
- deleteWidgetInfo(item);
+ getModelWriter().deleteWidgetInfo(item, getAppWidgetHost());
return null;
}
@@ -2130,7 +2124,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
*
* Implementation of the method from LauncherModel.Callbacks.
*/
- public void finishBindingItems() {
+ public void finishBindingItems(int currentScreen) {
TraceHelper.beginSection("finishBindingItems");
mWorkspace.restoreInstanceStateForRemainingPages();
@@ -2145,6 +2139,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
InstallShortcutReceiver.disableAndFlushInstallQueue(
InstallShortcutReceiver.FLAG_LOADER_RUNNING, this);
+ mWorkspace.setCurrentPage(currentScreen);
+
TraceHelper.endSection("finishBindingItems");
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index fe9f901c4..7a90a55d4 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -144,9 +144,10 @@ public class LauncherModel extends BroadcastReceiver
public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
public void bindScreens(ArrayList<Long> orderedScreenIds);
public void finishFirstPageBind(ViewOnDrawExecutor executor);
- public void finishBindingItems();
+ public void finishBindingItems(int currentScreen);
public void bindAllApplications(ArrayList<AppInfo> apps);
public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps);
+ public void preAddApps();
public void bindAppsAdded(ArrayList<Long> newScreens,
ArrayList<ItemInfo> addNotAnimated,
ArrayList<ItemInfo> addAnimated);
@@ -196,6 +197,10 @@ public class LauncherModel extends BroadcastReceiver
* Adds the provided items to the workspace.
*/
public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
+ Callbacks callbacks = getCallback();
+ if (callbacks != null) {
+ callbacks.preAddApps();
+ }
enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
}
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 76e85e255..6083415d5 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -2,7 +2,6 @@ package com.android.launcher3;
import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE;
-
import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_MASK;
import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
@@ -10,7 +9,6 @@ import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.UNINSTALL;
import android.appwidget.AppWidgetHostView;
-import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index d652fe0cd..353916fbb 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -819,7 +819,9 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
if (!removeScreens.isEmpty()) {
// Update the model if we have changed any screens
- LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
+ mLauncher.getModelWriter().enqueueDeleteRunnable(
+ () -> LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder));
+
}
if (pageShift >= 0) {
@@ -2848,7 +2850,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
*/
public void onDropCompleted(final View target, final DragObject d,
final boolean success) {
-
if (success) {
if (target != this && mDragInfo != null) {
removeWorkspaceItem(mDragInfo.cell);
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 80077549a..03dc66ee2 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -396,7 +396,7 @@ public class DragController implements DragDriver.EventListener, TouchController
@Override
public void onDriverDragEnd(float x, float y) {
DropTarget dropTarget;
- Runnable flingAnimation = mFlingToDeleteHelper.getFlingAnimation(mDragObject);
+ Runnable flingAnimation = mFlingToDeleteHelper.getFlingAnimation(mDragObject, mOptions);
if (flingAnimation != null) {
dropTarget = mFlingToDeleteHelper.getDropTarget();
} else {
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index f108f8b53..2d19f3642 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -37,6 +37,8 @@ public class DragOptions {
/** Scale of the icons over the workspace icon size. */
public float intrinsicIconScaleFactor = 1f;
+ public boolean isFlingToDelete;
+
/**
* Specifies a condition that must be met before DragListener#onDragStart() is called.
* By default, there is no condition and onDragStart() is called immediately following
diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
index e79474483..589ad25e4 100644
--- a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
+++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
@@ -91,12 +91,13 @@ public class FlingToDeleteHelper {
return mDropTarget;
}
- public Runnable getFlingAnimation(DropTarget.DragObject dragObject) {
+ public Runnable getFlingAnimation(DropTarget.DragObject dragObject, DragOptions options) {
PointF vel = isFlingingToDelete();
- if (vel == null) {
+ options.isFlingToDelete = vel != null;
+ if (!options.isFlingToDelete) {
return null;
}
- return new FlingAnimation(dragObject, vel, mDropTarget, mLauncher);
+ return new FlingAnimation(dragObject, vel, mDropTarget, mLauncher, options);
}
/**
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index a059627df..c4d105800 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -25,7 +25,6 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.SuppressLint;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.text.InputType;
@@ -41,7 +40,6 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
-import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.EditorInfo;
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 6ef798d62..1277a2090 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -155,6 +155,7 @@ public class FolderAnimationManager {
final int finalColor = Themes.getAttrColor(mContext, android.R.attr.colorPrimary);
final int initialColor =
ColorUtils.setAlphaComponent(finalColor, mPreviewBackground.getBackgroundAlpha());
+ mFolderBackground.mutate();
mFolderBackground.setColor(mIsOpening ? initialColor : finalColor);
// Set up the reveal animation that clips the Folder.
diff --git a/src/com/android/launcher3/model/LoaderResults.java b/src/com/android/launcher3/model/LoaderResults.java
index 0fd9b735e..57789363a 100644
--- a/src/com/android/launcher3/model/LoaderResults.java
+++ b/src/com/android/launcher3/model/LoaderResults.java
@@ -181,7 +181,7 @@ public class LoaderResults {
public void run() {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
- callbacks.finishBindingItems();
+ callbacks.finishBindingItems(currentScreen);
}
}
};
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index eba751524..9f6e4f85d 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -28,6 +28,8 @@ import android.util.Log;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetHost;
+import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherModel.Callbacks;
import com.android.launcher3.LauncherProvider;
@@ -35,13 +37,14 @@ import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LooperExecutor;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -60,6 +63,10 @@ public class ModelWriter {
private final boolean mHasVerticalHotseat;
private final boolean mVerifyChanges;
+ // Keep track of delete operations that occur when an Undo option is present; we may not commit.
+ private final List<Runnable> mDeleteRunnables = new ArrayList<>();
+ private boolean mPreparingToUndo;
+
public ModelWriter(Context context, LauncherModel model, BgDataModel dataModel,
boolean hasVerticalHotseat, boolean verifyChanges) {
mContext = context;
@@ -152,7 +159,7 @@ public class ModelWriter {
.put(Favorites.RANK, item.rank)
.put(Favorites.SCREEN, item.screenId);
- mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
+ enqueueDeleteRunnable(new UpdateItemRunnable(item, writer));
}
/**
@@ -176,7 +183,7 @@ public class ModelWriter {
contentValues.add(values);
}
- mWorkerExecutor.execute(new UpdateItemsRunnable(items, contentValues));
+ enqueueDeleteRunnable(new UpdateItemsRunnable(items, contentValues));
}
/**
@@ -258,7 +265,7 @@ public class ModelWriter {
public void deleteItemsFromDatabase(final Iterable<? extends ItemInfo> items) {
ModelVerifier verifier = new ModelVerifier();
- mWorkerExecutor.execute(() -> {
+ enqueueDeleteRunnable(() -> {
for (ItemInfo item : items) {
final Uri uri = Favorites.getContentUri(item.id);
mContext.getContentResolver().delete(uri, null, null);
@@ -275,7 +282,7 @@ public class ModelWriter {
public void deleteFolderAndContentsFromDatabase(final FolderInfo info) {
ModelVerifier verifier = new ModelVerifier();
- mWorkerExecutor.execute(() -> {
+ enqueueDeleteRunnable(() -> {
ContentResolver cr = mContext.getContentResolver();
cr.delete(LauncherSettings.Favorites.CONTENT_URI,
LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
@@ -288,6 +295,63 @@ public class ModelWriter {
});
}
+ /**
+ * Deletes the widget info and the widget id.
+ */
+ public void deleteWidgetInfo(final LauncherAppWidgetInfo info, LauncherAppWidgetHost host) {
+ if (host != null && !info.isCustomWidget() && info.isWidgetIdAllocated()) {
+ // Deleting an app widget ID is a void call but writes to disk before returning
+ // to the caller...
+ enqueueDeleteRunnable(() -> host.deleteAppWidgetId(info.appWidgetId));
+ }
+ deleteItemFromDatabase(info);
+ }
+
+ /**
+ * Delete operations tracked using {@link #enqueueDeleteRunnable} will only be called
+ * if {@link #commitDelete} is called. Note that one of {@link #commitDelete()} or
+ * {@link #abortDelete()} MUST be called after this method, or else all delete
+ * operations will remain uncommitted indefinitely.
+ */
+ public void prepareToUndoDelete() {
+ if (!mPreparingToUndo) {
+ if (!mDeleteRunnables.isEmpty() && FeatureFlags.IS_DOGFOOD_BUILD) {
+ throw new IllegalStateException("There are still uncommitted delete operations!");
+ }
+ mDeleteRunnables.clear();
+ mPreparingToUndo = true;
+ }
+ }
+
+ /**
+ * If {@link #prepareToUndoDelete} has been called, we store the Runnable to be run when
+ * {@link #commitDelete()} is called (or abandoned if {@link #abortDelete()} is called).
+ * Otherwise, we run the Runnable immediately.
+ */
+ public void enqueueDeleteRunnable(Runnable r) {
+ if (mPreparingToUndo) {
+ mDeleteRunnables.add(r);
+ } else {
+ mWorkerExecutor.execute(r);
+ }
+ }
+
+ public void commitDelete() {
+ mPreparingToUndo = false;
+ for (Runnable runnable : mDeleteRunnables) {
+ mWorkerExecutor.execute(runnable);
+ }
+ mDeleteRunnables.clear();
+ }
+
+ public void abortDelete() {
+ mPreparingToUndo = false;
+ mDeleteRunnables.clear();
+ // We do a full reload here instead of just a rebind because Folders change their internal
+ // state when dragging an item out, which clobbers the rebind unless we load from the DB.
+ mModel.forceReload();
+ }
+
private class UpdateItemRunnable extends UpdateItemBaseRunnable {
private final ItemInfo mItem;
private final ContentWriter mWriter;
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 6f012f69a..babbcdd16 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -17,7 +17,6 @@ package com.android.launcher3.touch;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
-
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -30,7 +29,6 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index fe0571b8a..9d0ad22e9 100644
--- a/src/com/android/launcher3/util/FlingAnimation.java
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -14,6 +14,7 @@ import com.android.launcher3.ButtonDropTarget;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.Launcher;
import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
public class FlingAnimation implements AnimatorUpdateListener, Runnable {
@@ -28,6 +29,7 @@ public class FlingAnimation implements AnimatorUpdateListener, Runnable {
private final Launcher mLauncher;
protected final DragObject mDragObject;
+ protected final DragOptions mDragOptions;
protected final DragLayer mDragLayer;
protected final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
protected final float mUX, mUY;
@@ -39,13 +41,15 @@ public class FlingAnimation implements AnimatorUpdateListener, Runnable {
protected float mAX, mAY;
- public FlingAnimation(DragObject d, PointF vel, ButtonDropTarget dropTarget, Launcher launcher) {
+ public FlingAnimation(DragObject d, PointF vel, ButtonDropTarget dropTarget, Launcher launcher,
+ DragOptions options) {
mDropTarget = dropTarget;
mLauncher = launcher;
mDragObject = d;
mUX = vel.x / 1000;
mUY = vel.y / 1000;
mDragLayer = mLauncher.getDragLayer();
+ mDragOptions = options;
}
@Override
@@ -102,6 +106,7 @@ public class FlingAnimation implements AnimatorUpdateListener, Runnable {
}
};
+ mDropTarget.onDrop(mDragObject, mDragOptions);
mDragLayer.animateView(mDragObject.dragView, this, duration, tInterpolator,
onAnimationEndRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
}
diff --git a/src/com/android/launcher3/views/Snackbar.java b/src/com/android/launcher3/views/Snackbar.java
new file mode 100644
index 000000000..f515360e6
--- /dev/null
+++ b/src/com/android/launcher3/views/Snackbar.java
@@ -0,0 +1,151 @@
+/*
+ * 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.launcher3.views;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.widget.TextView;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dragndrop.DragLayer;
+
+/**
+ * A toast-like UI at the bottom of the screen with a label, button action, and dismiss action.
+ */
+public class Snackbar extends AbstractFloatingView {
+
+ private static final long SHOW_DURATION_MS = 180;
+ private static final long HIDE_DURATION_MS = 180;
+ private static final long TIMEOUT_DURATION_MS = 4000;
+
+ private final Launcher mLauncher;
+ private Runnable mOnDismissed;
+
+ public Snackbar(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public Snackbar(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mLauncher = Launcher.getLauncher(context);
+ inflate(context, R.layout.snackbar, this);
+ }
+
+ public static void show(Launcher launcher, int labelStringResId, int actionStringResId,
+ Runnable onDismissed, Runnable onActionClicked) {
+ closeOpenViews(launcher, true, TYPE_SNACKBAR);
+ Snackbar snackbar = new Snackbar(launcher, null);
+ // Set some properties here since inflated xml only contains the children.
+ snackbar.setOrientation(HORIZONTAL);
+ snackbar.setGravity(Gravity.CENTER_VERTICAL);
+ Resources res = launcher.getResources();
+ snackbar.setElevation(res.getDimension(R.dimen.deep_shortcuts_elevation));
+ int padding = res.getDimensionPixelSize(R.dimen.snackbar_padding);
+ snackbar.setPadding(padding, padding, padding, padding);
+ snackbar.setBackgroundResource(R.drawable.round_rect_primary);
+
+ snackbar.mIsOpen = true;
+ DragLayer dragLayer = launcher.getDragLayer();
+ dragLayer.addView(snackbar);
+
+ DragLayer.LayoutParams params = (DragLayer.LayoutParams) snackbar.getLayoutParams();
+ params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+ params.height = res.getDimensionPixelSize(R.dimen.snackbar_height);
+ int margin = res.getDimensionPixelSize(R.dimen.snackbar_margin);
+ Rect insets = launcher.getDeviceProfile().getInsets();
+ params.width = dragLayer.getWidth() - margin * 2 - insets.left - insets.right;
+ params.setMargins(0, margin + insets.top, 0, margin + insets.bottom);
+
+ ((TextView) snackbar.findViewById(R.id.label)).setText(labelStringResId);
+ ((TextView) snackbar.findViewById(R.id.action)).setText(actionStringResId);
+ snackbar.findViewById(R.id.action).setOnClickListener(v -> {
+ if (onActionClicked != null) {
+ onActionClicked.run();
+ }
+ snackbar.mOnDismissed = null;
+ snackbar.close(true);
+ });
+ snackbar.mOnDismissed = onDismissed;
+
+ snackbar.setAlpha(0);
+ snackbar.setScaleX(0.8f);
+ snackbar.setScaleY(0.8f);
+ snackbar.animate()
+ .alpha(1f)
+ .withLayer()
+ .scaleX(1)
+ .scaleY(1)
+ .setDuration(SHOW_DURATION_MS)
+ .setInterpolator(Interpolators.ACCEL_DEACCEL)
+ .start();
+ snackbar.postDelayed(() -> snackbar.close(true), TIMEOUT_DURATION_MS);
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ if (mIsOpen) {
+ if (animate) {
+ animate().alpha(0f)
+ .withLayer()
+ .setStartDelay(0)
+ .setDuration(HIDE_DURATION_MS)
+ .setInterpolator(Interpolators.ACCEL)
+ .withEndAction(this::onClosed)
+ .start();
+ } else {
+ animate().cancel();
+ onClosed();
+ }
+ mIsOpen = false;
+ }
+ }
+
+ private void onClosed() {
+ mLauncher.getDragLayer().removeView(this);
+ if (mOnDismissed != null) {
+ mOnDismissed.run();
+ }
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ // TODO
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_SNACKBAR) != 0;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ DragLayer dl = mLauncher.getDragLayer();
+ if (!dl.isEventOverView(this, ev)) {
+ close(true);
+ }
+ }
+ return false;
+ }
+}