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