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