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 package com.android.launcher3.allapps; 17 18 import android.content.Context; 19 import android.graphics.Color; 20 import android.graphics.Rect; 21 import android.graphics.drawable.ColorDrawable; 22 import android.graphics.drawable.InsetDrawable; 23 import android.support.v7.widget.LinearLayoutManager; 24 import android.text.Selection; 25 import android.text.SpannableStringBuilder; 26 import android.util.AttributeSet; 27 import android.view.KeyEvent; 28 import android.view.MotionEvent; 29 import android.view.View; 30 import android.view.ViewGroup; 31 32 import com.android.launcher3.AppInfo; 33 import com.android.launcher3.BaseContainerView; 34 import com.android.launcher3.BubbleTextView; 35 import com.android.launcher3.DeleteDropTarget; 36 import com.android.launcher3.DeviceProfile; 37 import com.android.launcher3.DragSource; 38 import com.android.launcher3.DropTarget; 39 import com.android.launcher3.Insettable; 40 import com.android.launcher3.ItemInfo; 41 import com.android.launcher3.Launcher; 42 import com.android.launcher3.PromiseAppInfo; 43 import com.android.launcher3.R; 44 import com.android.launcher3.Utilities; 45 import com.android.launcher3.anim.SpringAnimationHandler; 46 import com.android.launcher3.config.FeatureFlags; 47 import com.android.launcher3.dragndrop.DragController; 48 import com.android.launcher3.dragndrop.DragOptions; 49 import com.android.launcher3.folder.Folder; 50 import com.android.launcher3.keyboard.FocusedItemDecorator; 51 import com.android.launcher3.userevent.nano.LauncherLogProto.Target; 52 import com.android.launcher3.util.ComponentKey; 53 import com.android.launcher3.util.ComponentKeyMapper; 54 import com.android.launcher3.util.PackageUserKey; 55 56 import java.util.List; 57 import java.util.Set; 58 59 /** 60 * The all apps view container. 61 */ 62 public class AllAppsContainerView extends BaseContainerView implements DragSource, 63 View.OnLongClickListener, Insettable { 64 65 private final Launcher mLauncher; 66 private final AlphabeticalAppsList mApps; 67 private final AllAppsGridAdapter mAdapter; 68 private final LinearLayoutManager mLayoutManager; 69 70 private AllAppsRecyclerView mAppsRecyclerView; 71 private SearchUiManager mSearchUiManager; 72 private View mSearchContainer; 73 74 private SpannableStringBuilder mSearchQueryBuilder = null; 75 76 private int mNumAppsPerRow; 77 private int mNumPredictedAppsPerRow; 78 79 private SpringAnimationHandler mSpringAnimationHandler; 80 81 public AllAppsContainerView(Context context) { 82 this(context, null); 83 } 84 85 public AllAppsContainerView(Context context, AttributeSet attrs) { 86 this(context, attrs, 0); 87 } 88 89 public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) { 90 super(context, attrs, defStyleAttr); 91 92 mLauncher = Launcher.getLauncher(context); 93 mApps = new AlphabeticalAppsList(context); 94 mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this); 95 mSpringAnimationHandler = mAdapter.getSpringAnimationHandler(); 96 mApps.setAdapter(mAdapter); 97 mLayoutManager = mAdapter.getLayoutManager(); 98 mSearchQueryBuilder = new SpannableStringBuilder(); 99 100 Selection.setSelection(mSearchQueryBuilder, 0); 101 } 102 103 @Override 104 protected void updateBackground( 105 int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) { 106 if (mLauncher.getDeviceProfile().isVerticalBarLayout()) { 107 getRevealView().setBackground(new InsetDrawable(mBaseDrawable, 108 paddingLeft, paddingTop, paddingRight, paddingBottom)); 109 getContentView().setBackground( 110 new InsetDrawable(new ColorDrawable(Color.TRANSPARENT), 111 paddingLeft, paddingTop, paddingRight, paddingBottom)); 112 } else { 113 getRevealView().setBackground(mBaseDrawable); 114 } 115 } 116 117 /** 118 * Sets the current set of predicted apps. 119 */ 120 public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) { 121 mApps.setPredictedApps(apps); 122 } 123 124 /** 125 * Sets the current set of apps. 126 */ 127 public void setApps(List<AppInfo> apps) { 128 mApps.setApps(apps); 129 } 130 131 /** 132 * Adds or updates existing apps in the list 133 */ 134 public void addOrUpdateApps(List<AppInfo> apps) { 135 mApps.addOrUpdateApps(apps); 136 mSearchUiManager.refreshSearchResult(); 137 } 138 139 public void updatePromiseAppProgress(PromiseAppInfo app) { 140 int childCount = mAppsRecyclerView.getChildCount(); 141 for (int i = 0; i < childCount; i++) { 142 View child = mAppsRecyclerView.getChildAt(i); 143 if (child instanceof BubbleTextView && child.getTag() == app) { 144 BubbleTextView bubbleTextView = (BubbleTextView) child; 145 bubbleTextView.applyProgressLevel(app.level); 146 } 147 } 148 } 149 150 /** 151 * Removes some apps from the list. 152 */ 153 public void removeApps(List<AppInfo> apps) { 154 mApps.removeApps(apps); 155 mSearchUiManager.refreshSearchResult(); 156 } 157 158 /** 159 * Returns whether the view itself will handle the touch event or not. 160 */ 161 public boolean shouldContainerScroll(MotionEvent ev) { 162 // IF the MotionEvent is inside the search box, and the container keeps on receiving 163 // touch input, container should move down. 164 if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) { 165 return true; 166 } 167 168 int[] point = new int[2]; 169 point[0] = (int) ev.getX(); 170 point[1] = (int) ev.getY(); 171 Utilities.mapCoordInSelfToDescendant( 172 mAppsRecyclerView.getScrollBar(), mLauncher.getDragLayer(), point); 173 // IF the MotionEvent is inside the thumb, container should not be pulled down. 174 if (mAppsRecyclerView.getScrollBar().shouldBlockIntercept(point[0], point[1])) { 175 return false; 176 } 177 178 // IF scroller is at the very top OR there is no scroll bar because there is probably not 179 // enough items to scroll, THEN it's okay for the container to be pulled down. 180 if (mAppsRecyclerView.getCurrentScrollY() == 0) { 181 return true; 182 } 183 return false; 184 } 185 186 /** 187 * Resets the state of AllApps. 188 */ 189 public void reset() { 190 // Reset the search bar and base recycler view after transitioning home 191 mAppsRecyclerView.scrollToTop(); 192 mSearchUiManager.reset(); 193 } 194 195 @Override 196 protected void onFinishInflate() { 197 super.onFinishInflate(); 198 199 // This is a focus listener that proxies focus from a view into the list view. This is to 200 // work around the search box from getting first focus and showing the cursor. 201 getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() { 202 @Override 203 public void onFocusChange(View v, boolean hasFocus) { 204 if (hasFocus) { 205 mAppsRecyclerView.requestFocus(); 206 } 207 } 208 }); 209 210 // Load the all apps recycler view 211 mAppsRecyclerView = findViewById(R.id.apps_list_view); 212 mAppsRecyclerView.setApps(mApps); 213 mAppsRecyclerView.setLayoutManager(mLayoutManager); 214 mAppsRecyclerView.setAdapter(mAdapter); 215 mAppsRecyclerView.setHasFixedSize(true); 216 // No animations will occur when changes occur to the items in this RecyclerView. 217 mAppsRecyclerView.setItemAnimator(null); 218 if (FeatureFlags.LAUNCHER3_PHYSICS) { 219 mAppsRecyclerView.setSpringAnimationHandler(mSpringAnimationHandler); 220 } 221 222 mSearchContainer = findViewById(R.id.search_container_all_apps); 223 mSearchUiManager = (SearchUiManager) mSearchContainer; 224 mSearchUiManager.initialize(mApps, mAppsRecyclerView); 225 226 227 FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView); 228 mAppsRecyclerView.addItemDecoration(focusedItemDecorator); 229 mAppsRecyclerView.preMeasureViews(mAdapter); 230 mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener()); 231 232 getRevealView().setVisibility(View.VISIBLE); 233 getContentView().setVisibility(View.VISIBLE); 234 getContentView().setBackground(null); 235 } 236 237 public SearchUiManager getSearchUiManager() { 238 return mSearchUiManager; 239 } 240 241 @Override 242 public View getTouchDelegateTargetView() { 243 return mAppsRecyclerView; 244 } 245 246 @Override 247 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 248 DeviceProfile grid = mLauncher.getDeviceProfile(); 249 // Update the number of items in the grid before we measure the view 250 grid.updateAppsViewNumCols(); 251 252 if (mNumAppsPerRow != grid.inv.numColumns || 253 mNumPredictedAppsPerRow != grid.inv.numColumns) { 254 mNumAppsPerRow = grid.inv.numColumns; 255 mNumPredictedAppsPerRow = grid.inv.numColumns; 256 257 mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow); 258 mAdapter.setNumAppsPerRow(mNumAppsPerRow); 259 mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow); 260 } 261 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 262 } 263 264 @Override 265 public boolean dispatchKeyEvent(KeyEvent event) { 266 mSearchUiManager.preDispatchKeyEvent(event); 267 return super.dispatchKeyEvent(event); 268 } 269 270 @Override 271 public boolean onLongClick(final View v) { 272 // When we have exited all apps or are in transition, disregard long clicks 273 if (!mLauncher.isAppsViewVisible() || 274 mLauncher.getWorkspace().isSwitchingState()) return false; 275 // Return if global dragging is not enabled or we are already dragging 276 if (!mLauncher.isDraggingEnabled()) return false; 277 if (mLauncher.getDragController().isDragging()) return false; 278 279 // Start the drag 280 final DragController dragController = mLauncher.getDragController(); 281 dragController.addDragListener(new DragController.DragListener() { 282 @Override 283 public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { 284 v.setVisibility(INVISIBLE); 285 } 286 287 @Override 288 public void onDragEnd() { 289 v.setVisibility(VISIBLE); 290 dragController.removeDragListener(this); 291 } 292 }); 293 mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions()); 294 return false; 295 } 296 297 @Override 298 public boolean supportsAppInfoDropTarget() { 299 return true; 300 } 301 302 @Override 303 public boolean supportsDeleteDropTarget() { 304 return false; 305 } 306 307 @Override 308 public float getIntrinsicIconScaleFactor() { 309 DeviceProfile grid = mLauncher.getDeviceProfile(); 310 return (float) grid.allAppsIconSizePx / grid.iconSizePx; 311 } 312 313 @Override 314 public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete, 315 boolean success) { 316 if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && 317 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) { 318 // Exit spring loaded mode if we have not successfully dropped or have not handled the 319 // drop in Workspace 320 mLauncher.exitSpringLoadedDragModeDelayed(true, 321 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); 322 } 323 mLauncher.unlockScreenOrientation(false); 324 325 if (!success) { 326 d.deferDragViewCleanupPostAnimation = false; 327 } 328 } 329 330 @Override 331 public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) { 332 // This is filled in {@link AllAppsRecyclerView} 333 } 334 335 @Override 336 public void setInsets(Rect insets) { 337 DeviceProfile grid = mLauncher.getDeviceProfile(); 338 mAppsRecyclerView.setPadding( 339 mAppsRecyclerView.getPaddingLeft(), mAppsRecyclerView.getPaddingTop(), 340 mAppsRecyclerView.getPaddingRight(), insets.bottom); 341 342 if (grid.isVerticalBarLayout()) { 343 ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams(); 344 mlp.leftMargin = insets.left; 345 mlp.topMargin = insets.top; 346 mlp.rightMargin = insets.right; 347 setLayoutParams(mlp); 348 } else { 349 View navBarBg = findViewById(R.id.nav_bar_bg); 350 ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams(); 351 navBarBgLp.height = insets.bottom; 352 navBarBg.setLayoutParams(navBarBgLp); 353 } 354 } 355 356 public void updateIconBadges(Set<PackageUserKey> updatedBadges) { 357 final PackageUserKey packageUserKey = new PackageUserKey(null, null); 358 final int n = mAppsRecyclerView.getChildCount(); 359 for (int i = 0; i < n; i++) { 360 View child = mAppsRecyclerView.getChildAt(i); 361 if (!(child instanceof BubbleTextView) || !(child.getTag() instanceof ItemInfo)) { 362 continue; 363 } 364 ItemInfo info = (ItemInfo) child.getTag(); 365 if (packageUserKey.updateFromItemInfo(info) && updatedBadges.contains(packageUserKey)) { 366 ((BubbleTextView) child).applyBadgeState(info, true /* animate */); 367 } 368 } 369 } 370 371 public SpringAnimationHandler getSpringAnimationHandler() { 372 return mSpringAnimationHandler; 373 } 374 } 375