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