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