1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3;
18 
19 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_CANCEL;
20 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_REMOVE;
21 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
22 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.UNDO;
23 
24 import android.content.Context;
25 import android.text.TextUtils;
26 import android.util.AttributeSet;
27 import android.view.View;
28 
29 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
30 import com.android.launcher3.dragndrop.DragOptions;
31 import com.android.launcher3.logging.LoggerUtils;
32 import com.android.launcher3.logging.StatsLogManager;
33 import com.android.launcher3.model.ModelWriter;
34 import com.android.launcher3.model.data.FolderInfo;
35 import com.android.launcher3.model.data.ItemInfo;
36 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
37 import com.android.launcher3.model.data.WorkspaceItemInfo;
38 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
39 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
40 import com.android.launcher3.views.Snackbar;
41 
42 public class DeleteDropTarget extends ButtonDropTarget {
43 
44     private final StatsLogManager mStatsLogManager;
45 
46     private int mControlType = ControlType.DEFAULT_CONTROLTYPE;
47 
DeleteDropTarget(Context context, AttributeSet attrs)48     public DeleteDropTarget(Context context, AttributeSet attrs) {
49         this(context, attrs, 0);
50     }
51 
DeleteDropTarget(Context context, AttributeSet attrs, int defStyle)52     public DeleteDropTarget(Context context, AttributeSet attrs, int defStyle) {
53         super(context, attrs, defStyle);
54         this.mStatsLogManager = StatsLogManager.newInstance(context);
55     }
56 
57     @Override
onFinishInflate()58     protected void onFinishInflate() {
59         super.onFinishInflate();
60         // Get the hover color
61         mHoverColor = getResources().getColor(R.color.delete_target_hover_tint);
62 
63         setDrawable(R.drawable.ic_remove_shadow);
64     }
65 
66     @Override
onDragStart(DropTarget.DragObject dragObject, DragOptions options)67     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
68         super.onDragStart(dragObject, options);
69         setTextBasedOnDragSource(dragObject.dragInfo);
70         setControlTypeBasedOnDragSource(dragObject.dragInfo);
71     }
72 
73     /**
74      * @return true for items that should have a "Remove" action in accessibility.
75      */
76     @Override
supportsAccessibilityDrop(ItemInfo info, View view)77     public boolean supportsAccessibilityDrop(ItemInfo info, View view) {
78         if (info instanceof WorkspaceItemInfo) {
79             // Support the action unless the item is in a context menu.
80             return canRemove(info);
81         }
82 
83         return (info instanceof LauncherAppWidgetInfo)
84                 || (info instanceof FolderInfo);
85     }
86 
87     @Override
getAccessibilityAction()88     public int getAccessibilityAction() {
89         return LauncherAccessibilityDelegate.REMOVE;
90     }
91 
92     @Override
supportsDrop(ItemInfo info)93     protected boolean supportsDrop(ItemInfo info) {
94         return true;
95     }
96 
97     /**
98      * Set the drop target's text to either "Remove" or "Cancel" depending on the drag item.
99      */
setTextBasedOnDragSource(ItemInfo item)100     private void setTextBasedOnDragSource(ItemInfo item) {
101         if (!TextUtils.isEmpty(mText)) {
102             mText = getResources().getString(canRemove(item)
103                     ? R.string.remove_drop_target_label
104                     : android.R.string.cancel);
105             setContentDescription(mText);
106             requestLayout();
107         }
108     }
109 
canRemove(ItemInfo item)110     private boolean canRemove(ItemInfo item) {
111         return item.id != ItemInfo.NO_ID;
112     }
113 
114     /**
115      * Set mControlType depending on the drag item.
116      */
setControlTypeBasedOnDragSource(ItemInfo item)117     private void setControlTypeBasedOnDragSource(ItemInfo item) {
118         mControlType = item.id != ItemInfo.NO_ID ? ControlType.REMOVE_TARGET
119                 : ControlType.CANCEL_TARGET;
120     }
121 
122     @Override
onDrop(DragObject d, DragOptions options)123     public void onDrop(DragObject d, DragOptions options) {
124         if (canRemove(d.dragInfo)) {
125             mLauncher.getModelWriter().prepareToUndoDelete();
126             d.dragInfo.container = NO_ID;
127         }
128         super.onDrop(d, options);
129         mStatsLogManager.logger().withInstanceId(d.logInstanceId)
130                 .log(mControlType == ControlType.REMOVE_TARGET ? LAUNCHER_ITEM_DROPPED_ON_REMOVE
131                         : LAUNCHER_ITEM_DROPPED_ON_CANCEL);
132     }
133 
134     @Override
completeDrop(DragObject d)135     public void completeDrop(DragObject d) {
136         ItemInfo item = d.dragInfo;
137         if (canRemove(item)) {
138             int itemPage = mLauncher.getWorkspace().getCurrentPage();
139             onAccessibilityDrop(null, item);
140             ModelWriter modelWriter = mLauncher.getModelWriter();
141             Runnable onUndoClicked = () -> {
142                 mLauncher.setPageToBindSynchronously(itemPage);
143                 modelWriter.abortDelete();
144                 mLauncher.getUserEventDispatcher().logActionOnControl(TAP, UNDO);
145             };
146             Snackbar.show(mLauncher, R.string.item_removed, R.string.undo,
147                     modelWriter::commitDelete, onUndoClicked);
148         }
149     }
150 
151     /**
152      * Removes the item from the workspace. If the view is not null, it also removes the view.
153      */
154     @Override
onAccessibilityDrop(View view, ItemInfo item)155     public void onAccessibilityDrop(View view, ItemInfo item) {
156         // Remove the item from launcher and the db, we can ignore the containerInfo in this call
157         // because we already remove the drag view from the folder (if the drag originated from
158         // a folder) in Folder.beginDrag()
159         mLauncher.removeItem(view, item, true /* deleteFromDb */);
160         mLauncher.getWorkspace().stripEmptyScreens();
161         mLauncher.getDragLayer()
162                 .announceForAccessibility(getContext().getString(R.string.item_removed));
163     }
164 
165     @Override
getDropTargetForLogging()166     public Target getDropTargetForLogging() {
167         Target t = LoggerUtils.newTarget(Target.Type.CONTROL);
168         t.controlType = mControlType;
169         return t;
170     }
171 }
172