• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.widget;
18 
19 import android.content.Context;
20 import android.graphics.Bitmap;
21 import android.graphics.Rect;
22 import android.graphics.drawable.Drawable;
23 import android.support.v7.widget.LinearLayoutManager;
24 import android.support.v7.widget.RecyclerView.State;
25 import android.util.AttributeSet;
26 import android.util.Log;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.Toast;
30 
31 import com.android.launcher3.BaseContainerView;
32 import com.android.launcher3.CellLayout;
33 import com.android.launcher3.DeleteDropTarget;
34 import com.android.launcher3.DragSource;
35 import com.android.launcher3.DropTarget.DragObject;
36 import com.android.launcher3.dragndrop.DragOptions;
37 import com.android.launcher3.folder.Folder;
38 import com.android.launcher3.IconCache;
39 import com.android.launcher3.ItemInfo;
40 import com.android.launcher3.Launcher;
41 import com.android.launcher3.LauncherAppState;
42 import com.android.launcher3.PendingAddItemInfo;
43 import com.android.launcher3.R;
44 import com.android.launcher3.Utilities;
45 import com.android.launcher3.WidgetPreviewLoader;
46 import com.android.launcher3.Workspace;
47 import com.android.launcher3.dragndrop.DragController;
48 import com.android.launcher3.model.WidgetsModel;
49 import com.android.launcher3.userevent.nano.LauncherLogProto;
50 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
51 import com.android.launcher3.util.Thunk;
52 import com.android.launcher3.util.TransformingTouchDelegate;
53 
54 /**
55  * The widgets list view container.
56  */
57 public class WidgetsContainerView extends BaseContainerView
58         implements View.OnLongClickListener, View.OnClickListener, DragSource {
59     private static final String TAG = "WidgetsContainerView";
60     private static final boolean LOGD = false;
61 
62     /* Global instances that are used inside this container. */
63     @Thunk Launcher mLauncher;
64     private DragController mDragController;
65     private IconCache mIconCache;
66 
67     private final Rect mTmpBgPaddingRect = new Rect();
68 
69     /* Recycler view related member variables */
70     private WidgetsRecyclerView mRecyclerView;
71     private WidgetsListAdapter mAdapter;
72     private TransformingTouchDelegate mRecyclerViewTouchDelegate;
73 
74     /* Touch handling related member variables. */
75     private Toast mWidgetInstructionToast;
76 
77     /* Rendering related. */
78     private WidgetPreviewLoader mWidgetPreviewLoader;
79 
WidgetsContainerView(Context context)80     public WidgetsContainerView(Context context) {
81         this(context, null);
82     }
83 
WidgetsContainerView(Context context, AttributeSet attrs)84     public WidgetsContainerView(Context context, AttributeSet attrs) {
85         this(context, attrs, 0);
86     }
87 
WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr)88     public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
89         super(context, attrs, defStyleAttr);
90         mLauncher = Launcher.getLauncher(context);
91         mDragController = mLauncher.getDragController();
92         mAdapter = new WidgetsListAdapter(this, this, context);
93         mIconCache = (LauncherAppState.getInstance()).getIconCache();
94         if (LOGD) {
95             Log.d(TAG, "WidgetsContainerView constructor");
96         }
97     }
98 
99     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)100     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
101         super.onLayout(changed, left, top, right, bottom);
102         getRevealView().getBackground().getPadding(mTmpBgPaddingRect);
103         mRecyclerViewTouchDelegate.setBounds(
104                 mRecyclerView.getLeft() - mTmpBgPaddingRect.left,
105                 mRecyclerView.getTop() - mTmpBgPaddingRect.top,
106                 mRecyclerView.getRight() + mTmpBgPaddingRect.right,
107                 mRecyclerView.getBottom() + mTmpBgPaddingRect.bottom);
108     }
109 
110     @Override
onFinishInflate()111     protected void onFinishInflate() {
112         super.onFinishInflate();
113         mRecyclerView = (WidgetsRecyclerView) getContentView().findViewById(R.id.widgets_list_view);
114         mRecyclerView.setAdapter(mAdapter);
115         mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
116         mRecyclerViewTouchDelegate = new TransformingTouchDelegate(mRecyclerView);
117     }
118 
119     @Override
onAttachedToWindow()120     protected void onAttachedToWindow() {
121         super.onAttachedToWindow();
122         ((View) mRecyclerView.getParent()).setTouchDelegate(mRecyclerViewTouchDelegate);
123     }
124 
125     //
126     // Returns views used for launcher transitions.
127     //
128 
scrollToTop()129     public void scrollToTop() {
130         mRecyclerView.scrollToPosition(0);
131     }
132 
133     //
134     // Touch related handling.
135     //
136 
137     @Override
onClick(View v)138     public void onClick(View v) {
139         // When we have exited widget tray or are in transition, disregard clicks
140         if (!mLauncher.isWidgetsViewVisible()
141                 || mLauncher.getWorkspace().isSwitchingState()
142                 || !(v instanceof WidgetCell)) return;
143 
144         // Let the user know that they have to long press to add a widget
145         if (mWidgetInstructionToast != null) {
146             mWidgetInstructionToast.cancel();
147         }
148 
149         CharSequence msg = Utilities.wrapForTts(
150                 getContext().getText(R.string.long_press_widget_to_add),
151                 getContext().getString(R.string.long_accessible_way_to_add));
152         mWidgetInstructionToast = Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT);
153         mWidgetInstructionToast.show();
154     }
155 
156     @Override
onLongClick(View v)157     public boolean onLongClick(View v) {
158         if (LOGD) {
159             Log.d(TAG, String.format("onLonglick [v=%s]", v));
160         }
161         // Return early if this is not initiated from a touch
162         if (!v.isInTouchMode()) return false;
163         // When we have exited all apps or are in transition, disregard long clicks
164         if (!mLauncher.isWidgetsViewVisible() ||
165                 mLauncher.getWorkspace().isSwitchingState()) return false;
166         // Return if global dragging is not enabled
167         if (!mLauncher.isDraggingEnabled()) return false;
168 
169         boolean status = beginDragging(v);
170         if (status && v.getTag() instanceof PendingAddWidgetInfo) {
171             WidgetHostViewLoader hostLoader = new WidgetHostViewLoader(mLauncher, v);
172             boolean preloadStatus = hostLoader.preloadWidget();
173             if (LOGD) {
174                 Log.d(TAG, String.format("preloading widget [status=%s]", preloadStatus));
175             }
176             mLauncher.getDragController().addDragListener(hostLoader);
177         }
178         return status;
179     }
180 
beginDragging(View v)181     private boolean beginDragging(View v) {
182         if (v instanceof WidgetCell) {
183             if (!beginDraggingWidget((WidgetCell) v)) {
184                 return false;
185             }
186         } else {
187             Log.e(TAG, "Unexpected dragging view: " + v);
188         }
189 
190         // We don't enter spring-loaded mode if the drag has been cancelled
191         if (mLauncher.getDragController().isDragging()) {
192             // Go into spring loaded mode (must happen before we startDrag())
193             mLauncher.enterSpringLoadedDragMode();
194         }
195 
196         return true;
197     }
198 
beginDraggingWidget(WidgetCell v)199     private boolean beginDraggingWidget(WidgetCell v) {
200         // Get the widget preview as the drag representation
201         WidgetImageView image = (WidgetImageView) v.findViewById(R.id.widget_preview);
202         PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
203 
204         // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
205         // we abort the drag.
206         if (image.getBitmap() == null) {
207             return false;
208         }
209 
210         // Compose the drag image
211         Bitmap preview;
212         final float scale;
213         final Rect bounds = image.getBitmapBounds();
214 
215         if (createItemInfo instanceof PendingAddWidgetInfo) {
216             // This can happen in some weird cases involving multi-touch. We can't start dragging
217             // the widget if this is null, so we break out.
218 
219             PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo;
220             int[] size = mLauncher.getWorkspace().estimateItemSize(createWidgetInfo, true);
221 
222             Bitmap icon = image.getBitmap();
223             float minScale = 1.25f;
224             int maxWidth = Math.min((int) (icon.getWidth() * minScale), size[0]);
225 
226             int[] previewSizeBeforeScale = new int[1];
227             preview = getWidgetPreviewLoader().generateWidgetPreview(mLauncher,
228                     createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale);
229 
230             if (previewSizeBeforeScale[0] < icon.getWidth()) {
231                 // The icon has extra padding around it.
232                 int padding = (icon.getWidth() - previewSizeBeforeScale[0]) / 2;
233                 if (icon.getWidth() > image.getWidth()) {
234                     padding = padding * image.getWidth() / icon.getWidth();
235                 }
236 
237                 bounds.left += padding;
238                 bounds.right -= padding;
239             }
240             scale = bounds.width() / (float) preview.getWidth();
241         } else {
242             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
243             Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.activityInfo);
244             preview = Utilities.createIconBitmap(icon, mLauncher);
245             createItemInfo.spanX = createItemInfo.spanY = 1;
246             scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / preview.getWidth();
247         }
248 
249         // Since we are not going through the workspace for starting the drag, set drag related
250         // information on the workspace before starting the drag.
251         mLauncher.getWorkspace().prepareDragWithProvider(
252                 new PendingItemPreviewProvider(v, createItemInfo, preview));
253 
254         // Start the drag
255         mDragController.startDrag(image, preview, this, createItemInfo,
256                 bounds, scale, new DragOptions());
257         return true;
258     }
259 
260     //
261     // Drag related handling methods that implement {@link DragSource} interface.
262     //
263 
264     @Override
supportsFlingToDelete()265     public boolean supportsFlingToDelete() {
266         return true;
267     }
268 
269     @Override
supportsAppInfoDropTarget()270     public boolean supportsAppInfoDropTarget() {
271         return true;
272     }
273 
274     /*
275      * Both this method and {@link #supportsFlingToDelete} has to return {@code false} for the
276      * {@link DeleteDropTarget} to be invisible.)
277      */
278     @Override
supportsDeleteDropTarget()279     public boolean supportsDeleteDropTarget() {
280         return false;
281     }
282 
283     @Override
getIntrinsicIconScaleFactor()284     public float getIntrinsicIconScaleFactor() {
285         return 0;
286     }
287 
288     @Override
onFlingToDeleteCompleted()289     public void onFlingToDeleteCompleted() {
290         // We just dismiss the drag when we fling, so cleanup here
291         mLauncher.exitSpringLoadedDragModeDelayed(true,
292                 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
293         mLauncher.unlockScreenOrientation(false);
294     }
295 
296     @Override
onDropCompleted(View target, DragObject d, boolean isFlingToDelete, boolean success)297     public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
298             boolean success) {
299         if (LOGD) {
300             Log.d(TAG, "onDropCompleted");
301         }
302         if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
303                 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
304             // Exit spring loaded mode if we have not successfully dropped or have not handled the
305             // drop in Workspace
306             mLauncher.exitSpringLoadedDragModeDelayed(true,
307                     Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
308         }
309         mLauncher.unlockScreenOrientation(false);
310 
311         // Display an error message if the drag failed due to there not being enough space on the
312         // target layout we were dropping on.
313         if (!success) {
314             boolean showOutOfSpaceMessage = false;
315             if (target instanceof Workspace) {
316                 int currentScreen = mLauncher.getCurrentWorkspaceScreen();
317                 Workspace workspace = (Workspace) target;
318                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
319                 ItemInfo itemInfo = d.dragInfo;
320                 if (layout != null) {
321                     showOutOfSpaceMessage =
322                             !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
323                 }
324             }
325             if (showOutOfSpaceMessage) {
326                 mLauncher.showOutOfSpaceMessage(false);
327             }
328             d.deferDragViewCleanupPostAnimation = false;
329         }
330     }
331 
332     /**
333      * Initialize the widget data model.
334      */
addWidgets(WidgetsModel model)335     public void addWidgets(WidgetsModel model) {
336         mRecyclerView.setWidgets(model);
337         mAdapter.setWidgetsModel(model);
338         mAdapter.notifyDataSetChanged();
339 
340         View loader = getContentView().findViewById(R.id.loader);
341         if (loader != null) {
342             ((ViewGroup) getContentView()).removeView(loader);
343         }
344     }
345 
isEmpty()346     public boolean isEmpty() {
347         return mAdapter.getItemCount() == 0;
348     }
349 
getWidgetPreviewLoader()350     private WidgetPreviewLoader getWidgetPreviewLoader() {
351         if (mWidgetPreviewLoader == null) {
352             mWidgetPreviewLoader = LauncherAppState.getInstance().getWidgetCache();
353         }
354         return mWidgetPreviewLoader;
355     }
356 
357     @Override
fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent)358     public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
359         targetParent.containerType = LauncherLogProto.WIDGETS;
360     }
361 }