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 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 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 478 public void onExtractedColorsChanged() { 479 loadExtractedColorsAndColorItems(); 480 } 481 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 */ 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 516 public void onPostCreate(Bundle savedInstanceState) { 517 super.onPostCreate(savedInstanceState); 518 if (mLauncherCallbacks != null) { 519 mLauncherCallbacks.onPostCreate(savedInstanceState); 520 } 521 } 522 523 public void onInsetsChanged(Rect insets) { 524 mDeviceProfile.updateInsets(insets); 525 mDeviceProfile.layout(this, true /* notifyListeners */); 526 } 527 528 /** 529 * Call this after onCreate to set or clear overlay. 530 */ 531 public void setLauncherOverlay(LauncherOverlay overlay) { 532 if (overlay != null) { 533 overlay.setOverlayCallbacks(new LauncherOverlayCallbacksImpl()); 534 } 535 mWorkspace.setLauncherOverlay(overlay); 536 } 537 538 public boolean setLauncherCallbacks(LauncherCallbacks callbacks) { 539 mLauncherCallbacks = callbacks; 540 mLauncherCallbacks.setLauncherSearchCallback(new Launcher.LauncherSearchCallbacks() { 541 private boolean mWorkspaceImportanceStored = false; 542 private boolean mHotseatImportanceStored = false; 543 private int mWorkspaceImportanceForAccessibility = 544 View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; 545 private int mHotseatImportanceForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; 546 547 @Override 548 public void onSearchOverlayOpened() { 549 if (mWorkspaceImportanceStored || mHotseatImportanceStored) { 550 return; 551 } 552 // The underlying workspace and hotseat are temporarily suppressed by the search 553 // overlay. So they shouldn't be accessible. 554 if (mWorkspace != null) { 555 mWorkspaceImportanceForAccessibility = 556 mWorkspace.getImportantForAccessibility(); 557 mWorkspace.setImportantForAccessibility( 558 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 559 mWorkspaceImportanceStored = true; 560 } 561 if (mHotseat != null) { 562 mHotseatImportanceForAccessibility = mHotseat.getImportantForAccessibility(); 563 mHotseat.setImportantForAccessibility( 564 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 565 mHotseatImportanceStored = true; 566 } 567 } 568 569 @Override 570 public void onSearchOverlayClosed() { 571 if (mWorkspaceImportanceStored && mWorkspace != null) { 572 mWorkspace.setImportantForAccessibility(mWorkspaceImportanceForAccessibility); 573 } 574 if (mHotseatImportanceStored && mHotseat != null) { 575 mHotseat.setImportantForAccessibility(mHotseatImportanceForAccessibility); 576 } 577 mWorkspaceImportanceStored = false; 578 mHotseatImportanceStored = false; 579 } 580 }); 581 return true; 582 } 583 584 @Override 585 public void 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 */ 592 protected boolean hasCustomContentToLeft() { 593 if (mLauncherCallbacks != null) { 594 return mLauncherCallbacks.hasCustomContentToLeft(); 595 } 596 return false; 597 } 598 599 /** 600 * To be overridden by subclasses to populate the custom content container and call 601 * {@link #addToCustomContentPage}. This will only be invoked if 602 * {@link #hasCustomContentToLeft()} is {@code true}. 603 */ 604 protected void populateCustomContentContainer() { 605 if (mLauncherCallbacks != null) { 606 mLauncherCallbacks.populateCustomContentContainer(); 607 } 608 } 609 610 /** 611 * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to 612 * ensure the custom content page is added or removed if necessary. 613 */ 614 protected void invalidateHasCustomContentToLeft() { 615 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) { 616 // Not bound yet, wait for bindScreens to be called. 617 return; 618 } 619 620 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) { 621 // Create the custom content page and call the subclass to populate it. 622 mWorkspace.createCustomContentContainer(); 623 populateCustomContentContainer(); 624 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) { 625 mWorkspace.removeCustomContentPage(); 626 } 627 } 628 629 public UserEventDispatcher getUserEventDispatcher() { 630 if (mLauncherCallbacks != null) { 631 UserEventDispatcher dispatcher = mLauncherCallbacks.getUserEventDispatcher(); 632 if (dispatcher != null) { 633 return dispatcher; 634 } 635 } 636 637 // Logger object is a singleton and does not have to be coupled with the foreground 638 // activity. Since most user event logging is done on the UI, the object is retrieved 639 // from the callback for convenience. 640 if (mUserEventDispatcher == null) { 641 mUserEventDispatcher = new UserEventDispatcher(); 642 } 643 return mUserEventDispatcher; 644 } 645 646 public boolean isDraggingEnabled() { 647 // We prevent dragging when we are loading the workspace as it is possible to pick up a view 648 // that is subsequently removed from the workspace in startBinding(). 649 return !isWorkspaceLoading(); 650 } 651 652 public int getViewIdForItem(ItemInfo info) { 653 // aapt-generated IDs have the high byte nonzero; clamp to the range under that. 654 // This cast is safe as long as the id < 0x00FFFFFF 655 // Since we jail all the dynamically generated views, there should be no clashes 656 // with any other views. 657 return (int) info.id; 658 } 659 660 /** 661 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have 662 * a configuration step, this allows the proper animations to run after other transitions. 663 */ 664 private long completeAdd( 665 int requestCode, Intent intent, int appWidgetId, PendingRequestArgs info) { 666 long screenId = info.screenId; 667 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 668 // When the screen id represents an actual screen (as opposed to a rank) we make sure 669 // that the drop page actually exists. 670 screenId = ensurePendingDropLayoutExists(info.screenId); 671 } 672 673 switch (requestCode) { 674 case REQUEST_CREATE_SHORTCUT: 675 completeAddShortcut(intent, info.container, screenId, info.cellX, info.cellY, info); 676 break; 677 case REQUEST_CREATE_APPWIDGET: 678 completeAddAppWidget(appWidgetId, info, null, null); 679 break; 680 case REQUEST_RECONFIGURE_APPWIDGET: 681 completeRestoreAppWidget(appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED); 682 break; 683 case REQUEST_BIND_PENDING_APPWIDGET: { 684 int widgetId = appWidgetId; 685 LauncherAppWidgetInfo widgetInfo = 686 completeRestoreAppWidget(widgetId, LauncherAppWidgetInfo.FLAG_UI_NOT_READY); 687 if (widgetInfo != null) { 688 // Since the view was just bound, also launch the configure activity if needed 689 LauncherAppWidgetProviderInfo provider = mAppWidgetManager 690 .getLauncherAppWidgetInfo(widgetId); 691 if (provider != null && provider.configure != null) { 692 startRestoredWidgetReconfigActivity(provider, widgetInfo); 693 } 694 } 695 break; 696 } 697 } 698 699 return screenId; 700 } 701 702 private void handleActivityResult( 703 final int requestCode, final int resultCode, final Intent data) { 704 if (isWorkspaceLoading()) { 705 // process the result once the workspace has loaded. 706 mPendingActivityResult = new ActivityResultInfo(requestCode, resultCode, data); 707 return; 708 } 709 mPendingActivityResult = null; 710 711 // Reset the startActivity waiting flag 712 final PendingRequestArgs requestArgs = mPendingRequestArgs; 713 setWaitingForResult(null); 714 if (requestArgs == null) { 715 return; 716 } 717 718 final int pendingAddWidgetId = requestArgs.getWidgetId(); 719 720 Runnable exitSpringLoaded = new Runnable() { 721 @Override 722 public void run() { 723 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), 724 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); 725 } 726 }; 727 728 if (requestCode == REQUEST_BIND_APPWIDGET) { 729 // This is called only if the user did not previously have permissions to bind widgets 730 final int appWidgetId = data != null ? 731 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; 732 if (resultCode == RESULT_CANCELED) { 733 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs); 734 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, 735 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 736 } else if (resultCode == RESULT_OK) { 737 addAppWidgetImpl( 738 appWidgetId, requestArgs, null, 739 requestArgs.getWidgetProvider(), 740 ON_ACTIVITY_RESULT_ANIMATION_DELAY); 741 } 742 return; 743 } else if (requestCode == REQUEST_PICK_WALLPAPER) { 744 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) { 745 // User could have free-scrolled between pages before picking a wallpaper; make sure 746 // we move to the closest one now. 747 mWorkspace.setCurrentPage(mWorkspace.getPageNearestToCenterOfScreen()); 748 showWorkspace(false); 749 } 750 return; 751 } 752 753 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET || 754 requestCode == REQUEST_CREATE_APPWIDGET); 755 756 // We have special handling for widgets 757 if (isWidgetDrop) { 758 final int appWidgetId; 759 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) 760 : -1; 761 if (widgetId < 0) { 762 appWidgetId = pendingAddWidgetId; 763 } else { 764 appWidgetId = widgetId; 765 } 766 767 final int result; 768 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) { 769 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " + 770 "returned from the widget configuration activity."); 771 result = RESULT_CANCELED; 772 completeTwoStageWidgetDrop(result, appWidgetId, requestArgs); 773 final Runnable onComplete = new Runnable() { 774 @Override 775 public void run() { 776 exitSpringLoadedDragModeDelayed(false, 0, null); 777 } 778 }; 779 780 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, 781 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 782 } else { 783 if (requestArgs.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 784 // When the screen id represents an actual screen (as opposed to a rank) 785 // we make sure that the drop page actually exists. 786 requestArgs.screenId = 787 ensurePendingDropLayoutExists(requestArgs.screenId); 788 } 789 final CellLayout dropLayout = 790 mWorkspace.getScreenWithId(requestArgs.screenId); 791 792 dropLayout.setDropPending(true); 793 final Runnable onComplete = new Runnable() { 794 @Override 795 public void run() { 796 completeTwoStageWidgetDrop(resultCode, appWidgetId, requestArgs); 797 dropLayout.setDropPending(false); 798 } 799 }; 800 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, 801 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 802 } 803 return; 804 } 805 806 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET 807 || requestCode == REQUEST_BIND_PENDING_APPWIDGET) { 808 if (resultCode == RESULT_OK) { 809 // Update the widget view. 810 completeAdd(requestCode, data, pendingAddWidgetId, requestArgs); 811 } 812 // Leave the widget in the pending state if the user canceled the configure. 813 return; 814 } 815 816 if (requestCode == REQUEST_CREATE_SHORTCUT) { 817 // Handle custom shortcuts created using ACTION_CREATE_SHORTCUT. 818 if (resultCode == RESULT_OK && requestArgs.container != ItemInfo.NO_ID) { 819 completeAdd(requestCode, data, -1, requestArgs); 820 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, 821 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 822 823 } else if (resultCode == RESULT_CANCELED) { 824 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, 825 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); 826 } 827 } 828 mDragLayer.clearAnimatedView(); 829 } 830 831 @Override 832 protected void onActivityResult( 833 final int requestCode, final int resultCode, final Intent data) { 834 handleActivityResult(requestCode, resultCode, data); 835 if (mLauncherCallbacks != null) { 836 mLauncherCallbacks.onActivityResult(requestCode, resultCode, data); 837 } 838 } 839 840 /** @Override for MNC */ 841 public void onRequestPermissionsResult(int requestCode, String[] permissions, 842 int[] grantResults) { 843 PendingRequestArgs pendingArgs = mPendingRequestArgs; 844 if (requestCode == REQUEST_PERMISSION_CALL_PHONE && pendingArgs != null 845 && pendingArgs.getRequestCode() == REQUEST_PERMISSION_CALL_PHONE) { 846 setWaitingForResult(null); 847 848 View v = null; 849 CellLayout layout = getCellLayout(pendingArgs.container, pendingArgs.screenId); 850 if (layout != null) { 851 v = layout.getChildAt(pendingArgs.cellX, pendingArgs.cellY); 852 } 853 Intent intent = pendingArgs.getPendingIntent(); 854 855 if (grantResults.length > 0 856 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 857 startActivitySafely(v, intent, null); 858 } else { 859 // TODO: Show a snack bar with link to settings 860 Toast.makeText(this, getString(R.string.msg_no_phone_permission, 861 getString(R.string.derived_app_name)), Toast.LENGTH_SHORT).show(); 862 } 863 } 864 if (mLauncherCallbacks != null) { 865 mLauncherCallbacks.onRequestPermissionsResult(requestCode, permissions, 866 grantResults); 867 } 868 } 869 870 /** 871 * Check to see if a given screen id exists. If not, create it at the end, return the new id. 872 * 873 * @param screenId the screen id to check 874 * @return the new screen, or screenId if it exists 875 */ 876 private long ensurePendingDropLayoutExists(long screenId) { 877 CellLayout dropLayout = mWorkspace.getScreenWithId(screenId); 878 if (dropLayout == null) { 879 // it's possible that the add screen was removed because it was 880 // empty and a re-bind occurred 881 mWorkspace.addExtraEmptyScreen(); 882 return mWorkspace.commitExtraEmptyScreen(); 883 } else { 884 return screenId; 885 } 886 } 887 888 @Thunk void completeTwoStageWidgetDrop( 889 final int resultCode, final int appWidgetId, final PendingRequestArgs requestArgs) { 890 CellLayout cellLayout = mWorkspace.getScreenWithId(requestArgs.screenId); 891 Runnable onCompleteRunnable = null; 892 int animationType = 0; 893 894 AppWidgetHostView boundWidget = null; 895 if (resultCode == RESULT_OK) { 896 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION; 897 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId, 898 requestArgs.getWidgetProvider()); 899 boundWidget = layout; 900 onCompleteRunnable = new Runnable() { 901 @Override 902 public void run() { 903 completeAddAppWidget(appWidgetId, requestArgs, layout, null); 904 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), 905 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); 906 } 907 }; 908 } else if (resultCode == RESULT_CANCELED) { 909 mAppWidgetHost.deleteAppWidgetId(appWidgetId); 910 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION; 911 } 912 if (mDragLayer.getAnimatedView() != null) { 913 mWorkspace.animateWidgetDrop(requestArgs, cellLayout, 914 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable, 915 animationType, boundWidget, true); 916 } else if (onCompleteRunnable != null) { 917 // The animated view may be null in the case of a rotation during widget configuration 918 onCompleteRunnable.run(); 919 } 920 } 921 922 @Override 923 protected void onStop() { 924 super.onStop(); 925 FirstFrameAnimatorHelper.setIsVisible(false); 926 927 if (mLauncherCallbacks != null) { 928 mLauncherCallbacks.onStop(); 929 } 930 931 if (Utilities.isNycMR1OrAbove()) { 932 mAppWidgetHost.stopListening(); 933 } 934 } 935 936 @Override 937 protected void onStart() { 938 super.onStart(); 939 FirstFrameAnimatorHelper.setIsVisible(true); 940 941 if (mLauncherCallbacks != null) { 942 mLauncherCallbacks.onStart(); 943 } 944 945 if (Utilities.isNycMR1OrAbove()) { 946 mAppWidgetHost.startListening(); 947 } 948 } 949 950 @Override 951 protected void onResume() { 952 long startTime = 0; 953 if (DEBUG_RESUME_TIME) { 954 startTime = System.currentTimeMillis(); 955 Log.v(TAG, "Launcher.onResume()"); 956 } 957 958 if (mLauncherCallbacks != null) { 959 mLauncherCallbacks.preOnResume(); 960 } 961 962 super.onResume(); 963 getUserEventDispatcher().resetElapsedSessionMillis(); 964 965 // Restore the previous launcher state 966 if (mOnResumeState == State.WORKSPACE) { 967 showWorkspace(false); 968 } else if (mOnResumeState == State.APPS) { 969 boolean launchedFromApp = (mWaitingForResume != null); 970 // Don't update the predicted apps if the user is returning to launcher in the apps 971 // view after launching an app, as they may be depending on the UI to be static to 972 // switch to another app, otherwise, if it was 973 showAppsView(false /* animated */, !launchedFromApp /* updatePredictedApps */, 974 mAppsView.shouldRestoreImeState() /* focusSearchBar */); 975 } else if (mOnResumeState == State.WIDGETS) { 976 showWidgetsView(false, false); 977 } 978 mOnResumeState = State.NONE; 979 980 mPaused = false; 981 if (mOnResumeNeedsLoad) { 982 setWorkspaceLoading(true); 983 mModel.startLoader(getCurrentWorkspaceScreen()); 984 mOnResumeNeedsLoad = false; 985 } 986 if (mBindOnResumeCallbacks.size() > 0) { 987 // We might have postponed some bind calls until onResume (see waitUntilResume) -- 988 // execute them here 989 long startTimeCallbacks = 0; 990 if (DEBUG_RESUME_TIME) { 991 startTimeCallbacks = System.currentTimeMillis(); 992 } 993 994 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) { 995 mBindOnResumeCallbacks.get(i).run(); 996 } 997 mBindOnResumeCallbacks.clear(); 998 if (DEBUG_RESUME_TIME) { 999 Log.d(TAG, "Time spent processing callbacks in onResume: " + 1000 (System.currentTimeMillis() - startTimeCallbacks)); 1001 } 1002 } 1003 if (mOnResumeCallbacks.size() > 0) { 1004 for (int i = 0; i < mOnResumeCallbacks.size(); i++) { 1005 mOnResumeCallbacks.get(i).run(); 1006 } 1007 mOnResumeCallbacks.clear(); 1008 } 1009 1010 // Reset the pressed state of icons that were locked in the press state while activities 1011 // were launching 1012 if (mWaitingForResume != null) { 1013 // Resets the previous workspace icon press state 1014 mWaitingForResume.setStayPressed(false); 1015 } 1016 1017 // It is possible that widgets can receive updates while launcher is not in the foreground. 1018 // Consequently, the widgets will be inflated in the orientation of the foreground activity 1019 // (framework issue). On resuming, we ensure that any widgets are inflated for the current 1020 // orientation. 1021 if (!isWorkspaceLoading()) { 1022 getWorkspace().reinflateWidgetsIfNecessary(); 1023 } 1024 1025 if (DEBUG_RESUME_TIME) { 1026 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime)); 1027 } 1028 1029 // We want to suppress callbacks about CustomContent being shown if we have just received 1030 // onNewIntent while the user was present within launcher. In that case, we post a call 1031 // to move the user to the main screen (which will occur after onResume). We don't want to 1032 // have onHide (from onPause), then onShow, then onHide again, which we get if we don't 1033 // suppress here. 1034 if (mWorkspace.getCustomContentCallbacks() != null 1035 && !mMoveToDefaultScreenFromNewIntent) { 1036 // If we are resuming and the custom content is the current page, we call onShow(). 1037 // It is also possible that onShow will instead be called slightly after first layout 1038 // if PagedView#setRestorePage was set to the custom content page in onCreate(). 1039 if (mWorkspace.isOnOrMovingToCustomContent()) { 1040 mWorkspace.getCustomContentCallbacks().onShow(true); 1041 } 1042 } 1043 mMoveToDefaultScreenFromNewIntent = false; 1044 updateInteraction(Workspace.State.NORMAL, mWorkspace.getState()); 1045 mWorkspace.onResume(); 1046 1047 if (!isWorkspaceLoading()) { 1048 // Process any items that were added while Launcher was away. 1049 InstallShortcutReceiver.disableAndFlushInstallQueue(this); 1050 1051 // Refresh shortcuts if the permission changed. 1052 mModel.refreshShortcutsIfRequired(); 1053 } 1054 1055 if (shouldShowDiscoveryBounce()) { 1056 mAllAppsController.showDiscoveryBounce(); 1057 } 1058 mIsResumeFromActionScreenOff = false; 1059 if (mLauncherCallbacks != null) { 1060 mLauncherCallbacks.onResume(); 1061 } 1062 } 1063 1064 @Override 1065 protected void onPause() { 1066 // Ensure that items added to Launcher are queued until Launcher returns 1067 InstallShortcutReceiver.enableInstallQueue(); 1068 1069 super.onPause(); 1070 mPaused = true; 1071 mDragController.cancelDrag(); 1072 mDragController.resetLastGestureUpTime(); 1073 1074 // We call onHide() aggressively. The custom content callbacks should be able to 1075 // debounce excess onHide calls. 1076 if (mWorkspace.getCustomContentCallbacks() != null) { 1077 mWorkspace.getCustomContentCallbacks().onHide(); 1078 } 1079 1080 if (mLauncherCallbacks != null) { 1081 mLauncherCallbacks.onPause(); 1082 } 1083 } 1084 1085 public interface CustomContentCallbacks { 1086 // Custom content is completely shown. {@code fromResume} indicates whether this was caused 1087 // by a onResume or by scrolling otherwise. 1088 public void onShow(boolean fromResume); 1089 1090 // Custom content is completely hidden 1091 public void onHide(); 1092 1093 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing). 1094 public void onScrollProgressChanged(float progress); 1095 1096 // Indicates whether the user is allowed to scroll away from the custom content. 1097 boolean isScrollingAllowed(); 1098 } 1099 1100 public interface LauncherOverlay { 1101 1102 /** 1103 * Touch interaction leading to overscroll has begun 1104 */ 1105 public void onScrollInteractionBegin(); 1106 1107 /** 1108 * Touch interaction related to overscroll has ended 1109 */ 1110 public void onScrollInteractionEnd(); 1111 1112 /** 1113 * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost 1114 * screen (or in the case of RTL, the rightmost screen). 1115 */ 1116 public void onScrollChange(float progress, boolean rtl); 1117 1118 /** 1119 * Called when the launcher is ready to use the overlay 1120 * @param callbacks A set of callbacks provided by Launcher in relation to the overlay 1121 */ 1122 public void setOverlayCallbacks(LauncherOverlayCallbacks callbacks); 1123 } 1124 1125 public interface LauncherSearchCallbacks { 1126 /** 1127 * Called when the search overlay is shown. 1128 */ 1129 public void onSearchOverlayOpened(); 1130 1131 /** 1132 * Called when the search overlay is dismissed. 1133 */ 1134 public void onSearchOverlayClosed(); 1135 } 1136 1137 public interface LauncherOverlayCallbacks { 1138 public void onScrollChanged(float progress); 1139 } 1140 1141 class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks { 1142 1143 public void onScrollChanged(float progress) { 1144 if (mWorkspace != null) { 1145 mWorkspace.onOverlayScrollChanged(progress); 1146 } 1147 } 1148 } 1149 1150 protected boolean hasSettings() { 1151 if (mLauncherCallbacks != null) { 1152 return mLauncherCallbacks.hasSettings(); 1153 } else { 1154 // On devices with a locked orientation, we will at least have the allow rotation 1155 // setting. 1156 return !getResources().getBoolean(R.bool.allow_rotation); 1157 } 1158 } 1159 1160 public void addToCustomContentPage(View customContent, 1161 CustomContentCallbacks callbacks, String description) { 1162 mWorkspace.addToCustomContentPage(customContent, callbacks, description); 1163 } 1164 1165 // The custom content needs to offset its content to account for the QSB 1166 public int getTopOffsetForCustomContent() { 1167 return mWorkspace.getPaddingTop(); 1168 } 1169 1170 @Override 1171 public Object onRetainNonConfigurationInstance() { 1172 // Flag the loader to stop early before switching 1173 if (mModel.isCurrentCallbacks(this)) { 1174 mModel.stopLoader(); 1175 } 1176 //TODO(hyunyoungs): stop the widgets loader when there is a rotation. 1177 1178 return Boolean.TRUE; 1179 } 1180 1181 // We can't hide the IME if it was forced open. So don't bother 1182 @Override 1183 public void onWindowFocusChanged(boolean hasFocus) { 1184 super.onWindowFocusChanged(hasFocus); 1185 mHasFocus = hasFocus; 1186 1187 if (mLauncherCallbacks != null) { 1188 mLauncherCallbacks.onWindowFocusChanged(hasFocus); 1189 } 1190 } 1191 1192 private boolean acceptFilter() { 1193 final InputMethodManager inputManager = (InputMethodManager) 1194 getSystemService(Context.INPUT_METHOD_SERVICE); 1195 return !inputManager.isFullscreenMode(); 1196 } 1197 1198 @Override 1199 public boolean onKeyDown(int keyCode, KeyEvent event) { 1200 final int uniChar = event.getUnicodeChar(); 1201 final boolean handled = super.onKeyDown(keyCode, event); 1202 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar); 1203 if (!handled && acceptFilter() && isKeyNotWhitespace) { 1204 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb, 1205 keyCode, event); 1206 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) { 1207 // something usable has been typed - start a search 1208 // the typed text will be retrieved and cleared by 1209 // showSearchDialog() 1210 // If there are multiple keystrokes before the search dialog takes focus, 1211 // onSearchRequested() will be called for every keystroke, 1212 // but it is idempotent, so it's fine. 1213 return onSearchRequested(); 1214 } 1215 } 1216 1217 // Eat the long press event so the keyboard doesn't come up. 1218 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) { 1219 return true; 1220 } 1221 1222 return handled; 1223 } 1224 1225 @Override 1226 public boolean onKeyUp(int keyCode, KeyEvent event) { 1227 if (keyCode == KeyEvent.KEYCODE_MENU) { 1228 // Ignore the menu key if we are currently dragging or are on the custom content screen 1229 if (!isOnCustomContent() && !mDragController.isDragging()) { 1230 // Close any open 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 1251 private String getTypedText() { 1252 return mDefaultKeySsb.toString(); 1253 } 1254 1255 @Override 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 */ 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 */ 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 */ 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 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 */ 1435 public void setAllAppsButton(View allAppsButton) { 1436 mAllAppsButton = allAppsButton; 1437 } 1438 1439 public View getStartViewForAllAppsRevealAnimation() { 1440 return FeatureFlags.NO_ALL_APPS_ICON ? mWorkspace.getPageIndicator() : mAllAppsButton; 1441 } 1442 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 */ 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 */ 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 */ 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 */ 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 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 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 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 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 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 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 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 1741 private void removeWidgetToAutoAdvance(View hostView) { 1742 if (mWidgetsToAdvance.containsKey(hostView)) { 1743 mWidgetsToAdvance.remove(hostView); 1744 updateAutoAdvanceState(); 1745 } 1746 } 1747 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 1753 public DragLayer getDragLayer() { 1754 return mDragLayer; 1755 } 1756 1757 public AllAppsContainerView getAppsView() { 1758 return mAppsView; 1759 } 1760 1761 public WidgetsContainerView getWidgetsView() { 1762 return mWidgetsView; 1763 } 1764 1765 public Workspace getWorkspace() { 1766 return mWorkspace; 1767 } 1768 1769 public View getQsbContainer() { 1770 return mQsbContainer; 1771 } 1772 1773 public Hotseat getHotseat() { 1774 return mHotseat; 1775 } 1776 1777 public ViewGroup getOverviewPanel() { 1778 return mOverviewPanel; 1779 } 1780 1781 public DropTargetBar getDropTargetBar() { 1782 return mDropTargetBar; 1783 } 1784 1785 public LauncherAppWidgetHost getAppWidgetHost() { 1786 return mAppWidgetHost; 1787 } 1788 1789 public LauncherModel getModel() { 1790 return mModel; 1791 } 1792 1793 public SharedPreferences getSharedPrefs() { 1794 return mSharedPrefs; 1795 } 1796 1797 public DeviceProfile getDeviceProfile() { 1798 return mDeviceProfile; 1799 } 1800 1801 public void closeSystemDialogs() { 1802 getWindow().closeAllPanels(); 1803 1804 // Whatever we were doing is hereby canceled. 1805 setWaitingForResult(null); 1806 } 1807 1808 @Override 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 1903 public void onRestoreInstanceState(Bundle state) { 1904 super.onRestoreInstanceState(state); 1905 for (int page: mSynchronouslyBoundPages) { 1906 mWorkspace.restoreInstanceStateForChild(page); 1907 } 1908 } 1909 1910 @Override 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 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 1983 public LauncherAccessibilityDelegate getAccessibilityDelegate() { 1984 return mAccessibilityDelegate; 1985 } 1986 1987 public DragController getDragController() { 1988 return mDragController; 1989 } 1990 1991 @Override 1992 public void startActivityForResult(Intent intent, int requestCode, Bundle options) { 1993 super.startActivityForResult(intent, requestCode, options); 1994 } 1995 1996 @Override 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 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 */ 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 2074 public boolean isOnCustomContent() { 2075 return mWorkspace.isOnOrMovingToCustomContent(); 2076 } 2077 2078 @Override 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 2088 public boolean onSearchRequested() { 2089 startSearch(null, false, null, true); 2090 // Use a custom animation for launching search 2091 return true; 2092 } 2093 2094 public boolean isWorkspaceLocked() { 2095 return mWorkspaceLoading || mPendingRequestArgs != null; 2096 } 2097 2098 public boolean isWorkspaceLoading() { 2099 return mWorkspaceLoading; 2100 } 2101 2102 private void setWorkspaceLoading(boolean value) { 2103 boolean isLocked = isWorkspaceLocked(); 2104 mWorkspaceLoading = value; 2105 if (isLocked != isWorkspaceLocked()) { 2106 onWorkspaceLockedChanged(); 2107 } 2108 } 2109 2110 private void setWaitingForResult(PendingRequestArgs args) { 2111 boolean isLocked = isWorkspaceLocked(); 2112 mPendingRequestArgs = args; 2113 if (isLocked != isWorkspaceLocked()) { 2114 onWorkspaceLockedChanged(); 2115 } 2116 } 2117 2118 protected void onWorkspaceLockedChanged() { 2119 if (mLauncherCallbacks != null) { 2120 mLauncherCallbacks.onWorkspaceLockedChanged(); 2121 } 2122 } 2123 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 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 2156 protected void moveToCustomContentScreen(boolean animate) { 2157 // Close any folders that may be open. 2158 closeFolder(); 2159 mWorkspace.moveToCustomContentScreen(animate); 2160 } 2161 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 */ 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 */ 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 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 */ 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 */ 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 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 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 */ 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") 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 */ 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 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 */ 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 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 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 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 2689 public void onAccessibilityStateChanged(boolean enabled) { 2690 mDragLayer.onAccessibilityStateChanged(enabled); 2691 } 2692 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 */ 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 */ 2720 protected void onInteractionBegin() { 2721 if (mLauncherCallbacks != null) { 2722 mLauncherCallbacks.onInteractionBegin(); 2723 } 2724 } 2725 2726 /** Updates the interaction state. */ 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 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 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 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 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 */ 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 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 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 */ 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 3003 public void closeFolder() { 3004 closeFolder(true); 3005 } 3006 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 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 3041 public void closeShortcutsContainer() { 3042 closeShortcutsContainer(true); 3043 } 3044 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 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 */ 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 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 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 */ 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 */ 3175 public boolean isAllAppsVisible() { 3176 return isAppsViewVisible(); 3177 } 3178 3179 public boolean isAppsViewVisible() { 3180 return (mState == State.APPS) || (mOnResumeState == State.APPS); 3181 } 3182 3183 public boolean isWidgetsViewVisible() { 3184 return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS); 3185 } 3186 3187 @Override 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 3203 public boolean showWorkspace(boolean animated) { 3204 return showWorkspace(animated, null); 3205 } 3206 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 */ 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 */ 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 */ 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 */ 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. 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 */ 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 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 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 3405 boolean isStateSpringLoaded() { 3406 return mState == State.WORKSPACE_SPRING_LOADED || mState == State.APPS_SPRING_LOADED 3407 || mState == State.WIDGETS_SPRING_LOADED; 3408 } 3409 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 */ 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 3435 void lockAllApps() { 3436 // TODO 3437 } 3438 3439 void unlockAllApps() { 3440 // TODO 3441 } 3442 3443 @Override 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 */ 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 3488 private boolean waitUntilResume(Runnable run) { 3489 return waitUntilResume(run, false); 3490 } 3491 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 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 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 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 */ 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 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 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 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 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 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 */ 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 */ 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 3922 public void onPageBoundSynchronously(int page) { 3923 mSynchronouslyBoundPages.add(page); 3924 } 3925 3926 @Override 3927 public void executeOnNextDraw(ViewOnDrawExecutor executor) { 3928 if (mPendingExecutor != null) { 3929 mPendingExecutor.markCompleted(); 3930 } 3931 mPendingExecutor = executor; 3932 executor.attachTo(this); 3933 } 3934 3935 public void clearPendingExecutor(ViewOnDrawExecutor executor) { 3936 if (mPendingExecutor == executor) { 3937 mPendingExecutor = null; 3938 } 3939 } 3940 3941 @Override 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 */ 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 4012 private boolean canRunNewAppsAnimation() { 4013 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime(); 4014 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000); 4015 } 4016 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 4025 public boolean useVerticalBarLayout() { 4026 return mDeviceProfile.isVerticalBarLayout(); 4027 } 4028 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 */ 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 4072 public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) { 4073 mDeepShortcutMap = deepShortcutMapCopy; 4074 if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap); 4075 } 4076 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 */ 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 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 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 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 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 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 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 4262 public void notifyWidgetProvidersChanged() { 4263 if (mWorkspace.getState().shouldUpdateWidget) { 4264 mModel.refreshAndBindWidgetsAndShortcuts(this, mWidgetsView.isEmpty()); 4265 } 4266 } 4267 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) 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 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 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 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) 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 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 4380 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption, 4381 Bitmap icon) { 4382 return new ShortcutInfo(shortcutIntent, caption, caption, icon, 4383 UserHandleCompat.myUserHandle()); 4384 } 4385 4386 protected void moveWorkspaceToDefaultScreen() { 4387 mWorkspace.moveToDefaultScreen(false); 4388 } 4389 4390 /** 4391 * Returns a FastBitmapDrawable with the icon, accurately sized. 4392 */ 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 */ 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 */ 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 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 4460 public static CustomAppWidget getCustomAppWidget(String name) { 4461 return sCustomAppWidgets.get(name); 4462 } 4463 4464 public static HashMap<String, CustomAppWidget> getCustomAppWidgets() { 4465 return sCustomAppWidgets; 4466 } 4467 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 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 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 4497 public void run() { 4498 setOrientation(); 4499 } 4500 } 4501 } 4502