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