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