1 /* 2 * Copyright (C) 2020 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.secondarydisplay; 17 18 import android.animation.Animator; 19 import android.animation.AnimatorListenerAdapter; 20 import android.content.Intent; 21 import android.graphics.Rect; 22 import android.graphics.drawable.Drawable; 23 import android.os.Bundle; 24 import android.view.View; 25 import android.view.View.OnClickListener; 26 import android.view.ViewAnimationUtils; 27 import android.view.inputmethod.InputMethodManager; 28 29 import androidx.annotation.UiThread; 30 31 import com.android.launcher3.AbstractFloatingView; 32 import com.android.launcher3.BaseDraggingActivity; 33 import com.android.launcher3.BubbleTextView; 34 import com.android.launcher3.DragSource; 35 import com.android.launcher3.DropTarget; 36 import com.android.launcher3.InvariantDeviceProfile; 37 import com.android.launcher3.LauncherAppState; 38 import com.android.launcher3.LauncherModel; 39 import com.android.launcher3.LauncherSettings; 40 import com.android.launcher3.R; 41 import com.android.launcher3.allapps.ActivityAllAppsContainerView; 42 import com.android.launcher3.allapps.AllAppsStore; 43 import com.android.launcher3.dragndrop.DragController; 44 import com.android.launcher3.dragndrop.DragOptions; 45 import com.android.launcher3.dragndrop.DraggableView; 46 import com.android.launcher3.graphics.DragPreviewProvider; 47 import com.android.launcher3.icons.FastBitmapDrawable; 48 import com.android.launcher3.model.BgDataModel; 49 import com.android.launcher3.model.StringCache; 50 import com.android.launcher3.model.data.AppInfo; 51 import com.android.launcher3.model.data.ItemInfo; 52 import com.android.launcher3.model.data.ItemInfoWithIcon; 53 import com.android.launcher3.popup.PopupContainerWithArrow; 54 import com.android.launcher3.popup.PopupDataProvider; 55 import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy; 56 import com.android.launcher3.util.ComponentKey; 57 import com.android.launcher3.util.IntSet; 58 import com.android.launcher3.util.PackageUserKey; 59 import com.android.launcher3.util.Preconditions; 60 import com.android.launcher3.util.Themes; 61 import com.android.launcher3.views.BaseDragLayer; 62 63 import java.util.HashMap; 64 import java.util.Map; 65 66 /** 67 * Launcher activity for secondary displays 68 */ 69 public class SecondaryDisplayLauncher extends BaseDraggingActivity 70 implements BgDataModel.Callbacks, DragController.DragListener { 71 72 private LauncherModel mModel; 73 private SecondaryDragLayer mDragLayer; 74 private SecondaryDragController mDragController; 75 private ActivityAllAppsContainerView<SecondaryDisplayLauncher> mAppsView; 76 private View mAppsButton; 77 78 private PopupDataProvider mPopupDataProvider; 79 80 private boolean mAppDrawerShown = false; 81 82 private StringCache mStringCache; 83 private boolean mBindingItems = false; 84 private SecondaryDisplayPredictions mSecondaryDisplayPredictions; 85 86 private final int[] mTempXY = new int[2]; 87 88 @Override onCreate(Bundle savedInstanceState)89 protected void onCreate(Bundle savedInstanceState) { 90 super.onCreate(savedInstanceState); 91 mModel = LauncherAppState.getInstance(this).getModel(); 92 mDragController = new SecondaryDragController(this); 93 mSecondaryDisplayPredictions = SecondaryDisplayPredictions.newInstance(this); 94 if (getWindow().getDecorView().isAttachedToWindow()) { 95 initUi(); 96 } 97 } 98 99 @Override onAttachedToWindow()100 public void onAttachedToWindow() { 101 super.onAttachedToWindow(); 102 initUi(); 103 } 104 105 @Override onDetachedFromWindow()106 public void onDetachedFromWindow() { 107 super.onDetachedFromWindow(); 108 this.getDragController().removeDragListener(this); 109 } 110 initUi()111 private void initUi() { 112 if (mDragLayer != null) { 113 return; 114 } 115 InvariantDeviceProfile currentDisplayIdp = new InvariantDeviceProfile( 116 this, getWindow().getDecorView().getDisplay()); 117 118 // Disable transpose layout and use multi-window mode so that the icons are scaled properly 119 mDeviceProfile = currentDisplayIdp.getDeviceProfile(this) 120 .toBuilder(this) 121 .setMultiWindowMode(true) 122 .setTransposeLayoutWithOrientation(false) 123 .build(); 124 mDeviceProfile.autoResizeAllAppsCells(); 125 126 setContentView(R.layout.secondary_launcher); 127 mDragLayer = findViewById(R.id.drag_layer); 128 mAppsView = findViewById(R.id.apps_view); 129 mAppsButton = findViewById(R.id.all_apps_button); 130 131 mDragController.addDragListener(this); 132 mPopupDataProvider = new PopupDataProvider( 133 mAppsView.getAppsStore()::updateNotificationDots); 134 135 mModel.addCallbacksAndLoad(this); 136 } 137 138 @Override onPause()139 protected void onPause() { 140 super.onPause(); 141 mDragController.cancelDrag(); 142 } 143 144 @Override onNewIntent(Intent intent)145 public void onNewIntent(Intent intent) { 146 super.onNewIntent(intent); 147 148 if (Intent.ACTION_MAIN.equals(intent.getAction())) { 149 // Hide keyboard. 150 final View v = getWindow().peekDecorView(); 151 if (v != null && v.getWindowToken() != null) { 152 getSystemService(InputMethodManager.class).hideSoftInputFromWindow( 153 v.getWindowToken(), 0); 154 } 155 } 156 157 // A new intent will bring the launcher to top. Hide the app drawer to reset the state. 158 showAppDrawer(false); 159 } 160 getDragController()161 public DragController getDragController() { 162 return mDragController; 163 } 164 165 @Override onBackPressed()166 public void onBackPressed() { 167 if (finishAutoCancelActionMode()) { 168 return; 169 } 170 171 if (mDragController.isDragging()) { 172 mDragController.cancelDrag(); 173 return; 174 } 175 176 // Note: There should be at most one log per method call. This is enforced implicitly 177 // by using if-else statements. 178 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this); 179 if (topView != null && topView.canHandleBack()) { 180 // Handled by the floating view. 181 topView.onBackInvoked(); 182 } else { 183 showAppDrawer(false); 184 } 185 } 186 187 @Override onDestroy()188 protected void onDestroy() { 189 super.onDestroy(); 190 mModel.removeCallbacks(this); 191 } 192 isAppDrawerShown()193 public boolean isAppDrawerShown() { 194 return mAppDrawerShown; 195 } 196 197 @Override getAppsView()198 public ActivityAllAppsContainerView<SecondaryDisplayLauncher> getAppsView() { 199 return mAppsView; 200 } 201 202 @Override getRootView()203 public View getRootView() { 204 return mDragLayer; 205 } 206 207 @Override reapplyUi()208 protected void reapplyUi() { } 209 210 @Override getDragLayer()211 public BaseDragLayer getDragLayer() { 212 return mDragLayer; 213 } 214 215 @Override bindIncrementalDownloadProgressUpdated(AppInfo app)216 public void bindIncrementalDownloadProgressUpdated(AppInfo app) { 217 mAppsView.getAppsStore().updateProgressBar(app); 218 } 219 220 /** 221 * Called when apps-button is clicked 222 */ onAppsButtonClicked(View v)223 public void onAppsButtonClicked(View v) { 224 showAppDrawer(true); 225 } 226 227 /** 228 * Show/hide app drawer card with animation. 229 */ showAppDrawer(boolean show)230 public void showAppDrawer(boolean show) { 231 if (show == mAppDrawerShown) { 232 return; 233 } 234 235 float openR = (float) Math.hypot(mAppsView.getWidth(), mAppsView.getHeight()); 236 float closeR = Themes.getDialogCornerRadius(this); 237 float startR = mAppsButton.getWidth() / 2f; 238 239 float[] buttonPos = new float[]{startR, startR}; 240 mDragLayer.getDescendantCoordRelativeToSelf(mAppsButton, buttonPos); 241 mDragLayer.mapCoordInSelfToDescendant(mAppsView, buttonPos); 242 final Animator animator = ViewAnimationUtils.createCircularReveal(mAppsView, 243 (int) buttonPos[0], (int) buttonPos[1], 244 show ? closeR : openR, show ? openR : closeR); 245 246 if (show) { 247 mAppDrawerShown = true; 248 mAppsView.setVisibility(View.VISIBLE); 249 mAppsButton.setVisibility(View.INVISIBLE); 250 mSecondaryDisplayPredictions.updateAppDivider(); 251 } else { 252 mAppDrawerShown = false; 253 animator.addListener(new AnimatorListenerAdapter() { 254 @Override 255 public void onAnimationEnd(Animator animation) { 256 mAppsView.setVisibility(View.INVISIBLE); 257 mAppsButton.setVisibility(View.VISIBLE); 258 mAppsView.getSearchUiManager().resetSearch(); 259 } 260 }); 261 } 262 animator.start(); 263 } 264 265 @Override startBinding()266 public void startBinding() { 267 mBindingItems = true; 268 mDragController.cancelDrag(); 269 } 270 271 @Override isBindingItems()272 public boolean isBindingItems() { 273 return mBindingItems; 274 } 275 276 @Override finishBindingItems(IntSet pagesBoundFirst)277 public void finishBindingItems(IntSet pagesBoundFirst) { 278 mBindingItems = false; 279 } 280 281 @Override bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap)282 public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) { 283 mPopupDataProvider.setDeepShortcutMap(deepShortcutMap); 284 } 285 286 @UiThread 287 @Override bindAllApplications(AppInfo[] apps, int flags, Map<PackageUserKey, Integer> packageUserKeytoUidMap)288 public void bindAllApplications(AppInfo[] apps, int flags, 289 Map<PackageUserKey, Integer> packageUserKeytoUidMap) { 290 Preconditions.assertUIThread(); 291 AllAppsStore<SecondaryDisplayLauncher> appsStore = mAppsView.getAppsStore(); 292 appsStore.setApps(apps, flags, packageUserKeytoUidMap); 293 PopupContainerWithArrow.dismissInvalidPopup(this); 294 } 295 296 @Override bindExtraContainerItems(BgDataModel.FixedContainerItems item)297 public void bindExtraContainerItems(BgDataModel.FixedContainerItems item) { 298 if (item.containerId == LauncherSettings.Favorites.CONTAINER_PREDICTION) { 299 mSecondaryDisplayPredictions.setPredictedApps(item); 300 } 301 } 302 303 @Override getStringCache()304 public StringCache getStringCache() { 305 return mStringCache; 306 } 307 308 @Override bindStringCache(StringCache cache)309 public void bindStringCache(StringCache cache) { 310 mStringCache = cache; 311 } 312 getPopupDataProvider()313 public PopupDataProvider getPopupDataProvider() { 314 return mPopupDataProvider; 315 } 316 317 @Override getItemOnClickListener()318 public OnClickListener getItemOnClickListener() { 319 return this::onIconClicked; 320 } 321 322 @Override getAllAppsItemLongClickListener()323 public View.OnLongClickListener getAllAppsItemLongClickListener() { 324 return v -> mDragLayer.onIconLongClicked(v); 325 } 326 onIconClicked(View v)327 private void onIconClicked(View v) { 328 // Make sure that rogue clicks don't get through while allapps is launching, or after the 329 // view has detached (it's possible for this to happen if the view is removed mid touch). 330 if (v.getWindowToken() == null) return; 331 332 Object tag = v.getTag(); 333 if (tag instanceof ItemClickProxy) { 334 ((ItemClickProxy) tag).onItemClicked(v); 335 } else if (tag instanceof ItemInfo) { 336 ItemInfo item = (ItemInfo) tag; 337 Intent intent; 338 if (item instanceof ItemInfoWithIcon 339 && (((ItemInfoWithIcon) item).runtimeStatusFlags 340 & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) { 341 ItemInfoWithIcon appInfo = (ItemInfoWithIcon) item; 342 intent = appInfo.getMarketIntent(this); 343 } else { 344 intent = item.getIntent(); 345 } 346 if (intent == null) { 347 throw new IllegalArgumentException("Input must have a valid intent"); 348 } 349 startActivitySafely(v, intent, item); 350 } 351 } 352 353 /** 354 * Core functionality for beginning a drag operation for an item that will be dropped within 355 * the secondary display grid home screen 356 */ beginDragShared(View child, DragSource source, DragOptions options)357 public void beginDragShared(View child, DragSource source, DragOptions options) { 358 Object dragObject = child.getTag(); 359 if (!(dragObject instanceof ItemInfo)) { 360 String msg = "Drag started with a view that has no tag set. This " 361 + "will cause a crash (issue 11627249) down the line. " 362 + "View: " + child + " tag: " + child.getTag(); 363 throw new IllegalStateException(msg); 364 } 365 beginDragShared(child, source, (ItemInfo) dragObject, 366 new DragPreviewProvider(child), options); 367 } 368 beginDragShared(View child, DragSource source, ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions options)369 private void beginDragShared(View child, DragSource source, 370 ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions options) { 371 372 float iconScale = 1f; 373 if (child instanceof BubbleTextView) { 374 FastBitmapDrawable icon = ((BubbleTextView) child).getIcon(); 375 if (icon != null) { 376 iconScale = icon.getAnimatedScale(); 377 } 378 } 379 380 // clear pressed state if necessary 381 child.clearFocus(); 382 child.setPressed(false); 383 if (child instanceof BubbleTextView) { 384 BubbleTextView icon = (BubbleTextView) child; 385 icon.clearPressedBackground(); 386 } 387 388 DraggableView draggableView = null; 389 if (child instanceof DraggableView) { 390 draggableView = (DraggableView) child; 391 } 392 393 final View contentView = previewProvider.getContentView(); 394 final float scale; 395 // The draggable drawable follows the touch point around on the screen 396 final Drawable drawable; 397 if (contentView == null) { 398 drawable = previewProvider.createDrawable(); 399 scale = previewProvider.getScaleAndPosition(drawable, mTempXY); 400 } else { 401 drawable = null; 402 scale = previewProvider.getScaleAndPosition(contentView, mTempXY); 403 } 404 405 int dragLayerX = mTempXY[0]; 406 int dragLayerY = mTempXY[1]; 407 408 Rect dragRect = new Rect(); 409 if (draggableView != null) { 410 draggableView.getSourceVisualDragBounds(dragRect); 411 dragLayerY += dragRect.top; 412 } 413 414 if (options.preDragCondition != null) { 415 int xOffSet = options.preDragCondition.getDragOffset().x; 416 int yOffSet = options.preDragCondition.getDragOffset().y; 417 if (xOffSet != 0 && yOffSet != 0) { 418 dragLayerX += xOffSet; 419 dragLayerY += yOffSet; 420 } 421 } 422 423 if (contentView != null) { 424 mDragController.startDrag( 425 contentView, 426 draggableView, 427 dragLayerX, 428 dragLayerY, 429 source, 430 dragObject, 431 dragRect, 432 scale * iconScale, 433 scale, 434 options); 435 } else { 436 mDragController.startDrag( 437 drawable, 438 draggableView, 439 dragLayerX, 440 dragLayerY, 441 source, 442 dragObject, 443 dragRect, 444 scale * iconScale, 445 scale, 446 options); 447 } 448 } 449 450 @Override onDragStart(DropTarget.DragObject dragObject, DragOptions options)451 public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { } 452 453 @Override onDragEnd()454 public void onDragEnd() { } 455 } 456