1 /* 2 * Copyright (C) 2008 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; 18 19 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; 20 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; 21 22 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; 23 import static com.android.launcher3.LauncherState.ALL_APPS; 24 import static com.android.launcher3.LauncherState.NORMAL; 25 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD; 26 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; 27 import static com.android.launcher3.logging.LoggerUtils.newTarget; 28 29 import android.animation.Animator; 30 import android.animation.AnimatorListenerAdapter; 31 import android.animation.AnimatorSet; 32 import android.animation.ObjectAnimator; 33 import android.animation.ValueAnimator; 34 import android.annotation.TargetApi; 35 import android.app.ActivityOptions; 36 import android.appwidget.AppWidgetHostView; 37 import android.appwidget.AppWidgetManager; 38 import android.content.ActivityNotFoundException; 39 import android.content.BroadcastReceiver; 40 import android.content.ComponentCallbacks2; 41 import android.content.Context; 42 import android.content.ContextWrapper; 43 import android.content.Intent; 44 import android.content.IntentFilter; 45 import android.content.IntentSender; 46 import android.content.SharedPreferences; 47 import android.content.pm.PackageManager; 48 import android.content.res.Configuration; 49 import android.database.sqlite.SQLiteDatabase; 50 import android.graphics.Point; 51 import android.os.AsyncTask; 52 import android.os.Build; 53 import android.os.Bundle; 54 import android.os.Handler; 55 import android.os.Parcelable; 56 import android.os.Process; 57 import android.os.StrictMode; 58 import android.os.UserHandle; 59 import android.support.annotation.Nullable; 60 import android.text.TextUtils; 61 import android.text.method.TextKeyListener; 62 import android.util.Log; 63 import android.util.SparseArray; 64 import android.view.Display; 65 import android.view.KeyEvent; 66 import android.view.KeyboardShortcutGroup; 67 import android.view.KeyboardShortcutInfo; 68 import android.view.LayoutInflater; 69 import android.view.Menu; 70 import android.view.View; 71 import android.view.ViewGroup; 72 import android.view.accessibility.AccessibilityEvent; 73 import android.view.animation.OvershootInterpolator; 74 import android.widget.Toast; 75 76 import com.android.launcher3.DropTarget.DragObject; 77 import com.android.launcher3.Workspace.ItemOperator; 78 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; 79 import com.android.launcher3.allapps.AllAppsContainerView; 80 import com.android.launcher3.allapps.AllAppsTransitionController; 81 import com.android.launcher3.allapps.DiscoveryBounce; 82 import com.android.launcher3.badge.BadgeInfo; 83 import com.android.launcher3.compat.AppWidgetManagerCompat; 84 import com.android.launcher3.compat.LauncherAppsCompatVO; 85 import com.android.launcher3.config.FeatureFlags; 86 import com.android.launcher3.dragndrop.DragController; 87 import com.android.launcher3.dragndrop.DragLayer; 88 import com.android.launcher3.dragndrop.DragView; 89 import com.android.launcher3.folder.FolderIcon; 90 import com.android.launcher3.folder.FolderIconPreviewVerifier; 91 import com.android.launcher3.keyboard.CustomActionsPopup; 92 import com.android.launcher3.keyboard.ViewGroupFocusHelper; 93 import com.android.launcher3.logging.FileLog; 94 import com.android.launcher3.logging.UserEventDispatcher; 95 import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate; 96 import com.android.launcher3.model.ModelWriter; 97 import com.android.launcher3.notification.NotificationListener; 98 import com.android.launcher3.popup.PopupContainerWithArrow; 99 import com.android.launcher3.popup.PopupDataProvider; 100 import com.android.launcher3.shortcuts.DeepShortcutManager; 101 import com.android.launcher3.states.InternalStateHandler; 102 import com.android.launcher3.states.RotationHelper; 103 import com.android.launcher3.touch.ItemClickHandler; 104 import com.android.launcher3.uioverrides.UiFactory; 105 import com.android.launcher3.userevent.nano.LauncherLogProto; 106 import com.android.launcher3.userevent.nano.LauncherLogProto.Action; 107 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; 108 import com.android.launcher3.userevent.nano.LauncherLogProto.Target; 109 import com.android.launcher3.util.ActivityResultInfo; 110 import com.android.launcher3.util.ComponentKey; 111 import com.android.launcher3.util.ItemInfoMatcher; 112 import com.android.launcher3.util.MultiHashMap; 113 import com.android.launcher3.util.MultiValueAlpha; 114 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; 115 import com.android.launcher3.util.PackageManagerHelper; 116 import com.android.launcher3.util.PackageUserKey; 117 import com.android.launcher3.util.PendingRequestArgs; 118 import com.android.launcher3.util.SystemUiController; 119 import com.android.launcher3.util.Themes; 120 import com.android.launcher3.util.Thunk; 121 import com.android.launcher3.util.TraceHelper; 122 import com.android.launcher3.util.UiThreadHelper; 123 import com.android.launcher3.util.ViewOnDrawExecutor; 124 import com.android.launcher3.views.OptionsPopupView; 125 import com.android.launcher3.widget.LauncherAppWidgetHostView; 126 import com.android.launcher3.widget.PendingAddShortcutInfo; 127 import com.android.launcher3.widget.PendingAddWidgetInfo; 128 import com.android.launcher3.widget.PendingAppWidgetHostView; 129 import com.android.launcher3.widget.WidgetAddFlowHandler; 130 import com.android.launcher3.widget.WidgetHostViewLoader; 131 import com.android.launcher3.widget.WidgetListRowEntry; 132 import com.android.launcher3.widget.WidgetsFullSheet; 133 import com.android.launcher3.widget.custom.CustomWidgetParser; 134 135 import java.io.FileDescriptor; 136 import java.io.PrintWriter; 137 import java.util.ArrayList; 138 import java.util.Collection; 139 import java.util.HashSet; 140 import java.util.List; 141 import java.util.Set; 142 143 /** 144 * Default launcher application. 145 */ 146 public class Launcher extends BaseDraggingActivity implements LauncherExterns, 147 LauncherModel.Callbacks, LauncherProviderChangeListener, UserEventDelegate{ 148 public static final String TAG = "Launcher"; 149 static final boolean LOGD = false; 150 151 static final boolean DEBUG_STRICT_MODE = false; 152 153 private static final int REQUEST_CREATE_SHORTCUT = 1; 154 private static final int REQUEST_CREATE_APPWIDGET = 5; 155 156 private static final int REQUEST_PICK_APPWIDGET = 9; 157 158 private static final int REQUEST_BIND_APPWIDGET = 11; 159 public static final int REQUEST_BIND_PENDING_APPWIDGET = 12; 160 public static final int REQUEST_RECONFIGURE_APPWIDGET = 13; 161 162 private static final int REQUEST_PERMISSION_CALL_PHONE = 14; 163 164 private static final float BOUNCE_ANIMATION_TENSION = 1.3f; 165 166 /** 167 * IntentStarter uses request codes starting with this. This must be greater than all activity 168 * request codes used internally. 169 */ 170 protected static final int REQUEST_LAST = 100; 171 172 // Type: int 173 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen"; 174 // Type: int 175 private static final String RUNTIME_STATE = "launcher.state"; 176 // Type: PendingRequestArgs 177 private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args"; 178 // Type: ActivityResultInfo 179 private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result"; 180 // Type: SparseArray<Parcelable> 181 private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel"; 182 183 private LauncherStateManager mStateManager; 184 185 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500; 186 187 // How long to wait before the new-shortcut animation automatically pans the workspace 188 private static final int NEW_APPS_PAGE_MOVE_DELAY = 500; 189 private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5; 190 @Thunk static final int NEW_APPS_ANIMATION_DELAY = 500; 191 192 private LauncherAppTransitionManager mAppTransitionManager; 193 private Configuration mOldConfig; 194 195 @Thunk Workspace mWorkspace; 196 private View mLauncherView; 197 @Thunk DragLayer mDragLayer; 198 private DragController mDragController; 199 200 private AppWidgetManagerCompat mAppWidgetManager; 201 private LauncherAppWidgetHost mAppWidgetHost; 202 203 private final int[] mTmpAddItemCellCoordinates = new int[2]; 204 205 @Thunk Hotseat mHotseat; 206 @Nullable private View mHotseatSearchBox; 207 208 private DropTargetBar mDropTargetBar; 209 210 // Main container view for the all apps screen. 211 @Thunk AllAppsContainerView mAppsView; 212 AllAppsTransitionController mAllAppsController; 213 214 // UI and state for the overview panel 215 private View mOverviewPanel; 216 217 private View mOverviewPanelContainer; 218 219 @Thunk boolean mWorkspaceLoading = true; 220 221 private OnResumeCallback mOnResumeCallback; 222 223 private ViewOnDrawExecutor mPendingExecutor; 224 225 private LauncherModel mModel; 226 private ModelWriter mModelWriter; 227 private IconCache mIconCache; 228 private LauncherAccessibilityDelegate mAccessibilityDelegate; 229 230 private PopupDataProvider mPopupDataProvider; 231 232 private int mSynchronouslyBoundPage = PagedView.INVALID_PAGE; 233 234 // We only want to get the SharedPreferences once since it does an FS stat each time we get 235 // it from the context. 236 private SharedPreferences mSharedPrefs; 237 238 // Activity result which needs to be processed after workspace has loaded. 239 private ActivityResultInfo mPendingActivityResult; 240 /** 241 * Holds extra information required to handle a result from an external call, like 242 * {@link #startActivityForResult(Intent, int)} or {@link #requestPermissions(String[], int)} 243 */ 244 private PendingRequestArgs mPendingRequestArgs; 245 246 public ViewGroupFocusHelper mFocusHandler; 247 248 private RotationHelper mRotationHelper; 249 250 251 private final Handler mHandler = new Handler(); 252 private final Runnable mLogOnDelayedResume = this::logOnDelayedResume; 253 254 @Override 255 protected void onCreate(Bundle savedInstanceState) { 256 if (DEBUG_STRICT_MODE) { 257 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 258 .detectDiskReads() 259 .detectDiskWrites() 260 .detectNetwork() // or .detectAll() for all detectable problems 261 .penaltyLog() 262 .build()); 263 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() 264 .detectLeakedSqlLiteObjects() 265 .detectLeakedClosableObjects() 266 .penaltyLog() 267 .penaltyDeath() 268 .build()); 269 } 270 TraceHelper.beginSection("Launcher-onCreate"); 271 272 super.onCreate(savedInstanceState); 273 TraceHelper.partitionSection("Launcher-onCreate", "super call"); 274 275 LauncherAppState app = LauncherAppState.getInstance(this); 276 mOldConfig = new Configuration(getResources().getConfiguration()); 277 mModel = app.setLauncher(this); 278 initDeviceProfile(app.getInvariantDeviceProfile()); 279 280 mSharedPrefs = Utilities.getPrefs(this); 281 mIconCache = app.getIconCache(); 282 mAccessibilityDelegate = new LauncherAccessibilityDelegate(this); 283 284 mDragController = new DragController(this); 285 mAllAppsController = new AllAppsTransitionController(this); 286 mStateManager = new LauncherStateManager(this); 287 UiFactory.onCreate(this); 288 289 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); 290 291 mAppWidgetHost = new LauncherAppWidgetHost(this); 292 mAppWidgetHost.startListening(); 293 294 mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null); 295 296 setupViews(); 297 mPopupDataProvider = new PopupDataProvider(this); 298 299 mRotationHelper = new RotationHelper(this); 300 mAppTransitionManager = LauncherAppTransitionManager.newInstance(this); 301 302 boolean internalStateHandled = InternalStateHandler.handleCreate(this, getIntent()); 303 if (internalStateHandled) { 304 if (savedInstanceState != null) { 305 // InternalStateHandler has already set the appropriate state. 306 // We dont need to do anything. 307 savedInstanceState.remove(RUNTIME_STATE); 308 } 309 } 310 restoreState(savedInstanceState); 311 312 // We only load the page synchronously if the user rotates (or triggers a 313 // configuration change) while launcher is in the foreground 314 int currentScreen = PagedView.INVALID_RESTORE_PAGE; 315 if (savedInstanceState != null) { 316 currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen); 317 } 318 319 if (!mModel.startLoader(currentScreen)) { 320 if (!internalStateHandled) { 321 // If we are not binding synchronously, show a fade in animation when 322 // the first page bind completes. 323 mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).setValue(0); 324 } 325 } else { 326 // Pages bound synchronously. 327 mWorkspace.setCurrentPage(currentScreen); 328 329 setWorkspaceLoading(true); 330 } 331 332 // For handling default keys 333 setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); 334 335 setContentView(mLauncherView); 336 getRootView().dispatchInsets(); 337 338 // Listen for broadcasts 339 registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); 340 341 getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW, 342 Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText)); 343 344 if (mLauncherCallbacks != null) { 345 mLauncherCallbacks.onCreate(savedInstanceState); 346 } 347 mRotationHelper.initialize(); 348 349 TraceHelper.endSection("Launcher-onCreate"); 350 } 351 352 @Override 353 public void onConfigurationChanged(Configuration newConfig) { 354 int diff = newConfig.diff(mOldConfig); 355 if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) { 356 mUserEventDispatcher = null; 357 initDeviceProfile(mDeviceProfile.inv); 358 dispatchDeviceProfileChanged(); 359 reapplyUi(); 360 mDragLayer.recreateControllers(); 361 362 // TODO: We can probably avoid rebind when only screen size changed. 363 rebindModel(); 364 } 365 366 mOldConfig.setTo(newConfig); 367 UiFactory.onLauncherStateOrResumeChanged(this); 368 super.onConfigurationChanged(newConfig); 369 } 370 371 @Override 372 protected void reapplyUi() { 373 getRootView().dispatchInsets(); 374 getStateManager().reapplyState(true /* cancelCurrentAnimation */); 375 } 376 377 @Override 378 public void rebindModel() { 379 int currentPage = mWorkspace.getNextPage(); 380 if (mModel.startLoader(currentPage)) { 381 mWorkspace.setCurrentPage(currentPage); 382 setWorkspaceLoading(true); 383 } 384 } 385 386 private void initDeviceProfile(InvariantDeviceProfile idp) { 387 // Load configuration-specific DeviceProfile 388 mDeviceProfile = idp.getDeviceProfile(this); 389 if (isInMultiWindowModeCompat()) { 390 Display display = getWindowManager().getDefaultDisplay(); 391 Point mwSize = new Point(); 392 display.getSize(mwSize); 393 mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize); 394 } 395 onDeviceProfileInitiated(); 396 mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout(), true); 397 } 398 399 public RotationHelper getRotationHelper() { 400 return mRotationHelper; 401 } 402 403 public LauncherStateManager getStateManager() { 404 return mStateManager; 405 } 406 407 @Override 408 public <T extends View> T findViewById(int id) { 409 return mLauncherView.findViewById(id); 410 } 411 412 @Override 413 public void onAppWidgetHostReset() { 414 if (mAppWidgetHost != null) { 415 mAppWidgetHost.startListening(); 416 } 417 } 418 419 private LauncherCallbacks mLauncherCallbacks; 420 421 /** 422 * Call this after onCreate to set or clear overlay. 423 */ 424 public void setLauncherOverlay(LauncherOverlay overlay) { 425 if (overlay != null) { 426 overlay.setOverlayCallbacks(new LauncherOverlayCallbacksImpl()); 427 } 428 mWorkspace.setLauncherOverlay(overlay); 429 } 430 431 public boolean setLauncherCallbacks(LauncherCallbacks callbacks) { 432 mLauncherCallbacks = callbacks; 433 return true; 434 } 435 436 @Override 437 public void onLauncherProviderChanged() { 438 if (mLauncherCallbacks != null) { 439 mLauncherCallbacks.onLauncherProviderChange(); 440 } 441 } 442 443 public boolean isDraggingEnabled() { 444 // We prevent dragging when we are loading the workspace as it is possible to pick up a view 445 // that is subsequently removed from the workspace in startBinding(). 446 return !isWorkspaceLoading(); 447 } 448 449 public int getViewIdForItem(ItemInfo info) { 450 // aapt-generated IDs have the high byte nonzero; clamp to the range under that. 451 // This cast is safe as long as the id < 0x00FFFFFF 452 // Since we jail all the dynamically generated views, there should be no clashes 453 // with any other views. 454 return (int) info.id; 455 } 456 457 public PopupDataProvider getPopupDataProvider() { 458 return mPopupDataProvider; 459 } 460 461 @Override 462 public BadgeInfo getBadgeInfoForItem(ItemInfo info) { 463 return mPopupDataProvider.getBadgeInfoForItem(info); 464 } 465 466 @Override 467 public void invalidateParent(ItemInfo info) { 468 FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(getDeviceProfile().inv); 469 if (verifier.isItemInPreview(info.rank) && (info.container >= 0)) { 470 View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container); 471 if (folderIcon != null) { 472 folderIcon.invalidate(); 473 } 474 } 475 } 476 477 /** 478 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have 479 * a configuration step, this allows the proper animations to run after other transitions. 480 */ 481 private long completeAdd( 482 int requestCode, Intent intent, int appWidgetId, PendingRequestArgs info) { 483 long screenId = info.screenId; 484 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 485 // When the screen id represents an actual screen (as opposed to a rank) we make sure 486 // that the drop page actually exists. 487 screenId = ensurePendingDropLayoutExists(info.screenId); 488 } 489 490 switch (requestCode) { 491 case REQUEST_CREATE_SHORTCUT: 492 completeAddShortcut(intent, info.container, screenId, info.cellX, info.cellY, info); 493 break; 494 case REQUEST_CREATE_APPWIDGET: 495 completeAddAppWidget(appWidgetId, info, null, null); 496 break; 497 case REQUEST_RECONFIGURE_APPWIDGET: 498 completeRestoreAppWidget(appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED); 499 break; 500 case REQUEST_BIND_PENDING_APPWIDGET: { 501 int widgetId = appWidgetId; 502 LauncherAppWidgetInfo widgetInfo = 503 completeRestoreAppWidget(widgetId, LauncherAppWidgetInfo.FLAG_UI_NOT_READY); 504 if (widgetInfo != null) { 505 // Since the view was just bound, also launch the configure activity if needed 506 LauncherAppWidgetProviderInfo provider = mAppWidgetManager 507 .getLauncherAppWidgetInfo(widgetId); 508 if (provider != null) { 509 new WidgetAddFlowHandler(provider) 510 .startConfigActivity(this, widgetInfo, REQUEST_RECONFIGURE_APPWIDGET); 511 } 512 } 513 break; 514 } 515 } 516 517 return screenId; 518 } 519 520 private void handleActivityResult( 521 final int requestCode, final int resultCode, final Intent data) { 522 if (isWorkspaceLoading()) { 523 // process the result once the workspace has loaded. 524 mPendingActivityResult = new ActivityResultInfo(requestCode, resultCode, data); 525 return; 526 } 527 mPendingActivityResult = null; 528 529 // Reset the startActivity waiting flag 530 final PendingRequestArgs requestArgs = mPendingRequestArgs; 531 setWaitingForResult(null); 532 if (requestArgs == null) { 533 return; 534 } 535 536 final int pendingAddWidgetId = requestArgs.getWidgetId(); 537 538 Runnable exitSpringLoaded = new Runnable() { 539 @Override 540 public void run() { 541 mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); 542 } 543 }; 544 545 if (requestCode == REQUEST_BIND_APPWIDGET) { 546 // This is called only if the user did not previously have permissions to bind widgets 547 final int appWidgetId = data != null ? 548 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; 549 if (resultCode == RESULT_CANCELED) { 550 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs); 551 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, 552 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 553 } else if (resultCode == RESULT_OK) { 554 addAppWidgetImpl( 555 appWidgetId, requestArgs, null, 556 requestArgs.getWidgetHandler(), 557 ON_ACTIVITY_RESULT_ANIMATION_DELAY); 558 } 559 return; 560 } 561 562 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET || 563 requestCode == REQUEST_CREATE_APPWIDGET); 564 565 // We have special handling for widgets 566 if (isWidgetDrop) { 567 final int appWidgetId; 568 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) 569 : -1; 570 if (widgetId < 0) { 571 appWidgetId = pendingAddWidgetId; 572 } else { 573 appWidgetId = widgetId; 574 } 575 576 final int result; 577 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) { 578 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " + 579 "returned from the widget configuration activity."); 580 result = RESULT_CANCELED; 581 completeTwoStageWidgetDrop(result, appWidgetId, requestArgs); 582 final Runnable onComplete = new Runnable() { 583 @Override 584 public void run() { 585 getStateManager().goToState(NORMAL); 586 } 587 }; 588 589 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, 590 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 591 } else { 592 if (requestArgs.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 593 // When the screen id represents an actual screen (as opposed to a rank) 594 // we make sure that the drop page actually exists. 595 requestArgs.screenId = 596 ensurePendingDropLayoutExists(requestArgs.screenId); 597 } 598 final CellLayout dropLayout = 599 mWorkspace.getScreenWithId(requestArgs.screenId); 600 601 dropLayout.setDropPending(true); 602 final Runnable onComplete = new Runnable() { 603 @Override 604 public void run() { 605 completeTwoStageWidgetDrop(resultCode, appWidgetId, requestArgs); 606 dropLayout.setDropPending(false); 607 } 608 }; 609 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, 610 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 611 } 612 return; 613 } 614 615 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET 616 || requestCode == REQUEST_BIND_PENDING_APPWIDGET) { 617 if (resultCode == RESULT_OK) { 618 // Update the widget view. 619 completeAdd(requestCode, data, pendingAddWidgetId, requestArgs); 620 } 621 // Leave the widget in the pending state if the user canceled the configure. 622 return; 623 } 624 625 if (requestCode == REQUEST_CREATE_SHORTCUT) { 626 // Handle custom shortcuts created using ACTION_CREATE_SHORTCUT. 627 if (resultCode == RESULT_OK && requestArgs.container != ItemInfo.NO_ID) { 628 completeAdd(requestCode, data, -1, requestArgs); 629 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, 630 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 631 632 } else if (resultCode == RESULT_CANCELED) { 633 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, 634 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 635 } 636 } 637 mDragLayer.clearAnimatedView(); 638 } 639 640 @Override 641 public void onActivityResult( 642 final int requestCode, final int resultCode, final Intent data) { 643 handleActivityResult(requestCode, resultCode, data); 644 if (mLauncherCallbacks != null) { 645 mLauncherCallbacks.onActivityResult(requestCode, resultCode, data); 646 } 647 } 648 649 @Override 650 public void onRequestPermissionsResult(int requestCode, String[] permissions, 651 int[] grantResults) { 652 PendingRequestArgs pendingArgs = mPendingRequestArgs; 653 if (requestCode == REQUEST_PERMISSION_CALL_PHONE && pendingArgs != null 654 && pendingArgs.getRequestCode() == REQUEST_PERMISSION_CALL_PHONE) { 655 setWaitingForResult(null); 656 657 View v = null; 658 CellLayout layout = getCellLayout(pendingArgs.container, pendingArgs.screenId); 659 if (layout != null) { 660 v = layout.getChildAt(pendingArgs.cellX, pendingArgs.cellY); 661 } 662 Intent intent = pendingArgs.getPendingIntent(); 663 664 if (grantResults.length > 0 665 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 666 startActivitySafely(v, intent, null); 667 } else { 668 // TODO: Show a snack bar with link to settings 669 Toast.makeText(this, getString(R.string.msg_no_phone_permission, 670 getString(R.string.derived_app_name)), Toast.LENGTH_SHORT).show(); 671 } 672 } 673 if (mLauncherCallbacks != null) { 674 mLauncherCallbacks.onRequestPermissionsResult(requestCode, permissions, 675 grantResults); 676 } 677 } 678 679 /** 680 * Check to see if a given screen id exists. If not, create it at the end, return the new id. 681 * 682 * @param screenId the screen id to check 683 * @return the new screen, or screenId if it exists 684 */ 685 private long ensurePendingDropLayoutExists(long screenId) { 686 CellLayout dropLayout = mWorkspace.getScreenWithId(screenId); 687 if (dropLayout == null) { 688 // it's possible that the add screen was removed because it was 689 // empty and a re-bind occurred 690 mWorkspace.addExtraEmptyScreen(); 691 return mWorkspace.commitExtraEmptyScreen(); 692 } else { 693 return screenId; 694 } 695 } 696 697 @Thunk void completeTwoStageWidgetDrop( 698 final int resultCode, final int appWidgetId, final PendingRequestArgs requestArgs) { 699 CellLayout cellLayout = mWorkspace.getScreenWithId(requestArgs.screenId); 700 Runnable onCompleteRunnable = null; 701 int animationType = 0; 702 703 AppWidgetHostView boundWidget = null; 704 if (resultCode == RESULT_OK) { 705 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION; 706 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId, 707 requestArgs.getWidgetHandler().getProviderInfo(this)); 708 boundWidget = layout; 709 onCompleteRunnable = new Runnable() { 710 @Override 711 public void run() { 712 completeAddAppWidget(appWidgetId, requestArgs, layout, null); 713 mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); 714 } 715 }; 716 } else if (resultCode == RESULT_CANCELED) { 717 mAppWidgetHost.deleteAppWidgetId(appWidgetId); 718 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION; 719 } 720 if (mDragLayer.getAnimatedView() != null) { 721 mWorkspace.animateWidgetDrop(requestArgs, cellLayout, 722 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable, 723 animationType, boundWidget, true); 724 } else if (onCompleteRunnable != null) { 725 // The animated view may be null in the case of a rotation during widget configuration 726 onCompleteRunnable.run(); 727 } 728 } 729 730 @Override 731 protected void onStop() { 732 super.onStop(); 733 FirstFrameAnimatorHelper.setIsVisible(false); 734 735 if (mLauncherCallbacks != null) { 736 mLauncherCallbacks.onStop(); 737 } 738 getUserEventDispatcher().logActionCommand(Action.Command.STOP, 739 mStateManager.getState().containerType, -1); 740 741 mAppWidgetHost.setListenIfResumed(false); 742 743 NotificationListener.removeNotificationsChangedListener(); 744 getStateManager().moveToRestState(); 745 746 // Workaround for b/78520668, explicitly trim memory once UI is hidden 747 onTrimMemory(TRIM_MEMORY_UI_HIDDEN); 748 } 749 750 @Override 751 protected void onStart() { 752 super.onStart(); 753 FirstFrameAnimatorHelper.setIsVisible(true); 754 755 if (mLauncherCallbacks != null) { 756 mLauncherCallbacks.onStart(); 757 } 758 mAppWidgetHost.setListenIfResumed(true); 759 NotificationListener.setNotificationsChangedListener(mPopupDataProvider); 760 UiFactory.onStart(this); 761 } 762 763 private void logOnDelayedResume() { 764 if (hasBeenResumed()) { 765 getUserEventDispatcher().logActionCommand(Action.Command.RESUME, 766 mStateManager.getState().containerType, -1); 767 getUserEventDispatcher().startSession(); 768 } 769 } 770 771 @Override 772 protected void onResume() { 773 TraceHelper.beginSection("ON_RESUME"); 774 super.onResume(); 775 TraceHelper.partitionSection("ON_RESUME", "superCall"); 776 777 mHandler.removeCallbacks(mLogOnDelayedResume); 778 Utilities.postAsyncCallback(mHandler, mLogOnDelayedResume); 779 780 setOnResumeCallback(null); 781 // Process any items that were added while Launcher was away. 782 InstallShortcutReceiver.disableAndFlushInstallQueue( 783 InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this); 784 785 // Refresh shortcuts if the permission changed. 786 mModel.refreshShortcutsIfRequired(); 787 788 DiscoveryBounce.showForHomeIfNeeded(this); 789 if (mLauncherCallbacks != null) { 790 mLauncherCallbacks.onResume(); 791 } 792 UiFactory.onLauncherStateOrResumeChanged(this); 793 794 TraceHelper.endSection("ON_RESUME"); 795 } 796 797 @Override 798 protected void onPause() { 799 // Ensure that items added to Launcher are queued until Launcher returns 800 InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED); 801 802 super.onPause(); 803 mDragController.cancelDrag(); 804 mDragController.resetLastGestureUpTime(); 805 806 if (mLauncherCallbacks != null) { 807 mLauncherCallbacks.onPause(); 808 } 809 } 810 811 @Override 812 protected void onUserLeaveHint() { 813 super.onUserLeaveHint(); 814 UiFactory.onLauncherStateOrResumeChanged(this); 815 } 816 817 @Override 818 public void onWindowFocusChanged(boolean hasFocus) { 819 super.onWindowFocusChanged(hasFocus); 820 mStateManager.onWindowFocusChanged(); 821 } 822 823 public interface LauncherOverlay { 824 825 /** 826 * Touch interaction leading to overscroll has begun 827 */ 828 void onScrollInteractionBegin(); 829 830 /** 831 * Touch interaction related to overscroll has ended 832 */ 833 void onScrollInteractionEnd(); 834 835 /** 836 * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost 837 * screen (or in the case of RTL, the rightmost screen). 838 */ 839 void onScrollChange(float progress, boolean rtl); 840 841 /** 842 * Called when the launcher is ready to use the overlay 843 * @param callbacks A set of callbacks provided by Launcher in relation to the overlay 844 */ 845 void setOverlayCallbacks(LauncherOverlayCallbacks callbacks); 846 } 847 848 public interface LauncherOverlayCallbacks { 849 void onScrollChanged(float progress); 850 } 851 852 class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks { 853 854 public void onScrollChanged(float progress) { 855 if (mWorkspace != null) { 856 mWorkspace.onOverlayScrollChanged(progress); 857 } 858 } 859 } 860 861 public boolean hasSettings() { 862 if (mLauncherCallbacks != null) { 863 return mLauncherCallbacks.hasSettings(); 864 } else { 865 // On O and above we there is always some setting present settings (add icon to 866 // home screen or icon badging). On earlier APIs we will have the allow rotation 867 // setting, on devices with a locked orientation, 868 return Utilities.ATLEAST_OREO || !getResources().getBoolean(R.bool.allow_rotation); 869 } 870 } 871 872 public boolean isInState(LauncherState state) { 873 return mStateManager.getState() == state; 874 } 875 876 /** 877 * Restores the previous state, if it exists. 878 * 879 * @param savedState The previous state. 880 */ 881 private void restoreState(Bundle savedState) { 882 if (savedState == null) { 883 return; 884 } 885 886 int stateOrdinal = savedState.getInt(RUNTIME_STATE, NORMAL.ordinal); 887 LauncherState[] stateValues = LauncherState.values(); 888 LauncherState state = stateValues[stateOrdinal]; 889 if (!state.disableRestore) { 890 mStateManager.goToState(state, false /* animated */); 891 } 892 893 PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS); 894 if (requestArgs != null) { 895 setWaitingForResult(requestArgs); 896 } 897 898 mPendingActivityResult = savedState.getParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT); 899 900 SparseArray<Parcelable> widgetsState = 901 savedState.getSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL); 902 if (widgetsState != null) { 903 WidgetsFullSheet.show(this, false).restoreHierarchyState(widgetsState); 904 } 905 } 906 907 /** 908 * Finds all the views we need and configure them properly. 909 */ 910 private void setupViews() { 911 mDragLayer = findViewById(R.id.drag_layer); 912 mFocusHandler = mDragLayer.getFocusIndicatorHelper(); 913 mWorkspace = mDragLayer.findViewById(R.id.workspace); 914 mWorkspace.initParentViews(mDragLayer); 915 mOverviewPanel = findViewById(R.id.overview_panel); 916 mOverviewPanelContainer = findViewById(R.id.overview_panel_container); 917 mHotseat = findViewById(R.id.hotseat); 918 mHotseatSearchBox = findViewById(R.id.search_container_hotseat); 919 920 mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 921 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 922 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 923 924 // Setup the drag layer 925 mDragLayer.setup(mDragController, mWorkspace); 926 UiFactory.setOnTouchControllersChangedListener(this, mDragLayer::recreateControllers); 927 928 mWorkspace.setup(mDragController); 929 // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the 930 // default state, otherwise we will update to the wrong offsets in RTL 931 mWorkspace.lockWallpaperToDefaultPage(); 932 mWorkspace.bindAndInitFirstWorkspaceScreen(null /* recycled qsb */); 933 mDragController.addDragListener(mWorkspace); 934 935 // Get the search/delete/uninstall bar 936 mDropTargetBar = mDragLayer.findViewById(R.id.drop_target_bar); 937 938 // Setup Apps 939 mAppsView = findViewById(R.id.apps_view); 940 941 // Setup the drag controller (drop targets have to be added in reverse order in priority) 942 mDragController.setMoveTarget(mWorkspace); 943 mDropTargetBar.setup(mDragController); 944 945 mAllAppsController.setupViews(mAppsView); 946 } 947 948 /** 949 * Creates a view representing a shortcut. 950 * 951 * @param info The data structure describing the shortcut. 952 */ 953 View createShortcut(ShortcutInfo info) { 954 return createShortcut((ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info); 955 } 956 957 /** 958 * Creates a view representing a shortcut inflated from the specified resource. 959 * 960 * @param parent The group the shortcut belongs to. 961 * @param info The data structure describing the shortcut. 962 * 963 * @return A View inflated from layoutResId. 964 */ 965 public View createShortcut(ViewGroup parent, ShortcutInfo info) { 966 BubbleTextView favorite = (BubbleTextView) LayoutInflater.from(parent.getContext()) 967 .inflate(R.layout.app_icon, parent, false); 968 favorite.applyFromShortcutInfo(info); 969 favorite.setOnClickListener(ItemClickHandler.INSTANCE); 970 favorite.setOnFocusChangeListener(mFocusHandler); 971 return favorite; 972 } 973 974 /** 975 * Add a shortcut to the workspace or to a Folder. 976 * 977 * @param data The intent describing the shortcut. 978 */ 979 private void completeAddShortcut(Intent data, long container, long screenId, int cellX, 980 int cellY, PendingRequestArgs args) { 981 if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT 982 || args.getPendingIntent().getComponent() == null) { 983 return; 984 } 985 986 int[] cellXY = mTmpAddItemCellCoordinates; 987 CellLayout layout = getCellLayout(container, screenId); 988 989 ShortcutInfo info = null; 990 if (Utilities.ATLEAST_OREO) { 991 info = LauncherAppsCompatVO.createShortcutInfoFromPinItemRequest( 992 this, LauncherAppsCompatVO.getPinItemRequest(data), 0); 993 } 994 995 if (info == null) { 996 // Legacy shortcuts are only supported for primary profile. 997 info = Process.myUserHandle().equals(args.user) 998 ? InstallShortcutReceiver.fromShortcutIntent(this, data) : null; 999 1000 if (info == null) { 1001 Log.e(TAG, "Unable to parse a valid custom shortcut result"); 1002 return; 1003 } else if (!new PackageManagerHelper(this).hasPermissionForActivity( 1004 info.intent, args.getPendingIntent().getComponent().getPackageName())) { 1005 // The app is trying to add a shortcut without sufficient permissions 1006 Log.e(TAG, "Ignoring malicious intent " + info.intent.toUri(0)); 1007 return; 1008 } 1009 } 1010 1011 if (container < 0) { 1012 // Adding a shortcut to the Workspace. 1013 final View view = createShortcut(info); 1014 boolean foundCellSpan = false; 1015 // First we check if we already know the exact location where we want to add this item. 1016 if (cellX >= 0 && cellY >= 0) { 1017 cellXY[0] = cellX; 1018 cellXY[1] = cellY; 1019 foundCellSpan = true; 1020 1021 // If appropriate, either create a folder or add to an existing folder 1022 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0, 1023 true, null)) { 1024 return; 1025 } 1026 DragObject dragObject = new DragObject(); 1027 dragObject.dragInfo = info; 1028 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject, 1029 true)) { 1030 return; 1031 } 1032 } else { 1033 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1); 1034 } 1035 1036 if (!foundCellSpan) { 1037 mWorkspace.onNoCellFound(layout); 1038 return; 1039 } 1040 1041 getModelWriter().addItemToDatabase(info, container, screenId, cellXY[0], cellXY[1]); 1042 mWorkspace.addInScreen(view, info); 1043 } else { 1044 // Adding a shortcut to a Folder. 1045 FolderIcon folderIcon = findFolderIcon(container); 1046 if (folderIcon != null) { 1047 FolderInfo folderInfo = (FolderInfo) folderIcon.getTag(); 1048 folderInfo.add(info, args.rank, false); 1049 } else { 1050 Log.e(TAG, "Could not find folder with id " + container + " to add shortcut."); 1051 } 1052 } 1053 } 1054 1055 public FolderIcon findFolderIcon(final long folderIconId) { 1056 return (FolderIcon) mWorkspace.getFirstMatch(new ItemOperator() { 1057 @Override 1058 public boolean evaluate(ItemInfo info, View view) { 1059 return info != null && info.id == folderIconId; 1060 } 1061 }); 1062 } 1063 1064 /** 1065 * Add a widget to the workspace. 1066 * 1067 * @param appWidgetId The app widget id 1068 */ 1069 @Thunk void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo, 1070 AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) { 1071 1072 if (appWidgetInfo == null) { 1073 appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId); 1074 } 1075 1076 LauncherAppWidgetInfo launcherInfo; 1077 launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider); 1078 launcherInfo.spanX = itemInfo.spanX; 1079 launcherInfo.spanY = itemInfo.spanY; 1080 launcherInfo.minSpanX = itemInfo.minSpanX; 1081 launcherInfo.minSpanY = itemInfo.minSpanY; 1082 launcherInfo.user = appWidgetInfo.getProfile(); 1083 1084 getModelWriter().addItemToDatabase(launcherInfo, 1085 itemInfo.container, itemInfo.screenId, itemInfo.cellX, itemInfo.cellY); 1086 1087 if (hostView == null) { 1088 // Perform actual inflation because we're live 1089 hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); 1090 } 1091 hostView.setVisibility(View.VISIBLE); 1092 prepareAppWidget(hostView, launcherInfo); 1093 mWorkspace.addInScreen(hostView, launcherInfo); 1094 } 1095 1096 private void prepareAppWidget(AppWidgetHostView hostView, LauncherAppWidgetInfo item) { 1097 hostView.setTag(item); 1098 item.onBindAppWidget(this, hostView); 1099 hostView.setFocusable(true); 1100 hostView.setOnFocusChangeListener(mFocusHandler); 1101 } 1102 1103 private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { 1104 @Override 1105 public void onReceive(Context context, Intent intent) { 1106 // Reset AllApps to its initial state only if we are not in the middle of 1107 // processing a multi-step drop 1108 if (mPendingRequestArgs == null) { 1109 mStateManager.goToState(NORMAL); 1110 } 1111 } 1112 }; 1113 1114 public void updateIconBadges(final Set<PackageUserKey> updatedBadges) { 1115 mWorkspace.updateIconBadges(updatedBadges); 1116 mAppsView.getAppsStore().updateIconBadges(updatedBadges); 1117 1118 PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(Launcher.this); 1119 if (popup != null) { 1120 popup.updateNotificationHeader(updatedBadges); 1121 } 1122 } 1123 1124 @Override 1125 public void onAttachedToWindow() { 1126 super.onAttachedToWindow(); 1127 1128 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView()); 1129 if (mLauncherCallbacks != null) { 1130 mLauncherCallbacks.onAttachedToWindow(); 1131 } 1132 } 1133 1134 @Override 1135 public void onDetachedFromWindow() { 1136 super.onDetachedFromWindow(); 1137 1138 if (mLauncherCallbacks != null) { 1139 mLauncherCallbacks.onDetachedFromWindow(); 1140 } 1141 } 1142 1143 public AllAppsTransitionController getAllAppsController() { 1144 return mAllAppsController; 1145 } 1146 1147 @Override 1148 public LauncherRootView getRootView() { 1149 return (LauncherRootView) mLauncherView; 1150 } 1151 1152 @Override 1153 public DragLayer getDragLayer() { 1154 return mDragLayer; 1155 } 1156 1157 public AllAppsContainerView getAppsView() { 1158 return mAppsView; 1159 } 1160 1161 public Workspace getWorkspace() { 1162 return mWorkspace; 1163 } 1164 1165 public Hotseat getHotseat() { 1166 return mHotseat; 1167 } 1168 1169 public View getHotseatSearchBox() { 1170 return mHotseatSearchBox; 1171 } 1172 1173 public <T extends View> T getOverviewPanel() { 1174 return (T) mOverviewPanel; 1175 } 1176 1177 public <T extends View> T getOverviewPanelContainer() { 1178 return (T) mOverviewPanelContainer; 1179 } 1180 1181 public DropTargetBar getDropTargetBar() { 1182 return mDropTargetBar; 1183 } 1184 1185 public LauncherAppWidgetHost getAppWidgetHost() { 1186 return mAppWidgetHost; 1187 } 1188 1189 public LauncherModel getModel() { 1190 return mModel; 1191 } 1192 1193 public ModelWriter getModelWriter() { 1194 return mModelWriter; 1195 } 1196 1197 public SharedPreferences getSharedPrefs() { 1198 return mSharedPrefs; 1199 } 1200 1201 public int getOrientation() { return mOldConfig.orientation; } 1202 1203 @Override 1204 protected void onNewIntent(Intent intent) { 1205 TraceHelper.beginSection("NEW_INTENT"); 1206 super.onNewIntent(intent); 1207 1208 boolean alreadyOnHome = hasWindowFocus() && ((intent.getFlags() & 1209 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) 1210 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); 1211 1212 // Check this condition before handling isActionMain, as this will get reset. 1213 boolean shouldMoveToDefaultScreen = alreadyOnHome && isInState(NORMAL) 1214 && AbstractFloatingView.getTopOpenView(this) == null; 1215 boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction()); 1216 boolean internalStateHandled = InternalStateHandler 1217 .handleNewIntent(this, intent, isStarted()); 1218 1219 if (isActionMain) { 1220 if (!internalStateHandled) { 1221 // Note: There should be at most one log per method call. This is enforced 1222 // implicitly by using if-else statements. 1223 UserEventDispatcher ued = getUserEventDispatcher(); 1224 AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(this); 1225 if (topOpenView != null) { 1226 topOpenView.logActionCommand(Action.Command.HOME_INTENT); 1227 } else if (alreadyOnHome) { 1228 Target target = newContainerTarget(mStateManager.getState().containerType); 1229 target.pageIndex = mWorkspace.getCurrentPage(); 1230 ued.logActionCommand(Action.Command.HOME_INTENT, target, 1231 newContainerTarget(ContainerType.WORKSPACE)); 1232 } 1233 1234 // In all these cases, only animate if we're already on home 1235 AbstractFloatingView.closeAllOpenViews(this, isStarted()); 1236 1237 if (!isInState(NORMAL)) { 1238 // Only change state, if not already the same. This prevents cancelling any 1239 // animations running as part of resume 1240 mStateManager.goToState(NORMAL); 1241 } 1242 1243 // Reset the apps view 1244 if (!alreadyOnHome) { 1245 mAppsView.reset(isStarted() /* animate */); 1246 } 1247 1248 if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive()) { 1249 mWorkspace.post(mWorkspace::moveToDefaultScreen); 1250 } 1251 } 1252 1253 final View v = getWindow().peekDecorView(); 1254 if (v != null && v.getWindowToken() != null) { 1255 UiThreadHelper.hideKeyboardAsync(this, v.getWindowToken()); 1256 } 1257 1258 if (mLauncherCallbacks != null) { 1259 mLauncherCallbacks.onHomeIntent(internalStateHandled); 1260 } 1261 } 1262 1263 TraceHelper.endSection("NEW_INTENT"); 1264 } 1265 1266 @Override 1267 public void onRestoreInstanceState(Bundle state) { 1268 super.onRestoreInstanceState(state); 1269 mWorkspace.restoreInstanceStateForChild(mSynchronouslyBoundPage); 1270 } 1271 1272 @Override 1273 protected void onSaveInstanceState(Bundle outState) { 1274 if (mWorkspace.getChildCount() > 0) { 1275 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getNextPage()); 1276 1277 } 1278 outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal); 1279 1280 1281 AbstractFloatingView widgets = AbstractFloatingView 1282 .getOpenView(this, AbstractFloatingView.TYPE_WIDGETS_FULL_SHEET); 1283 if (widgets != null) { 1284 SparseArray<Parcelable> widgetsState = new SparseArray<>(); 1285 widgets.saveHierarchyState(widgetsState); 1286 outState.putSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL, widgetsState); 1287 } else { 1288 outState.remove(RUNTIME_STATE_WIDGET_PANEL); 1289 } 1290 1291 // We close any open folders and shortcut containers since they will not be re-opened, 1292 // and we need to make sure this state is reflected. 1293 AbstractFloatingView.closeAllOpenViews(this, false); 1294 1295 if (mPendingRequestArgs != null) { 1296 outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs); 1297 } 1298 if (mPendingActivityResult != null) { 1299 outState.putParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT, mPendingActivityResult); 1300 } 1301 1302 super.onSaveInstanceState(outState); 1303 1304 if (mLauncherCallbacks != null) { 1305 mLauncherCallbacks.onSaveInstanceState(outState); 1306 } 1307 } 1308 1309 @Override 1310 public void onDestroy() { 1311 super.onDestroy(); 1312 1313 unregisterReceiver(mScreenOffReceiver); 1314 mWorkspace.removeFolderListeners(); 1315 1316 UiFactory.setOnTouchControllersChangedListener(this, null); 1317 1318 // Stop callbacks from LauncherModel 1319 // It's possible to receive onDestroy after a new Launcher activity has 1320 // been created. In this case, don't interfere with the new Launcher. 1321 if (mModel.isCurrentCallbacks(this)) { 1322 mModel.stopLoader(); 1323 LauncherAppState.getInstance(this).setLauncher(null); 1324 } 1325 mRotationHelper.destroy(); 1326 1327 try { 1328 mAppWidgetHost.stopListening(); 1329 } catch (NullPointerException ex) { 1330 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex); 1331 } 1332 1333 TextKeyListener.getInstance().release(); 1334 1335 LauncherAnimUtils.onDestroyActivity(); 1336 1337 clearPendingBinds(); 1338 1339 if (mLauncherCallbacks != null) { 1340 mLauncherCallbacks.onDestroy(); 1341 } 1342 } 1343 1344 public LauncherAccessibilityDelegate getAccessibilityDelegate() { 1345 return mAccessibilityDelegate; 1346 } 1347 1348 public DragController getDragController() { 1349 return mDragController; 1350 } 1351 1352 @Override 1353 public void startActivityForResult(Intent intent, int requestCode, Bundle options) { 1354 super.startActivityForResult(intent, requestCode, options); 1355 } 1356 1357 @Override 1358 public void startIntentSenderForResult (IntentSender intent, int requestCode, 1359 Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) { 1360 try { 1361 super.startIntentSenderForResult(intent, requestCode, 1362 fillInIntent, flagsMask, flagsValues, extraFlags, options); 1363 } catch (IntentSender.SendIntentException e) { 1364 throw new ActivityNotFoundException(); 1365 } 1366 } 1367 1368 /** 1369 * Indicates that we want global search for this activity by setting the globalSearch 1370 * argument for {@link #startSearch} to true. 1371 */ 1372 @Override 1373 public void startSearch(String initialQuery, boolean selectInitialQuery, 1374 Bundle appSearchData, boolean globalSearch) { 1375 if (appSearchData == null) { 1376 appSearchData = new Bundle(); 1377 appSearchData.putString("source", "launcher-search"); 1378 } 1379 1380 if (mLauncherCallbacks == null || 1381 !mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData)) { 1382 // Starting search from the callbacks failed. Start the default global search. 1383 super.startSearch(initialQuery, selectInitialQuery, appSearchData, true); 1384 } 1385 1386 // We need to show the workspace after starting the search 1387 mStateManager.goToState(NORMAL); 1388 } 1389 1390 public boolean isWorkspaceLocked() { 1391 return mWorkspaceLoading || mPendingRequestArgs != null; 1392 } 1393 1394 public boolean isWorkspaceLoading() { 1395 return mWorkspaceLoading; 1396 } 1397 1398 private void setWorkspaceLoading(boolean value) { 1399 mWorkspaceLoading = value; 1400 } 1401 1402 public void setWaitingForResult(PendingRequestArgs args) { 1403 mPendingRequestArgs = args; 1404 } 1405 1406 void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget, 1407 WidgetAddFlowHandler addFlowHandler) { 1408 if (LOGD) { 1409 Log.d(TAG, "Adding widget from drop"); 1410 } 1411 addAppWidgetImpl(appWidgetId, info, boundWidget, addFlowHandler, 0); 1412 } 1413 1414 void addAppWidgetImpl(int appWidgetId, ItemInfo info, 1415 AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay) { 1416 if (!addFlowHandler.startConfigActivity(this, appWidgetId, info, REQUEST_CREATE_APPWIDGET)) { 1417 // If the configuration flow was not started, add the widget 1418 1419 Runnable onComplete = new Runnable() { 1420 @Override 1421 public void run() { 1422 // Exit spring loaded mode if necessary after adding the widget 1423 mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); 1424 } 1425 }; 1426 completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this)); 1427 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false); 1428 } 1429 } 1430 1431 public void addPendingItem(PendingAddItemInfo info, long container, long screenId, 1432 int[] cell, int spanX, int spanY) { 1433 info.container = container; 1434 info.screenId = screenId; 1435 if (cell != null) { 1436 info.cellX = cell[0]; 1437 info.cellY = cell[1]; 1438 } 1439 info.spanX = spanX; 1440 info.spanY = spanY; 1441 1442 switch (info.itemType) { 1443 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 1444 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1445 addAppWidgetFromDrop((PendingAddWidgetInfo) info); 1446 break; 1447 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1448 processShortcutFromDrop((PendingAddShortcutInfo) info); 1449 break; 1450 default: 1451 throw new IllegalStateException("Unknown item type: " + info.itemType); 1452 } 1453 } 1454 1455 /** 1456 * Process a shortcut drop. 1457 */ 1458 private void processShortcutFromDrop(PendingAddShortcutInfo info) { 1459 Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(info.componentName); 1460 setWaitingForResult(PendingRequestArgs.forIntent(REQUEST_CREATE_SHORTCUT, intent, info)); 1461 if (!info.activityInfo.startConfigActivity(this, REQUEST_CREATE_SHORTCUT)) { 1462 handleActivityResult(REQUEST_CREATE_SHORTCUT, RESULT_CANCELED, null); 1463 } 1464 } 1465 1466 /** 1467 * Process a widget drop. 1468 */ 1469 private void addAppWidgetFromDrop(PendingAddWidgetInfo info) { 1470 AppWidgetHostView hostView = info.boundWidget; 1471 final int appWidgetId; 1472 WidgetAddFlowHandler addFlowHandler = info.getHandler(); 1473 if (hostView != null) { 1474 // In the case where we've prebound the widget, we remove it from the DragLayer 1475 if (LOGD) { 1476 Log.d(TAG, "Removing widget view from drag layer and setting boundWidget to null"); 1477 } 1478 getDragLayer().removeView(hostView); 1479 1480 appWidgetId = hostView.getAppWidgetId(); 1481 addAppWidgetFromDropImpl(appWidgetId, info, hostView, addFlowHandler); 1482 1483 // Clear the boundWidget so that it doesn't get destroyed. 1484 info.boundWidget = null; 1485 } else { 1486 // In this case, we either need to start an activity to get permission to bind 1487 // the widget, or we need to start an activity to configure the widget, or both. 1488 if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && 1489 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET) { 1490 appWidgetId = CustomWidgetParser.getWidgetIdForCustomProvider( 1491 this, info.componentName); 1492 } else { 1493 appWidgetId = getAppWidgetHost().allocateAppWidgetId(); 1494 } 1495 Bundle options = info.bindOptions; 1496 1497 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( 1498 appWidgetId, info.info, options); 1499 if (success) { 1500 addAppWidgetFromDropImpl(appWidgetId, info, null, addFlowHandler); 1501 } else { 1502 addFlowHandler.startBindFlow(this, appWidgetId, info, REQUEST_BIND_APPWIDGET); 1503 } 1504 } 1505 } 1506 1507 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX, 1508 int cellY) { 1509 final FolderInfo folderInfo = new FolderInfo(); 1510 folderInfo.title = getText(R.string.folder_name); 1511 1512 // Update the model 1513 getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY); 1514 1515 // Create the view 1516 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo); 1517 mWorkspace.addInScreen(newFolder, folderInfo); 1518 // Force measure the new folder icon 1519 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder); 1520 parent.getShortcutsAndWidgets().measureChild(newFolder); 1521 return newFolder; 1522 } 1523 1524 /** 1525 * Unbinds the view for the specified item, and removes the item and all its children. 1526 * 1527 * @param v the view being removed. 1528 * @param itemInfo the {@link ItemInfo} for this view. 1529 * @param deleteFromDb whether or not to delete this item from the db. 1530 */ 1531 public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb) { 1532 if (itemInfo instanceof ShortcutInfo) { 1533 // Remove the shortcut from the folder before removing it from launcher 1534 View folderIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container); 1535 if (folderIcon instanceof FolderIcon) { 1536 ((FolderInfo) folderIcon.getTag()).remove((ShortcutInfo) itemInfo, true); 1537 } else { 1538 mWorkspace.removeWorkspaceItem(v); 1539 } 1540 if (deleteFromDb) { 1541 getModelWriter().deleteItemFromDatabase(itemInfo); 1542 } 1543 } else if (itemInfo instanceof FolderInfo) { 1544 final FolderInfo folderInfo = (FolderInfo) itemInfo; 1545 if (v instanceof FolderIcon) { 1546 ((FolderIcon) v).removeListeners(); 1547 } 1548 mWorkspace.removeWorkspaceItem(v); 1549 if (deleteFromDb) { 1550 getModelWriter().deleteFolderAndContentsFromDatabase(folderInfo); 1551 } 1552 } else if (itemInfo instanceof LauncherAppWidgetInfo) { 1553 final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo; 1554 mWorkspace.removeWorkspaceItem(v); 1555 if (deleteFromDb) { 1556 deleteWidgetInfo(widgetInfo); 1557 } 1558 } else { 1559 return false; 1560 } 1561 return true; 1562 } 1563 1564 /** 1565 * Deletes the widget info and the widget id. 1566 */ 1567 private void deleteWidgetInfo(final LauncherAppWidgetInfo widgetInfo) { 1568 final LauncherAppWidgetHost appWidgetHost = getAppWidgetHost(); 1569 if (appWidgetHost != null && !widgetInfo.isCustomWidget() && widgetInfo.isWidgetIdAllocated()) { 1570 // Deleting an app widget ID is a void call but writes to disk before returning 1571 // to the caller... 1572 new AsyncTask<Void, Void, Void>() { 1573 public Void doInBackground(Void ... args) { 1574 appWidgetHost.deleteAppWidgetId(widgetInfo.appWidgetId); 1575 return null; 1576 } 1577 }.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR); 1578 } 1579 getModelWriter().deleteItemFromDatabase(widgetInfo); 1580 } 1581 1582 @Override 1583 public boolean dispatchKeyEvent(KeyEvent event) { 1584 return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event); 1585 } 1586 1587 @Override 1588 public void onBackPressed() { 1589 if (finishAutoCancelActionMode()) { 1590 return; 1591 } 1592 if (mLauncherCallbacks != null && mLauncherCallbacks.handleBackPressed()) { 1593 return; 1594 } 1595 1596 if (mDragController.isDragging()) { 1597 mDragController.cancelDrag(); 1598 return; 1599 } 1600 1601 // Note: There should be at most one log per method call. This is enforced implicitly 1602 // by using if-else statements. 1603 UserEventDispatcher ued = getUserEventDispatcher(); 1604 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this); 1605 if (topView != null && topView.onBackPressed()) { 1606 // Handled by the floating view. 1607 } else if (!isInState(NORMAL)) { 1608 LauncherState lastState = mStateManager.getLastState(); 1609 ued.logActionCommand(Action.Command.BACK, mStateManager.getState().containerType, 1610 lastState.containerType); 1611 mStateManager.goToState(lastState); 1612 } else { 1613 // Back button is a no-op here, but give at least some feedback for the button press 1614 mWorkspace.showOutlinesTemporarily(); 1615 } 1616 } 1617 1618 @TargetApi(Build.VERSION_CODES.M) 1619 @Override 1620 public ActivityOptions getActivityLaunchOptions(View v) { 1621 return mAppTransitionManager.getActivityLaunchOptions(this, v); 1622 } 1623 1624 public LauncherAppTransitionManager getAppTransitionManager() { 1625 return mAppTransitionManager; 1626 } 1627 1628 @TargetApi(Build.VERSION_CODES.M) 1629 @Override 1630 protected boolean onErrorStartingShortcut(Intent intent, ItemInfo info) { 1631 // Due to legacy reasons, direct call shortcuts require Launchers to have the 1632 // corresponding permission. Show the appropriate permission prompt if that 1633 // is the case. 1634 if (intent.getComponent() == null 1635 && Intent.ACTION_CALL.equals(intent.getAction()) 1636 && checkSelfPermission(android.Manifest.permission.CALL_PHONE) != 1637 PackageManager.PERMISSION_GRANTED) { 1638 1639 setWaitingForResult(PendingRequestArgs 1640 .forIntent(REQUEST_PERMISSION_CALL_PHONE, intent, info)); 1641 requestPermissions(new String[]{android.Manifest.permission.CALL_PHONE}, 1642 REQUEST_PERMISSION_CALL_PHONE); 1643 return true; 1644 } else { 1645 return false; 1646 } 1647 } 1648 1649 @Override 1650 public void modifyUserEvent(LauncherLogProto.LauncherEvent event) { 1651 if (event.srcTarget != null && event.srcTarget.length > 0 && 1652 event.srcTarget[1].containerType == ContainerType.PREDICTION) { 1653 Target[] targets = new Target[3]; 1654 targets[0] = event.srcTarget[0]; 1655 targets[1] = event.srcTarget[1]; 1656 targets[2] = newTarget(Target.Type.CONTAINER); 1657 event.srcTarget = targets; 1658 LauncherState state = mStateManager.getState(); 1659 if (state == LauncherState.ALL_APPS) { 1660 event.srcTarget[2].containerType = ContainerType.ALLAPPS; 1661 } else if (state == LauncherState.OVERVIEW) { 1662 event.srcTarget[2].containerType = ContainerType.TASKSWITCHER; 1663 } 1664 } 1665 } 1666 1667 public boolean startActivitySafely(View v, Intent intent, ItemInfo item) { 1668 boolean success = super.startActivitySafely(v, intent, item); 1669 if (success && v instanceof BubbleTextView) { 1670 // This is set to the view that launched the activity that navigated the user away 1671 // from launcher. Since there is no callback for when the activity has finished 1672 // launching, enable the press state and keep this reference to reset the press 1673 // state when we return to launcher. 1674 BubbleTextView btv = (BubbleTextView) v; 1675 btv.setStayPressed(true); 1676 setOnResumeCallback(btv); 1677 } 1678 return success; 1679 } 1680 1681 boolean isHotseatLayout(View layout) { 1682 // TODO: Remove this method 1683 return mHotseat != null && layout != null && 1684 (layout instanceof CellLayout) && (layout == mHotseat.getLayout()); 1685 } 1686 1687 /** 1688 * Returns the CellLayout of the specified container at the specified screen. 1689 */ 1690 public CellLayout getCellLayout(long container, long screenId) { 1691 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 1692 if (mHotseat != null) { 1693 return mHotseat.getLayout(); 1694 } else { 1695 return null; 1696 } 1697 } else { 1698 return mWorkspace.getScreenWithId(screenId); 1699 } 1700 } 1701 1702 @Override 1703 public void onTrimMemory(int level) { 1704 super.onTrimMemory(level); 1705 if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { 1706 // The widget preview db can result in holding onto over 1707 // 3MB of memory for caching which isn't necessary. 1708 SQLiteDatabase.releaseMemory(); 1709 1710 // This clears all widget bitmaps from the widget tray 1711 // TODO(hyunyoungs) 1712 } 1713 if (mLauncherCallbacks != null) { 1714 mLauncherCallbacks.onTrimMemory(level); 1715 } 1716 UiFactory.onTrimMemory(this, level); 1717 } 1718 1719 @Override 1720 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 1721 final boolean result = super.dispatchPopulateAccessibilityEvent(event); 1722 final List<CharSequence> text = event.getText(); 1723 text.clear(); 1724 // Populate event with a fake title based on the current state. 1725 // TODO: When can workspace be null? 1726 text.add(mWorkspace == null 1727 ? getString(R.string.all_apps_home_button_label) 1728 : mStateManager.getState().getDescription(this)); 1729 return result; 1730 } 1731 1732 public void setOnResumeCallback(OnResumeCallback callback) { 1733 if (mOnResumeCallback != null) { 1734 mOnResumeCallback.onLauncherResume(); 1735 } 1736 mOnResumeCallback = callback; 1737 } 1738 1739 /** 1740 * Implementation of the method from LauncherModel.Callbacks. 1741 */ 1742 @Override 1743 public int getCurrentWorkspaceScreen() { 1744 if (mWorkspace != null) { 1745 return mWorkspace.getCurrentPage(); 1746 } else { 1747 return 0; 1748 } 1749 } 1750 1751 /** 1752 * Clear any pending bind callbacks. This is called when is loader is planning to 1753 * perform a full rebind from scratch. 1754 */ 1755 @Override 1756 public void clearPendingBinds() { 1757 if (mPendingExecutor != null) { 1758 mPendingExecutor.markCompleted(); 1759 mPendingExecutor = null; 1760 } 1761 } 1762 1763 /** 1764 * Refreshes the shortcuts shown on the workspace. 1765 * 1766 * Implementation of the method from LauncherModel.Callbacks. 1767 */ 1768 public void startBinding() { 1769 TraceHelper.beginSection("startBinding"); 1770 // Floating panels (except the full widget sheet) are associated with individual icons. If 1771 // we are starting a fresh bind, close all such panels as all the icons are about 1772 // to go away. 1773 AbstractFloatingView.closeOpenViews(this, true, 1774 AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE); 1775 1776 setWorkspaceLoading(true); 1777 1778 // Clear the workspace because it's going to be rebound 1779 mWorkspace.clearDropTargets(); 1780 mWorkspace.removeAllWorkspaceScreens(); 1781 mAppWidgetHost.clearViews(); 1782 1783 if (mHotseat != null) { 1784 mHotseat.resetLayout(mDeviceProfile.isVerticalBarLayout()); 1785 } 1786 TraceHelper.endSection("startBinding"); 1787 } 1788 1789 @Override 1790 public void bindScreens(ArrayList<Long> orderedScreenIds) { 1791 // Make sure the first screen is always at the start. 1792 if (FeatureFlags.QSB_ON_FIRST_SCREEN && 1793 orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) { 1794 orderedScreenIds.remove(Workspace.FIRST_SCREEN_ID); 1795 orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID); 1796 LauncherModel.updateWorkspaceScreenOrder(this, orderedScreenIds); 1797 } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) { 1798 // If there are no screens, we need to have an empty screen 1799 mWorkspace.addExtraEmptyScreen(); 1800 } 1801 bindAddScreens(orderedScreenIds); 1802 1803 // After we have added all the screens, if the wallpaper was locked to the default state, 1804 // then notify to indicate that it can be released and a proper wallpaper offset can be 1805 // computed before the next layout 1806 mWorkspace.unlockWallpaperFromDefaultPageOnNextLayout(); 1807 } 1808 1809 private void bindAddScreens(ArrayList<Long> orderedScreenIds) { 1810 int count = orderedScreenIds.size(); 1811 for (int i = 0; i < count; i++) { 1812 long screenId = orderedScreenIds.get(i); 1813 if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) { 1814 // No need to bind the first screen, as its always bound. 1815 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId); 1816 } 1817 } 1818 } 1819 1820 @Override 1821 public void bindAppsAdded(ArrayList<Long> newScreens, ArrayList<ItemInfo> addNotAnimated, 1822 ArrayList<ItemInfo> addAnimated) { 1823 // Add the new screens 1824 if (newScreens != null) { 1825 bindAddScreens(newScreens); 1826 } 1827 1828 // We add the items without animation on non-visible pages, and with 1829 // animations on the new page (which we will try and snap to). 1830 if (addNotAnimated != null && !addNotAnimated.isEmpty()) { 1831 bindItems(addNotAnimated, false); 1832 } 1833 if (addAnimated != null && !addAnimated.isEmpty()) { 1834 bindItems(addAnimated, true); 1835 } 1836 1837 // Remove the extra empty screen 1838 mWorkspace.removeExtraEmptyScreen(false, false); 1839 } 1840 1841 /** 1842 * Bind the items start-end from the list. 1843 * 1844 * Implementation of the method from LauncherModel.Callbacks. 1845 */ 1846 @Override 1847 public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) { 1848 // Get the list of added items and intersect them with the set of items here 1849 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); 1850 final Collection<Animator> bounceAnims = new ArrayList<>(); 1851 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation(); 1852 Workspace workspace = mWorkspace; 1853 long newItemsScreenId = -1; 1854 int end = items.size(); 1855 for (int i = 0; i < end; i++) { 1856 final ItemInfo item = items.get(i); 1857 1858 // Short circuit if we are loading dock items for a configuration which has no dock 1859 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && 1860 mHotseat == null) { 1861 continue; 1862 } 1863 1864 final View view; 1865 switch (item.itemType) { 1866 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 1867 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1868 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { 1869 ShortcutInfo info = (ShortcutInfo) item; 1870 view = createShortcut(info); 1871 break; 1872 } 1873 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: { 1874 view = FolderIcon.fromXml(R.layout.folder_icon, this, 1875 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), 1876 (FolderInfo) item); 1877 break; 1878 } 1879 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1880 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: { 1881 view = inflateAppWidget((LauncherAppWidgetInfo) item); 1882 if (view == null) { 1883 continue; 1884 } 1885 break; 1886 } 1887 default: 1888 throw new RuntimeException("Invalid Item Type"); 1889 } 1890 1891 /* 1892 * Remove colliding items. 1893 */ 1894 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1895 CellLayout cl = mWorkspace.getScreenWithId(item.screenId); 1896 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) { 1897 View v = cl.getChildAt(item.cellX, item.cellY); 1898 Object tag = v.getTag(); 1899 String desc = "Collision while binding workspace item: " + item 1900 + ". Collides with " + tag; 1901 if (FeatureFlags.IS_DOGFOOD_BUILD) { 1902 throw (new RuntimeException(desc)); 1903 } else { 1904 Log.d(TAG, desc); 1905 getModelWriter().deleteItemFromDatabase(item); 1906 continue; 1907 } 1908 } 1909 } 1910 workspace.addInScreenFromBind(view, item); 1911 if (animateIcons) { 1912 // Animate all the applications up now 1913 view.setAlpha(0f); 1914 view.setScaleX(0f); 1915 view.setScaleY(0f); 1916 bounceAnims.add(createNewAppBounceAnimation(view, i)); 1917 newItemsScreenId = item.screenId; 1918 } 1919 } 1920 1921 if (animateIcons) { 1922 // Animate to the correct page 1923 if (newItemsScreenId > -1) { 1924 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage()); 1925 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId); 1926 final Runnable startBounceAnimRunnable = new Runnable() { 1927 public void run() { 1928 anim.playTogether(bounceAnims); 1929 anim.start(); 1930 } 1931 }; 1932 if (newItemsScreenId != currentScreenId) { 1933 // We post the animation slightly delayed to prevent slowdowns 1934 // when we are loading right after we return to launcher. 1935 mWorkspace.postDelayed(new Runnable() { 1936 public void run() { 1937 if (mWorkspace != null) { 1938 AbstractFloatingView.closeAllOpenViews(Launcher.this, false); 1939 1940 mWorkspace.snapToPage(newScreenIndex); 1941 mWorkspace.postDelayed(startBounceAnimRunnable, 1942 NEW_APPS_ANIMATION_DELAY); 1943 } 1944 } 1945 }, NEW_APPS_PAGE_MOVE_DELAY); 1946 } else { 1947 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY); 1948 } 1949 } 1950 } 1951 workspace.requestLayout(); 1952 } 1953 1954 /** 1955 * Add the views for a widget to the workspace. 1956 */ 1957 public void bindAppWidget(LauncherAppWidgetInfo item) { 1958 View view = inflateAppWidget(item); 1959 if (view != null) { 1960 mWorkspace.addInScreen(view, item); 1961 mWorkspace.requestLayout(); 1962 } 1963 } 1964 1965 private View inflateAppWidget(LauncherAppWidgetInfo item) { 1966 if (mIsSafeModeEnabled) { 1967 PendingAppWidgetHostView view = 1968 new PendingAppWidgetHostView(this, item, mIconCache, true); 1969 prepareAppWidget(view, item); 1970 return view; 1971 } 1972 1973 TraceHelper.beginSection("BIND_WIDGET"); 1974 1975 final LauncherAppWidgetProviderInfo appWidgetInfo; 1976 1977 if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) { 1978 // If the provider is not ready, bind as a pending widget. 1979 appWidgetInfo = null; 1980 } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) { 1981 // The widget id is not valid. Try to find the widget based on the provider info. 1982 appWidgetInfo = mAppWidgetManager.findProvider(item.providerName, item.user); 1983 } else { 1984 appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(item.appWidgetId); 1985 } 1986 1987 // If the provider is ready, but the width is not yet restored, try to restore it. 1988 if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) && 1989 (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) { 1990 if (appWidgetInfo == null) { 1991 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId 1992 + " belongs to component " + item.providerName 1993 + ", as the provider is null"); 1994 getModelWriter().deleteItemFromDatabase(item); 1995 return null; 1996 } 1997 1998 // If we do not have a valid id, try to bind an id. 1999 if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) { 2000 if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) { 2001 // Id has not been allocated yet. Allocate a new id. 2002 item.appWidgetId = mAppWidgetHost.allocateAppWidgetId(); 2003 item.restoreStatus |= LauncherAppWidgetInfo.FLAG_ID_ALLOCATED; 2004 2005 // Also try to bind the widget. If the bind fails, the user will be shown 2006 // a click to setup UI, which will ask for the bind permission. 2007 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo); 2008 pendingInfo.spanX = item.spanX; 2009 pendingInfo.spanY = item.spanY; 2010 pendingInfo.minSpanX = item.minSpanX; 2011 pendingInfo.minSpanY = item.minSpanY; 2012 Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo); 2013 2014 boolean isDirectConfig = 2015 item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG); 2016 if (isDirectConfig && item.bindOptions != null) { 2017 Bundle newOptions = item.bindOptions.getExtras(); 2018 if (options != null) { 2019 newOptions.putAll(options); 2020 } 2021 options = newOptions; 2022 } 2023 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( 2024 item.appWidgetId, appWidgetInfo, options); 2025 2026 // We tried to bind once. If we were not able to bind, we would need to 2027 // go through the permission dialog, which means we cannot skip the config 2028 // activity. 2029 item.bindOptions = null; 2030 item.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG; 2031 2032 // Bind succeeded 2033 if (success) { 2034 // If the widget has a configure activity, it is still needs to set it up, 2035 // otherwise the widget is ready to go. 2036 item.restoreStatus = (appWidgetInfo.configure == null) || isDirectConfig 2037 ? LauncherAppWidgetInfo.RESTORE_COMPLETED 2038 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY; 2039 } 2040 2041 getModelWriter().updateItemInDatabase(item); 2042 } 2043 } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY) 2044 && (appWidgetInfo.configure == null)) { 2045 // The widget was marked as UI not ready, but there is no configure activity to 2046 // update the UI. 2047 item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED; 2048 getModelWriter().updateItemInDatabase(item); 2049 } 2050 } 2051 2052 final AppWidgetHostView view; 2053 if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) { 2054 // Verify that we own the widget 2055 if (appWidgetInfo == null) { 2056 FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId); 2057 deleteWidgetInfo(item); 2058 return null; 2059 } 2060 2061 item.minSpanX = appWidgetInfo.minSpanX; 2062 item.minSpanY = appWidgetInfo.minSpanY; 2063 view = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo); 2064 } else { 2065 view = new PendingAppWidgetHostView(this, item, mIconCache, false); 2066 } 2067 prepareAppWidget(view, item); 2068 2069 TraceHelper.endSection("BIND_WIDGET", "id=" + item.appWidgetId); 2070 return view; 2071 } 2072 2073 /** 2074 * Restores a pending widget. 2075 * 2076 * @param appWidgetId The app widget id 2077 */ 2078 private LauncherAppWidgetInfo completeRestoreAppWidget(int appWidgetId, int finalRestoreFlag) { 2079 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId); 2080 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) { 2081 Log.e(TAG, "Widget update called, when the widget no longer exists."); 2082 return null; 2083 } 2084 2085 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag(); 2086 info.restoreStatus = finalRestoreFlag; 2087 if (info.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) { 2088 info.pendingItemInfo = null; 2089 } 2090 2091 if (((PendingAppWidgetHostView) view).isReinflateIfNeeded()) { 2092 view.reInflate(); 2093 } 2094 2095 getModelWriter().updateItemInDatabase(info); 2096 return info; 2097 } 2098 2099 public void onPageBoundSynchronously(int page) { 2100 mSynchronouslyBoundPage = page; 2101 } 2102 2103 @Override 2104 public void executeOnNextDraw(ViewOnDrawExecutor executor) { 2105 if (mPendingExecutor != null) { 2106 mPendingExecutor.markCompleted(); 2107 } 2108 mPendingExecutor = executor; 2109 if (!isInState(ALL_APPS)) { 2110 mAppsView.getAppsStore().setDeferUpdates(true); 2111 mPendingExecutor.execute(() -> mAppsView.getAppsStore().setDeferUpdates(false)); 2112 } 2113 2114 executor.attachTo(this); 2115 } 2116 2117 public void clearPendingExecutor(ViewOnDrawExecutor executor) { 2118 if (mPendingExecutor == executor) { 2119 mPendingExecutor = null; 2120 } 2121 } 2122 2123 @Override 2124 public void finishFirstPageBind(final ViewOnDrawExecutor executor) { 2125 AlphaProperty property = mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD); 2126 if (property.getValue() < 1) { 2127 ObjectAnimator anim = ObjectAnimator.ofFloat(property, MultiValueAlpha.VALUE, 1); 2128 if (executor != null) { 2129 anim.addListener(new AnimatorListenerAdapter() { 2130 @Override 2131 public void onAnimationEnd(Animator animation) { 2132 executor.onLoadAnimationCompleted(); 2133 } 2134 }); 2135 } 2136 anim.start(); 2137 } else if (executor != null) { 2138 executor.onLoadAnimationCompleted(); 2139 } 2140 } 2141 2142 /** 2143 * Callback saying that there aren't any more items to bind. 2144 * 2145 * Implementation of the method from LauncherModel.Callbacks. 2146 */ 2147 public void finishBindingItems() { 2148 TraceHelper.beginSection("finishBindingItems"); 2149 mWorkspace.restoreInstanceStateForRemainingPages(); 2150 2151 setWorkspaceLoading(false); 2152 2153 if (mPendingActivityResult != null) { 2154 handleActivityResult(mPendingActivityResult.requestCode, 2155 mPendingActivityResult.resultCode, mPendingActivityResult.data); 2156 mPendingActivityResult = null; 2157 } 2158 2159 InstallShortcutReceiver.disableAndFlushInstallQueue( 2160 InstallShortcutReceiver.FLAG_LOADER_RUNNING, this); 2161 2162 TraceHelper.endSection("finishBindingItems"); 2163 } 2164 2165 private boolean canRunNewAppsAnimation() { 2166 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime(); 2167 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000); 2168 } 2169 2170 private ValueAnimator createNewAppBounceAnimation(View v, int i) { 2171 ValueAnimator bounceAnim = LauncherAnimUtils.ofViewAlphaAndScale(v, 1, 1, 1); 2172 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION); 2173 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY); 2174 bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION)); 2175 return bounceAnim; 2176 } 2177 2178 /** 2179 * Add the icons for all apps. 2180 * 2181 * Implementation of the method from LauncherModel.Callbacks. 2182 */ 2183 public void bindAllApplications(ArrayList<AppInfo> apps) { 2184 mAppsView.getAppsStore().setApps(apps); 2185 2186 if (mLauncherCallbacks != null) { 2187 mLauncherCallbacks.bindAllApplications(apps); 2188 } 2189 } 2190 2191 /** 2192 * Copies LauncherModel's map of activities to shortcut ids to Launcher's. This is necessary 2193 * because LauncherModel's map is updated in the background, while Launcher runs on the UI. 2194 */ 2195 @Override 2196 public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) { 2197 mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy); 2198 } 2199 2200 /** 2201 * A package was updated. 2202 * 2203 * Implementation of the method from LauncherModel.Callbacks. 2204 */ 2205 @Override 2206 public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps) { 2207 mAppsView.getAppsStore().addOrUpdateApps(apps); 2208 } 2209 2210 @Override 2211 public void bindPromiseAppProgressUpdated(PromiseAppInfo app) { 2212 mAppsView.getAppsStore().updatePromiseAppProgress(app); 2213 } 2214 2215 @Override 2216 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { 2217 mWorkspace.widgetsRestored(widgets); 2218 } 2219 2220 /** 2221 * Some shortcuts were updated in the background. 2222 * Implementation of the method from LauncherModel.Callbacks. 2223 * 2224 * @param updated list of shortcuts which have changed. 2225 */ 2226 @Override 2227 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, final UserHandle user) { 2228 if (!updated.isEmpty()) { 2229 mWorkspace.updateShortcuts(updated); 2230 } 2231 } 2232 2233 /** 2234 * Update the state of a package, typically related to install state. 2235 * 2236 * Implementation of the method from LauncherModel.Callbacks. 2237 */ 2238 @Override 2239 public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { 2240 mWorkspace.updateRestoreItems(updates); 2241 } 2242 2243 /** 2244 * A package was uninstalled/updated. We take both the super set of packageNames 2245 * in addition to specific applications to remove, the reason being that 2246 * this can be called when a package is updated as well. In that scenario, 2247 * we only remove specific components from the workspace and hotseat, where as 2248 * package-removal should clear all items by package name. 2249 */ 2250 @Override 2251 public void bindWorkspaceComponentsRemoved(final ItemInfoMatcher matcher) { 2252 mWorkspace.removeItemsByMatcher(matcher); 2253 mDragController.onAppsRemoved(matcher); 2254 } 2255 2256 @Override 2257 public void bindAppInfosRemoved(final ArrayList<AppInfo> appInfos) { 2258 mAppsView.getAppsStore().removeApps(appInfos); 2259 } 2260 2261 @Override 2262 public void bindAllWidgets(final ArrayList<WidgetListRowEntry> allWidgets) { 2263 mPopupDataProvider.setAllWidgets(allWidgets); 2264 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this); 2265 if (topView != null) { 2266 topView.onWidgetsBound(); 2267 } 2268 } 2269 2270 /** 2271 * @param packageUser if null, refreshes all widgets and shortcuts, otherwise only 2272 * refreshes the widgets and shortcuts associated with the given package/user 2273 */ 2274 public void refreshAndBindWidgetsForPackageUser(@Nullable PackageUserKey packageUser) { 2275 mModel.refreshAndBindWidgetsAndShortcuts(packageUser); 2276 } 2277 2278 /** 2279 * $ adb shell dumpsys activity com.android.launcher3.Launcher [--all] 2280 */ 2281 @Override 2282 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 2283 super.dump(prefix, fd, writer, args); 2284 2285 if (args.length > 0 && TextUtils.equals(args[0], "--all")) { 2286 writer.println(prefix + "Workspace Items"); 2287 for (int i = 0; i < mWorkspace.getPageCount(); i++) { 2288 writer.println(prefix + " Homescreen " + i); 2289 2290 ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets(); 2291 for (int j = 0; j < layout.getChildCount(); j++) { 2292 Object tag = layout.getChildAt(j).getTag(); 2293 if (tag != null) { 2294 writer.println(prefix + " " + tag.toString()); 2295 } 2296 } 2297 } 2298 2299 writer.println(prefix + " Hotseat"); 2300 ViewGroup layout = mHotseat.getLayout().getShortcutsAndWidgets(); 2301 for (int j = 0; j < layout.getChildCount(); j++) { 2302 Object tag = layout.getChildAt(j).getTag(); 2303 if (tag != null) { 2304 writer.println(prefix + " " + tag.toString()); 2305 } 2306 } 2307 } 2308 2309 writer.println(prefix + "Misc:"); 2310 writer.print(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading); 2311 writer.print(" mPendingRequestArgs=" + mPendingRequestArgs); 2312 writer.println(" mPendingActivityResult=" + mPendingActivityResult); 2313 writer.println(" mRotationHelper: " + mRotationHelper); 2314 dumpMisc(writer); 2315 2316 try { 2317 FileLog.flushAll(writer); 2318 } catch (Exception e) { 2319 // Ignore 2320 } 2321 2322 mModel.dumpState(prefix, fd, writer, args); 2323 2324 if (mLauncherCallbacks != null) { 2325 mLauncherCallbacks.dump(prefix, fd, writer, args); 2326 } 2327 } 2328 2329 @Override 2330 @TargetApi(Build.VERSION_CODES.N) 2331 public void onProvideKeyboardShortcuts( 2332 List<KeyboardShortcutGroup> data, Menu menu, int deviceId) { 2333 2334 ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>(); 2335 if (isInState(NORMAL)) { 2336 shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label), 2337 KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON)); 2338 } 2339 final View currentFocus = getCurrentFocus(); 2340 if (currentFocus != null) { 2341 if (new CustomActionsPopup(this, currentFocus).canShow()) { 2342 shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.custom_actions), 2343 KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON)); 2344 } 2345 if (currentFocus.getTag() instanceof ItemInfo 2346 && DeepShortcutManager.supportsShortcuts((ItemInfo) currentFocus.getTag())) { 2347 shortcutInfos.add(new KeyboardShortcutInfo( 2348 getString(R.string.shortcuts_menu_with_notifications_description), 2349 KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON)); 2350 } 2351 } 2352 if (!shortcutInfos.isEmpty()) { 2353 data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos)); 2354 } 2355 2356 super.onProvideKeyboardShortcuts(data, menu, deviceId); 2357 } 2358 2359 @Override 2360 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 2361 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 2362 switch (keyCode) { 2363 case KeyEvent.KEYCODE_A: 2364 if (isInState(NORMAL)) { 2365 getStateManager().goToState(ALL_APPS); 2366 return true; 2367 } 2368 break; 2369 case KeyEvent.KEYCODE_S: { 2370 View focusedView = getCurrentFocus(); 2371 if (focusedView instanceof BubbleTextView 2372 && focusedView.getTag() instanceof ItemInfo 2373 && mAccessibilityDelegate.performAction(focusedView, 2374 (ItemInfo) focusedView.getTag(), 2375 LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) { 2376 PopupContainerWithArrow.getOpen(this).requestFocus(); 2377 return true; 2378 } 2379 break; 2380 } 2381 case KeyEvent.KEYCODE_O: 2382 if (new CustomActionsPopup(this, getCurrentFocus()).show()) { 2383 return true; 2384 } 2385 break; 2386 } 2387 } 2388 return super.onKeyShortcut(keyCode, event); 2389 } 2390 2391 @Override 2392 public boolean onKeyUp(int keyCode, KeyEvent event) { 2393 if (keyCode == KeyEvent.KEYCODE_MENU) { 2394 // KEYCODE_MENU is sent by some tests, for example 2395 // LauncherJankTests#testWidgetsContainerFling. Don't just remove its handling. 2396 if (!mDragController.isDragging() && !mWorkspace.isSwitchingState() && 2397 isInState(NORMAL)) { 2398 // Close any open floating views. 2399 AbstractFloatingView.closeAllOpenViews(this); 2400 2401 // Setting the touch point to (-1, -1) will show the options popup in the center of 2402 // the screen. 2403 OptionsPopupView.showDefaultOptions(this, -1, -1); 2404 } 2405 return true; 2406 } 2407 return super.onKeyUp(keyCode, event); 2408 } 2409 2410 public static Launcher getLauncher(Context context) { 2411 if (context instanceof Launcher) { 2412 return (Launcher) context; 2413 } 2414 return ((Launcher) ((ContextWrapper) context).getBaseContext()); 2415 } 2416 2417 /** 2418 * Callback for listening for onResume 2419 */ 2420 public interface OnResumeCallback { 2421 2422 void onLauncherResume(); 2423 } 2424 } 2425