Home | History | Annotate | Download | only in widget
      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 
     73     public WidgetsContainerView(Context context) {
     74         this(context, null);
     75     }
     76 
     77     public WidgetsContainerView(Context context, AttributeSet attrs) {
     78         this(context, attrs, 0);
     79     }
     80 
     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
     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 
    104     public void scrollToTop() {
    105         mRecyclerView.scrollToPosition(0);
    106     }
    107 
    108     //
    109     // Touch related handling.
    110     //
    111 
    112     @Override
    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
    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 
    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 
    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
    243     public boolean supportsFlingToDelete() {
    244         return false;
    245     }
    246 
    247     @Override
    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
    257     public boolean supportsDeleteDropTarget() {
    258         return false;
    259     }
    260 
    261     @Override
    262     public float getIntrinsicIconScaleFactor() {
    263         return 0;
    264     }
    265 
    266     @Override
    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
    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
    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      */
    329     public void addWidgets(WidgetsModel model) {
    330         mRecyclerView.setWidgets(model);
    331         mAdapter.setWidgetsModel(model);
    332         mAdapter.notifyDataSetChanged();
    333     }
    334 
    335     public boolean isEmpty() {
    336         return mAdapter.getItemCount() == 0;
    337     }
    338 
    339     private WidgetPreviewLoader getWidgetPreviewLoader() {
    340         if (mWidgetPreviewLoader == null) {
    341             mWidgetPreviewLoader = LauncherAppState.getInstance().getWidgetCache();
    342         }
    343         return mWidgetPreviewLoader;
    344     }
    345 }