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