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