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