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.Point; 21 import android.support.v7.widget.LinearLayoutManager; 22 import android.util.AttributeSet; 23 import android.util.Log; 24 import android.view.View; 25 import android.view.ViewGroup; 26 import android.widget.Toast; 27 28 import com.android.launcher3.BaseContainerView; 29 import com.android.launcher3.DeleteDropTarget; 30 import com.android.launcher3.DragSource; 31 import com.android.launcher3.DropTarget.DragObject; 32 import com.android.launcher3.ItemInfo; 33 import com.android.launcher3.Launcher; 34 import com.android.launcher3.R; 35 import com.android.launcher3.Utilities; 36 import com.android.launcher3.dragndrop.DragOptions; 37 import com.android.launcher3.folder.Folder; 38 import com.android.launcher3.model.PackageItemInfo; 39 import com.android.launcher3.model.WidgetItem; 40 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; 41 import com.android.launcher3.userevent.nano.LauncherLogProto.Target; 42 import com.android.launcher3.util.MultiHashMap; 43 import com.android.launcher3.util.PackageUserKey; 44 import com.android.launcher3.util.Thunk; 45 46 import java.util.List; 47 48 /** 49 * The widgets list view container. 50 */ 51 public class WidgetsContainerView extends BaseContainerView 52 implements View.OnLongClickListener, View.OnClickListener, DragSource { 53 private static final String TAG = "WidgetsContainerView"; 54 private static final boolean LOGD = false; 55 56 /* Global instances that are used inside this container. */ 57 @Thunk Launcher mLauncher; 58 59 /* Recycler view related member variables */ 60 private WidgetsRecyclerView mRecyclerView; 61 private WidgetsListAdapter mAdapter; 62 63 /* Touch handling related member variables. */ 64 private Toast mWidgetInstructionToast; 65 WidgetsContainerView(Context context)66 public WidgetsContainerView(Context context) { 67 this(context, null); 68 } 69 WidgetsContainerView(Context context, AttributeSet attrs)70 public WidgetsContainerView(Context context, AttributeSet attrs) { 71 this(context, attrs, 0); 72 } 73 WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr)74 public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) { 75 super(context, attrs, defStyleAttr); 76 mLauncher = Launcher.getLauncher(context); 77 mAdapter = new WidgetsListAdapter(this, this, context); 78 if (LOGD) { 79 Log.d(TAG, "WidgetsContainerView constructor"); 80 } 81 } 82 83 @Override getTouchDelegateTargetView()84 public View getTouchDelegateTargetView() { 85 return mRecyclerView; 86 } 87 88 @Override onFinishInflate()89 protected void onFinishInflate() { 90 super.onFinishInflate(); 91 mRecyclerView = (WidgetsRecyclerView) getContentView().findViewById(R.id.widgets_list_view); 92 mRecyclerView.setAdapter(mAdapter); 93 mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 94 } 95 96 // 97 // Returns views used for launcher transitions. 98 // 99 scrollToTop()100 public void scrollToTop() { 101 mRecyclerView.scrollToPosition(0); 102 } 103 104 // 105 // Touch related handling. 106 // 107 108 @Override onClick(View v)109 public void onClick(View v) { 110 // When we have exited widget tray or are in transition, disregard clicks 111 if (!mLauncher.isWidgetsViewVisible() 112 || mLauncher.getWorkspace().isSwitchingState() 113 || !(v instanceof WidgetCell)) return; 114 115 handleClick(); 116 } 117 handleClick()118 public void handleClick() { 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 // When we have exited the widget tray, disregard long clicks 134 if (!mLauncher.isWidgetsViewVisible()) return false; 135 return handleLongClick(v); 136 } 137 handleLongClick(View v)138 public boolean handleLongClick(View v) { 139 if (LOGD) { 140 Log.d(TAG, String.format("onLongClick [v=%s]", v)); 141 } 142 // Return early if this is not initiated from a touch 143 if (!v.isInTouchMode()) return false; 144 // When we are in transition, disregard long clicks 145 if (mLauncher.getWorkspace().isSwitchingState()) return false; 146 // Return if global dragging is not enabled 147 if (!mLauncher.isDraggingEnabled()) return false; 148 149 return beginDragging(v); 150 } 151 beginDragging(View v)152 private boolean beginDragging(View v) { 153 if (v instanceof WidgetCell) { 154 if (!beginDraggingWidget((WidgetCell) v)) { 155 return false; 156 } 157 } else { 158 Log.e(TAG, "Unexpected dragging view: " + v); 159 } 160 161 // We don't enter spring-loaded mode if the drag has been cancelled 162 if (mLauncher.getDragController().isDragging()) { 163 // Go into spring loaded mode (must happen before we startDrag()) 164 mLauncher.enterSpringLoadedDragMode(); 165 } 166 167 return true; 168 } 169 beginDraggingWidget(WidgetCell v)170 private boolean beginDraggingWidget(WidgetCell v) { 171 // Get the widget preview as the drag representation 172 WidgetImageView image = (WidgetImageView) v.findViewById(R.id.widget_preview); 173 174 // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and 175 // we abort the drag. 176 if (image.getBitmap() == null) { 177 return false; 178 } 179 180 int[] loc = new int[2]; 181 mLauncher.getDragLayer().getLocationInDragLayer(image, loc); 182 183 new PendingItemDragHelper(v).startDrag( 184 image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(), 185 new Point(loc[0], loc[1]), this, new DragOptions()); 186 return true; 187 } 188 189 // 190 // Drag related handling methods that implement {@link DragSource} interface. 191 // 192 193 @Override supportsAppInfoDropTarget()194 public boolean supportsAppInfoDropTarget() { 195 return true; 196 } 197 198 /* 199 * Both this method and {@link #supportsFlingToDelete} has to return {@code false} for the 200 * {@link DeleteDropTarget} to be invisible.) 201 */ 202 @Override supportsDeleteDropTarget()203 public boolean supportsDeleteDropTarget() { 204 return false; 205 } 206 207 @Override getIntrinsicIconScaleFactor()208 public float getIntrinsicIconScaleFactor() { 209 return 0; 210 } 211 212 @Override onDropCompleted(View target, DragObject d, boolean isFlingToDelete, boolean success)213 public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, 214 boolean success) { 215 if (LOGD) { 216 Log.d(TAG, "onDropCompleted"); 217 } 218 if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && 219 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) { 220 // Exit spring loaded mode if we have not successfully dropped or have not handled the 221 // drop in Workspace 222 mLauncher.exitSpringLoadedDragModeDelayed(true, 223 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); 224 } 225 mLauncher.unlockScreenOrientation(false); 226 227 if (!success) { 228 d.deferDragViewCleanupPostAnimation = false; 229 } 230 } 231 232 /** 233 * Initialize the widget data model. 234 */ setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> model)235 public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> model) { 236 mAdapter.setWidgets(model); 237 mAdapter.notifyDataSetChanged(); 238 239 View loader = getContentView().findViewById(R.id.loader); 240 if (loader != null) { 241 ((ViewGroup) getContentView()).removeView(loader); 242 } 243 } 244 isEmpty()245 public boolean isEmpty() { 246 return mAdapter.getItemCount() == 0; 247 } 248 getWidgetsForPackageUser(PackageUserKey packageUserKey)249 public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) { 250 return mAdapter.copyWidgetsForPackageUser(packageUserKey); 251 } 252 253 @Override fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent)254 public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) { 255 targetParent.containerType = ContainerType.WIDGETS; 256 } 257 }