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