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