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