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