Launcher.java revision bf78f3cd41983c2c5bab5bbb4fa5cdcee4e3b14e
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 17package com.android.launcher3; 18 19import android.Manifest; 20import android.animation.Animator; 21import android.animation.AnimatorListenerAdapter; 22import android.animation.AnimatorSet; 23import android.animation.ObjectAnimator; 24import android.animation.ValueAnimator; 25import android.annotation.SuppressLint; 26import android.annotation.TargetApi; 27import android.app.ActivityOptions; 28import android.app.AlertDialog; 29import android.app.SearchManager; 30import android.appwidget.AppWidgetHostView; 31import android.appwidget.AppWidgetManager; 32import android.content.ActivityNotFoundException; 33import android.content.BroadcastReceiver; 34import android.content.ComponentCallbacks2; 35import android.content.ComponentName; 36import android.content.Context; 37import android.content.ContextWrapper; 38import android.content.DialogInterface; 39import android.content.Intent; 40import android.content.IntentFilter; 41import android.content.IntentSender; 42import android.content.SharedPreferences; 43import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 44import android.content.pm.ActivityInfo; 45import android.content.pm.PackageManager; 46import android.database.sqlite.SQLiteDatabase; 47import android.graphics.Point; 48import android.graphics.Rect; 49import android.graphics.drawable.Drawable; 50import android.os.AsyncTask; 51import android.os.Build; 52import android.os.Bundle; 53import android.os.Handler; 54import android.os.Process; 55import android.os.StrictMode; 56import android.os.SystemClock; 57import android.os.Trace; 58import android.os.UserHandle; 59import android.support.annotation.Nullable; 60import android.text.Selection; 61import android.text.SpannableStringBuilder; 62import android.text.TextUtils; 63import android.text.method.TextKeyListener; 64import android.util.Log; 65import android.view.Display; 66import android.view.HapticFeedbackConstants; 67import android.view.KeyEvent; 68import android.view.KeyboardShortcutGroup; 69import android.view.KeyboardShortcutInfo; 70import android.view.LayoutInflater; 71import android.view.Menu; 72import android.view.MotionEvent; 73import android.view.View; 74import android.view.View.OnLongClickListener; 75import android.view.ViewGroup; 76import android.view.ViewTreeObserver; 77import android.view.WindowManager; 78import android.view.accessibility.AccessibilityEvent; 79import android.view.accessibility.AccessibilityManager; 80import android.view.animation.OvershootInterpolator; 81import android.view.inputmethod.InputMethodManager; 82import android.widget.Toast; 83 84import com.android.launcher3.DropTarget.DragObject; 85import com.android.launcher3.LauncherSettings.Favorites; 86import com.android.launcher3.Workspace.ItemOperator; 87import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; 88import com.android.launcher3.allapps.AllAppsContainerView; 89import com.android.launcher3.allapps.AllAppsTransitionController; 90import com.android.launcher3.anim.AnimationLayerSet; 91import com.android.launcher3.compat.AppWidgetManagerCompat; 92import com.android.launcher3.compat.LauncherAppsCompat; 93import com.android.launcher3.compat.LauncherAppsCompatVO; 94import com.android.launcher3.config.FeatureFlags; 95import com.android.launcher3.dragndrop.DragController; 96import com.android.launcher3.dragndrop.DragLayer; 97import com.android.launcher3.dragndrop.DragOptions; 98import com.android.launcher3.dragndrop.DragView; 99import com.android.launcher3.dragndrop.PinItemDragListener; 100import com.android.launcher3.dynamicui.ExtractedColors; 101import com.android.launcher3.dynamicui.WallpaperColorInfo; 102import com.android.launcher3.folder.Folder; 103import com.android.launcher3.folder.FolderIcon; 104import com.android.launcher3.keyboard.CustomActionsPopup; 105import com.android.launcher3.keyboard.ViewGroupFocusHelper; 106import com.android.launcher3.logging.FileLog; 107import com.android.launcher3.logging.UserEventDispatcher; 108import com.android.launcher3.model.ModelWriter; 109import com.android.launcher3.model.PackageItemInfo; 110import com.android.launcher3.model.WidgetItem; 111import com.android.launcher3.notification.NotificationListener; 112import com.android.launcher3.pageindicators.PageIndicator; 113import com.android.launcher3.popup.PopupContainerWithArrow; 114import com.android.launcher3.popup.PopupDataProvider; 115import com.android.launcher3.shortcuts.DeepShortcutManager; 116import com.android.launcher3.shortcuts.ShortcutKey; 117import com.android.launcher3.userevent.nano.LauncherLogProto; 118import com.android.launcher3.userevent.nano.LauncherLogProto.Action; 119import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; 120import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; 121import com.android.launcher3.util.ActivityResultInfo; 122import com.android.launcher3.util.ComponentKey; 123import com.android.launcher3.util.ItemInfoMatcher; 124import com.android.launcher3.util.MultiHashMap; 125import com.android.launcher3.util.PackageManagerHelper; 126import com.android.launcher3.util.PackageUserKey; 127import com.android.launcher3.util.PendingRequestArgs; 128import com.android.launcher3.util.SystemUiController; 129import com.android.launcher3.util.TestingUtils; 130import com.android.launcher3.util.Themes; 131import com.android.launcher3.util.Thunk; 132import com.android.launcher3.util.ViewOnDrawExecutor; 133import com.android.launcher3.widget.PendingAddShortcutInfo; 134import com.android.launcher3.widget.PendingAddWidgetInfo; 135import com.android.launcher3.widget.WidgetAddFlowHandler; 136import com.android.launcher3.widget.WidgetHostViewLoader; 137import com.android.launcher3.widget.WidgetsContainerView; 138 139import java.io.FileDescriptor; 140import java.io.PrintWriter; 141import java.util.ArrayList; 142import java.util.Collection; 143import java.util.HashMap; 144import java.util.HashSet; 145import java.util.List; 146import java.util.Set; 147 148/** 149 * Default launcher application. 150 */ 151public class Launcher extends BaseActivity 152 implements LauncherExterns, View.OnClickListener, OnLongClickListener, 153 LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener, 154 AccessibilityManager.AccessibilityStateChangeListener, 155 WallpaperColorInfo.OnThemeChangeListener { 156 public static final String TAG = "Launcher"; 157 static final boolean LOGD = false; 158 159 static final boolean DEBUG_WIDGETS = false; 160 static final boolean DEBUG_STRICT_MODE = false; 161 static final boolean DEBUG_RESUME_TIME = false; 162 163 private static final int REQUEST_CREATE_SHORTCUT = 1; 164 private static final int REQUEST_CREATE_APPWIDGET = 5; 165 166 private static final int REQUEST_PICK_APPWIDGET = 9; 167 private static final int REQUEST_PICK_WALLPAPER = 10; 168 169 private static final int REQUEST_BIND_APPWIDGET = 11; 170 private static final int REQUEST_BIND_PENDING_APPWIDGET = 12; 171 private static final int REQUEST_RECONFIGURE_APPWIDGET = 13; 172 173 private static final int REQUEST_PERMISSION_CALL_PHONE = 14; 174 175 private static final float BOUNCE_ANIMATION_TENSION = 1.3f; 176 177 /** 178 * IntentStarter uses request codes starting with this. This must be greater than all activity 179 * request codes used internally. 180 */ 181 protected static final int REQUEST_LAST = 100; 182 183 private static final int SOFT_INPUT_MODE_DEFAULT = 184 WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; 185 private static final int SOFT_INPUT_MODE_ALL_APPS = 186 WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 187 188 // The Intent extra that defines whether to ignore the launch animation 189 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION = 190 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION"; 191 192 // Type: int 193 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen"; 194 // Type: int 195 private static final String RUNTIME_STATE = "launcher.state"; 196 // Type: PendingRequestArgs 197 private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args"; 198 // Type: ActivityResultInfo 199 private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result"; 200 201 static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown"; 202 203 /** The different states that Launcher can be in. */ 204 enum State { NONE, WORKSPACE, WORKSPACE_SPRING_LOADED, APPS, APPS_SPRING_LOADED, 205 WIDGETS, WIDGETS_SPRING_LOADED } 206 207 @Thunk State mState = State.WORKSPACE; 208 @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation; 209 210 private boolean mIsSafeModeEnabled; 211 212 public static final int APPWIDGET_HOST_ID = 1024; 213 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 500; 214 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500; 215 private static final int ACTIVITY_START_DELAY = 1000; 216 217 // How long to wait before the new-shortcut animation automatically pans the workspace 218 private static final int NEW_APPS_PAGE_MOVE_DELAY = 500; 219 private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5; 220 @Thunk static final int NEW_APPS_ANIMATION_DELAY = 500; 221 222 private final ExtractedColors mExtractedColors = new ExtractedColors(); 223 224 @Thunk Workspace mWorkspace; 225 private View mLauncherView; 226 @Thunk DragLayer mDragLayer; 227 private DragController mDragController; 228 229 public View mWeightWatcher; 230 231 private AppWidgetManagerCompat mAppWidgetManager; 232 private LauncherAppWidgetHost mAppWidgetHost; 233 234 private final int[] mTmpAddItemCellCoordinates = new int[2]; 235 236 @Thunk Hotseat mHotseat; 237 private ViewGroup mOverviewPanel; 238 239 private View mAllAppsButton; 240 private View mWidgetsButton; 241 242 private DropTargetBar mDropTargetBar; 243 244 // Main container view for the all apps screen. 245 @Thunk AllAppsContainerView mAppsView; 246 AllAppsTransitionController mAllAppsController; 247 248 // Main container view and the model for the widget tray screen. 249 @Thunk WidgetsContainerView mWidgetsView; 250 @Thunk MultiHashMap<PackageItemInfo, WidgetItem> mAllWidgets; 251 252 // We set the state in both onCreate and then onNewIntent in some cases, which causes both 253 // scroll issues (because the workspace may not have been measured yet) and extra work. 254 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume. 255 private State mOnResumeState = State.NONE; 256 257 private SpannableStringBuilder mDefaultKeySsb = null; 258 259 @Thunk boolean mWorkspaceLoading = true; 260 261 private boolean mPaused = true; 262 private boolean mOnResumeNeedsLoad; 263 264 private final ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<>(); 265 private final ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<>(); 266 private ViewOnDrawExecutor mPendingExecutor; 267 268 private LauncherModel mModel; 269 private ModelWriter mModelWriter; 270 private IconCache mIconCache; 271 private LauncherAccessibilityDelegate mAccessibilityDelegate; 272 private final Handler mHandler = new Handler(); 273 private boolean mIsResumeFromActionScreenOff; 274 private boolean mHasFocus = false; 275 276 private ObjectAnimator mScrimAnimator; 277 278 private PopupDataProvider mPopupDataProvider; 279 280 private View.OnTouchListener mHapticFeedbackTouchListener; 281 282 // Determines how long to wait after a rotation before restoring the screen orientation to 283 // match the sensor state. 284 private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500; 285 286 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<>(); 287 288 // We only want to get the SharedPreferences once since it does an FS stat each time we get 289 // it from the context. 290 private SharedPreferences mSharedPrefs; 291 292 private boolean mMoveToDefaultScreenFromNewIntent; 293 294 // This is set to the view that launched the activity that navigated the user away from 295 // launcher. Since there is no callback for when the activity has finished launching, enable 296 // the press state and keep this reference to reset the press state when we return to launcher. 297 private BubbleTextView mWaitingForResume; 298 299 protected static final HashMap<String, CustomAppWidget> sCustomAppWidgets = 300 new HashMap<>(); 301 302 static { 303 if (TestingUtils.ENABLE_CUSTOM_WIDGET_TEST) { 304 TestingUtils.addDummyWidget(sCustomAppWidgets); 305 } 306 } 307 308 // Exiting spring loaded mode happens with a delay. This runnable object triggers the 309 // state transition. If another state transition happened during this delay, 310 // simply unregister this runnable. 311 private Runnable mExitSpringLoadedModeRunnable; 312 313 @Thunk final Runnable mBuildLayersRunnable = new Runnable() { 314 public void run() { 315 if (mWorkspace != null) { 316 mWorkspace.buildPageHardwareLayers(); 317 } 318 } 319 }; 320 321 // Activity result which needs to be processed after workspace has loaded. 322 private ActivityResultInfo mPendingActivityResult; 323 /** 324 * Holds extra information required to handle a result from an external call, like 325 * {@link #startActivityForResult(Intent, int)} or {@link #requestPermissions(String[], int)} 326 */ 327 private PendingRequestArgs mPendingRequestArgs; 328 329 private float mLastDispatchTouchEventX = 0.0f; 330 331 public ViewGroupFocusHelper mFocusHandler; 332 private boolean mRotationEnabled = false; 333 334 @Thunk void setOrientation() { 335 if (mRotationEnabled) { 336 unlockScreenOrientation(true); 337 } else { 338 setRequestedOrientation( 339 ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); 340 } 341 } 342 343 private RotationPrefChangeHandler mRotationPrefChangeHandler; 344 345 @Override 346 protected void onCreate(Bundle savedInstanceState) { 347 if (DEBUG_STRICT_MODE) { 348 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 349 .detectDiskReads() 350 .detectDiskWrites() 351 .detectNetwork() // or .detectAll() for all detectable problems 352 .penaltyLog() 353 .build()); 354 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() 355 .detectLeakedSqlLiteObjects() 356 .detectLeakedClosableObjects() 357 .penaltyLog() 358 .penaltyDeath() 359 .build()); 360 } 361 if (LauncherAppState.PROFILE_STARTUP) { 362 Trace.beginSection("Launcher-onCreate"); 363 } 364 365 if (mLauncherCallbacks != null) { 366 mLauncherCallbacks.preOnCreate(); 367 } 368 369 WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.getInstance(this); 370 wallpaperColorInfo.setOnThemeChangeListener(this); 371 overrideTheme(wallpaperColorInfo.isDark(), wallpaperColorInfo.supportsDarkText()); 372 373 super.onCreate(savedInstanceState); 374 375 LauncherAppState app = LauncherAppState.getInstance(this); 376 377 // Load configuration-specific DeviceProfile 378 mDeviceProfile = app.getInvariantDeviceProfile().getDeviceProfile(this); 379 if (isInMultiWindowModeCompat()) { 380 Display display = getWindowManager().getDefaultDisplay(); 381 Point mwSize = new Point(); 382 display.getSize(mwSize); 383 mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize); 384 } 385 386 mSharedPrefs = Utilities.getPrefs(this); 387 mIsSafeModeEnabled = getPackageManager().isSafeMode(); 388 mModel = app.setLauncher(this); 389 mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout()); 390 mIconCache = app.getIconCache(); 391 mAccessibilityDelegate = new LauncherAccessibilityDelegate(this); 392 393 mDragController = new DragController(this); 394 mAllAppsController = new AllAppsTransitionController(this); 395 mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, mAllAppsController); 396 397 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); 398 399 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID); 400 mAppWidgetHost.startListening(); 401 402 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here, 403 // this also ensures that any synchronous binding below doesn't re-trigger another 404 // LauncherModel load. 405 mPaused = false; 406 407 mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null); 408 409 setupViews(); 410 mDeviceProfile.layout(this, false /* notifyListeners */); 411 loadExtractedColorsAndColorItems(); 412 413 mPopupDataProvider = new PopupDataProvider(this); 414 415 ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE)) 416 .addAccessibilityStateChangeListener(this); 417 418 lockAllApps(); 419 420 restoreState(savedInstanceState); 421 422 if (LauncherAppState.PROFILE_STARTUP) { 423 Trace.endSection(); 424 } 425 426 // We only load the page synchronously if the user rotates (or triggers a 427 // configuration change) while launcher is in the foreground 428 int currentScreen = PagedView.INVALID_RESTORE_PAGE; 429 if (savedInstanceState != null) { 430 currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen); 431 } 432 if (!mModel.startLoader(currentScreen)) { 433 // If we are not binding synchronously, show a fade in animation when 434 // the first page bind completes. 435 mDragLayer.setAlpha(0); 436 } else { 437 // Pages bound synchronously. 438 mWorkspace.setCurrentPage(currentScreen); 439 440 setWorkspaceLoading(true); 441 } 442 443 // For handling default keys 444 mDefaultKeySsb = new SpannableStringBuilder(); 445 Selection.setSelection(mDefaultKeySsb, 0); 446 447 mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation); 448 // In case we are on a device with locked rotation, we should look at preferences to check 449 // if the user has specifically allowed rotation. 450 if (!mRotationEnabled) { 451 mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext()); 452 mRotationPrefChangeHandler = new RotationPrefChangeHandler(); 453 mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler); 454 } 455 456 if (PinItemDragListener.handleDragRequest(this, getIntent())) { 457 // Temporarily enable the rotation 458 mRotationEnabled = true; 459 } 460 461 // On large interfaces, or on devices that a user has specifically enabled screen rotation, 462 // we want the screen to auto-rotate based on the current orientation 463 setOrientation(); 464 465 setContentView(mLauncherView); 466 if (mLauncherCallbacks != null) { 467 mLauncherCallbacks.onCreate(savedInstanceState); 468 } 469 470 // Listen for broadcasts screen off 471 registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); 472 473 getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW, 474 Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText)); 475 } 476 477 @Override 478 public void onThemeChanged() { 479 recreate(); 480 } 481 482 protected void overrideTheme(boolean isDark, boolean supportsDarkText) { 483 if (isDark) { 484 setTheme(R.style.LauncherThemeDark); 485 } else if (supportsDarkText) { 486 setTheme(R.style.LauncherThemeDarkText); 487 } 488 } 489 490 @Override 491 public <T extends View> T findViewById(int id) { 492 return mLauncherView.findViewById(id); 493 } 494 495 @Override 496 public void onExtractedColorsChanged() { 497 loadExtractedColorsAndColorItems(); 498 mExtractedColors.notifyChange(); 499 } 500 501 public ExtractedColors getExtractedColors() { 502 return mExtractedColors; 503 } 504 505 @Override 506 public void onAppWidgetHostReset() { 507 if (mAppWidgetHost != null) { 508 mAppWidgetHost.startListening(); 509 } 510 } 511 512 private void loadExtractedColorsAndColorItems() { 513 // TODO: do this in pre-N as well, once the extraction part is complete. 514 if (Utilities.ATLEAST_NOUGAT) { 515 mExtractedColors.load(this); 516 mHotseat.updateColor(mExtractedColors, !mPaused); 517 mWorkspace.getPageIndicator().updateColor(mExtractedColors); 518 } 519 } 520 521 private LauncherCallbacks mLauncherCallbacks; 522 523 public void onPostCreate(Bundle savedInstanceState) { 524 super.onPostCreate(savedInstanceState); 525 if (mLauncherCallbacks != null) { 526 mLauncherCallbacks.onPostCreate(savedInstanceState); 527 } 528 } 529 530 public void onInsetsChanged(Rect insets) { 531 mDeviceProfile.updateInsets(insets); 532 mDeviceProfile.layout(this, true /* notifyListeners */); 533 } 534 535 /** 536 * Call this after onCreate to set or clear overlay. 537 */ 538 public void setLauncherOverlay(LauncherOverlay overlay) { 539 if (overlay != null) { 540 overlay.setOverlayCallbacks(new LauncherOverlayCallbacksImpl()); 541 } 542 mWorkspace.setLauncherOverlay(overlay); 543 } 544 545 public boolean setLauncherCallbacks(LauncherCallbacks callbacks) { 546 mLauncherCallbacks = callbacks; 547 return true; 548 } 549 550 @Override 551 public void onLauncherProviderChanged() { 552 if (mLauncherCallbacks != null) { 553 mLauncherCallbacks.onLauncherProviderChange(); 554 } 555 } 556 557 /** To be overridden by subclasses to hint to Launcher that we have custom content */ 558 protected boolean hasCustomContentToLeft() { 559 if (mLauncherCallbacks != null) { 560 return mLauncherCallbacks.hasCustomContentToLeft(); 561 } 562 return false; 563 } 564 565 /** 566 * To be overridden by subclasses to populate the custom content container and call 567 * {@link #addToCustomContentPage}. This will only be invoked if 568 * {@link #hasCustomContentToLeft()} is {@code true}. 569 */ 570 protected void populateCustomContentContainer() { 571 if (mLauncherCallbacks != null) { 572 mLauncherCallbacks.populateCustomContentContainer(); 573 } 574 } 575 576 /** 577 * Invoked by subclasses to signal a change to the {@link #addToCustomContentPage} value to 578 * ensure the custom content page is added or removed if necessary. 579 */ 580 protected void invalidateHasCustomContentToLeft() { 581 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) { 582 // Not bound yet, wait for bindScreens to be called. 583 return; 584 } 585 586 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) { 587 // Create the custom content page and call the subclass to populate it. 588 mWorkspace.createCustomContentContainer(); 589 populateCustomContentContainer(); 590 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) { 591 mWorkspace.removeCustomContentPage(); 592 } 593 } 594 595 public boolean isDraggingEnabled() { 596 // We prevent dragging when we are loading the workspace as it is possible to pick up a view 597 // that is subsequently removed from the workspace in startBinding(). 598 return !isWorkspaceLoading(); 599 } 600 601 public int getViewIdForItem(ItemInfo info) { 602 // aapt-generated IDs have the high byte nonzero; clamp to the range under that. 603 // This cast is safe as long as the id < 0x00FFFFFF 604 // Since we jail all the dynamically generated views, there should be no clashes 605 // with any other views. 606 return (int) info.id; 607 } 608 609 public PopupDataProvider getPopupDataProvider() { 610 return mPopupDataProvider; 611 } 612 613 /** 614 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have 615 * a configuration step, this allows the proper animations to run after other transitions. 616 */ 617 private long completeAdd( 618 int requestCode, Intent intent, int appWidgetId, PendingRequestArgs info) { 619 long screenId = info.screenId; 620 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 621 // When the screen id represents an actual screen (as opposed to a rank) we make sure 622 // that the drop page actually exists. 623 screenId = ensurePendingDropLayoutExists(info.screenId); 624 } 625 626 switch (requestCode) { 627 case REQUEST_CREATE_SHORTCUT: 628 completeAddShortcut(intent, info.container, screenId, info.cellX, info.cellY, info); 629 break; 630 case REQUEST_CREATE_APPWIDGET: 631 completeAddAppWidget(appWidgetId, info, null, null); 632 break; 633 case REQUEST_RECONFIGURE_APPWIDGET: 634 completeRestoreAppWidget(appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED); 635 break; 636 case REQUEST_BIND_PENDING_APPWIDGET: { 637 int widgetId = appWidgetId; 638 LauncherAppWidgetInfo widgetInfo = 639 completeRestoreAppWidget(widgetId, LauncherAppWidgetInfo.FLAG_UI_NOT_READY); 640 if (widgetInfo != null) { 641 // Since the view was just bound, also launch the configure activity if needed 642 LauncherAppWidgetProviderInfo provider = mAppWidgetManager 643 .getLauncherAppWidgetInfo(widgetId); 644 if (provider != null) { 645 new WidgetAddFlowHandler(provider) 646 .startConfigActivity(this, widgetInfo, REQUEST_RECONFIGURE_APPWIDGET); 647 } 648 } 649 break; 650 } 651 } 652 653 return screenId; 654 } 655 656 private void handleActivityResult( 657 final int requestCode, final int resultCode, final Intent data) { 658 if (isWorkspaceLoading()) { 659 // process the result once the workspace has loaded. 660 mPendingActivityResult = new ActivityResultInfo(requestCode, resultCode, data); 661 return; 662 } 663 mPendingActivityResult = null; 664 665 // Reset the startActivity waiting flag 666 final PendingRequestArgs requestArgs = mPendingRequestArgs; 667 setWaitingForResult(null); 668 if (requestArgs == null) { 669 return; 670 } 671 672 final int pendingAddWidgetId = requestArgs.getWidgetId(); 673 674 Runnable exitSpringLoaded = new Runnable() { 675 @Override 676 public void run() { 677 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), 678 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); 679 } 680 }; 681 682 if (requestCode == REQUEST_BIND_APPWIDGET) { 683 // This is called only if the user did not previously have permissions to bind widgets 684 final int appWidgetId = data != null ? 685 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; 686 if (resultCode == RESULT_CANCELED) { 687 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs); 688 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, 689 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 690 } else if (resultCode == RESULT_OK) { 691 addAppWidgetImpl( 692 appWidgetId, requestArgs, null, 693 requestArgs.getWidgetHandler(), 694 ON_ACTIVITY_RESULT_ANIMATION_DELAY); 695 } 696 return; 697 } else if (requestCode == REQUEST_PICK_WALLPAPER) { 698 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) { 699 // User could have free-scrolled between pages before picking a wallpaper; make sure 700 // we move to the closest one now. 701 mWorkspace.setCurrentPage(mWorkspace.getPageNearestToCenterOfScreen()); 702 showWorkspace(false); 703 } 704 return; 705 } 706 707 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET || 708 requestCode == REQUEST_CREATE_APPWIDGET); 709 710 // We have special handling for widgets 711 if (isWidgetDrop) { 712 final int appWidgetId; 713 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) 714 : -1; 715 if (widgetId < 0) { 716 appWidgetId = pendingAddWidgetId; 717 } else { 718 appWidgetId = widgetId; 719 } 720 721 final int result; 722 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) { 723 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " + 724 "returned from the widget configuration activity."); 725 result = RESULT_CANCELED; 726 completeTwoStageWidgetDrop(result, appWidgetId, requestArgs); 727 final Runnable onComplete = new Runnable() { 728 @Override 729 public void run() { 730 exitSpringLoadedDragModeDelayed(false, 0, null); 731 } 732 }; 733 734 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, 735 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 736 } else { 737 if (requestArgs.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 738 // When the screen id represents an actual screen (as opposed to a rank) 739 // we make sure that the drop page actually exists. 740 requestArgs.screenId = 741 ensurePendingDropLayoutExists(requestArgs.screenId); 742 } 743 final CellLayout dropLayout = 744 mWorkspace.getScreenWithId(requestArgs.screenId); 745 746 dropLayout.setDropPending(true); 747 final Runnable onComplete = new Runnable() { 748 @Override 749 public void run() { 750 completeTwoStageWidgetDrop(resultCode, appWidgetId, requestArgs); 751 dropLayout.setDropPending(false); 752 } 753 }; 754 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, 755 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 756 } 757 return; 758 } 759 760 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET 761 || requestCode == REQUEST_BIND_PENDING_APPWIDGET) { 762 if (resultCode == RESULT_OK) { 763 // Update the widget view. 764 completeAdd(requestCode, data, pendingAddWidgetId, requestArgs); 765 } 766 // Leave the widget in the pending state if the user canceled the configure. 767 return; 768 } 769 770 if (requestCode == REQUEST_CREATE_SHORTCUT) { 771 // Handle custom shortcuts created using ACTION_CREATE_SHORTCUT. 772 if (resultCode == RESULT_OK && requestArgs.container != ItemInfo.NO_ID) { 773 completeAdd(requestCode, data, -1, requestArgs); 774 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, 775 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 776 777 } else if (resultCode == RESULT_CANCELED) { 778 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, 779 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 780 } 781 } 782 mDragLayer.clearAnimatedView(); 783 } 784 785 @Override 786 protected void onActivityResult( 787 final int requestCode, final int resultCode, final Intent data) { 788 handleActivityResult(requestCode, resultCode, data); 789 if (mLauncherCallbacks != null) { 790 mLauncherCallbacks.onActivityResult(requestCode, resultCode, data); 791 } 792 } 793 794 /** @Override for MNC */ 795 public void onRequestPermissionsResult(int requestCode, String[] permissions, 796 int[] grantResults) { 797 PendingRequestArgs pendingArgs = mPendingRequestArgs; 798 if (requestCode == REQUEST_PERMISSION_CALL_PHONE && pendingArgs != null 799 && pendingArgs.getRequestCode() == REQUEST_PERMISSION_CALL_PHONE) { 800 setWaitingForResult(null); 801 802 View v = null; 803 CellLayout layout = getCellLayout(pendingArgs.container, pendingArgs.screenId); 804 if (layout != null) { 805 v = layout.getChildAt(pendingArgs.cellX, pendingArgs.cellY); 806 } 807 Intent intent = pendingArgs.getPendingIntent(); 808 809 if (grantResults.length > 0 810 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 811 startActivitySafely(v, intent, null); 812 } else { 813 // TODO: Show a snack bar with link to settings 814 Toast.makeText(this, getString(R.string.msg_no_phone_permission, 815 getString(R.string.derived_app_name)), Toast.LENGTH_SHORT).show(); 816 } 817 } 818 if (mLauncherCallbacks != null) { 819 mLauncherCallbacks.onRequestPermissionsResult(requestCode, permissions, 820 grantResults); 821 } 822 } 823 824 /** 825 * Check to see if a given screen id exists. If not, create it at the end, return the new id. 826 * 827 * @param screenId the screen id to check 828 * @return the new screen, or screenId if it exists 829 */ 830 private long ensurePendingDropLayoutExists(long screenId) { 831 CellLayout dropLayout = mWorkspace.getScreenWithId(screenId); 832 if (dropLayout == null) { 833 // it's possible that the add screen was removed because it was 834 // empty and a re-bind occurred 835 mWorkspace.addExtraEmptyScreen(); 836 return mWorkspace.commitExtraEmptyScreen(); 837 } else { 838 return screenId; 839 } 840 } 841 842 @Thunk void completeTwoStageWidgetDrop( 843 final int resultCode, final int appWidgetId, final PendingRequestArgs requestArgs) { 844 CellLayout cellLayout = mWorkspace.getScreenWithId(requestArgs.screenId); 845 Runnable onCompleteRunnable = null; 846 int animationType = 0; 847 848 AppWidgetHostView boundWidget = null; 849 if (resultCode == RESULT_OK) { 850 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION; 851 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId, 852 requestArgs.getWidgetHandler().getProviderInfo(this)); 853 boundWidget = layout; 854 onCompleteRunnable = new Runnable() { 855 @Override 856 public void run() { 857 completeAddAppWidget(appWidgetId, requestArgs, layout, null); 858 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), 859 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); 860 } 861 }; 862 } else if (resultCode == RESULT_CANCELED) { 863 mAppWidgetHost.deleteAppWidgetId(appWidgetId); 864 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION; 865 } 866 if (mDragLayer.getAnimatedView() != null) { 867 mWorkspace.animateWidgetDrop(requestArgs, cellLayout, 868 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable, 869 animationType, boundWidget, true); 870 } else if (onCompleteRunnable != null) { 871 // The animated view may be null in the case of a rotation during widget configuration 872 onCompleteRunnable.run(); 873 } 874 } 875 876 @Override 877 protected void onStop() { 878 super.onStop(); 879 FirstFrameAnimatorHelper.setIsVisible(false); 880 881 if (mLauncherCallbacks != null) { 882 mLauncherCallbacks.onStop(); 883 } 884 885 if (Utilities.ATLEAST_NOUGAT_MR1) { 886 mAppWidgetHost.stopListening(); 887 } 888 889 NotificationListener.removeNotificationsChangedListener(); 890 } 891 892 @Override 893 protected void onStart() { 894 super.onStart(); 895 FirstFrameAnimatorHelper.setIsVisible(true); 896 897 if (mLauncherCallbacks != null) { 898 mLauncherCallbacks.onStart(); 899 } 900 901 if (Utilities.ATLEAST_NOUGAT_MR1) { 902 mAppWidgetHost.startListening(); 903 } 904 905 if (!isWorkspaceLoading()) { 906 NotificationListener.setNotificationsChangedListener(mPopupDataProvider); 907 } 908 909 if (mIsResumeFromActionScreenOff && mDragLayer.getBackground() != null) { 910 if (mScrimAnimator != null) { 911 mScrimAnimator.cancel(); 912 } 913 mDragLayer.getBackground().setAlpha(0); 914 mScrimAnimator = ObjectAnimator.ofInt(mDragLayer.getBackground(), 915 LauncherAnimUtils.DRAWABLE_ALPHA, 0, 255); 916 mScrimAnimator.addListener(new AnimatorListenerAdapter() { 917 @Override 918 public void onAnimationEnd(Animator animation) { 919 mScrimAnimator = null; 920 } 921 }); 922 mScrimAnimator.setDuration(600); 923 mScrimAnimator.setStartDelay(getWindow().getTransitionBackgroundFadeDuration()); 924 mScrimAnimator.start(); 925 } 926 } 927 928 @Override 929 protected void onResume() { 930 long startTime = 0; 931 if (DEBUG_RESUME_TIME) { 932 startTime = System.currentTimeMillis(); 933 Log.v(TAG, "Launcher.onResume()"); 934 } 935 936 if (mLauncherCallbacks != null) { 937 mLauncherCallbacks.preOnResume(); 938 } 939 940 super.onResume(); 941 getUserEventDispatcher().resetElapsedSessionMillis(); 942 943 // Restore the previous launcher state 944 if (mOnResumeState == State.WORKSPACE) { 945 showWorkspace(false); 946 } else if (mOnResumeState == State.APPS) { 947 boolean launchedFromApp = (mWaitingForResume != null); 948 // Don't update the predicted apps if the user is returning to launcher in the apps 949 // view after launching an app, as they may be depending on the UI to be static to 950 // switch to another app, otherwise, if it was 951 showAppsView(false /* animated */, !launchedFromApp /* updatePredictedApps */); 952 } else if (mOnResumeState == State.WIDGETS) { 953 showWidgetsView(false, false); 954 } 955 mOnResumeState = State.NONE; 956 957 mPaused = false; 958 if (mOnResumeNeedsLoad) { 959 setWorkspaceLoading(true); 960 mModel.startLoader(getCurrentWorkspaceScreen()); 961 mOnResumeNeedsLoad = false; 962 } 963 if (mBindOnResumeCallbacks.size() > 0) { 964 // We might have postponed some bind calls until onResume (see waitUntilResume) -- 965 // execute them here 966 long startTimeCallbacks = 0; 967 if (DEBUG_RESUME_TIME) { 968 startTimeCallbacks = System.currentTimeMillis(); 969 } 970 971 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) { 972 mBindOnResumeCallbacks.get(i).run(); 973 } 974 mBindOnResumeCallbacks.clear(); 975 if (DEBUG_RESUME_TIME) { 976 Log.d(TAG, "Time spent processing callbacks in onResume: " + 977 (System.currentTimeMillis() - startTimeCallbacks)); 978 } 979 } 980 if (mOnResumeCallbacks.size() > 0) { 981 for (int i = 0; i < mOnResumeCallbacks.size(); i++) { 982 mOnResumeCallbacks.get(i).run(); 983 } 984 mOnResumeCallbacks.clear(); 985 } 986 987 // Reset the pressed state of icons that were locked in the press state while activities 988 // were launching 989 if (mWaitingForResume != null) { 990 // Resets the previous workspace icon press state 991 mWaitingForResume.setStayPressed(false); 992 } 993 994 // It is possible that widgets can receive updates while launcher is not in the foreground. 995 // Consequently, the widgets will be inflated in the orientation of the foreground activity 996 // (framework issue). On resuming, we ensure that any widgets are inflated for the current 997 // orientation. 998 if (!isWorkspaceLoading()) { 999 getWorkspace().reinflateWidgetsIfNecessary(); 1000 } 1001 1002 if (DEBUG_RESUME_TIME) { 1003 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime)); 1004 } 1005 1006 // We want to suppress callbacks about CustomContent being shown if we have just received 1007 // onNewIntent while the user was present within launcher. In that case, we post a call 1008 // to move the user to the main screen (which will occur after onResume). We don't want to 1009 // have onHide (from onPause), then onShow, then onHide again, which we get if we don't 1010 // suppress here. 1011 if (mWorkspace.getCustomContentCallbacks() != null 1012 && !mMoveToDefaultScreenFromNewIntent) { 1013 // If we are resuming and the custom content is the current page, we call onShow(). 1014 // It is also possible that onShow will instead be called slightly after first layout 1015 // if PagedView#setRestorePage was set to the custom content page in onCreate(). 1016 if (mWorkspace.isOnOrMovingToCustomContent()) { 1017 mWorkspace.getCustomContentCallbacks().onShow(true); 1018 } 1019 } 1020 mMoveToDefaultScreenFromNewIntent = false; 1021 updateInteraction(Workspace.State.NORMAL, mWorkspace.getState()); 1022 mWorkspace.onResume(); 1023 1024 // Process any items that were added while Launcher was away. 1025 InstallShortcutReceiver.disableAndFlushInstallQueue( 1026 InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this); 1027 1028 // Refresh shortcuts if the permission changed. 1029 mModel.refreshShortcutsIfRequired(); 1030 1031 if (shouldShowDiscoveryBounce()) { 1032 mAllAppsController.showDiscoveryBounce(); 1033 } 1034 mIsResumeFromActionScreenOff = false; 1035 if (mLauncherCallbacks != null) { 1036 mLauncherCallbacks.onResume(); 1037 } 1038 1039 } 1040 1041 @Override 1042 protected void onPause() { 1043 // Ensure that items added to Launcher are queued until Launcher returns 1044 InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED); 1045 1046 super.onPause(); 1047 mPaused = true; 1048 mDragController.cancelDrag(); 1049 mDragController.resetLastGestureUpTime(); 1050 1051 // We call onHide() aggressively. The custom content callbacks should be able to 1052 // debounce excess onHide calls. 1053 if (mWorkspace.getCustomContentCallbacks() != null) { 1054 mWorkspace.getCustomContentCallbacks().onHide(); 1055 } 1056 1057 if (mLauncherCallbacks != null) { 1058 mLauncherCallbacks.onPause(); 1059 } 1060 } 1061 1062 public interface CustomContentCallbacks { 1063 // Custom content is completely shown. {@code fromResume} indicates whether this was caused 1064 // by a onResume or by scrolling otherwise. 1065 void onShow(boolean fromResume); 1066 1067 // Custom content is completely hidden 1068 void onHide(); 1069 1070 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing). 1071 void onScrollProgressChanged(float progress); 1072 1073 // Indicates whether the user is allowed to scroll away from the custom content. 1074 boolean isScrollingAllowed(); 1075 } 1076 1077 public interface LauncherOverlay { 1078 1079 /** 1080 * Touch interaction leading to overscroll has begun 1081 */ 1082 void onScrollInteractionBegin(); 1083 1084 /** 1085 * Touch interaction related to overscroll has ended 1086 */ 1087 void onScrollInteractionEnd(); 1088 1089 /** 1090 * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost 1091 * screen (or in the case of RTL, the rightmost screen). 1092 */ 1093 void onScrollChange(float progress, boolean rtl); 1094 1095 /** 1096 * Called when the launcher is ready to use the overlay 1097 * @param callbacks A set of callbacks provided by Launcher in relation to the overlay 1098 */ 1099 void setOverlayCallbacks(LauncherOverlayCallbacks callbacks); 1100 } 1101 1102 public interface LauncherOverlayCallbacks { 1103 void onScrollChanged(float progress); 1104 } 1105 1106 class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks { 1107 1108 public void onScrollChanged(float progress) { 1109 if (mWorkspace != null) { 1110 mWorkspace.onOverlayScrollChanged(progress); 1111 } 1112 } 1113 } 1114 1115 protected boolean hasSettings() { 1116 if (mLauncherCallbacks != null) { 1117 return mLauncherCallbacks.hasSettings(); 1118 } else { 1119 // On O and above we there is always some setting present settings (add icon to 1120 // home screen or icon badging). On earlier APIs we will have the allow rotation 1121 // setting, on devices with a locked orientation, 1122 return Utilities.isAtLeastO() || !getResources().getBoolean(R.bool.allow_rotation); 1123 } 1124 } 1125 1126 public void addToCustomContentPage(View customContent, 1127 CustomContentCallbacks callbacks, String description) { 1128 mWorkspace.addToCustomContentPage(customContent, callbacks, description); 1129 } 1130 1131 // The custom content needs to offset its content to account for the QSB 1132 public int getTopOffsetForCustomContent() { 1133 return mWorkspace.getPaddingTop(); 1134 } 1135 1136 @Override 1137 public Object onRetainNonConfigurationInstance() { 1138 // Flag the loader to stop early before switching 1139 if (mModel.isCurrentCallbacks(this)) { 1140 mModel.stopLoader(); 1141 } 1142 //TODO(hyunyoungs): stop the widgets loader when there is a rotation. 1143 1144 return Boolean.TRUE; 1145 } 1146 1147 // We can't hide the IME if it was forced open. So don't bother 1148 @Override 1149 public void onWindowFocusChanged(boolean hasFocus) { 1150 super.onWindowFocusChanged(hasFocus); 1151 mHasFocus = hasFocus; 1152 1153 if (mLauncherCallbacks != null) { 1154 mLauncherCallbacks.onWindowFocusChanged(hasFocus); 1155 } 1156 } 1157 1158 private boolean acceptFilter() { 1159 final InputMethodManager inputManager = (InputMethodManager) 1160 getSystemService(Context.INPUT_METHOD_SERVICE); 1161 return !inputManager.isFullscreenMode(); 1162 } 1163 1164 @Override 1165 public boolean onKeyDown(int keyCode, KeyEvent event) { 1166 final int uniChar = event.getUnicodeChar(); 1167 final boolean handled = super.onKeyDown(keyCode, event); 1168 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar); 1169 if (!handled && acceptFilter() && isKeyNotWhitespace) { 1170 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb, 1171 keyCode, event); 1172 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) { 1173 // something usable has been typed - start a search 1174 // the typed text will be retrieved and cleared by 1175 // showSearchDialog() 1176 // If there are multiple keystrokes before the search dialog takes focus, 1177 // onSearchRequested() will be called for every keystroke, 1178 // but it is idempotent, so it's fine. 1179 return onSearchRequested(); 1180 } 1181 } 1182 1183 // Eat the long press event so the keyboard doesn't come up. 1184 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) { 1185 return true; 1186 } 1187 1188 return handled; 1189 } 1190 1191 @Override 1192 public boolean onKeyUp(int keyCode, KeyEvent event) { 1193 if (keyCode == KeyEvent.KEYCODE_MENU) { 1194 // Ignore the menu key if we are currently dragging or are on the custom content screen 1195 if (!isOnCustomContent() && !mDragController.isDragging()) { 1196 // Close any open floating view 1197 AbstractFloatingView.closeAllOpenViews(this); 1198 1199 // Stop resizing any widgets 1200 mWorkspace.exitWidgetResizeMode(); 1201 1202 // Show the overview mode if we are on the workspace 1203 if (mState == State.WORKSPACE && !mWorkspace.isInOverviewMode() && 1204 !mWorkspace.isSwitchingState()) { 1205 mOverviewPanel.requestFocus(); 1206 showOverviewMode(true, true /* requestButtonFocus */); 1207 } 1208 } 1209 return true; 1210 } 1211 return super.onKeyUp(keyCode, event); 1212 } 1213 1214 private String getTypedText() { 1215 return mDefaultKeySsb.toString(); 1216 } 1217 1218 @Override 1219 public void clearTypedText() { 1220 mDefaultKeySsb.clear(); 1221 mDefaultKeySsb.clearSpans(); 1222 Selection.setSelection(mDefaultKeySsb, 0); 1223 } 1224 1225 /** 1226 * Restores the previous state, if it exists. 1227 * 1228 * @param savedState The previous state. 1229 */ 1230 private void restoreState(Bundle savedState) { 1231 if (savedState == null) { 1232 return; 1233 } 1234 1235 int stateOrdinal = savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()); 1236 State[] stateValues = State.values(); 1237 State state = (stateOrdinal >= 0 && stateOrdinal < stateValues.length) 1238 ? stateValues[stateOrdinal] : State.WORKSPACE; 1239 if (state == State.APPS || state == State.WIDGETS) { 1240 mOnResumeState = state; 1241 } 1242 1243 PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS); 1244 if (requestArgs != null) { 1245 setWaitingForResult(requestArgs); 1246 } 1247 1248 mPendingActivityResult = savedState.getParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT); 1249 } 1250 1251 /** 1252 * Finds all the views we need and configure them properly. 1253 */ 1254 private void setupViews() { 1255 mDragLayer = (DragLayer) findViewById(R.id.drag_layer); 1256 mFocusHandler = mDragLayer.getFocusIndicatorHelper(); 1257 mWorkspace = mDragLayer.findViewById(R.id.workspace); 1258 mWorkspace.initParentViews(mDragLayer); 1259 1260 mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 1261 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 1262 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 1263 1264 // Setup the drag layer 1265 mDragLayer.setup(this, mDragController, mAllAppsController); 1266 1267 // Setup the hotseat 1268 mHotseat = (Hotseat) findViewById(R.id.hotseat); 1269 if (mHotseat != null) { 1270 mHotseat.setOnLongClickListener(this); 1271 } 1272 1273 // Setup the overview panel 1274 setupOverviewPanel(); 1275 1276 // Setup the workspace 1277 mWorkspace.setHapticFeedbackEnabled(false); 1278 mWorkspace.setOnLongClickListener(this); 1279 mWorkspace.setup(mDragController); 1280 // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the 1281 // default state, otherwise we will update to the wrong offsets in RTL 1282 mWorkspace.lockWallpaperToDefaultPage(); 1283 mWorkspace.bindAndInitFirstWorkspaceScreen(null /* recycled qsb */); 1284 mDragController.addDragListener(mWorkspace); 1285 1286 // Get the search/delete/uninstall bar 1287 mDropTargetBar = mDragLayer.findViewById(R.id.drop_target_bar); 1288 1289 // Setup Apps and Widgets 1290 mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view); 1291 mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view); 1292 1293 // Setup the drag controller (drop targets have to be added in reverse order in priority) 1294 mDragController.setMoveTarget(mWorkspace); 1295 mDragController.addDropTarget(mWorkspace); 1296 mDropTargetBar.setup(mDragController); 1297 1298 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { 1299 mAllAppsController.setupViews(mAppsView, mHotseat, mWorkspace); 1300 } 1301 1302 if (TestingUtils.MEMORY_DUMP_ENABLED) { 1303 TestingUtils.addWeightWatcher(this); 1304 } 1305 } 1306 1307 private void setupOverviewPanel() { 1308 mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel); 1309 1310 // Bind wallpaper button actions 1311 View wallpaperButton = findViewById(R.id.wallpaper_button); 1312 new OverviewButtonClickListener(ControlType.WALLPAPER_BUTTON) { 1313 @Override 1314 public void handleViewClick(View view) { 1315 onClickWallpaperPicker(view); 1316 } 1317 }.attachTo(wallpaperButton); 1318 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener()); 1319 1320 // Bind widget button actions 1321 mWidgetsButton = findViewById(R.id.widget_button); 1322 new OverviewButtonClickListener(ControlType.WIDGETS_BUTTON) { 1323 @Override 1324 public void handleViewClick(View view) { 1325 onClickAddWidgetButton(view); 1326 } 1327 }.attachTo(mWidgetsButton); 1328 mWidgetsButton.setOnTouchListener(getHapticFeedbackTouchListener()); 1329 1330 // Bind settings actions 1331 View settingsButton = findViewById(R.id.settings_button); 1332 boolean hasSettings = hasSettings(); 1333 if (hasSettings) { 1334 new OverviewButtonClickListener(ControlType.SETTINGS_BUTTON) { 1335 @Override 1336 public void handleViewClick(View view) { 1337 onClickSettingsButton(view); 1338 } 1339 }.attachTo(settingsButton); 1340 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener()); 1341 } else { 1342 settingsButton.setVisibility(View.GONE); 1343 } 1344 1345 mOverviewPanel.setAlpha(0f); 1346 } 1347 1348 /** 1349 * Sets the all apps button. This method is called from {@link Hotseat}. 1350 * TODO: Get rid of this. 1351 */ 1352 public void setAllAppsButton(View allAppsButton) { 1353 mAllAppsButton = allAppsButton; 1354 } 1355 1356 public View getStartViewForAllAppsRevealAnimation() { 1357 return FeatureFlags.NO_ALL_APPS_ICON ? mWorkspace.getPageIndicator() : mAllAppsButton; 1358 } 1359 1360 public View getWidgetsButton() { 1361 return mWidgetsButton; 1362 } 1363 1364 /** 1365 * Creates a view representing a shortcut. 1366 * 1367 * @param info The data structure describing the shortcut. 1368 */ 1369 View createShortcut(ShortcutInfo info) { 1370 return createShortcut((ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info); 1371 } 1372 1373 /** 1374 * Creates a view representing a shortcut inflated from the specified resource. 1375 * 1376 * @param parent The group the shortcut belongs to. 1377 * @param info The data structure describing the shortcut. 1378 * 1379 * @return A View inflated from layoutResId. 1380 */ 1381 public View createShortcut(ViewGroup parent, ShortcutInfo info) { 1382 BubbleTextView favorite = (BubbleTextView) LayoutInflater.from(parent.getContext()) 1383 .inflate(R.layout.app_icon, parent, false); 1384 favorite.applyFromShortcutInfo(info); 1385 favorite.setOnClickListener(this); 1386 favorite.setOnFocusChangeListener(mFocusHandler); 1387 return favorite; 1388 } 1389 1390 /** 1391 * Add a shortcut to the workspace or to a Folder. 1392 * 1393 * @param data The intent describing the shortcut. 1394 */ 1395 private void completeAddShortcut(Intent data, long container, long screenId, int cellX, 1396 int cellY, PendingRequestArgs args) { 1397 if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT 1398 || args.getPendingIntent().getComponent() == null) { 1399 return; 1400 } 1401 1402 int[] cellXY = mTmpAddItemCellCoordinates; 1403 CellLayout layout = getCellLayout(container, screenId); 1404 1405 ShortcutInfo info = null; 1406 if (Utilities.isAtLeastO()) { 1407 info = LauncherAppsCompatVO.createShortcutInfoFromPinItemRequest( 1408 this, LauncherAppsCompatVO.getPinItemRequest(data), 0); 1409 } 1410 1411 if (info == null) { 1412 // Legacy shortcuts are only supported for primary profile. 1413 info = Process.myUserHandle().equals(args.user) 1414 ? InstallShortcutReceiver.fromShortcutIntent(this, data) : null; 1415 1416 if (info == null) { 1417 Log.e(TAG, "Unable to parse a valid custom shortcut result"); 1418 return; 1419 } else if (!new PackageManagerHelper(this).hasPermissionForActivity( 1420 info.intent, args.getPendingIntent().getComponent().getPackageName())) { 1421 // The app is trying to add a shortcut without sufficient permissions 1422 Log.e(TAG, "Ignoring malicious intent " + info.intent.toUri(0)); 1423 return; 1424 } 1425 } 1426 1427 if (container < 0) { 1428 // Adding a shortcut to the Workspace. 1429 final View view = createShortcut(info); 1430 boolean foundCellSpan = false; 1431 // First we check if we already know the exact location where we want to add this item. 1432 if (cellX >= 0 && cellY >= 0) { 1433 cellXY[0] = cellX; 1434 cellXY[1] = cellY; 1435 foundCellSpan = true; 1436 1437 // If appropriate, either create a folder or add to an existing folder 1438 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0, 1439 true, null, null)) { 1440 return; 1441 } 1442 DragObject dragObject = new DragObject(); 1443 dragObject.dragInfo = info; 1444 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject, 1445 true)) { 1446 return; 1447 } 1448 } else { 1449 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1); 1450 } 1451 1452 if (!foundCellSpan) { 1453 mWorkspace.onNoCellFound(layout); 1454 return; 1455 } 1456 1457 getModelWriter().addItemToDatabase(info, container, screenId, cellXY[0], cellXY[1]); 1458 mWorkspace.addInScreen(view, info); 1459 } else { 1460 // Adding a shortcut to a Folder. 1461 final long folderIconId = container; 1462 FolderIcon folderIcon = (FolderIcon) mWorkspace.getFirstMatch(new ItemOperator() { 1463 @Override 1464 public boolean evaluate(ItemInfo info, View view) { 1465 return info != null && info.id == folderIconId; 1466 } 1467 }); 1468 1469 if (folderIcon != null) { 1470 FolderInfo folderInfo = (FolderInfo) folderIcon.getTag(); 1471 folderInfo.add(info, args.rank, false); 1472 } else { 1473 Log.e(TAG, "Could not find folder with id " + folderIconId + " to add shortcut."); 1474 } 1475 } 1476 } 1477 1478 /** 1479 * Add a widget to the workspace. 1480 * 1481 * @param appWidgetId The app widget id 1482 */ 1483 @Thunk void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo, 1484 AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) { 1485 1486 if (appWidgetInfo == null) { 1487 appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId); 1488 } 1489 1490 if (appWidgetInfo.isCustomWidget) { 1491 appWidgetId = LauncherAppWidgetInfo.CUSTOM_WIDGET_ID; 1492 } 1493 1494 LauncherAppWidgetInfo launcherInfo; 1495 launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider); 1496 launcherInfo.spanX = itemInfo.spanX; 1497 launcherInfo.spanY = itemInfo.spanY; 1498 launcherInfo.minSpanX = itemInfo.minSpanX; 1499 launcherInfo.minSpanY = itemInfo.minSpanY; 1500 launcherInfo.user = appWidgetInfo.getUser(); 1501 1502 getModelWriter().addItemToDatabase(launcherInfo, 1503 itemInfo.container, itemInfo.screenId, itemInfo.cellX, itemInfo.cellY); 1504 1505 if (hostView == null) { 1506 // Perform actual inflation because we're live 1507 hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); 1508 } 1509 hostView.setVisibility(View.VISIBLE); 1510 prepareAppWidget(hostView, launcherInfo); 1511 mWorkspace.addInScreen(hostView, launcherInfo); 1512 } 1513 1514 private void prepareAppWidget(AppWidgetHostView hostView, LauncherAppWidgetInfo item) { 1515 hostView.setTag(item); 1516 item.onBindAppWidget(this, hostView); 1517 hostView.setFocusable(true); 1518 hostView.setOnFocusChangeListener(mFocusHandler); 1519 } 1520 1521 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 1522 @Override 1523 public void onReceive(Context context, Intent intent) { 1524 final String action = intent.getAction(); 1525 if (Intent.ACTION_SCREEN_OFF.equals(action)) { 1526 mDragLayer.clearResizeFrame(); 1527 1528 // Reset AllApps to its initial state only if we are not in the middle of 1529 // processing a multi-step drop 1530 if (mAppsView != null && mWidgetsView != null && mPendingRequestArgs == null) { 1531 if (!showWorkspace(false)) { 1532 // If we are already on the workspace, then manually reset all apps 1533 mAppsView.reset(); 1534 } 1535 } 1536 mIsResumeFromActionScreenOff = true; 1537 } 1538 } 1539 }; 1540 1541 public void updateIconBadges(final Set<PackageUserKey> updatedBadges) { 1542 Runnable r = new Runnable() { 1543 @Override 1544 public void run() { 1545 mWorkspace.updateIconBadges(updatedBadges); 1546 mAppsView.updateIconBadges(updatedBadges); 1547 1548 PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(Launcher.this); 1549 if (popup != null) { 1550 popup.updateNotificationHeader(updatedBadges); 1551 } 1552 } 1553 }; 1554 if (!waitUntilResume(r)) { 1555 r.run(); 1556 } 1557 } 1558 1559 @Override 1560 public void onAttachedToWindow() { 1561 super.onAttachedToWindow(); 1562 1563 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView()); 1564 if (mLauncherCallbacks != null) { 1565 mLauncherCallbacks.onAttachedToWindow(); 1566 } 1567 } 1568 1569 @Override 1570 public void onDetachedFromWindow() { 1571 super.onDetachedFromWindow(); 1572 1573 if (mLauncherCallbacks != null) { 1574 mLauncherCallbacks.onDetachedFromWindow(); 1575 } 1576 } 1577 1578 public void onWindowVisibilityChanged(int visibility) { 1579 // The following code used to be in onResume, but it turns out onResume is called when 1580 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged 1581 // is a more appropriate event to handle 1582 if (visibility == View.VISIBLE) { 1583 if (!mWorkspaceLoading) { 1584 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver(); 1585 // We want to let Launcher draw itself at least once before we force it to build 1586 // layers on all the workspace pages, so that transitioning to Launcher from other 1587 // apps is nice and speedy. 1588 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() { 1589 private boolean mStarted = false; 1590 public void onDraw() { 1591 if (mStarted) return; 1592 mStarted = true; 1593 // We delay the layer building a bit in order to give 1594 // other message processing a time to run. In particular 1595 // this avoids a delay in hiding the IME if it was 1596 // currently shown, because doing that may involve 1597 // some communication back with the app. 1598 mWorkspace.postDelayed(mBuildLayersRunnable, 500); 1599 final ViewTreeObserver.OnDrawListener listener = this; 1600 mWorkspace.post(new Runnable() { 1601 public void run() { 1602 if (mWorkspace != null && 1603 mWorkspace.getViewTreeObserver() != null) { 1604 mWorkspace.getViewTreeObserver(). 1605 removeOnDrawListener(listener); 1606 } 1607 } 1608 }); 1609 } 1610 }); 1611 } 1612 clearTypedText(); 1613 } 1614 } 1615 1616 public DragLayer getDragLayer() { 1617 return mDragLayer; 1618 } 1619 1620 public AllAppsContainerView getAppsView() { 1621 return mAppsView; 1622 } 1623 1624 public WidgetsContainerView getWidgetsView() { 1625 return mWidgetsView; 1626 } 1627 1628 public Workspace getWorkspace() { 1629 return mWorkspace; 1630 } 1631 1632 public Hotseat getHotseat() { 1633 return mHotseat; 1634 } 1635 1636 public ViewGroup getOverviewPanel() { 1637 return mOverviewPanel; 1638 } 1639 1640 public DropTargetBar getDropTargetBar() { 1641 return mDropTargetBar; 1642 } 1643 1644 public LauncherAppWidgetHost getAppWidgetHost() { 1645 return mAppWidgetHost; 1646 } 1647 1648 public LauncherModel getModel() { 1649 return mModel; 1650 } 1651 1652 public ModelWriter getModelWriter() { 1653 return mModelWriter; 1654 } 1655 1656 public SharedPreferences getSharedPrefs() { 1657 return mSharedPrefs; 1658 } 1659 1660 @Override 1661 protected void onNewIntent(Intent intent) { 1662 long startTime = 0; 1663 if (DEBUG_RESUME_TIME) { 1664 startTime = System.currentTimeMillis(); 1665 } 1666 super.onNewIntent(intent); 1667 1668 boolean alreadyOnHome = mHasFocus && ((intent.getFlags() & 1669 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) 1670 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); 1671 1672 // Check this condition before handling isActionMain, as this will get reset. 1673 boolean shouldMoveToDefaultScreen = alreadyOnHome && 1674 mState == State.WORKSPACE && AbstractFloatingView.getTopOpenView(this) == null; 1675 1676 boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction()); 1677 if (isActionMain) { 1678 if (mWorkspace == null) { 1679 // Can be cases where mWorkspace is null, this prevents a NPE 1680 return; 1681 } 1682 1683 // Note: There should be at most one log per method call. This is enforced implicitly 1684 // by using if-else statements. 1685 UserEventDispatcher ued = getUserEventDispatcher(); 1686 1687 // TODO: Log this case. 1688 mWorkspace.exitWidgetResizeMode(); 1689 1690 AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(this); 1691 if (topOpenView instanceof PopupContainerWithArrow) { 1692 ued.logActionCommand(Action.Command.HOME_INTENT, 1693 topOpenView.getExtendedTouchView(), ContainerType.DEEPSHORTCUTS); 1694 } else if (topOpenView instanceof Folder) { 1695 ued.logActionCommand(Action.Command.HOME_INTENT, 1696 ((Folder) topOpenView).getFolderIcon(), ContainerType.FOLDER); 1697 } else if (alreadyOnHome) { 1698 ued.logActionCommand(Action.Command.HOME_INTENT, 1699 mWorkspace.getState().containerType, mWorkspace.getCurrentPage()); 1700 } 1701 1702 // In all these cases, only animate if we're already on home 1703 AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome); 1704 exitSpringLoadedDragMode(); 1705 1706 // If we are already on home, then just animate back to the workspace, 1707 // otherwise, just wait until onResume to set the state back to Workspace 1708 if (alreadyOnHome) { 1709 showWorkspace(true); 1710 } else { 1711 mOnResumeState = State.WORKSPACE; 1712 } 1713 1714 final View v = getWindow().peekDecorView(); 1715 if (v != null && v.getWindowToken() != null) { 1716 InputMethodManager imm = (InputMethodManager) getSystemService( 1717 INPUT_METHOD_SERVICE); 1718 imm.hideSoftInputFromWindow(v.getWindowToken(), 0); 1719 } 1720 1721 // Reset the apps view 1722 if (!alreadyOnHome && mAppsView != null) { 1723 mAppsView.reset(); 1724 } 1725 1726 // Reset the widgets view 1727 if (!alreadyOnHome && mWidgetsView != null) { 1728 mWidgetsView.scrollToTop(); 1729 } 1730 1731 if (mLauncherCallbacks != null) { 1732 mLauncherCallbacks.onHomeIntent(); 1733 } 1734 } 1735 PinItemDragListener.handleDragRequest(this, intent); 1736 1737 if (mLauncherCallbacks != null) { 1738 mLauncherCallbacks.onNewIntent(intent); 1739 } 1740 1741 // Defer moving to the default screen until after we callback to the LauncherCallbacks 1742 // as slow logic in the callbacks eat into the time the scroller expects for the snapToPage 1743 // animation. 1744 if (isActionMain) { 1745 boolean callbackAllowsMoveToDefaultScreen = 1746 mLauncherCallbacks == null || mLauncherCallbacks 1747 .shouldMoveToDefaultScreenOnHomeIntent(); 1748 if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive() 1749 && callbackAllowsMoveToDefaultScreen) { 1750 1751 // We use this flag to suppress noisy callbacks above custom content state 1752 // from onResume. 1753 mMoveToDefaultScreenFromNewIntent = true; 1754 mWorkspace.post(new Runnable() { 1755 @Override 1756 public void run() { 1757 if (mWorkspace != null) { 1758 mWorkspace.moveToDefaultScreen(true); 1759 } 1760 } 1761 }); 1762 } 1763 } 1764 1765 if (DEBUG_RESUME_TIME) { 1766 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime)); 1767 } 1768 } 1769 1770 @Override 1771 public void onRestoreInstanceState(Bundle state) { 1772 super.onRestoreInstanceState(state); 1773 for (int page: mSynchronouslyBoundPages) { 1774 mWorkspace.restoreInstanceStateForChild(page); 1775 } 1776 } 1777 1778 @Override 1779 protected void onSaveInstanceState(Bundle outState) { 1780 if (mWorkspace.getChildCount() > 0) { 1781 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, 1782 mWorkspace.getCurrentPageOffsetFromCustomContent()); 1783 1784 } 1785 super.onSaveInstanceState(outState); 1786 1787 outState.putInt(RUNTIME_STATE, mState.ordinal()); 1788 // We close any open folders and shortcut containers since they will not be re-opened, 1789 // and we need to make sure this state is reflected. 1790 AbstractFloatingView.closeAllOpenViews(this, false); 1791 1792 if (mPendingRequestArgs != null) { 1793 outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs); 1794 } 1795 if (mPendingActivityResult != null) { 1796 outState.putParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT, mPendingActivityResult); 1797 } 1798 1799 if (mLauncherCallbacks != null) { 1800 mLauncherCallbacks.onSaveInstanceState(outState); 1801 } 1802 } 1803 1804 @Override 1805 public void onDestroy() { 1806 super.onDestroy(); 1807 1808 unregisterReceiver(mReceiver); 1809 mWorkspace.removeCallbacks(mBuildLayersRunnable); 1810 mWorkspace.removeFolderListeners(); 1811 1812 // Stop callbacks from LauncherModel 1813 // It's possible to receive onDestroy after a new Launcher activity has 1814 // been created. In this case, don't interfere with the new Launcher. 1815 if (mModel.isCurrentCallbacks(this)) { 1816 mModel.stopLoader(); 1817 LauncherAppState.getInstance(this).setLauncher(null); 1818 } 1819 1820 if (mRotationPrefChangeHandler != null) { 1821 mSharedPrefs.unregisterOnSharedPreferenceChangeListener(mRotationPrefChangeHandler); 1822 } 1823 1824 try { 1825 mAppWidgetHost.stopListening(); 1826 } catch (NullPointerException ex) { 1827 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex); 1828 } 1829 mAppWidgetHost = null; 1830 1831 TextKeyListener.getInstance().release(); 1832 1833 ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE)) 1834 .removeAccessibilityStateChangeListener(this); 1835 1836 WallpaperColorInfo.getInstance(this).setOnThemeChangeListener(null); 1837 1838 LauncherAnimUtils.onDestroyActivity(); 1839 1840 if (mLauncherCallbacks != null) { 1841 mLauncherCallbacks.onDestroy(); 1842 } 1843 } 1844 1845 public LauncherAccessibilityDelegate getAccessibilityDelegate() { 1846 return mAccessibilityDelegate; 1847 } 1848 1849 public DragController getDragController() { 1850 return mDragController; 1851 } 1852 1853 @Override 1854 public void startActivityForResult(Intent intent, int requestCode, Bundle options) { 1855 super.startActivityForResult(intent, requestCode, options); 1856 } 1857 1858 @Override 1859 public void startIntentSenderForResult (IntentSender intent, int requestCode, 1860 Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) { 1861 try { 1862 super.startIntentSenderForResult(intent, requestCode, 1863 fillInIntent, flagsMask, flagsValues, extraFlags, options); 1864 } catch (IntentSender.SendIntentException e) { 1865 throw new ActivityNotFoundException(); 1866 } 1867 } 1868 1869 /** 1870 * Indicates that we want global search for this activity by setting the globalSearch 1871 * argument for {@link #startSearch} to true. 1872 */ 1873 @Override 1874 public void startSearch(String initialQuery, boolean selectInitialQuery, 1875 Bundle appSearchData, boolean globalSearch) { 1876 1877 if (initialQuery == null) { 1878 // Use any text typed in the launcher as the initial query 1879 initialQuery = getTypedText(); 1880 } 1881 if (appSearchData == null) { 1882 appSearchData = new Bundle(); 1883 appSearchData.putString("source", "launcher-search"); 1884 } 1885 1886 if (mLauncherCallbacks == null || 1887 !mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData)) { 1888 // Starting search from the callbacks failed. Start the default global search. 1889 startGlobalSearch(initialQuery, selectInitialQuery, appSearchData, null); 1890 } 1891 1892 // We need to show the workspace after starting the search 1893 showWorkspace(true); 1894 } 1895 1896 /** 1897 * Starts the global search activity. This code is a copied from SearchManager 1898 */ 1899 public void startGlobalSearch(String initialQuery, 1900 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) { 1901 final SearchManager searchManager = 1902 (SearchManager) getSystemService(Context.SEARCH_SERVICE); 1903 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity(); 1904 if (globalSearchActivity == null) { 1905 Log.w(TAG, "No global search activity found."); 1906 return; 1907 } 1908 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH); 1909 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1910 intent.setComponent(globalSearchActivity); 1911 // Make sure that we have a Bundle to put source in 1912 if (appSearchData == null) { 1913 appSearchData = new Bundle(); 1914 } else { 1915 appSearchData = new Bundle(appSearchData); 1916 } 1917 // Set source to package name of app that starts global search if not set already. 1918 if (!appSearchData.containsKey("source")) { 1919 appSearchData.putString("source", getPackageName()); 1920 } 1921 intent.putExtra(SearchManager.APP_DATA, appSearchData); 1922 if (!TextUtils.isEmpty(initialQuery)) { 1923 intent.putExtra(SearchManager.QUERY, initialQuery); 1924 } 1925 if (selectInitialQuery) { 1926 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery); 1927 } 1928 intent.setSourceBounds(sourceBounds); 1929 try { 1930 startActivity(intent); 1931 } catch (ActivityNotFoundException ex) { 1932 Log.e(TAG, "Global search activity not found: " + globalSearchActivity); 1933 } 1934 } 1935 1936 public boolean isOnCustomContent() { 1937 return mWorkspace.isOnOrMovingToCustomContent(); 1938 } 1939 1940 @Override 1941 public boolean onPrepareOptionsMenu(Menu menu) { 1942 super.onPrepareOptionsMenu(menu); 1943 if (mLauncherCallbacks != null) { 1944 return mLauncherCallbacks.onPrepareOptionsMenu(menu); 1945 } 1946 return false; 1947 } 1948 1949 @Override 1950 public boolean onSearchRequested() { 1951 startSearch(null, false, null, true); 1952 // Use a custom animation for launching search 1953 return true; 1954 } 1955 1956 public boolean isWorkspaceLocked() { 1957 return mWorkspaceLoading || mPendingRequestArgs != null; 1958 } 1959 1960 public boolean isWorkspaceLoading() { 1961 return mWorkspaceLoading; 1962 } 1963 1964 private void setWorkspaceLoading(boolean value) { 1965 boolean isLocked = isWorkspaceLocked(); 1966 mWorkspaceLoading = value; 1967 if (isLocked != isWorkspaceLocked()) { 1968 onWorkspaceLockedChanged(); 1969 } 1970 } 1971 1972 public void setWaitingForResult(PendingRequestArgs args) { 1973 boolean isLocked = isWorkspaceLocked(); 1974 mPendingRequestArgs = args; 1975 if (isLocked != isWorkspaceLocked()) { 1976 onWorkspaceLockedChanged(); 1977 } 1978 } 1979 1980 protected void onWorkspaceLockedChanged() { 1981 if (mLauncherCallbacks != null) { 1982 mLauncherCallbacks.onWorkspaceLockedChanged(); 1983 } 1984 } 1985 1986 void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget, 1987 WidgetAddFlowHandler addFlowHandler) { 1988 if (LOGD) { 1989 Log.d(TAG, "Adding widget from drop"); 1990 } 1991 addAppWidgetImpl(appWidgetId, info, boundWidget, addFlowHandler, 0); 1992 } 1993 1994 void addAppWidgetImpl(int appWidgetId, ItemInfo info, 1995 AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay) { 1996 if (!addFlowHandler.startConfigActivity(this, appWidgetId, info, REQUEST_CREATE_APPWIDGET)) { 1997 // If the configuration flow was not started, add the widget 1998 1999 Runnable onComplete = new Runnable() { 2000 @Override 2001 public void run() { 2002 // Exit spring loaded mode if necessary after adding the widget 2003 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, 2004 null); 2005 } 2006 }; 2007 completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this)); 2008 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false); 2009 } 2010 } 2011 2012 protected void moveToCustomContentScreen(boolean animate) { 2013 // Close any folders that may be open. 2014 AbstractFloatingView.closeAllOpenViews(this, animate); 2015 mWorkspace.moveToCustomContentScreen(animate); 2016 } 2017 2018 public void addPendingItem(PendingAddItemInfo info, long container, long screenId, 2019 int[] cell, int spanX, int spanY) { 2020 info.container = container; 2021 info.screenId = screenId; 2022 if (cell != null) { 2023 info.cellX = cell[0]; 2024 info.cellY = cell[1]; 2025 } 2026 info.spanX = spanX; 2027 info.spanY = spanY; 2028 2029 switch (info.itemType) { 2030 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: 2031 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 2032 addAppWidgetFromDrop((PendingAddWidgetInfo) info); 2033 break; 2034 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 2035 processShortcutFromDrop((PendingAddShortcutInfo) info); 2036 break; 2037 default: 2038 throw new IllegalStateException("Unknown item type: " + info.itemType); 2039 } 2040 } 2041 2042 /** 2043 * Process a shortcut drop. 2044 */ 2045 private void processShortcutFromDrop(PendingAddShortcutInfo info) { 2046 Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(info.componentName); 2047 setWaitingForResult(PendingRequestArgs.forIntent(REQUEST_CREATE_SHORTCUT, intent, info)); 2048 if (!info.activityInfo.startConfigActivity(this, REQUEST_CREATE_SHORTCUT)) { 2049 handleActivityResult(REQUEST_CREATE_SHORTCUT, RESULT_CANCELED, null); 2050 } 2051 } 2052 2053 /** 2054 * Process a widget drop. 2055 */ 2056 private void addAppWidgetFromDrop(PendingAddWidgetInfo info) { 2057 AppWidgetHostView hostView = info.boundWidget; 2058 int appWidgetId; 2059 WidgetAddFlowHandler addFlowHandler = info.getHandler(); 2060 if (hostView != null) { 2061 // In the case where we've prebound the widget, we remove it from the DragLayer 2062 if (LOGD) { 2063 Log.d(TAG, "Removing widget view from drag layer and setting boundWidget to null"); 2064 } 2065 getDragLayer().removeView(hostView); 2066 2067 appWidgetId = hostView.getAppWidgetId(); 2068 addAppWidgetFromDropImpl(appWidgetId, info, hostView, addFlowHandler); 2069 2070 // Clear the boundWidget so that it doesn't get destroyed. 2071 info.boundWidget = null; 2072 } else { 2073 // In this case, we either need to start an activity to get permission to bind 2074 // the widget, or we need to start an activity to configure the widget, or both. 2075 appWidgetId = getAppWidgetHost().allocateAppWidgetId(); 2076 Bundle options = info.bindOptions; 2077 2078 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( 2079 appWidgetId, info.info, options); 2080 if (success) { 2081 addAppWidgetFromDropImpl(appWidgetId, info, null, addFlowHandler); 2082 } else { 2083 addFlowHandler.startBindFlow(this, appWidgetId, info, REQUEST_BIND_APPWIDGET); 2084 } 2085 } 2086 } 2087 2088 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX, 2089 int cellY) { 2090 final FolderInfo folderInfo = new FolderInfo(); 2091 folderInfo.title = getText(R.string.folder_name); 2092 2093 // Update the model 2094 getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY); 2095 2096 // Create the view 2097 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo); 2098 mWorkspace.addInScreen(newFolder, folderInfo); 2099 // Force measure the new folder icon 2100 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder); 2101 parent.getShortcutsAndWidgets().measureChild(newFolder); 2102 return newFolder; 2103 } 2104 2105 /** 2106 * Unbinds the view for the specified item, and removes the item and all its children. 2107 * 2108 * @param v the view being removed. 2109 * @param itemInfo the {@link ItemInfo} for this view. 2110 * @param deleteFromDb whether or not to delete this item from the db. 2111 */ 2112 public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb) { 2113 if (itemInfo instanceof ShortcutInfo) { 2114 // Remove the shortcut from the folder before removing it from launcher 2115 View folderIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container); 2116 if (folderIcon instanceof FolderIcon) { 2117 ((FolderInfo) folderIcon.getTag()).remove((ShortcutInfo) itemInfo, true); 2118 } else { 2119 mWorkspace.removeWorkspaceItem(v); 2120 } 2121 if (deleteFromDb) { 2122 getModelWriter().deleteItemFromDatabase(itemInfo); 2123 } 2124 } else if (itemInfo instanceof FolderInfo) { 2125 final FolderInfo folderInfo = (FolderInfo) itemInfo; 2126 if (v instanceof FolderIcon) { 2127 ((FolderIcon) v).removeListeners(); 2128 } 2129 mWorkspace.removeWorkspaceItem(v); 2130 if (deleteFromDb) { 2131 getModelWriter().deleteFolderAndContentsFromDatabase(folderInfo); 2132 } 2133 } else if (itemInfo instanceof LauncherAppWidgetInfo) { 2134 final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo; 2135 mWorkspace.removeWorkspaceItem(v); 2136 if (deleteFromDb) { 2137 deleteWidgetInfo(widgetInfo); 2138 } 2139 } else { 2140 return false; 2141 } 2142 return true; 2143 } 2144 2145 /** 2146 * Deletes the widget info and the widget id. 2147 */ 2148 private void deleteWidgetInfo(final LauncherAppWidgetInfo widgetInfo) { 2149 final LauncherAppWidgetHost appWidgetHost = getAppWidgetHost(); 2150 if (appWidgetHost != null && !widgetInfo.isCustomWidget() && widgetInfo.isWidgetIdAllocated()) { 2151 // Deleting an app widget ID is a void call but writes to disk before returning 2152 // to the caller... 2153 new AsyncTask<Void, Void, Void>() { 2154 public Void doInBackground(Void ... args) { 2155 appWidgetHost.deleteAppWidgetId(widgetInfo.appWidgetId); 2156 return null; 2157 } 2158 }.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR); 2159 } 2160 getModelWriter().deleteItemFromDatabase(widgetInfo); 2161 } 2162 2163 @Override 2164 public boolean dispatchKeyEvent(KeyEvent event) { 2165 return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event); 2166 } 2167 2168 @Override 2169 public void onBackPressed() { 2170 if (mLauncherCallbacks != null && mLauncherCallbacks.handleBackPressed()) { 2171 return; 2172 } 2173 2174 if (mDragController.isDragging()) { 2175 mDragController.cancelDrag(); 2176 return; 2177 } 2178 2179 // Note: There should be at most one log per method call. This is enforced implicitly 2180 // by using if-else statements. 2181 UserEventDispatcher ued = getUserEventDispatcher(); 2182 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this); 2183 if (topView != null) { 2184 if (topView.getActiveTextView() != null) { 2185 topView.getActiveTextView().dispatchBackKey(); 2186 } else { 2187 if (topView instanceof PopupContainerWithArrow) { 2188 ued.logActionCommand(Action.Command.BACK, 2189 topView.getExtendedTouchView(), ContainerType.DEEPSHORTCUTS); 2190 } else if (topView instanceof Folder) { 2191 ued.logActionCommand(Action.Command.BACK, 2192 ((Folder) topView).getFolderIcon(), ContainerType.FOLDER); 2193 } 2194 topView.close(true); 2195 } 2196 } else if (isAppsViewVisible()) { 2197 ued.logActionCommand(Action.Command.BACK, ContainerType.ALLAPPS); 2198 showWorkspace(true); 2199 } else if (isWidgetsViewVisible()) { 2200 ued.logActionCommand(Action.Command.BACK, ContainerType.WIDGETS); 2201 showOverviewMode(true); 2202 } else if (mWorkspace.isInOverviewMode()) { 2203 ued.logActionCommand(Action.Command.BACK, ContainerType.OVERVIEW); 2204 showWorkspace(true); 2205 } else { 2206 // TODO: Log this case. 2207 mWorkspace.exitWidgetResizeMode(); 2208 2209 // Back button is a no-op here, but give at least some feedback for the button press 2210 mWorkspace.showOutlinesTemporarily(); 2211 } 2212 } 2213 2214 /** 2215 * Launches the intent referred by the clicked shortcut. 2216 * 2217 * @param v The view representing the clicked shortcut. 2218 */ 2219 public void onClick(View v) { 2220 // Make sure that rogue clicks don't get through while allapps is launching, or after the 2221 // view has detached (it's possible for this to happen if the view is removed mid touch). 2222 if (v.getWindowToken() == null) { 2223 return; 2224 } 2225 2226 if (!mWorkspace.isFinishedSwitchingState()) { 2227 return; 2228 } 2229 2230 if (v instanceof Workspace) { 2231 if (mWorkspace.isInOverviewMode()) { 2232 getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH, 2233 LauncherLogProto.Action.Direction.NONE, 2234 LauncherLogProto.ContainerType.OVERVIEW, mWorkspace.getCurrentPage()); 2235 showWorkspace(true); 2236 } 2237 return; 2238 } 2239 2240 if (v instanceof CellLayout) { 2241 if (mWorkspace.isInOverviewMode()) { 2242 int page = mWorkspace.indexOfChild(v); 2243 getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH, 2244 LauncherLogProto.Action.Direction.NONE, 2245 LauncherLogProto.ContainerType.OVERVIEW, page); 2246 mWorkspace.snapToPageFromOverView(page); 2247 showWorkspace(true); 2248 } 2249 return; 2250 } 2251 2252 Object tag = v.getTag(); 2253 if (tag instanceof ShortcutInfo) { 2254 onClickAppShortcut(v); 2255 } else if (tag instanceof FolderInfo) { 2256 if (v instanceof FolderIcon) { 2257 onClickFolderIcon(v); 2258 } 2259 } else if ((FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && v instanceof PageIndicator) || 2260 (v == mAllAppsButton && mAllAppsButton != null)) { 2261 onClickAllAppsButton(v); 2262 } else if (tag instanceof AppInfo) { 2263 startAppShortcutOrInfoActivity(v); 2264 } else if (tag instanceof LauncherAppWidgetInfo) { 2265 if (v instanceof PendingAppWidgetHostView) { 2266 onClickPendingWidget((PendingAppWidgetHostView) v); 2267 } 2268 } 2269 } 2270 2271 @SuppressLint("ClickableViewAccessibility") 2272 public boolean onTouch(View v, MotionEvent event) { 2273 return false; 2274 } 2275 2276 /** 2277 * Event handler for the app widget view which has not fully restored. 2278 */ 2279 public void onClickPendingWidget(final PendingAppWidgetHostView v) { 2280 if (mIsSafeModeEnabled) { 2281 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show(); 2282 return; 2283 } 2284 2285 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag(); 2286 if (v.isReadyForClickSetup()) { 2287 LauncherAppWidgetProviderInfo appWidgetInfo = 2288 mAppWidgetManager.findProvider(info.providerName, info.user); 2289 if (appWidgetInfo == null) { 2290 return; 2291 } 2292 WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo); 2293 2294 if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) { 2295 if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) { 2296 // This should not happen, as we make sure that an Id is allocated during bind. 2297 return; 2298 } 2299 addFlowHandler.startBindFlow(this, info.appWidgetId, info, 2300 REQUEST_BIND_PENDING_APPWIDGET); 2301 } else { 2302 addFlowHandler.startConfigActivity(this, info, REQUEST_RECONFIGURE_APPWIDGET); 2303 } 2304 } else { 2305 final String packageName = info.providerName.getPackageName(); 2306 onClickPendingAppItem(v, packageName, info.installProgress >= 0); 2307 } 2308 } 2309 2310 /** 2311 * Event handler for the "grid" button or "caret" that appears on the home screen, which 2312 * enters all apps mode. In verticalBarLayout the caret can be seen when all apps is open, and 2313 * so in that case reverses the action. 2314 * 2315 * @param v The view that was clicked. 2316 */ 2317 protected void onClickAllAppsButton(View v) { 2318 if (LOGD) Log.d(TAG, "onClickAllAppsButton"); 2319 if (!isAppsViewVisible()) { 2320 getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, 2321 ControlType.ALL_APPS_BUTTON); 2322 showAppsView(true /* animated */, true /* updatePredictedApps */); 2323 } else { 2324 showWorkspace(true); 2325 } 2326 } 2327 2328 private void onClickPendingAppItem(final View v, final String packageName, 2329 boolean downloadStarted) { 2330 if (downloadStarted) { 2331 // If the download has started, simply direct to the market app. 2332 startMarketIntentForPackage(v, packageName); 2333 return; 2334 } 2335 new AlertDialog.Builder(this) 2336 .setTitle(R.string.abandoned_promises_title) 2337 .setMessage(R.string.abandoned_promise_explanation) 2338 .setPositiveButton(R.string.abandoned_search, new DialogInterface.OnClickListener() { 2339 @Override 2340 public void onClick(DialogInterface dialogInterface, int i) { 2341 startMarketIntentForPackage(v, packageName); 2342 } 2343 }) 2344 .setNeutralButton(R.string.abandoned_clean_this, 2345 new DialogInterface.OnClickListener() { 2346 public void onClick(DialogInterface dialog, int id) { 2347 final UserHandle user = Process.myUserHandle(); 2348 mWorkspace.removeAbandonedPromise(packageName, user); 2349 } 2350 }) 2351 .create().show(); 2352 } 2353 2354 private void startMarketIntentForPackage(View v, String packageName) { 2355 ItemInfo item = (ItemInfo) v.getTag(); 2356 Intent intent = PackageManagerHelper.getMarketIntent(packageName); 2357 boolean success = startActivitySafely(v, intent, item); 2358 if (success && v instanceof BubbleTextView) { 2359 mWaitingForResume = (BubbleTextView) v; 2360 mWaitingForResume.setStayPressed(true); 2361 } 2362 } 2363 2364 /** 2365 * Event handler for an app shortcut click. 2366 * 2367 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}. 2368 */ 2369 protected void onClickAppShortcut(final View v) { 2370 if (LOGD) Log.d(TAG, "onClickAppShortcut"); 2371 Object tag = v.getTag(); 2372 if (!(tag instanceof ShortcutInfo)) { 2373 throw new IllegalArgumentException("Input must be a Shortcut"); 2374 } 2375 2376 // Open shortcut 2377 final ShortcutInfo shortcut = (ShortcutInfo) tag; 2378 2379 if (shortcut.isDisabled != 0) { 2380 if ((shortcut.isDisabled & 2381 ~ShortcutInfo.FLAG_DISABLED_SUSPENDED & 2382 ~ShortcutInfo.FLAG_DISABLED_QUIET_USER) == 0) { 2383 // If the app is only disabled because of the above flags, launch activity anyway. 2384 // Framework will tell the user why the app is suspended. 2385 } else { 2386 if (!TextUtils.isEmpty(shortcut.disabledMessage)) { 2387 // Use a message specific to this shortcut, if it has one. 2388 Toast.makeText(this, shortcut.disabledMessage, Toast.LENGTH_SHORT).show(); 2389 return; 2390 } 2391 // Otherwise just use a generic error message. 2392 int error = R.string.activity_not_available; 2393 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) { 2394 error = R.string.safemode_shortcut_error; 2395 } else if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_BY_PUBLISHER) != 0 || 2396 (shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_LOCKED_USER) != 0) { 2397 error = R.string.shortcut_not_available; 2398 } 2399 Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); 2400 return; 2401 } 2402 } 2403 2404 // Check for abandoned promise 2405 if ((v instanceof BubbleTextView) && shortcut.isPromise()) { 2406 String packageName = shortcut.intent.getComponent() != null ? 2407 shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage(); 2408 if (!TextUtils.isEmpty(packageName)) { 2409 onClickPendingAppItem(v, packageName, 2410 shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)); 2411 return; 2412 } 2413 } 2414 2415 // Start activities 2416 startAppShortcutOrInfoActivity(v); 2417 } 2418 2419 private void startAppShortcutOrInfoActivity(View v) { 2420 ItemInfo item = (ItemInfo) v.getTag(); 2421 Intent intent; 2422 if (item instanceof PromiseAppInfo) { 2423 PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item; 2424 intent = promiseAppInfo.getMarketIntent(); 2425 } else { 2426 intent = item.getIntent(); 2427 } 2428 if (intent == null) { 2429 throw new IllegalArgumentException("Input must have a valid intent"); 2430 } 2431 boolean success = startActivitySafely(v, intent, item); 2432 getUserEventDispatcher().logAppLaunch(v, intent); // TODO for discovered apps b/35802115 2433 2434 if (success && v instanceof BubbleTextView) { 2435 mWaitingForResume = (BubbleTextView) v; 2436 mWaitingForResume.setStayPressed(true); 2437 } 2438 } 2439 2440 /** 2441 * Event handler for a folder icon click. 2442 * 2443 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}. 2444 */ 2445 protected void onClickFolderIcon(View v) { 2446 if (LOGD) Log.d(TAG, "onClickFolder"); 2447 if (!(v instanceof FolderIcon)){ 2448 throw new IllegalArgumentException("Input must be a FolderIcon"); 2449 } 2450 2451 Folder folder = ((FolderIcon) v).getFolder(); 2452 if (!folder.isOpen() && !folder.isDestroyed()) { 2453 // Open the requested folder 2454 folder.animateOpen(); 2455 } 2456 } 2457 2458 /** 2459 * Event handler for the (Add) Widgets button that appears after a long press 2460 * on the home screen. 2461 */ 2462 public void onClickAddWidgetButton(View view) { 2463 if (LOGD) Log.d(TAG, "onClickAddWidgetButton"); 2464 if (mIsSafeModeEnabled) { 2465 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show(); 2466 } else { 2467 showWidgetsView(true /* animated */, true /* resetPageToZero */); 2468 } 2469 } 2470 2471 /** 2472 * Event handler for the wallpaper picker button that appears after a long press 2473 * on the home screen. 2474 */ 2475 public void onClickWallpaperPicker(View v) { 2476 if (!Utilities.isWallpaperAllowed(this)) { 2477 Toast.makeText(this, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show(); 2478 return; 2479 } 2480 2481 int pageScroll = mWorkspace.getScrollForPage(mWorkspace.getPageNearestToCenterOfScreen()); 2482 float offset = mWorkspace.mWallpaperOffset.wallpaperOffsetForScroll(pageScroll); 2483 setWaitingForResult(new PendingRequestArgs(new ItemInfo())); 2484 Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER) 2485 .putExtra(Utilities.EXTRA_WALLPAPER_OFFSET, offset); 2486 2487 String pickerPackage = getString(R.string.wallpaper_picker_package); 2488 boolean hasTargetPackage = !TextUtils.isEmpty(pickerPackage); 2489 if (hasTargetPackage) { 2490 intent.setPackage(pickerPackage); 2491 } 2492 2493 intent.setSourceBounds(getViewBounds(v)); 2494 try { 2495 startActivityForResult(intent, REQUEST_PICK_WALLPAPER, 2496 // If there is no target package, use the default intent chooser animation 2497 hasTargetPackage ? getActivityLaunchOptions(v) : null); 2498 } catch (ActivityNotFoundException e) { 2499 setWaitingForResult(null); 2500 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 2501 } 2502 } 2503 2504 /** 2505 * Event handler for a click on the settings button that appears after a long press 2506 * on the home screen. 2507 */ 2508 public void onClickSettingsButton(View v) { 2509 if (LOGD) Log.d(TAG, "onClickSettingsButton"); 2510 Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES) 2511 .setPackage(getPackageName()); 2512 intent.setSourceBounds(getViewBounds(v)); 2513 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 2514 startActivity(intent, getActivityLaunchOptions(v)); 2515 } 2516 2517 public View.OnTouchListener getHapticFeedbackTouchListener() { 2518 if (mHapticFeedbackTouchListener == null) { 2519 mHapticFeedbackTouchListener = new View.OnTouchListener() { 2520 @SuppressLint("ClickableViewAccessibility") 2521 @Override 2522 public boolean onTouch(View v, MotionEvent event) { 2523 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { 2524 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 2525 } 2526 return false; 2527 } 2528 }; 2529 } 2530 return mHapticFeedbackTouchListener; 2531 } 2532 2533 @Override 2534 public void onAccessibilityStateChanged(boolean enabled) { 2535 mDragLayer.onAccessibilityStateChanged(enabled); 2536 } 2537 2538 public void onDragStarted() { 2539 if (isOnCustomContent()) { 2540 // Custom content screen doesn't participate in drag and drop. If on custom 2541 // content screen, move to default. 2542 moveWorkspaceToDefaultScreen(); 2543 } 2544 } 2545 2546 /** 2547 * Called when the user stops interacting with the launcher. 2548 * This implies that the user is now on the homescreen and is not doing housekeeping. 2549 */ 2550 protected void onInteractionEnd() { 2551 if (mLauncherCallbacks != null) { 2552 mLauncherCallbacks.onInteractionEnd(); 2553 } 2554 } 2555 2556 /** 2557 * Called when the user starts interacting with the launcher. 2558 * The possible interactions are: 2559 * - open all apps 2560 * - reorder an app shortcut, or a widget 2561 * - open the overview mode. 2562 * This is a good time to stop doing things that only make sense 2563 * when the user is on the homescreen and not doing housekeeping. 2564 */ 2565 protected void onInteractionBegin() { 2566 if (mLauncherCallbacks != null) { 2567 mLauncherCallbacks.onInteractionBegin(); 2568 } 2569 } 2570 2571 /** Updates the interaction state. */ 2572 public void updateInteraction(Workspace.State fromState, Workspace.State toState) { 2573 // Only update the interacting state if we are transitioning to/from a view with an 2574 // overlay 2575 boolean fromStateWithOverlay = fromState != Workspace.State.NORMAL; 2576 boolean toStateWithOverlay = toState != Workspace.State.NORMAL; 2577 if (toStateWithOverlay) { 2578 onInteractionBegin(); 2579 } else if (fromStateWithOverlay) { 2580 onInteractionEnd(); 2581 } 2582 } 2583 2584 private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) { 2585 try { 2586 StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy(); 2587 try { 2588 // Temporarily disable deathPenalty on all default checks. For eg, shortcuts 2589 // containing file Uri's would cause a crash as penaltyDeathOnFileUriExposure 2590 // is enabled by default on NYC. 2591 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll() 2592 .penaltyLog().build()); 2593 2594 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 2595 String id = ((ShortcutInfo) info).getDeepShortcutId(); 2596 String packageName = intent.getPackage(); 2597 DeepShortcutManager.getInstance(this).startShortcut( 2598 packageName, id, intent.getSourceBounds(), optsBundle, info.user); 2599 } else { 2600 // Could be launching some bookkeeping activity 2601 startActivity(intent, optsBundle); 2602 } 2603 } finally { 2604 StrictMode.setVmPolicy(oldPolicy); 2605 } 2606 } catch (SecurityException e) { 2607 // Due to legacy reasons, direct call shortcuts require Launchers to have the 2608 // corresponding permission. Show the appropriate permission prompt if that 2609 // is the case. 2610 if (intent.getComponent() == null 2611 && Intent.ACTION_CALL.equals(intent.getAction()) 2612 && checkSelfPermission(Manifest.permission.CALL_PHONE) != 2613 PackageManager.PERMISSION_GRANTED) { 2614 2615 setWaitingForResult(PendingRequestArgs 2616 .forIntent(REQUEST_PERMISSION_CALL_PHONE, intent, info)); 2617 requestPermissions(new String[]{Manifest.permission.CALL_PHONE}, 2618 REQUEST_PERMISSION_CALL_PHONE); 2619 } else { 2620 // No idea why this was thrown. 2621 throw e; 2622 } 2623 } 2624 } 2625 2626 @TargetApi(Build.VERSION_CODES.M) 2627 public Bundle getActivityLaunchOptions(View v) { 2628 if (Utilities.ATLEAST_MARSHMALLOW) { 2629 int left = 0, top = 0; 2630 int width = v.getMeasuredWidth(), height = v.getMeasuredHeight(); 2631 if (v instanceof BubbleTextView) { 2632 // Launch from center of icon, not entire view 2633 Drawable icon = ((BubbleTextView) v).getIcon(); 2634 if (icon != null) { 2635 Rect bounds = icon.getBounds(); 2636 left = (width - bounds.width()) / 2; 2637 top = v.getPaddingTop(); 2638 width = bounds.width(); 2639 height = bounds.height(); 2640 } 2641 } 2642 return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height).toBundle(); 2643 } else if (Utilities.ATLEAST_LOLLIPOP_MR1) { 2644 // On L devices, we use the device default slide-up transition. 2645 // On L MR1 devices, we use a custom version of the slide-up transition which 2646 // doesn't have the delay present in the device default. 2647 return ActivityOptions.makeCustomAnimation( 2648 this, R.anim.task_open_enter, R.anim.no_anim).toBundle(); 2649 } 2650 return null; 2651 } 2652 2653 public Rect getViewBounds(View v) { 2654 int[] pos = new int[2]; 2655 v.getLocationOnScreen(pos); 2656 return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight()); 2657 } 2658 2659 public boolean startActivitySafely(View v, Intent intent, ItemInfo item) { 2660 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) { 2661 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); 2662 return false; 2663 } 2664 // Only launch using the new animation if the shortcut has not opted out (this is a 2665 // private contract between launcher and may be ignored in the future). 2666 boolean useLaunchAnimation = (v != null) && 2667 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION); 2668 Bundle optsBundle = useLaunchAnimation ? getActivityLaunchOptions(v) : null; 2669 2670 UserHandle user = item == null ? null : item.user; 2671 2672 // Prepare intent 2673 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 2674 if (v != null) { 2675 intent.setSourceBounds(getViewBounds(v)); 2676 } 2677 try { 2678 if (Utilities.ATLEAST_MARSHMALLOW 2679 && (item instanceof ShortcutInfo) 2680 && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT 2681 || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) 2682 && !((ShortcutInfo) item).isPromise()) { 2683 // Shortcuts need some special checks due to legacy reasons. 2684 startShortcutIntentSafely(intent, optsBundle, item); 2685 } else if (user == null || user.equals(Process.myUserHandle())) { 2686 // Could be launching some bookkeeping activity 2687 startActivity(intent, optsBundle); 2688 } else { 2689 LauncherAppsCompat.getInstance(this).startActivityForProfile( 2690 intent.getComponent(), user, intent.getSourceBounds(), optsBundle); 2691 } 2692 return true; 2693 } catch (ActivityNotFoundException|SecurityException e) { 2694 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 2695 Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e); 2696 } 2697 return false; 2698 } 2699 2700 @Override 2701 public boolean dispatchTouchEvent(MotionEvent ev) { 2702 mLastDispatchTouchEventX = ev.getX(); 2703 return super.dispatchTouchEvent(ev); 2704 } 2705 2706 @Override 2707 public boolean onLongClick(View v) { 2708 if (!isDraggingEnabled()) return false; 2709 if (isWorkspaceLocked()) return false; 2710 if (mState != State.WORKSPACE) return false; 2711 2712 boolean ignoreLongPressToOverview = 2713 mDeviceProfile.shouldIgnoreLongPressToOverview(mLastDispatchTouchEventX); 2714 2715 if (v instanceof Workspace) { 2716 if (!mWorkspace.isInOverviewMode()) { 2717 if (!mWorkspace.isTouchActive() && !ignoreLongPressToOverview) { 2718 getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS, 2719 Action.Direction.NONE, ContainerType.WORKSPACE, 2720 mWorkspace.getCurrentPage()); 2721 showOverviewMode(true); 2722 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, 2723 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 2724 return true; 2725 } else { 2726 return false; 2727 } 2728 } else { 2729 return false; 2730 } 2731 } 2732 2733 CellLayout.CellInfo longClickCellInfo = null; 2734 View itemUnderLongClick = null; 2735 if (v.getTag() instanceof ItemInfo) { 2736 ItemInfo info = (ItemInfo) v.getTag(); 2737 longClickCellInfo = new CellLayout.CellInfo(v, info); 2738 itemUnderLongClick = longClickCellInfo.cell; 2739 mPendingRequestArgs = null; 2740 } 2741 2742 // The hotseat touch handling does not go through Workspace, and we always allow long press 2743 // on hotseat items. 2744 if (!mDragController.isDragging()) { 2745 if (itemUnderLongClick == null) { 2746 // User long pressed on empty space 2747 if (mWorkspace.isInOverviewMode()) { 2748 mWorkspace.startReordering(v); 2749 getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS, 2750 Action.Direction.NONE, ContainerType.OVERVIEW); 2751 } else { 2752 if (ignoreLongPressToOverview) { 2753 return false; 2754 } 2755 getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS, 2756 Action.Direction.NONE, ContainerType.WORKSPACE, 2757 mWorkspace.getCurrentPage()); 2758 showOverviewMode(true); 2759 } 2760 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, 2761 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 2762 } else { 2763 final boolean isAllAppsButton = 2764 !FeatureFlags.NO_ALL_APPS_ICON && isHotseatLayout(v) && 2765 mDeviceProfile.inv.isAllAppsButtonRank(mHotseat.getOrderInHotseat( 2766 longClickCellInfo.cellX, longClickCellInfo.cellY)); 2767 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) { 2768 // User long pressed on an item 2769 mWorkspace.startDrag(longClickCellInfo, new DragOptions()); 2770 } 2771 } 2772 } 2773 return true; 2774 } 2775 2776 boolean isHotseatLayout(View layout) { 2777 // TODO: Remove this method 2778 return mHotseat != null && layout != null && 2779 (layout instanceof CellLayout) && (layout == mHotseat.getLayout()); 2780 } 2781 2782 /** 2783 * Returns the CellLayout of the specified container at the specified screen. 2784 */ 2785 public CellLayout getCellLayout(long container, long screenId) { 2786 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 2787 if (mHotseat != null) { 2788 return mHotseat.getLayout(); 2789 } else { 2790 return null; 2791 } 2792 } else { 2793 return mWorkspace.getScreenWithId(screenId); 2794 } 2795 } 2796 2797 /** 2798 * For overridden classes. 2799 */ 2800 public boolean isAllAppsVisible() { 2801 return isAppsViewVisible(); 2802 } 2803 2804 public boolean isAppsViewVisible() { 2805 return (mState == State.APPS) || (mOnResumeState == State.APPS); 2806 } 2807 2808 public boolean isWidgetsViewVisible() { 2809 return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS); 2810 } 2811 2812 @Override 2813 public void onTrimMemory(int level) { 2814 super.onTrimMemory(level); 2815 if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { 2816 // The widget preview db can result in holding onto over 2817 // 3MB of memory for caching which isn't necessary. 2818 SQLiteDatabase.releaseMemory(); 2819 2820 // This clears all widget bitmaps from the widget tray 2821 // TODO(hyunyoungs) 2822 } 2823 if (mLauncherCallbacks != null) { 2824 mLauncherCallbacks.onTrimMemory(level); 2825 } 2826 } 2827 2828 public boolean showWorkspace(boolean animated) { 2829 return showWorkspace(animated, null); 2830 } 2831 2832 public boolean showWorkspace(boolean animated, Runnable onCompleteRunnable) { 2833 boolean changed = mState != State.WORKSPACE || 2834 mWorkspace.getState() != Workspace.State.NORMAL; 2835 if (changed || mAllAppsController.isTransitioning()) { 2836 mWorkspace.setVisibility(View.VISIBLE); 2837 mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(), 2838 Workspace.State.NORMAL, animated, onCompleteRunnable); 2839 2840 // Set focus to the AppsCustomize button 2841 if (mAllAppsButton != null) { 2842 mAllAppsButton.requestFocus(); 2843 } 2844 } 2845 2846 // Change the state *after* we've called all the transition code 2847 setState(State.WORKSPACE); 2848 2849 if (changed) { 2850 // Send an accessibility event to announce the context change 2851 getWindow().getDecorView() 2852 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 2853 } 2854 return changed; 2855 } 2856 2857 /** 2858 * Shows the overview button. 2859 */ 2860 public void showOverviewMode(boolean animated) { 2861 showOverviewMode(animated, false); 2862 } 2863 2864 /** 2865 * Shows the overview button, and if {@param requestButtonFocus} is set, will force the focus 2866 * onto one of the overview panel buttons. 2867 */ 2868 void showOverviewMode(boolean animated, boolean requestButtonFocus) { 2869 Runnable postAnimRunnable = null; 2870 if (requestButtonFocus) { 2871 postAnimRunnable = new Runnable() { 2872 @Override 2873 public void run() { 2874 // Hitting the menu button when in touch mode does not trigger touch mode to 2875 // be disabled, so if requested, force focus on one of the overview panel 2876 // buttons. 2877 mOverviewPanel.requestFocusFromTouch(); 2878 } 2879 }; 2880 } 2881 mWorkspace.setVisibility(View.VISIBLE); 2882 mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(), 2883 Workspace.State.OVERVIEW, animated, postAnimRunnable); 2884 setState(State.WORKSPACE); 2885 2886 // If animated from long press, then don't allow any of the controller in the drag 2887 // layer to intercept any remaining touch. 2888 mWorkspace.requestDisallowInterceptTouchEvent(animated); 2889 } 2890 2891 private void setState(State state) { 2892 this.mState = state; 2893 updateSoftInputMode(); 2894 } 2895 2896 private void updateSoftInputMode() { 2897 if (FeatureFlags.LAUNCHER3_UPDATE_SOFT_INPUT_MODE) { 2898 final int mode; 2899 if (isAppsViewVisible()) { 2900 mode = SOFT_INPUT_MODE_ALL_APPS; 2901 } else { 2902 mode = SOFT_INPUT_MODE_DEFAULT; 2903 } 2904 getWindow().setSoftInputMode(mode); 2905 } 2906 } 2907 2908 /** 2909 * Shows the apps view. 2910 */ 2911 public void showAppsView(boolean animated, boolean updatePredictedApps) { 2912 markAppsViewShown(); 2913 if (updatePredictedApps) { 2914 tryAndUpdatePredictedApps(); 2915 } 2916 showAppsOrWidgets(State.APPS, animated); 2917 } 2918 2919 /** 2920 * Shows the widgets view. 2921 */ 2922 void showWidgetsView(boolean animated, boolean resetPageToZero) { 2923 if (LOGD) Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero); 2924 if (resetPageToZero) { 2925 mWidgetsView.scrollToTop(); 2926 } 2927 showAppsOrWidgets(State.WIDGETS, animated); 2928 2929 mWidgetsView.post(new Runnable() { 2930 @Override 2931 public void run() { 2932 mWidgetsView.requestFocus(); 2933 } 2934 }); 2935 } 2936 2937 /** 2938 * Sets up the transition to show the apps/widgets view. 2939 * 2940 * @return whether the current from and to state allowed this operation 2941 */ 2942 // TODO: calling method should use the return value so that when {@code false} is returned 2943 // the workspace transition doesn't fall into invalid state. 2944 private boolean showAppsOrWidgets(State toState, boolean animated) { 2945 if (!(mState == State.WORKSPACE || 2946 mState == State.APPS_SPRING_LOADED || 2947 mState == State.WIDGETS_SPRING_LOADED || 2948 (mState == State.APPS && mAllAppsController.isTransitioning()))) { 2949 return false; 2950 } 2951 if (toState != State.APPS && toState != State.WIDGETS) { 2952 return false; 2953 } 2954 2955 // This is a safe and supported transition to bypass spring_loaded mode. 2956 if (mExitSpringLoadedModeRunnable != null) { 2957 mHandler.removeCallbacks(mExitSpringLoadedModeRunnable); 2958 mExitSpringLoadedModeRunnable = null; 2959 } 2960 2961 if (toState == State.APPS) { 2962 mStateTransitionAnimation.startAnimationToAllApps(animated); 2963 } else { 2964 mStateTransitionAnimation.startAnimationToWidgets(animated); 2965 } 2966 2967 // Change the state *after* we've called all the transition code 2968 setState(toState); 2969 AbstractFloatingView.closeAllOpenViews(this); 2970 2971 // Send an accessibility event to announce the context change 2972 getWindow().getDecorView() 2973 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 2974 return true; 2975 } 2976 2977 /** 2978 * Updates the workspace and interaction state on state change, and return the animation to this 2979 * new state. 2980 */ 2981 public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, 2982 boolean animated, AnimationLayerSet layerViews) { 2983 Workspace.State fromState = mWorkspace.getState(); 2984 Animator anim = mWorkspace.setStateWithAnimation(toState, animated, layerViews); 2985 updateInteraction(fromState, toState); 2986 return anim; 2987 } 2988 2989 public void enterSpringLoadedDragMode() { 2990 if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name())); 2991 if (isStateSpringLoaded()) { 2992 return; 2993 } 2994 2995 mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(), 2996 Workspace.State.SPRING_LOADED, true /* animated */, 2997 null /* onCompleteRunnable */); 2998 setState(State.WORKSPACE_SPRING_LOADED); 2999 } 3000 3001 public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay, 3002 final Runnable onCompleteRunnable) { 3003 if (!isStateSpringLoaded()) return; 3004 3005 if (mExitSpringLoadedModeRunnable != null) { 3006 mHandler.removeCallbacks(mExitSpringLoadedModeRunnable); 3007 } 3008 mExitSpringLoadedModeRunnable = new Runnable() { 3009 @Override 3010 public void run() { 3011 if (successfulDrop) { 3012 // TODO(hyunyoungs): verify if this hack is still needed, if not, delete. 3013 // 3014 // Before we show workspace, hide all apps again because 3015 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should 3016 // clean up our state transition functions 3017 mWidgetsView.setVisibility(View.GONE); 3018 showWorkspace(true, onCompleteRunnable); 3019 } else { 3020 exitSpringLoadedDragMode(); 3021 } 3022 mExitSpringLoadedModeRunnable = null; 3023 } 3024 }; 3025 mHandler.postDelayed(mExitSpringLoadedModeRunnable, delay); 3026 } 3027 3028 boolean isStateSpringLoaded() { 3029 return mState == State.WORKSPACE_SPRING_LOADED || mState == State.APPS_SPRING_LOADED 3030 || mState == State.WIDGETS_SPRING_LOADED; 3031 } 3032 3033 public void exitSpringLoadedDragMode() { 3034 if (mState == State.APPS_SPRING_LOADED) { 3035 showAppsView(true /* animated */, false /* updatePredictedApps */); 3036 } else if (mState == State.WIDGETS_SPRING_LOADED) { 3037 showWidgetsView(true, false); 3038 } else if (mState == State.WORKSPACE_SPRING_LOADED) { 3039 showWorkspace(true); 3040 } 3041 } 3042 3043 /** 3044 * Updates the set of predicted apps if it hasn't been updated since the last time Launcher was 3045 * resumed. 3046 */ 3047 public void tryAndUpdatePredictedApps() { 3048 if (mLauncherCallbacks != null) { 3049 List<ComponentKey> apps = mLauncherCallbacks.getPredictedApps(); 3050 if (apps != null) { 3051 mAppsView.setPredictedApps(apps); 3052 getUserEventDispatcher().setPredictedApps(apps); 3053 } 3054 } 3055 } 3056 3057 void lockAllApps() { 3058 // TODO 3059 } 3060 3061 void unlockAllApps() { 3062 // TODO 3063 } 3064 3065 @Override 3066 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 3067 final boolean result = super.dispatchPopulateAccessibilityEvent(event); 3068 final List<CharSequence> text = event.getText(); 3069 text.clear(); 3070 // Populate event with a fake title based on the current state. 3071 if (mState == State.APPS) { 3072 text.add(getString(R.string.all_apps_button_label)); 3073 } else if (mState == State.WIDGETS) { 3074 text.add(getString(R.string.widget_button_text)); 3075 } else if (mWorkspace != null) { 3076 text.add(mWorkspace.getCurrentPageDescription()); 3077 } else { 3078 text.add(getString(R.string.all_apps_home_button_label)); 3079 } 3080 return result; 3081 } 3082 3083 /** 3084 * If the activity is currently paused, signal that we need to run the passed Runnable 3085 * in onResume. 3086 * 3087 * This needs to be called from incoming places where resources might have been loaded 3088 * while the activity is paused. That is because the Configuration (e.g., rotation) might be 3089 * wrong when we're not running, and if the activity comes back to what the configuration was 3090 * when we were paused, activity is not restarted. 3091 * 3092 * Implementation of the method from LauncherModel.Callbacks. 3093 * 3094 * @return {@code true} if we are currently paused. The caller might be able to skip some work 3095 */ 3096 @Thunk boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) { 3097 if (mPaused) { 3098 if (LOGD) Log.d(TAG, "Deferring update until onResume"); 3099 if (deletePreviousRunnables) { 3100 while (mBindOnResumeCallbacks.remove(run)) { 3101 } 3102 } 3103 mBindOnResumeCallbacks.add(run); 3104 return true; 3105 } else { 3106 return false; 3107 } 3108 } 3109 3110 private boolean waitUntilResume(Runnable run) { 3111 return waitUntilResume(run, false); 3112 } 3113 3114 public void addOnResumeCallback(Runnable run) { 3115 mOnResumeCallbacks.add(run); 3116 } 3117 3118 /** 3119 * If the activity is currently paused, signal that we need to re-run the loader 3120 * in onResume. 3121 * 3122 * This needs to be called from incoming places where resources might have been loaded 3123 * while we are paused. That is becaues the Configuration might be wrong 3124 * when we're not running, and if it comes back to what it was when we 3125 * were paused, we are not restarted. 3126 * 3127 * Implementation of the method from LauncherModel.Callbacks. 3128 * 3129 * @return true if we are currently paused. The caller might be able to 3130 * skip some work in that case since we will come back again. 3131 */ 3132 @Override 3133 public boolean setLoadOnResume() { 3134 if (mPaused) { 3135 if (LOGD) Log.d(TAG, "setLoadOnResume"); 3136 mOnResumeNeedsLoad = true; 3137 return true; 3138 } else { 3139 return false; 3140 } 3141 } 3142 3143 /** 3144 * Implementation of the method from LauncherModel.Callbacks. 3145 */ 3146 @Override 3147 public int getCurrentWorkspaceScreen() { 3148 if (mWorkspace != null) { 3149 return mWorkspace.getCurrentPage(); 3150 } else { 3151 return 0; 3152 } 3153 } 3154 3155 /** 3156 * Clear any pending bind callbacks. This is called when is loader is planning to 3157 * perform a full rebind from scratch. 3158 */ 3159 @Override 3160 public void clearPendingBinds() { 3161 mBindOnResumeCallbacks.clear(); 3162 if (mPendingExecutor != null) { 3163 mPendingExecutor.markCompleted(); 3164 mPendingExecutor = null; 3165 } 3166 } 3167 3168 /** 3169 * Refreshes the shortcuts shown on the workspace. 3170 * 3171 * Implementation of the method from LauncherModel.Callbacks. 3172 */ 3173 public void startBinding() { 3174 if (LauncherAppState.PROFILE_STARTUP) { 3175 Trace.beginSection("Starting page bind"); 3176 } 3177 3178 AbstractFloatingView.closeAllOpenViews(this); 3179 3180 setWorkspaceLoading(true); 3181 3182 // Clear the workspace because it's going to be rebound 3183 mWorkspace.clearDropTargets(); 3184 mWorkspace.removeAllWorkspaceScreens(); 3185 3186 if (mHotseat != null) { 3187 mHotseat.resetLayout(); 3188 } 3189 if (LauncherAppState.PROFILE_STARTUP) { 3190 Trace.endSection(); 3191 } 3192 } 3193 3194 @Override 3195 public void bindScreens(ArrayList<Long> orderedScreenIds) { 3196 // Make sure the first screen is always at the start. 3197 if (FeatureFlags.QSB_ON_FIRST_SCREEN && 3198 orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) { 3199 orderedScreenIds.remove(Workspace.FIRST_SCREEN_ID); 3200 orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID); 3201 LauncherModel.updateWorkspaceScreenOrder(this, orderedScreenIds); 3202 } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) { 3203 // If there are no screens, we need to have an empty screen 3204 mWorkspace.addExtraEmptyScreen(); 3205 } 3206 bindAddScreens(orderedScreenIds); 3207 3208 // Create the custom content page (this call updates mDefaultScreen which calls 3209 // setCurrentPage() so ensure that all pages are added before calling this). 3210 if (hasCustomContentToLeft()) { 3211 mWorkspace.createCustomContentContainer(); 3212 populateCustomContentContainer(); 3213 } 3214 3215 // After we have added all the screens, if the wallpaper was locked to the default state, 3216 // then notify to indicate that it can be released and a proper wallpaper offset can be 3217 // computed before the next layout 3218 mWorkspace.unlockWallpaperFromDefaultPageOnNextLayout(); 3219 } 3220 3221 private void bindAddScreens(ArrayList<Long> orderedScreenIds) { 3222 int count = orderedScreenIds.size(); 3223 for (int i = 0; i < count; i++) { 3224 long screenId = orderedScreenIds.get(i); 3225 if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) { 3226 // No need to bind the first screen, as its always bound. 3227 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId); 3228 } 3229 } 3230 } 3231 3232 public void bindAppsAdded(final ArrayList<Long> newScreens, 3233 final ArrayList<ItemInfo> addNotAnimated, 3234 final ArrayList<ItemInfo> addAnimated, 3235 final ArrayList<AppInfo> addedApps) { 3236 Runnable r = new Runnable() { 3237 public void run() { 3238 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps); 3239 } 3240 }; 3241 if (waitUntilResume(r)) { 3242 return; 3243 } 3244 3245 // Add the new screens 3246 if (newScreens != null) { 3247 bindAddScreens(newScreens); 3248 } 3249 3250 // We add the items without animation on non-visible pages, and with 3251 // animations on the new page (which we will try and snap to). 3252 if (addNotAnimated != null && !addNotAnimated.isEmpty()) { 3253 bindItems(addNotAnimated, 0, 3254 addNotAnimated.size(), false); 3255 } 3256 if (addAnimated != null && !addAnimated.isEmpty()) { 3257 bindItems(addAnimated, 0, 3258 addAnimated.size(), true); 3259 } 3260 3261 // Remove the extra empty screen 3262 mWorkspace.removeExtraEmptyScreen(false, false); 3263 3264 if (addedApps != null && mAppsView != null) { 3265 mAppsView.addApps(addedApps); 3266 } 3267 } 3268 3269 /** 3270 * Bind the items start-end from the list. 3271 * 3272 * Implementation of the method from LauncherModel.Callbacks. 3273 */ 3274 @Override 3275 public void bindItems(final ArrayList<ItemInfo> items, final int start, final int end, 3276 final boolean forceAnimateIcons) { 3277 Runnable r = new Runnable() { 3278 public void run() { 3279 bindItems(items, start, end, forceAnimateIcons); 3280 } 3281 }; 3282 if (waitUntilResume(r)) { 3283 return; 3284 } 3285 3286 // Get the list of added items and intersect them with the set of items here 3287 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); 3288 final Collection<Animator> bounceAnims = new ArrayList<>(); 3289 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation(); 3290 Workspace workspace = mWorkspace; 3291 long newItemsScreenId = -1; 3292 for (int i = start; i < end; i++) { 3293 final ItemInfo item = items.get(i); 3294 3295 // Short circuit if we are loading dock items for a configuration which has no dock 3296 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && 3297 mHotseat == null) { 3298 continue; 3299 } 3300 3301 final View view; 3302 switch (item.itemType) { 3303 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 3304 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 3305 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { 3306 ShortcutInfo info = (ShortcutInfo) item; 3307 view = createShortcut(info); 3308 break; 3309 } 3310 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: { 3311 view = FolderIcon.fromXml(R.layout.folder_icon, this, 3312 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), 3313 (FolderInfo) item); 3314 break; 3315 } 3316 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: { 3317 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item; 3318 if (mIsSafeModeEnabled) { 3319 view = new PendingAppWidgetHostView(this, info, mIconCache, true); 3320 } else { 3321 LauncherAppWidgetProviderInfo providerInfo = 3322 mAppWidgetManager.getLauncherAppWidgetInfo(info.appWidgetId); 3323 if (providerInfo == null) { 3324 deleteWidgetInfo(info); 3325 continue; 3326 } 3327 view = mAppWidgetHost.createView(this, info.appWidgetId, providerInfo); 3328 } 3329 prepareAppWidget((AppWidgetHostView) view, info); 3330 break; 3331 } 3332 default: 3333 throw new RuntimeException("Invalid Item Type"); 3334 } 3335 3336 /* 3337 * Remove colliding items. 3338 */ 3339 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 3340 CellLayout cl = mWorkspace.getScreenWithId(item.screenId); 3341 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) { 3342 View v = cl.getChildAt(item.cellX, item.cellY); 3343 Object tag = v.getTag(); 3344 String desc = "Collision while binding workspace item: " + item 3345 + ". Collides with " + tag; 3346 if (FeatureFlags.IS_DOGFOOD_BUILD) { 3347 throw (new RuntimeException(desc)); 3348 } else { 3349 Log.d(TAG, desc); 3350 getModelWriter().deleteItemFromDatabase(item); 3351 continue; 3352 } 3353 } 3354 } 3355 workspace.addInScreenFromBind(view, item); 3356 if (animateIcons) { 3357 // Animate all the applications up now 3358 view.setAlpha(0f); 3359 view.setScaleX(0f); 3360 view.setScaleY(0f); 3361 bounceAnims.add(createNewAppBounceAnimation(view, i)); 3362 newItemsScreenId = item.screenId; 3363 } 3364 } 3365 3366 if (animateIcons) { 3367 // Animate to the correct page 3368 if (newItemsScreenId > -1) { 3369 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage()); 3370 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId); 3371 final Runnable startBounceAnimRunnable = new Runnable() { 3372 public void run() { 3373 anim.playTogether(bounceAnims); 3374 anim.start(); 3375 } 3376 }; 3377 if (newItemsScreenId != currentScreenId) { 3378 // We post the animation slightly delayed to prevent slowdowns 3379 // when we are loading right after we return to launcher. 3380 mWorkspace.postDelayed(new Runnable() { 3381 public void run() { 3382 if (mWorkspace != null) { 3383 mWorkspace.snapToPage(newScreenIndex); 3384 mWorkspace.postDelayed(startBounceAnimRunnable, 3385 NEW_APPS_ANIMATION_DELAY); 3386 } 3387 } 3388 }, NEW_APPS_PAGE_MOVE_DELAY); 3389 } else { 3390 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY); 3391 } 3392 } 3393 } 3394 workspace.requestLayout(); 3395 } 3396 3397 /** 3398 * Add the views for a widget to the workspace. 3399 * 3400 * Implementation of the method from LauncherModel.Callbacks. 3401 */ 3402 public void bindAppWidget(final LauncherAppWidgetInfo item) { 3403 Runnable r = new Runnable() { 3404 public void run() { 3405 bindAppWidget(item); 3406 } 3407 }; 3408 if (waitUntilResume(r)) { 3409 return; 3410 } 3411 3412 if (mIsSafeModeEnabled) { 3413 PendingAppWidgetHostView view = 3414 new PendingAppWidgetHostView(this, item, mIconCache, true); 3415 prepareAppWidget(view, item); 3416 mWorkspace.addInScreen(view, item); 3417 mWorkspace.requestLayout(); 3418 return; 3419 } 3420 3421 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0; 3422 if (DEBUG_WIDGETS) { 3423 Log.d(TAG, "bindAppWidget: " + item); 3424 } 3425 3426 final LauncherAppWidgetProviderInfo appWidgetInfo; 3427 3428 if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) { 3429 // If the provider is not ready, bind as a pending widget. 3430 appWidgetInfo = null; 3431 } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) { 3432 // The widget id is not valid. Try to find the widget based on the provider info. 3433 appWidgetInfo = mAppWidgetManager.findProvider(item.providerName, item.user); 3434 } else { 3435 appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(item.appWidgetId); 3436 } 3437 3438 // If the provider is ready, but the width is not yet restored, try to restore it. 3439 if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) && 3440 (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) { 3441 if (appWidgetInfo == null) { 3442 if (DEBUG_WIDGETS) { 3443 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId 3444 + " belongs to component " + item.providerName 3445 + ", as the provider is null"); 3446 } 3447 getModelWriter().deleteItemFromDatabase(item); 3448 return; 3449 } 3450 3451 // If we do not have a valid id, try to bind an id. 3452 if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) { 3453 if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) { 3454 // Id has not been allocated yet. Allocate a new id. 3455 item.appWidgetId = mAppWidgetHost.allocateAppWidgetId(); 3456 item.restoreStatus |= LauncherAppWidgetInfo.FLAG_ID_ALLOCATED; 3457 3458 // Also try to bind the widget. If the bind fails, the user will be shown 3459 // a click to setup UI, which will ask for the bind permission. 3460 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo); 3461 pendingInfo.spanX = item.spanX; 3462 pendingInfo.spanY = item.spanY; 3463 pendingInfo.minSpanX = item.minSpanX; 3464 pendingInfo.minSpanY = item.minSpanY; 3465 Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo); 3466 3467 boolean isDirectConfig = 3468 item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG); 3469 if (isDirectConfig && item.bindOptions != null) { 3470 Bundle newOptions = item.bindOptions.getExtras(); 3471 if (options != null) { 3472 newOptions.putAll(options); 3473 } 3474 options = newOptions; 3475 } 3476 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( 3477 item.appWidgetId, appWidgetInfo, options); 3478 3479 // We tried to bind once. If we were not able to bind, we would need to 3480 // go through the permission dialog, which means we cannot skip the config 3481 // activity. 3482 item.bindOptions = null; 3483 item.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG; 3484 3485 // Bind succeeded 3486 if (success) { 3487 // If the widget has a configure activity, it is still needs to set it up, 3488 // otherwise the widget is ready to go. 3489 item.restoreStatus = (appWidgetInfo.configure == null) || isDirectConfig 3490 ? LauncherAppWidgetInfo.RESTORE_COMPLETED 3491 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY; 3492 } 3493 3494 getModelWriter().updateItemInDatabase(item); 3495 } 3496 } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY) 3497 && (appWidgetInfo.configure == null)) { 3498 // The widget was marked as UI not ready, but there is no configure activity to 3499 // update the UI. 3500 item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED; 3501 getModelWriter().updateItemInDatabase(item); 3502 } 3503 } 3504 3505 final AppWidgetHostView view; 3506 if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) { 3507 if (DEBUG_WIDGETS) { 3508 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " 3509 + appWidgetInfo.provider); 3510 } 3511 3512 // Verify that we own the widget 3513 if (appWidgetInfo == null) { 3514 FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId); 3515 deleteWidgetInfo(item); 3516 return; 3517 } 3518 3519 item.minSpanX = appWidgetInfo.minSpanX; 3520 item.minSpanY = appWidgetInfo.minSpanY; 3521 view = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo); 3522 } else { 3523 view = new PendingAppWidgetHostView(this, item, mIconCache, false); 3524 } 3525 prepareAppWidget(view, item); 3526 mWorkspace.addInScreen(view, item); 3527 mWorkspace.requestLayout(); 3528 3529 if (DEBUG_WIDGETS) { 3530 Log.d(TAG, "bound widget id="+item.appWidgetId+" in " 3531 + (SystemClock.uptimeMillis()-start) + "ms"); 3532 } 3533 } 3534 3535 /** 3536 * Restores a pending widget. 3537 * 3538 * @param appWidgetId The app widget id 3539 */ 3540 private LauncherAppWidgetInfo completeRestoreAppWidget(int appWidgetId, int finalRestoreFlag) { 3541 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId); 3542 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) { 3543 Log.e(TAG, "Widget update called, when the widget no longer exists."); 3544 return null; 3545 } 3546 3547 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag(); 3548 info.restoreStatus = finalRestoreFlag; 3549 if (info.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) { 3550 info.pendingItemInfo = null; 3551 } 3552 3553 mWorkspace.reinflateWidgetsIfNecessary(); 3554 getModelWriter().updateItemInDatabase(info); 3555 return info; 3556 } 3557 3558 public void onPageBoundSynchronously(int page) { 3559 mSynchronouslyBoundPages.add(page); 3560 } 3561 3562 @Override 3563 public void executeOnNextDraw(ViewOnDrawExecutor executor) { 3564 if (mPendingExecutor != null) { 3565 mPendingExecutor.markCompleted(); 3566 } 3567 mPendingExecutor = executor; 3568 executor.attachTo(this); 3569 } 3570 3571 public void clearPendingExecutor(ViewOnDrawExecutor executor) { 3572 if (mPendingExecutor == executor) { 3573 mPendingExecutor = null; 3574 } 3575 } 3576 3577 @Override 3578 public void finishFirstPageBind(final ViewOnDrawExecutor executor) { 3579 Runnable r = new Runnable() { 3580 public void run() { 3581 finishFirstPageBind(executor); 3582 } 3583 }; 3584 if (waitUntilResume(r)) { 3585 return; 3586 } 3587 3588 Runnable onComplete = new Runnable() { 3589 @Override 3590 public void run() { 3591 if (executor != null) { 3592 executor.onLoadAnimationCompleted(); 3593 } 3594 } 3595 }; 3596 if (mDragLayer.getAlpha() < 1) { 3597 mDragLayer.animate().alpha(1).withEndAction(onComplete).start(); 3598 } else { 3599 onComplete.run(); 3600 } 3601 } 3602 3603 /** 3604 * Callback saying that there aren't any more items to bind. 3605 * 3606 * Implementation of the method from LauncherModel.Callbacks. 3607 */ 3608 public void finishBindingItems() { 3609 Runnable r = new Runnable() { 3610 public void run() { 3611 finishBindingItems(); 3612 } 3613 }; 3614 if (waitUntilResume(r)) { 3615 return; 3616 } 3617 if (LauncherAppState.PROFILE_STARTUP) { 3618 Trace.beginSection("Page bind completed"); 3619 } 3620 mWorkspace.restoreInstanceStateForRemainingPages(); 3621 3622 setWorkspaceLoading(false); 3623 3624 if (mPendingActivityResult != null) { 3625 handleActivityResult(mPendingActivityResult.requestCode, 3626 mPendingActivityResult.resultCode, mPendingActivityResult.data); 3627 mPendingActivityResult = null; 3628 } 3629 3630 InstallShortcutReceiver.disableAndFlushInstallQueue( 3631 InstallShortcutReceiver.FLAG_LOADER_RUNNING, this); 3632 3633 NotificationListener.setNotificationsChangedListener(mPopupDataProvider); 3634 3635 if (mLauncherCallbacks != null) { 3636 mLauncherCallbacks.finishBindingItems(false); 3637 } 3638 if (LauncherAppState.PROFILE_STARTUP) { 3639 Trace.endSection(); 3640 } 3641 } 3642 3643 private boolean canRunNewAppsAnimation() { 3644 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime(); 3645 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000); 3646 } 3647 3648 private ValueAnimator createNewAppBounceAnimation(View v, int i) { 3649 ValueAnimator bounceAnim = LauncherAnimUtils.ofViewAlphaAndScale(v, 1, 1, 1); 3650 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION); 3651 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY); 3652 bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION)); 3653 return bounceAnim; 3654 } 3655 3656 public boolean useVerticalBarLayout() { 3657 return mDeviceProfile.isVerticalBarLayout(); 3658 } 3659 3660 public int getSearchBarHeight() { 3661 if (mLauncherCallbacks != null) { 3662 return mLauncherCallbacks.getSearchBarHeight(); 3663 } 3664 return LauncherCallbacks.SEARCH_BAR_HEIGHT_NORMAL; 3665 } 3666 3667 /** 3668 * A runnable that we can dequeue and re-enqueue when all applications are bound (to prevent 3669 * multiple calls to bind the same list.) 3670 */ 3671 @Thunk ArrayList<AppInfo> mTmpAppsList; 3672 private final Runnable mBindAllApplicationsRunnable = new Runnable() { 3673 public void run() { 3674 bindAllApplications(mTmpAppsList); 3675 mTmpAppsList = null; 3676 } 3677 }; 3678 3679 /** 3680 * Add the icons for all apps. 3681 * 3682 * Implementation of the method from LauncherModel.Callbacks. 3683 */ 3684 public void bindAllApplications(final ArrayList<AppInfo> apps) { 3685 if (waitUntilResume(mBindAllApplicationsRunnable, true)) { 3686 mTmpAppsList = apps; 3687 return; 3688 } 3689 3690 if (mAppsView != null) { 3691 mAppsView.setApps(apps); 3692 } 3693 if (mLauncherCallbacks != null) { 3694 mLauncherCallbacks.bindAllApplications(apps); 3695 } 3696 } 3697 3698 /** 3699 * Copies LauncherModel's map of activities to shortcut ids to Launcher's. This is necessary 3700 * because LauncherModel's map is updated in the background, while Launcher runs on the UI. 3701 */ 3702 @Override 3703 public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) { 3704 mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy); 3705 } 3706 3707 /** 3708 * A package was updated. 3709 * 3710 * Implementation of the method from LauncherModel.Callbacks. 3711 */ 3712 public void bindAppsUpdated(final ArrayList<AppInfo> apps) { 3713 Runnable r = new Runnable() { 3714 public void run() { 3715 bindAppsUpdated(apps); 3716 } 3717 }; 3718 if (waitUntilResume(r)) { 3719 return; 3720 } 3721 3722 if (mAppsView != null) { 3723 mAppsView.updateApps(apps); 3724 } 3725 } 3726 3727 @Override 3728 public void bindPromiseAppProgressUpdated(final PromiseAppInfo app) { 3729 Runnable r = new Runnable() { 3730 public void run() { 3731 bindPromiseAppProgressUpdated(app); 3732 } 3733 }; 3734 if (waitUntilResume(r)) { 3735 return; 3736 } 3737 3738 if (mAppsView != null) { 3739 mAppsView.updatePromiseAppProgress(app); 3740 } 3741 } 3742 3743 @Override 3744 public void bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets) { 3745 Runnable r = new Runnable() { 3746 public void run() { 3747 bindWidgetsRestored(widgets); 3748 } 3749 }; 3750 if (waitUntilResume(r)) { 3751 return; 3752 } 3753 mWorkspace.widgetsRestored(widgets); 3754 } 3755 3756 /** 3757 * Some shortcuts were updated in the background. 3758 * Implementation of the method from LauncherModel.Callbacks. 3759 * 3760 * @param updated list of shortcuts which have changed. 3761 * @param removed list of shortcuts which were deleted in the background. This can happen when 3762 * an app gets removed from the system or some of its components are no longer 3763 * available. 3764 */ 3765 @Override 3766 public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated, 3767 final ArrayList<ShortcutInfo> removed, final UserHandle user) { 3768 Runnable r = new Runnable() { 3769 public void run() { 3770 bindShortcutsChanged(updated, removed, user); 3771 } 3772 }; 3773 if (waitUntilResume(r)) { 3774 return; 3775 } 3776 3777 if (!updated.isEmpty()) { 3778 mWorkspace.updateShortcuts(updated); 3779 } 3780 3781 if (!removed.isEmpty()) { 3782 HashSet<ComponentName> removedComponents = new HashSet<>(); 3783 HashSet<ShortcutKey> removedDeepShortcuts = new HashSet<>(); 3784 3785 for (ShortcutInfo si : removed) { 3786 if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 3787 removedDeepShortcuts.add(ShortcutKey.fromItemInfo(si)); 3788 } else { 3789 removedComponents.add(si.getTargetComponent()); 3790 } 3791 } 3792 3793 if (!removedComponents.isEmpty()) { 3794 ItemInfoMatcher matcher = ItemInfoMatcher.ofComponents(removedComponents, user); 3795 mWorkspace.removeItemsByMatcher(matcher); 3796 mDragController.onAppsRemoved(matcher); 3797 } 3798 3799 if (!removedDeepShortcuts.isEmpty()) { 3800 ItemInfoMatcher matcher = ItemInfoMatcher.ofShortcutKeys(removedDeepShortcuts); 3801 mWorkspace.removeItemsByMatcher(matcher); 3802 mDragController.onAppsRemoved(matcher); 3803 } 3804 } 3805 } 3806 3807 /** 3808 * Update the state of a package, typically related to install state. 3809 * 3810 * Implementation of the method from LauncherModel.Callbacks. 3811 */ 3812 @Override 3813 public void bindRestoreItemsChange(final HashSet<ItemInfo> updates) { 3814 Runnable r = new Runnable() { 3815 public void run() { 3816 bindRestoreItemsChange(updates); 3817 } 3818 }; 3819 if (waitUntilResume(r)) { 3820 return; 3821 } 3822 3823 mWorkspace.updateRestoreItems(updates); 3824 } 3825 3826 /** 3827 * A package was uninstalled/updated. We take both the super set of packageNames 3828 * in addition to specific applications to remove, the reason being that 3829 * this can be called when a package is updated as well. In that scenario, 3830 * we only remove specific components from the workspace and hotseat, where as 3831 * package-removal should clear all items by package name. 3832 */ 3833 @Override 3834 public void bindWorkspaceComponentsRemoved( 3835 final HashSet<String> packageNames, final HashSet<ComponentName> components, 3836 final UserHandle user) { 3837 Runnable r = new Runnable() { 3838 public void run() { 3839 bindWorkspaceComponentsRemoved(packageNames, components, user); 3840 } 3841 }; 3842 if (waitUntilResume(r)) { 3843 return; 3844 } 3845 if (!packageNames.isEmpty()) { 3846 ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageNames, user); 3847 mWorkspace.removeItemsByMatcher(matcher); 3848 mDragController.onAppsRemoved(matcher); 3849 3850 } 3851 if (!components.isEmpty()) { 3852 ItemInfoMatcher matcher = ItemInfoMatcher.ofComponents(components, user); 3853 mWorkspace.removeItemsByMatcher(matcher); 3854 mDragController.onAppsRemoved(matcher); 3855 } 3856 } 3857 3858 @Override 3859 public void bindAppInfosRemoved(final ArrayList<AppInfo> appInfos) { 3860 Runnable r = new Runnable() { 3861 public void run() { 3862 bindAppInfosRemoved(appInfos); 3863 } 3864 }; 3865 if (waitUntilResume(r)) { 3866 return; 3867 } 3868 3869 // Update AllApps 3870 if (mAppsView != null) { 3871 mAppsView.removeApps(appInfos); 3872 tryAndUpdatePredictedApps(); 3873 } 3874 } 3875 3876 private final Runnable mBindAllWidgetsRunnable = new Runnable() { 3877 public void run() { 3878 bindAllWidgets(mAllWidgets); 3879 } 3880 }; 3881 3882 @Override 3883 public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> allWidgets) { 3884 if (waitUntilResume(mBindAllWidgetsRunnable, true)) { 3885 mAllWidgets = allWidgets; 3886 return; 3887 } 3888 3889 if (mWidgetsView != null && allWidgets != null) { 3890 mWidgetsView.setWidgets(allWidgets); 3891 mAllWidgets = null; 3892 } 3893 3894 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this); 3895 if (topView != null) { 3896 topView.onWidgetsBound(); 3897 } 3898 } 3899 3900 public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) { 3901 return mWidgetsView.getWidgetsForPackageUser(packageUserKey); 3902 } 3903 3904 @Override 3905 public void notifyWidgetProvidersChanged() { 3906 if (mWorkspace.getState().shouldUpdateWidget) { 3907 refreshAndBindWidgetsForPackageUser(null); 3908 } 3909 } 3910 3911 /** 3912 * @param packageUser if null, refreshes all widgets and shortcuts, otherwise only 3913 * refreshes the widgets and shortcuts associated with the given package/user 3914 */ 3915 public void refreshAndBindWidgetsForPackageUser(@Nullable PackageUserKey packageUser) { 3916 mModel.refreshAndBindWidgetsAndShortcuts(packageUser); 3917 } 3918 3919 public void lockScreenOrientation() { 3920 if (mRotationEnabled) { 3921 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); 3922 } 3923 } 3924 3925 public void unlockScreenOrientation(boolean immediate) { 3926 if (mRotationEnabled) { 3927 if (immediate) { 3928 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 3929 } else { 3930 mHandler.postDelayed(new Runnable() { 3931 public void run() { 3932 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 3933 } 3934 }, RESTORE_SCREEN_ORIENTATION_DELAY); 3935 } 3936 } 3937 } 3938 3939 private void markAppsViewShown() { 3940 if (mSharedPrefs.getBoolean(APPS_VIEW_SHOWN, false)) { 3941 return; 3942 } 3943 mSharedPrefs.edit().putBoolean(APPS_VIEW_SHOWN, true).apply(); 3944 } 3945 3946 private boolean shouldShowDiscoveryBounce() { 3947 if (mState != State.WORKSPACE) { 3948 return false; 3949 } 3950 if (mLauncherCallbacks != null && mLauncherCallbacks.shouldShowDiscoveryBounce()) { 3951 return true; 3952 } 3953 if (!mIsResumeFromActionScreenOff) { 3954 return false; 3955 } 3956 return !mSharedPrefs.getBoolean(APPS_VIEW_SHOWN, false); 3957 } 3958 3959 protected void moveWorkspaceToDefaultScreen() { 3960 mWorkspace.moveToDefaultScreen(false); 3961 } 3962 3963 /** 3964 * $ adb shell dumpsys activity com.android.launcher3.Launcher [--all] 3965 */ 3966 @Override 3967 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 3968 super.dump(prefix, fd, writer, args); 3969 3970 if (args.length > 0 && TextUtils.equals(args[0], "--all")) { 3971 writer.println(prefix + "Workspace Items"); 3972 for (int i = mWorkspace.numCustomPages(); i < mWorkspace.getPageCount(); i++) { 3973 writer.println(prefix + " Homescreen " + i); 3974 3975 ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets(); 3976 for (int j = 0; j < layout.getChildCount(); j++) { 3977 Object tag = layout.getChildAt(j).getTag(); 3978 if (tag != null) { 3979 writer.println(prefix + " " + tag.toString()); 3980 } 3981 } 3982 } 3983 3984 writer.println(prefix + " Hotseat"); 3985 ViewGroup layout = mHotseat.getLayout().getShortcutsAndWidgets(); 3986 for (int j = 0; j < layout.getChildCount(); j++) { 3987 Object tag = layout.getChildAt(j).getTag(); 3988 if (tag != null) { 3989 writer.println(prefix + " " + tag.toString()); 3990 } 3991 } 3992 3993 try { 3994 FileLog.flushAll(writer); 3995 } catch (Exception e) { 3996 // Ignore 3997 } 3998 } 3999 4000 writer.println(prefix + "Misc:"); 4001 writer.print(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading); 4002 writer.print(" mPendingRequestArgs=" + mPendingRequestArgs); 4003 writer.println(" mPendingActivityResult=" + mPendingActivityResult); 4004 4005 mModel.dumpState(prefix, fd, writer, args); 4006 4007 if (mLauncherCallbacks != null) { 4008 mLauncherCallbacks.dump(prefix, fd, writer, args); 4009 } 4010 } 4011 4012 @Override 4013 @TargetApi(Build.VERSION_CODES.N) 4014 public void onProvideKeyboardShortcuts( 4015 List<KeyboardShortcutGroup> data, Menu menu, int deviceId) { 4016 4017 ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>(); 4018 if (mState == State.WORKSPACE) { 4019 shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label), 4020 KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON)); 4021 } 4022 View currentFocus = getCurrentFocus(); 4023 if (new CustomActionsPopup(this, currentFocus).canShow()) { 4024 shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.custom_actions), 4025 KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON)); 4026 } 4027 if (currentFocus.getTag() instanceof ItemInfo 4028 && DeepShortcutManager.supportsShortcuts((ItemInfo) currentFocus.getTag())) { 4029 shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.action_deep_shortcut), 4030 KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON)); 4031 } 4032 if (!shortcutInfos.isEmpty()) { 4033 data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos)); 4034 } 4035 4036 super.onProvideKeyboardShortcuts(data, menu, deviceId); 4037 } 4038 4039 @Override 4040 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 4041 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 4042 switch (keyCode) { 4043 case KeyEvent.KEYCODE_A: 4044 if (mState == State.WORKSPACE) { 4045 showAppsView(true, true); 4046 return true; 4047 } 4048 break; 4049 case KeyEvent.KEYCODE_S: { 4050 View focusedView = getCurrentFocus(); 4051 if (focusedView instanceof BubbleTextView 4052 && focusedView.getTag() instanceof ItemInfo 4053 && mAccessibilityDelegate.performAction(focusedView, 4054 (ItemInfo) focusedView.getTag(), 4055 LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) { 4056 PopupContainerWithArrow.getOpen(this).requestFocus(); 4057 return true; 4058 } 4059 break; 4060 } 4061 case KeyEvent.KEYCODE_O: 4062 if (new CustomActionsPopup(this, getCurrentFocus()).show()) { 4063 return true; 4064 } 4065 break; 4066 } 4067 } 4068 return super.onKeyShortcut(keyCode, event); 4069 } 4070 4071 public static CustomAppWidget getCustomAppWidget(String name) { 4072 return sCustomAppWidgets.get(name); 4073 } 4074 4075 public static HashMap<String, CustomAppWidget> getCustomAppWidgets() { 4076 return sCustomAppWidgets; 4077 } 4078 4079 public static Launcher getLauncher(Context context) { 4080 if (context instanceof Launcher) { 4081 return (Launcher) context; 4082 } 4083 return ((Launcher) ((ContextWrapper) context).getBaseContext()); 4084 } 4085 4086 private class RotationPrefChangeHandler implements OnSharedPreferenceChangeListener { 4087 4088 @Override 4089 public void onSharedPreferenceChanged( 4090 SharedPreferences sharedPreferences, String key) { 4091 if (Utilities.ALLOW_ROTATION_PREFERENCE_KEY.equals(key)) { 4092 // Recreate the activity so that it initializes the rotation preference again. 4093 recreate(); 4094 } 4095 } 4096 } 4097} 4098