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