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