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