OSDN Git Service

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