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