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