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