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