Home | History | Annotate | Download | only in shortcuts
      1 /*
      2  * Copyright (C) 2017 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.shortcuts;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorSet;
     21 import android.content.Context;
     22 import android.graphics.Point;
     23 import android.support.v4.content.ContextCompat;
     24 import android.util.AttributeSet;
     25 import android.view.MotionEvent;
     26 import android.view.View;
     27 import android.widget.LinearLayout;
     28 
     29 import com.android.launcher3.AbstractFloatingView;
     30 import com.android.launcher3.BubbleTextView;
     31 import com.android.launcher3.ItemInfo;
     32 import com.android.launcher3.Launcher;
     33 import com.android.launcher3.LauncherAnimUtils;
     34 import com.android.launcher3.R;
     35 import com.android.launcher3.anim.PropertyListBuilder;
     36 import com.android.launcher3.dragndrop.DragOptions;
     37 import com.android.launcher3.dragndrop.DragView;
     38 import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
     39 import com.android.launcher3.popup.PopupContainerWithArrow;
     40 import com.android.launcher3.popup.PopupItemView;
     41 import com.android.launcher3.popup.PopupPopulator;
     42 import com.android.launcher3.popup.SystemShortcut;
     43 import com.android.launcher3.userevent.nano.LauncherLogProto;
     44 
     45 import java.util.ArrayList;
     46 import java.util.Collections;
     47 import java.util.List;
     48 
     49 /**
     50  * A {@link PopupItemView} that contains all of the {@link DeepShortcutView}s for an app,
     51  * as well as the system shortcuts such as Widgets and App Info.
     52  */
     53 public class ShortcutsItemView extends PopupItemView implements View.OnLongClickListener,
     54         View.OnTouchListener, LogContainerProvider {
     55 
     56     private Launcher mLauncher;
     57     private LinearLayout mShortcutsLayout;
     58     private LinearLayout mSystemShortcutIcons;
     59     private final Point mIconShift = new Point();
     60     private final Point mIconLastTouchPos = new Point();
     61     private final List<DeepShortcutView> mDeepShortcutViews = new ArrayList<>();
     62     private final List<View> mSystemShortcutViews = new ArrayList<>();
     63 
     64     public ShortcutsItemView(Context context) {
     65         this(context, null, 0);
     66     }
     67 
     68     public ShortcutsItemView(Context context, AttributeSet attrs) {
     69         this(context, attrs, 0);
     70     }
     71 
     72     public ShortcutsItemView(Context context, AttributeSet attrs, int defStyle) {
     73         super(context, attrs, defStyle);
     74 
     75         mLauncher = Launcher.getLauncher(context);
     76     }
     77 
     78     @Override
     79     protected void onFinishInflate() {
     80         super.onFinishInflate();
     81         mShortcutsLayout = findViewById(R.id.deep_shortcuts);
     82     }
     83 
     84     @Override
     85     public boolean onTouch(View v, MotionEvent ev) {
     86         // Touched a shortcut, update where it was touched so we can drag from there on long click.
     87         switch (ev.getAction()) {
     88             case MotionEvent.ACTION_DOWN:
     89             case MotionEvent.ACTION_MOVE:
     90                 mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
     91                 break;
     92         }
     93         return false;
     94     }
     95 
     96     @Override
     97     public boolean onLongClick(View v) {
     98         // Return early if this is not initiated from a touch or not the correct view
     99         if (!v.isInTouchMode() || !(v.getParent() instanceof DeepShortcutView)) return false;
    100         // Return early if global dragging is not enabled
    101         if (!mLauncher.isDraggingEnabled()) return false;
    102         // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
    103         if (mLauncher.getDragController().isDragging()) return false;
    104 
    105         // Long clicked on a shortcut.
    106         DeepShortcutView sv = (DeepShortcutView) v.getParent();
    107         sv.setWillDrawIcon(false);
    108 
    109         // Move the icon to align with the center-top of the touch point
    110         mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
    111         mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
    112 
    113         DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
    114                 (PopupContainerWithArrow) getParent(), sv.getFinalInfo(),
    115                 new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
    116         dv.animateShift(-mIconShift.x, -mIconShift.y);
    117 
    118         // TODO: support dragging from within folder without having to close it
    119         AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
    120         return false;
    121     }
    122 
    123     public void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType) {
    124         addShortcutView(shortcutView, shortcutType, -1);
    125     }
    126 
    127     private void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType, int index) {
    128         if (shortcutType == PopupPopulator.Item.SHORTCUT) {
    129             mDeepShortcutViews.add((DeepShortcutView) shortcutView);
    130         } else {
    131             mSystemShortcutViews.add(shortcutView);
    132         }
    133         if (shortcutType == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
    134             // System shortcut icons are added to a header that is separate from the full shortcuts.
    135             if (mSystemShortcutIcons == null) {
    136                 mSystemShortcutIcons = (LinearLayout) mLauncher.getLayoutInflater().inflate(
    137                         R.layout.system_shortcut_icons, mShortcutsLayout, false);
    138                 mShortcutsLayout.addView(mSystemShortcutIcons, 0);
    139             }
    140             mSystemShortcutIcons.addView(shortcutView, index);
    141         } else {
    142             if (mShortcutsLayout.getChildCount() > 0) {
    143                 View prevChild = mShortcutsLayout.getChildAt(mShortcutsLayout.getChildCount() - 1);
    144                 if (prevChild instanceof DeepShortcutView) {
    145                     prevChild.findViewById(R.id.divider).setVisibility(VISIBLE);
    146                 }
    147             }
    148             mShortcutsLayout.addView(shortcutView, index);
    149         }
    150     }
    151 
    152     public List<DeepShortcutView> getDeepShortcutViews(boolean reverseOrder) {
    153         if (reverseOrder) {
    154             Collections.reverse(mDeepShortcutViews);
    155         }
    156         return mDeepShortcutViews;
    157     }
    158 
    159     public List<View> getSystemShortcutViews(boolean reverseOrder) {
    160         // Always reverse system shortcut icons (in the header)
    161         // so they are in priority order from right to left.
    162         if (reverseOrder || mSystemShortcutIcons != null) {
    163             Collections.reverse(mSystemShortcutViews);
    164         }
    165         return mSystemShortcutViews;
    166     }
    167 
    168     /**
    169      * Adds a {@link SystemShortcut.Widgets} item if there are widgets for the given ItemInfo.
    170      */
    171     public void enableWidgetsIfExist(final BubbleTextView originalIcon) {
    172         ItemInfo itemInfo = (ItemInfo) originalIcon.getTag();
    173         SystemShortcut widgetInfo = new SystemShortcut.Widgets();
    174         View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
    175         View widgetsView = null;
    176         for (View systemShortcutView : mSystemShortcutViews) {
    177             if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
    178                 widgetsView = systemShortcutView;
    179                 break;
    180             }
    181         }
    182         final PopupPopulator.Item widgetsItem = mSystemShortcutIcons == null
    183                 ? PopupPopulator.Item.SYSTEM_SHORTCUT
    184                 : PopupPopulator.Item.SYSTEM_SHORTCUT_ICON;
    185         if (onClickListener != null && widgetsView == null) {
    186             // We didn't have any widgets cached but now there are some, so enable the shortcut.
    187             widgetsView = mLauncher.getLayoutInflater().inflate(widgetsItem.layoutId, this, false);
    188             PopupPopulator.initializeSystemShortcut(getContext(), widgetsView, widgetInfo);
    189             widgetsView.setOnClickListener(onClickListener);
    190             if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
    191                 addShortcutView(widgetsView, widgetsItem, 0);
    192             } else {
    193                 // If using the expanded system shortcut (as opposed to just the icon), we need to
    194                 // reopen the container to ensure measurements etc. all work out. While this could
    195                 // be quite janky, in practice the user would typically see a small flicker as the
    196                 // animation restarts partway through, and this is a very rare edge case anyway.
    197                 ((PopupContainerWithArrow) getParent()).close(false);
    198                 PopupContainerWithArrow.showForIcon(originalIcon);
    199             }
    200         } else if (onClickListener == null && widgetsView != null) {
    201             // No widgets exist, but we previously added the shortcut so remove it.
    202             if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
    203                 mSystemShortcutViews.remove(widgetsView);
    204                 mSystemShortcutIcons.removeView(widgetsView);
    205             } else {
    206                 ((PopupContainerWithArrow) getParent()).close(false);
    207                 PopupContainerWithArrow.showForIcon(originalIcon);
    208             }
    209         }
    210     }
    211 
    212     @Override
    213     public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
    214         AnimatorSet openAnimation = LauncherAnimUtils.createAnimatorSet();
    215         openAnimation.play(super.createOpenAnimation(isContainerAboveIcon, pivotLeft));
    216         for (int i = 0; i < mShortcutsLayout.getChildCount(); i++) {
    217             if (!(mShortcutsLayout.getChildAt(i) instanceof DeepShortcutView)) {
    218                 continue;
    219             }
    220             DeepShortcutView shortcutView = ((DeepShortcutView) mShortcutsLayout.getChildAt(i));
    221             View deepShortcutIcon = shortcutView.getIconView();
    222             deepShortcutIcon.setScaleX(0);
    223             deepShortcutIcon.setScaleY(0);
    224             openAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder(
    225                     deepShortcutIcon, new PropertyListBuilder().scale(1).build()));
    226         }
    227         return openAnimation;
    228     }
    229 
    230     @Override
    231     public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
    232             long duration) {
    233         AnimatorSet closeAnimation = LauncherAnimUtils.createAnimatorSet();
    234         closeAnimation.play(super.createCloseAnimation(isContainerAboveIcon, pivotLeft, duration));
    235         for (int i = 0; i < mShortcutsLayout.getChildCount(); i++) {
    236             if (!(mShortcutsLayout.getChildAt(i) instanceof DeepShortcutView)) {
    237                 continue;
    238             }
    239             DeepShortcutView shortcutView = ((DeepShortcutView) mShortcutsLayout.getChildAt(i));
    240             View deepShortcutIcon = shortcutView.getIconView();
    241             deepShortcutIcon.setScaleX(1);
    242             deepShortcutIcon.setScaleY(1);
    243             closeAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder(
    244                     deepShortcutIcon, new PropertyListBuilder().scale(0).build()));
    245         }
    246         return closeAnimation;
    247     }
    248 
    249     @Override
    250     public int getArrowColor(boolean isArrowAttachedToBottom) {
    251         return ContextCompat.getColor(getContext(),
    252                 isArrowAttachedToBottom || mSystemShortcutIcons == null
    253                         ? R.color.popup_background_color
    254                         : R.color.popup_header_background_color);
    255     }
    256 
    257     @Override
    258     public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
    259             LauncherLogProto.Target targetParent) {
    260         target.itemType = LauncherLogProto.ItemType.DEEPSHORTCUT;
    261         target.rank = info.rank;
    262         targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
    263     }
    264 }
    265