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