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