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