2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.launcher3;
19 import android.Manifest;
20 import android.animation.Animator;
21 import android.animation.AnimatorSet;
22 import android.animation.ValueAnimator;
23 import android.annotation.SuppressLint;
24 import android.annotation.TargetApi;
25 import android.app.ActivityOptions;
26 import android.app.AlertDialog;
27 import android.app.SearchManager;
28 import android.appwidget.AppWidgetHostView;
29 import android.appwidget.AppWidgetManager;
30 import android.content.ActivityNotFoundException;
31 import android.content.BroadcastReceiver;
32 import android.content.ComponentCallbacks2;
33 import android.content.ComponentName;
34 import android.content.Context;
35 import android.content.ContextWrapper;
36 import android.content.DialogInterface;
37 import android.content.Intent;
38 import android.content.IntentFilter;
39 import android.content.IntentSender;
40 import android.content.SharedPreferences;
41 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
42 import android.content.pm.ActivityInfo;
43 import android.content.pm.PackageManager;
44 import android.database.sqlite.SQLiteDatabase;
45 import android.graphics.Point;
46 import android.graphics.Rect;
47 import android.graphics.drawable.Drawable;
48 import android.os.AsyncTask;
49 import android.os.Build;
50 import android.os.Bundle;
51 import android.os.Handler;
52 import android.os.Process;
53 import android.os.StrictMode;
54 import android.os.SystemClock;
55 import android.os.Trace;
56 import android.os.UserHandle;
57 import android.support.annotation.Nullable;
58 import android.text.Selection;
59 import android.text.SpannableStringBuilder;
60 import android.text.TextUtils;
61 import android.text.method.TextKeyListener;
62 import android.util.Log;
63 import android.view.Display;
64 import android.view.HapticFeedbackConstants;
65 import android.view.KeyEvent;
66 import android.view.KeyboardShortcutGroup;
67 import android.view.KeyboardShortcutInfo;
68 import android.view.Menu;
69 import android.view.MotionEvent;
70 import android.view.View;
71 import android.view.View.OnLongClickListener;
72 import android.view.ViewGroup;
73 import android.view.ViewTreeObserver;
74 import android.view.WindowManager;
75 import android.view.accessibility.AccessibilityEvent;
76 import android.view.accessibility.AccessibilityManager;
77 import android.view.animation.OvershootInterpolator;
78 import android.view.inputmethod.InputMethodManager;
79 import android.widget.TextView;
80 import android.widget.Toast;
82 import com.android.launcher3.DropTarget.DragObject;
83 import com.android.launcher3.LauncherSettings.Favorites;
84 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
85 import com.android.launcher3.allapps.AllAppsContainerView;
86 import com.android.launcher3.allapps.AllAppsTransitionController;
87 import com.android.launcher3.allapps.DefaultAppSearchController;
88 import com.android.launcher3.anim.AnimationLayerSet;
89 import com.android.launcher3.compat.AppWidgetManagerCompat;
90 import com.android.launcher3.compat.LauncherAppsCompat;
91 import com.android.launcher3.compat.PinItemRequestCompat;
92 import com.android.launcher3.config.FeatureFlags;
93 import com.android.launcher3.config.ProviderConfig;
94 import com.android.launcher3.dragndrop.DragController;
95 import com.android.launcher3.dragndrop.DragLayer;
96 import com.android.launcher3.dragndrop.DragOptions;
97 import com.android.launcher3.dragndrop.DragView;
98 import com.android.launcher3.dragndrop.PinItemDragListener;
99 import com.android.launcher3.dynamicui.ExtractedColors;
100 import com.android.launcher3.folder.Folder;
101 import com.android.launcher3.folder.FolderIcon;
102 import com.android.launcher3.keyboard.CustomActionsPopup;
103 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
104 import com.android.launcher3.logging.FileLog;
105 import com.android.launcher3.logging.UserEventDispatcher;
106 import com.android.launcher3.model.ModelWriter;
107 import com.android.launcher3.model.PackageItemInfo;
108 import com.android.launcher3.model.WidgetItem;
109 import com.android.launcher3.notification.NotificationListener;
110 import com.android.launcher3.pageindicators.PageIndicator;
111 import com.android.launcher3.popup.PopupContainerWithArrow;
112 import com.android.launcher3.popup.PopupDataProvider;
113 import com.android.launcher3.shortcuts.DeepShortcutManager;
114 import com.android.launcher3.shortcuts.ShortcutKey;
115 import com.android.launcher3.userevent.nano.LauncherLogProto;
116 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
117 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
118 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
119 import com.android.launcher3.util.ActivityResultInfo;
120 import com.android.launcher3.util.ComponentKey;
121 import com.android.launcher3.util.ItemInfoMatcher;
122 import com.android.launcher3.util.MultiHashMap;
123 import com.android.launcher3.util.PackageManagerHelper;
124 import com.android.launcher3.util.PackageUserKey;
125 import com.android.launcher3.util.PendingRequestArgs;
126 import com.android.launcher3.util.TestingUtils;
127 import com.android.launcher3.util.Thunk;
128 import com.android.launcher3.util.ViewOnDrawExecutor;
129 import com.android.launcher3.widget.PendingAddShortcutInfo;
130 import com.android.launcher3.widget.PendingAddWidgetInfo;
131 import com.android.launcher3.widget.WidgetAddFlowHandler;
132 import com.android.launcher3.widget.WidgetHostViewLoader;
133 import com.android.launcher3.widget.WidgetsContainerView;
135 import java.io.FileDescriptor;
136 import java.io.PrintWriter;
137 import java.util.ArrayList;
138 import java.util.Collection;
139 import java.util.HashMap;
140 import java.util.HashSet;
141 import java.util.List;
142 import java.util.Set;
145 * Default launcher application.
147 public class Launcher extends BaseActivity
148 implements LauncherExterns, View.OnClickListener, OnLongClickListener,
149 LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener,
150 AccessibilityManager.AccessibilityStateChangeListener {
151 public static final String TAG = "Launcher";
152 static final boolean LOGD = false;
154 static final boolean DEBUG_WIDGETS = false;
155 static final boolean DEBUG_STRICT_MODE = false;
156 static final boolean DEBUG_RESUME_TIME = false;
158 private static final int REQUEST_CREATE_SHORTCUT = 1;
159 private static final int REQUEST_CREATE_APPWIDGET = 5;
160 private static final int REQUEST_PICK_APPWIDGET = 9;
161 private static final int REQUEST_PICK_WALLPAPER = 10;
163 private static final int REQUEST_BIND_APPWIDGET = 11;
164 private static final int REQUEST_BIND_PENDING_APPWIDGET = 14;
165 private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
167 private static final int REQUEST_PERMISSION_CALL_PHONE = 13;
169 private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
172 * IntentStarter uses request codes starting with this. This must be greater than all activity
173 * request codes used internally.
175 protected static final int REQUEST_LAST = 100;
177 private static final int SOFT_INPUT_MODE_DEFAULT =
178 WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
179 private static final int SOFT_INPUT_MODE_ALL_APPS =
180 WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
182 // The Intent extra that defines whether to ignore the launch animation
183 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
184 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
187 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
189 private static final String RUNTIME_STATE = "launcher.state";
190 // Type: PendingRequestArgs
191 private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args";
192 // Type: ActivityResultInfo
193 private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result";
195 static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown";
197 /** The different states that Launcher can be in. */
198 enum State { NONE, WORKSPACE, WORKSPACE_SPRING_LOADED, APPS, APPS_SPRING_LOADED,
199 WIDGETS, WIDGETS_SPRING_LOADED }
201 @Thunk State mState = State.WORKSPACE;
202 @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
204 private boolean mIsSafeModeEnabled;
206 public static final int APPWIDGET_HOST_ID = 1024;
207 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 500;
208 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
209 private static final int ACTIVITY_START_DELAY = 1000;
211 // How long to wait before the new-shortcut animation automatically pans the workspace
212 private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
213 private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
214 @Thunk static int NEW_APPS_ANIMATION_DELAY = 500;
216 @Thunk Workspace mWorkspace;
217 private View mLauncherView;
218 @Thunk DragLayer mDragLayer;
219 private DragController mDragController;
220 private View mQsbContainer;
222 public View mWeightWatcher;
224 private AppWidgetManagerCompat mAppWidgetManager;
225 private LauncherAppWidgetHost mAppWidgetHost;
227 private int[] mTmpAddItemCellCoordinates = new int[2];
229 @Thunk Hotseat mHotseat;
230 private ViewGroup mOverviewPanel;
232 private View mAllAppsButton;
233 private View mWidgetsButton;
235 private DropTargetBar mDropTargetBar;
237 // Main container view for the all apps screen.
238 @Thunk AllAppsContainerView mAppsView;
239 AllAppsTransitionController mAllAppsController;
241 // Main container view and the model for the widget tray screen.
242 @Thunk WidgetsContainerView mWidgetsView;
243 @Thunk MultiHashMap<PackageItemInfo, WidgetItem> mAllWidgets;
245 // We set the state in both onCreate and then onNewIntent in some cases, which causes both
246 // scroll issues (because the workspace may not have been measured yet) and extra work.
247 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
248 private State mOnResumeState = State.NONE;
250 private SpannableStringBuilder mDefaultKeySsb = null;
252 @Thunk boolean mWorkspaceLoading = true;
254 private boolean mPaused = true;
255 private boolean mOnResumeNeedsLoad;
257 private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
258 private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
259 private ViewOnDrawExecutor mPendingExecutor;
261 private LauncherModel mModel;
262 private ModelWriter mModelWriter;
263 private IconCache mIconCache;
264 private ExtractedColors mExtractedColors;
265 private LauncherAccessibilityDelegate mAccessibilityDelegate;
266 private Handler mHandler = new Handler();
267 private boolean mIsResumeFromActionScreenOff;
268 private boolean mHasFocus = false;
269 private boolean mAttached = false;
271 private PopupDataProvider mPopupDataProvider;
273 private View.OnTouchListener mHapticFeedbackTouchListener;
275 // Determines how long to wait after a rotation before restoring the screen orientation to
276 // match the sensor state.
277 private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
279 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
281 // We only want to get the SharedPreferences once since it does an FS stat each time we get
282 // it from the context.
283 private SharedPreferences mSharedPrefs;
285 private boolean mMoveToDefaultScreenFromNewIntent;
287 // This is set to the view that launched the activity that navigated the user away from
288 // launcher. Since there is no callback for when the activity has finished launching, enable
289 // the press state and keep this reference to reset the press state when we return to launcher.
290 private BubbleTextView mWaitingForResume;
292 protected static HashMap<String, CustomAppWidget> sCustomAppWidgets =
293 new HashMap<String, CustomAppWidget>();
296 if (TestingUtils.ENABLE_CUSTOM_WIDGET_TEST) {
297 TestingUtils.addDummyWidget(sCustomAppWidgets);
301 // Exiting spring loaded mode happens with a delay. This runnable object triggers the
302 // state transition. If another state transition happened during this delay,
303 // simply unregister this runnable.
304 private Runnable mExitSpringLoadedModeRunnable;
306 @Thunk Runnable mBuildLayersRunnable = new Runnable() {
308 if (mWorkspace != null) {
309 mWorkspace.buildPageHardwareLayers();
314 // Activity result which needs to be processed after workspace has loaded.
315 private ActivityResultInfo mPendingActivityResult;
317 * Holds extra information required to handle a result from an external call, like
318 * {@link #startActivityForResult(Intent, int)} or {@link #requestPermissions(String[], int)}
320 private PendingRequestArgs mPendingRequestArgs;
322 private float mLastDispatchTouchEventX = 0.0f;
324 public ViewGroupFocusHelper mFocusHandler;
325 private boolean mRotationEnabled = false;
327 @Thunk void setOrientation() {
328 if (mRotationEnabled) {
329 unlockScreenOrientation(true);
331 setRequestedOrientation(
332 ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
336 private RotationPrefChangeHandler mRotationPrefChangeHandler;
339 protected void onCreate(Bundle savedInstanceState) {
340 if (DEBUG_STRICT_MODE) {
341 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
344 .detectNetwork() // or .detectAll() for all detectable problems
347 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
348 .detectLeakedSqlLiteObjects()
349 .detectLeakedClosableObjects()
354 if (LauncherAppState.PROFILE_STARTUP) {
355 Trace.beginSection("Launcher-onCreate");
358 if (mLauncherCallbacks != null) {
359 mLauncherCallbacks.preOnCreate();
362 super.onCreate(savedInstanceState);
364 LauncherAppState app = LauncherAppState.getInstance(this);
366 // Load configuration-specific DeviceProfile
367 mDeviceProfile = app.getInvariantDeviceProfile().getDeviceProfile(this);
368 if (isInMultiWindowModeCompat()) {
369 Display display = getWindowManager().getDefaultDisplay();
370 Point mwSize = new Point();
371 display.getSize(mwSize);
372 mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
375 mSharedPrefs = Utilities.getPrefs(this);
376 mIsSafeModeEnabled = getPackageManager().isSafeMode();
377 mModel = app.setLauncher(this);
378 mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout());
379 mIconCache = app.getIconCache();
380 mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
382 mDragController = new DragController(this);
383 mAllAppsController = new AllAppsTransitionController(this);
384 mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, mAllAppsController);
386 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
388 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
389 mAppWidgetHost.startListening();
391 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
392 // this also ensures that any synchronous binding below doesn't re-trigger another
393 // LauncherModel load.
396 mLauncherView = getLayoutInflater().inflate(R.layout.launcher, null);
399 mDeviceProfile.layout(this, false /* notifyListeners */);
400 mExtractedColors = new ExtractedColors();
401 loadExtractedColorsAndColorItems();
403 mPopupDataProvider = new PopupDataProvider(this);
405 ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
406 .addAccessibilityStateChangeListener(this);
410 restoreState(savedInstanceState);
412 if (LauncherAppState.PROFILE_STARTUP) {
416 // We only load the page synchronously if the user rotates (or triggers a
417 // configuration change) while launcher is in the foreground
418 int currentScreen = PagedView.INVALID_RESTORE_PAGE;
419 if (savedInstanceState != null) {
420 currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
422 if (!mModel.startLoader(currentScreen)) {
423 // If we are not binding synchronously, show a fade in animation when
424 // the first page bind completes.
425 mDragLayer.setAlpha(0);
427 // Pages bound synchronously.
428 mWorkspace.setCurrentPage(currentScreen);
430 setWorkspaceLoading(true);
433 // For handling default keys
434 mDefaultKeySsb = new SpannableStringBuilder();
435 Selection.setSelection(mDefaultKeySsb, 0);
437 mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
438 // In case we are on a device with locked rotation, we should look at preferences to check
439 // if the user has specifically allowed rotation.
440 if (!mRotationEnabled) {
441 mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
442 mRotationPrefChangeHandler = new RotationPrefChangeHandler();
443 mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
446 if (PinItemDragListener.handleDragRequest(this, getIntent())) {
447 // Temporarily enable the rotation
448 mRotationEnabled = true;
451 // On large interfaces, or on devices that a user has specifically enabled screen rotation,
452 // we want the screen to auto-rotate based on the current orientation
455 setContentView(mLauncherView);
456 if (mLauncherCallbacks != null) {
457 mLauncherCallbacks.onCreate(savedInstanceState);
462 public View findViewById(int id) {
463 return mLauncherView.findViewById(id);
467 public void onExtractedColorsChanged() {
468 loadExtractedColorsAndColorItems();
472 public void onAppWidgetHostReset() {
473 if (mAppWidgetHost != null) {
474 mAppWidgetHost.startListening();
478 private void loadExtractedColorsAndColorItems() {
479 // TODO: do this in pre-N as well, once the extraction part is complete.
480 if (Utilities.ATLEAST_NOUGAT) {
481 mExtractedColors.load(this);
482 mHotseat.updateColor(mExtractedColors, !mPaused);
483 mWorkspace.getPageIndicator().updateColor(mExtractedColors);
484 boolean lightStatusBar = (FeatureFlags.LIGHT_STATUS_BAR
485 && mExtractedColors.getColor(ExtractedColors.STATUS_BAR_INDEX,
486 ExtractedColors.DEFAULT_DARK) == ExtractedColors.DEFAULT_LIGHT);
487 // It's possible that All Apps is visible when this is run,
488 // so always use light status bar in that case. Only change nav bar color to status bar
489 // color when All Apps is visible.
490 activateLightSystemBars(lightStatusBar || isAllAppsVisible(), true, isAllAppsVisible());
494 // TODO: use platform flag on API >= 26
495 private static final int SYSTEM_UI_FLAG_LIGHT_NAV_BAR = 0x10;
498 * Sets the status and/or nav bar to be light or not. Light status bar means dark icons.
499 * @param isLight make sure the system bar is light.
500 * @param statusBar if true, make the status bar theme match the isLight param.
501 * @param navBar if true, make the nav bar theme match the isLight param.
503 public void activateLightSystemBars(boolean isLight, boolean statusBar, boolean navBar) {
504 int oldSystemUiFlags = getWindow().getDecorView().getSystemUiVisibility();
505 int newSystemUiFlags = oldSystemUiFlags;
508 newSystemUiFlags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
510 if (navBar && Utilities.isAtLeastO()) {
511 newSystemUiFlags |= SYSTEM_UI_FLAG_LIGHT_NAV_BAR;
515 newSystemUiFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
517 if (navBar && Utilities.isAtLeastO()) {
518 newSystemUiFlags &= ~(SYSTEM_UI_FLAG_LIGHT_NAV_BAR);
522 if (newSystemUiFlags != oldSystemUiFlags) {
523 getWindow().getDecorView().setSystemUiVisibility(newSystemUiFlags);
527 private LauncherCallbacks mLauncherCallbacks;
529 public void onPostCreate(Bundle savedInstanceState) {
530 super.onPostCreate(savedInstanceState);
531 if (mLauncherCallbacks != null) {
532 mLauncherCallbacks.onPostCreate(savedInstanceState);
536 public void onInsetsChanged(Rect insets) {
537 mDeviceProfile.updateInsets(insets);
538 mDeviceProfile.layout(this, true /* notifyListeners */);
542 * Call this after onCreate to set or clear overlay.
544 public void setLauncherOverlay(LauncherOverlay overlay) {
545 if (overlay != null) {
546 overlay.setOverlayCallbacks(new LauncherOverlayCallbacksImpl());
548 mWorkspace.setLauncherOverlay(overlay);
551 public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
552 mLauncherCallbacks = callbacks;
553 mLauncherCallbacks.setLauncherSearchCallback(new Launcher.LauncherSearchCallbacks() {
554 private boolean mWorkspaceImportanceStored = false;
555 private boolean mHotseatImportanceStored = false;
556 private int mWorkspaceImportanceForAccessibility =
557 View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
558 private int mHotseatImportanceForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
561 public void onSearchOverlayOpened() {
562 if (mWorkspaceImportanceStored || mHotseatImportanceStored) {
565 // The underlying workspace and hotseat are temporarily suppressed by the search
566 // overlay. So they shouldn't be accessible.
567 if (mWorkspace != null) {
568 mWorkspaceImportanceForAccessibility =
569 mWorkspace.getImportantForAccessibility();
570 mWorkspace.setImportantForAccessibility(
571 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
572 mWorkspaceImportanceStored = true;
574 if (mHotseat != null) {
575 mHotseatImportanceForAccessibility = mHotseat.getImportantForAccessibility();
576 mHotseat.setImportantForAccessibility(
577 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
578 mHotseatImportanceStored = true;
583 public void onSearchOverlayClosed() {
584 if (mWorkspaceImportanceStored && mWorkspace != null) {
585 mWorkspace.setImportantForAccessibility(mWorkspaceImportanceForAccessibility);
587 if (mHotseatImportanceStored && mHotseat != null) {
588 mHotseat.setImportantForAccessibility(mHotseatImportanceForAccessibility);
590 mWorkspaceImportanceStored = false;
591 mHotseatImportanceStored = false;
598 public void onLauncherProviderChanged() {
599 if (mLauncherCallbacks != null) {
600 mLauncherCallbacks.onLauncherProviderChange();
604 /** To be overridden by subclasses to hint to Launcher that we have custom content */
605 protected boolean hasCustomContentToLeft() {
606 if (mLauncherCallbacks != null) {
607 return mLauncherCallbacks.hasCustomContentToLeft();
613 * To be overridden by subclasses to populate the custom content container and call
614 * {@link #addToCustomContentPage}. This will only be invoked if
615 * {@link #hasCustomContentToLeft()} is {@code true}.
617 protected void populateCustomContentContainer() {
618 if (mLauncherCallbacks != null) {
619 mLauncherCallbacks.populateCustomContentContainer();
624 * Invoked by subclasses to signal a change to the {@link #addToCustomContentPage} value to
625 * ensure the custom content page is added or removed if necessary.
627 protected void invalidateHasCustomContentToLeft() {
628 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
629 // Not bound yet, wait for bindScreens to be called.
633 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
634 // Create the custom content page and call the subclass to populate it.
635 mWorkspace.createCustomContentContainer();
636 populateCustomContentContainer();
637 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
638 mWorkspace.removeCustomContentPage();
642 public boolean isDraggingEnabled() {
643 // We prevent dragging when we are loading the workspace as it is possible to pick up a view
644 // that is subsequently removed from the workspace in startBinding().
645 return !isWorkspaceLoading();
648 public int getViewIdForItem(ItemInfo info) {
649 // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
650 // This cast is safe as long as the id < 0x00FFFFFF
651 // Since we jail all the dynamically generated views, there should be no clashes
652 // with any other views.
653 return (int) info.id;
656 public PopupDataProvider getPopupDataProvider() {
657 return mPopupDataProvider;
661 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
662 * a configuration step, this allows the proper animations to run after other transitions.
664 private long completeAdd(
665 int requestCode, Intent intent, int appWidgetId, PendingRequestArgs info) {
666 long screenId = info.screenId;
667 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
668 // When the screen id represents an actual screen (as opposed to a rank) we make sure
669 // that the drop page actually exists.
670 screenId = ensurePendingDropLayoutExists(info.screenId);
673 switch (requestCode) {
674 case REQUEST_CREATE_SHORTCUT:
675 completeAddShortcut(intent, info.container, screenId, info.cellX, info.cellY, info);
677 case REQUEST_CREATE_APPWIDGET:
678 completeAddAppWidget(appWidgetId, info, null, null);
680 case REQUEST_RECONFIGURE_APPWIDGET:
681 completeRestoreAppWidget(appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED);
683 case REQUEST_BIND_PENDING_APPWIDGET: {
684 int widgetId = appWidgetId;
685 LauncherAppWidgetInfo widgetInfo =
686 completeRestoreAppWidget(widgetId, LauncherAppWidgetInfo.FLAG_UI_NOT_READY);
687 if (widgetInfo != null) {
688 // Since the view was just bound, also launch the configure activity if needed
689 LauncherAppWidgetProviderInfo provider = mAppWidgetManager
690 .getLauncherAppWidgetInfo(widgetId);
691 if (provider != null) {
692 new WidgetAddFlowHandler(provider)
693 .startConfigActivity(this, widgetInfo, REQUEST_RECONFIGURE_APPWIDGET);
703 private void handleActivityResult(
704 final int requestCode, final int resultCode, final Intent data) {
705 if (isWorkspaceLoading()) {
706 // process the result once the workspace has loaded.
707 mPendingActivityResult = new ActivityResultInfo(requestCode, resultCode, data);
710 mPendingActivityResult = null;
712 // Reset the startActivity waiting flag
713 final PendingRequestArgs requestArgs = mPendingRequestArgs;
714 setWaitingForResult(null);
715 if (requestArgs == null) {
719 final int pendingAddWidgetId = requestArgs.getWidgetId();
721 Runnable exitSpringLoaded = new Runnable() {
724 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
725 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
729 if (requestCode == REQUEST_BIND_APPWIDGET) {
730 // This is called only if the user did not previously have permissions to bind widgets
731 final int appWidgetId = data != null ?
732 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
733 if (resultCode == RESULT_CANCELED) {
734 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs);
735 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
736 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
737 } else if (resultCode == RESULT_OK) {
739 appWidgetId, requestArgs, null,
740 requestArgs.getWidgetHandler(),
741 ON_ACTIVITY_RESULT_ANIMATION_DELAY);
744 } else if (requestCode == REQUEST_PICK_WALLPAPER) {
745 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
746 // User could have free-scrolled between pages before picking a wallpaper; make sure
747 // we move to the closest one now.
748 mWorkspace.setCurrentPage(mWorkspace.getPageNearestToCenterOfScreen());
749 showWorkspace(false);
754 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
755 requestCode == REQUEST_CREATE_APPWIDGET);
757 // We have special handling for widgets
759 final int appWidgetId;
760 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
763 appWidgetId = pendingAddWidgetId;
765 appWidgetId = widgetId;
769 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
770 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
771 "returned from the widget configuration activity.");
772 result = RESULT_CANCELED;
773 completeTwoStageWidgetDrop(result, appWidgetId, requestArgs);
774 final Runnable onComplete = new Runnable() {
777 exitSpringLoadedDragModeDelayed(false, 0, null);
781 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
782 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
784 if (requestArgs.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
785 // When the screen id represents an actual screen (as opposed to a rank)
786 // we make sure that the drop page actually exists.
787 requestArgs.screenId =
788 ensurePendingDropLayoutExists(requestArgs.screenId);
790 final CellLayout dropLayout =
791 mWorkspace.getScreenWithId(requestArgs.screenId);
793 dropLayout.setDropPending(true);
794 final Runnable onComplete = new Runnable() {
797 completeTwoStageWidgetDrop(resultCode, appWidgetId, requestArgs);
798 dropLayout.setDropPending(false);
801 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
802 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
807 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET
808 || requestCode == REQUEST_BIND_PENDING_APPWIDGET) {
809 if (resultCode == RESULT_OK) {
810 // Update the widget view.
811 completeAdd(requestCode, data, pendingAddWidgetId, requestArgs);
813 // Leave the widget in the pending state if the user canceled the configure.
817 if (requestCode == REQUEST_CREATE_SHORTCUT) {
818 // Handle custom shortcuts created using ACTION_CREATE_SHORTCUT.
819 if (resultCode == RESULT_OK && requestArgs.container != ItemInfo.NO_ID) {
820 completeAdd(requestCode, data, -1, requestArgs);
821 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
822 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
824 } else if (resultCode == RESULT_CANCELED) {
825 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
826 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
829 mDragLayer.clearAnimatedView();
833 protected void onActivityResult(
834 final int requestCode, final int resultCode, final Intent data) {
835 handleActivityResult(requestCode, resultCode, data);
836 if (mLauncherCallbacks != null) {
837 mLauncherCallbacks.onActivityResult(requestCode, resultCode, data);
841 /** @Override for MNC */
842 public void onRequestPermissionsResult(int requestCode, String[] permissions,
843 int[] grantResults) {
844 PendingRequestArgs pendingArgs = mPendingRequestArgs;
845 if (requestCode == REQUEST_PERMISSION_CALL_PHONE && pendingArgs != null
846 && pendingArgs.getRequestCode() == REQUEST_PERMISSION_CALL_PHONE) {
847 setWaitingForResult(null);
850 CellLayout layout = getCellLayout(pendingArgs.container, pendingArgs.screenId);
851 if (layout != null) {
852 v = layout.getChildAt(pendingArgs.cellX, pendingArgs.cellY);
854 Intent intent = pendingArgs.getPendingIntent();
856 if (grantResults.length > 0
857 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
858 startActivitySafely(v, intent, null);
860 // TODO: Show a snack bar with link to settings
861 Toast.makeText(this, getString(R.string.msg_no_phone_permission,
862 getString(R.string.derived_app_name)), Toast.LENGTH_SHORT).show();
865 if (mLauncherCallbacks != null) {
866 mLauncherCallbacks.onRequestPermissionsResult(requestCode, permissions,
872 * Check to see if a given screen id exists. If not, create it at the end, return the new id.
874 * @param screenId the screen id to check
875 * @return the new screen, or screenId if it exists
877 private long ensurePendingDropLayoutExists(long screenId) {
878 CellLayout dropLayout = mWorkspace.getScreenWithId(screenId);
879 if (dropLayout == null) {
880 // it's possible that the add screen was removed because it was
881 // empty and a re-bind occurred
882 mWorkspace.addExtraEmptyScreen();
883 return mWorkspace.commitExtraEmptyScreen();
889 @Thunk void completeTwoStageWidgetDrop(
890 final int resultCode, final int appWidgetId, final PendingRequestArgs requestArgs) {
891 CellLayout cellLayout = mWorkspace.getScreenWithId(requestArgs.screenId);
892 Runnable onCompleteRunnable = null;
893 int animationType = 0;
895 AppWidgetHostView boundWidget = null;
896 if (resultCode == RESULT_OK) {
897 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
898 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
899 requestArgs.getWidgetHandler().getProviderInfo(this));
900 boundWidget = layout;
901 onCompleteRunnable = new Runnable() {
904 completeAddAppWidget(appWidgetId, requestArgs, layout, null);
905 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
906 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
909 } else if (resultCode == RESULT_CANCELED) {
910 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
911 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
913 if (mDragLayer.getAnimatedView() != null) {
914 mWorkspace.animateWidgetDrop(requestArgs, cellLayout,
915 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
916 animationType, boundWidget, true);
917 } else if (onCompleteRunnable != null) {
918 // The animated view may be null in the case of a rotation during widget configuration
919 onCompleteRunnable.run();
924 protected void onStop() {
926 FirstFrameAnimatorHelper.setIsVisible(false);
928 if (mLauncherCallbacks != null) {
929 mLauncherCallbacks.onStop();
932 if (Utilities.ATLEAST_NOUGAT_MR1) {
933 mAppWidgetHost.stopListening();
936 NotificationListener.removeNotificationsChangedListener();
940 protected void onStart() {
942 FirstFrameAnimatorHelper.setIsVisible(true);
944 if (mLauncherCallbacks != null) {
945 mLauncherCallbacks.onStart();
948 if (Utilities.ATLEAST_NOUGAT_MR1) {
949 mAppWidgetHost.startListening();
952 if (!isWorkspaceLoading()) {
953 NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
958 protected void onResume() {
960 if (DEBUG_RESUME_TIME) {
961 startTime = System.currentTimeMillis();
962 Log.v(TAG, "Launcher.onResume()");
965 if (mLauncherCallbacks != null) {
966 mLauncherCallbacks.preOnResume();
970 getUserEventDispatcher().resetElapsedSessionMillis();
972 // Restore the previous launcher state
973 if (mOnResumeState == State.WORKSPACE) {
974 showWorkspace(false);
975 } else if (mOnResumeState == State.APPS) {
976 boolean launchedFromApp = (mWaitingForResume != null);
977 // Don't update the predicted apps if the user is returning to launcher in the apps
978 // view after launching an app, as they may be depending on the UI to be static to
979 // switch to another app, otherwise, if it was
980 showAppsView(false /* animated */, !launchedFromApp /* updatePredictedApps */,
981 mAppsView.shouldRestoreImeState() /* focusSearchBar */);
982 } else if (mOnResumeState == State.WIDGETS) {
983 showWidgetsView(false, false);
985 mOnResumeState = State.NONE;
988 if (mOnResumeNeedsLoad) {
989 setWorkspaceLoading(true);
990 mModel.startLoader(getCurrentWorkspaceScreen());
991 mOnResumeNeedsLoad = false;
993 if (mBindOnResumeCallbacks.size() > 0) {
994 // We might have postponed some bind calls until onResume (see waitUntilResume) --
996 long startTimeCallbacks = 0;
997 if (DEBUG_RESUME_TIME) {
998 startTimeCallbacks = System.currentTimeMillis();
1001 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
1002 mBindOnResumeCallbacks.get(i).run();
1004 mBindOnResumeCallbacks.clear();
1005 if (DEBUG_RESUME_TIME) {
1006 Log.d(TAG, "Time spent processing callbacks in onResume: " +
1007 (System.currentTimeMillis() - startTimeCallbacks));
1010 if (mOnResumeCallbacks.size() > 0) {
1011 for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
1012 mOnResumeCallbacks.get(i).run();
1014 mOnResumeCallbacks.clear();
1017 // Reset the pressed state of icons that were locked in the press state while activities
1019 if (mWaitingForResume != null) {
1020 // Resets the previous workspace icon press state
1021 mWaitingForResume.setStayPressed(false);
1024 // It is possible that widgets can receive updates while launcher is not in the foreground.
1025 // Consequently, the widgets will be inflated in the orientation of the foreground activity
1026 // (framework issue). On resuming, we ensure that any widgets are inflated for the current
1028 if (!isWorkspaceLoading()) {
1029 getWorkspace().reinflateWidgetsIfNecessary();
1032 if (DEBUG_RESUME_TIME) {
1033 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
1036 // We want to suppress callbacks about CustomContent being shown if we have just received
1037 // onNewIntent while the user was present within launcher. In that case, we post a call
1038 // to move the user to the main screen (which will occur after onResume). We don't want to
1039 // have onHide (from onPause), then onShow, then onHide again, which we get if we don't
1041 if (mWorkspace.getCustomContentCallbacks() != null
1042 && !mMoveToDefaultScreenFromNewIntent) {
1043 // If we are resuming and the custom content is the current page, we call onShow().
1044 // It is also possible that onShow will instead be called slightly after first layout
1045 // if PagedView#setRestorePage was set to the custom content page in onCreate().
1046 if (mWorkspace.isOnOrMovingToCustomContent()) {
1047 mWorkspace.getCustomContentCallbacks().onShow(true);
1050 mMoveToDefaultScreenFromNewIntent = false;
1051 updateInteraction(Workspace.State.NORMAL, mWorkspace.getState());
1052 mWorkspace.onResume();
1054 if (!isWorkspaceLoading()) {
1055 // Process any items that were added while Launcher was away.
1056 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
1058 // Refresh shortcuts if the permission changed.
1059 mModel.refreshShortcutsIfRequired();
1062 if (shouldShowDiscoveryBounce()) {
1063 mAllAppsController.showDiscoveryBounce();
1065 mIsResumeFromActionScreenOff = false;
1066 if (mLauncherCallbacks != null) {
1067 mLauncherCallbacks.onResume();
1073 protected void onPause() {
1074 // Ensure that items added to Launcher are queued until Launcher returns
1075 InstallShortcutReceiver.enableInstallQueue();
1079 mDragController.cancelDrag();
1080 mDragController.resetLastGestureUpTime();
1082 // We call onHide() aggressively. The custom content callbacks should be able to
1083 // debounce excess onHide calls.
1084 if (mWorkspace.getCustomContentCallbacks() != null) {
1085 mWorkspace.getCustomContentCallbacks().onHide();
1088 if (mLauncherCallbacks != null) {
1089 mLauncherCallbacks.onPause();
1093 public interface CustomContentCallbacks {
1094 // Custom content is completely shown. {@code fromResume} indicates whether this was caused
1095 // by a onResume or by scrolling otherwise.
1096 public void onShow(boolean fromResume);
1098 // Custom content is completely hidden
1099 public void onHide();
1101 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
1102 public void onScrollProgressChanged(float progress);
1104 // Indicates whether the user is allowed to scroll away from the custom content.
1105 boolean isScrollingAllowed();
1108 public interface LauncherOverlay {
1111 * Touch interaction leading to overscroll has begun
1113 public void onScrollInteractionBegin();
1116 * Touch interaction related to overscroll has ended
1118 public void onScrollInteractionEnd();
1121 * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
1122 * screen (or in the case of RTL, the rightmost screen).
1124 public void onScrollChange(float progress, boolean rtl);
1127 * Called when the launcher is ready to use the overlay
1128 * @param callbacks A set of callbacks provided by Launcher in relation to the overlay
1130 public void setOverlayCallbacks(LauncherOverlayCallbacks callbacks);
1133 public interface LauncherSearchCallbacks {
1135 * Called when the search overlay is shown.
1137 public void onSearchOverlayOpened();
1140 * Called when the search overlay is dismissed.
1142 public void onSearchOverlayClosed();
1145 public interface LauncherOverlayCallbacks {
1146 public void onScrollChanged(float progress);
1149 class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
1151 public void onScrollChanged(float progress) {
1152 if (mWorkspace != null) {
1153 mWorkspace.onOverlayScrollChanged(progress);
1158 protected boolean hasSettings() {
1159 if (mLauncherCallbacks != null) {
1160 return mLauncherCallbacks.hasSettings();
1162 // On O and above we there is always some setting present settings (add icon to
1163 // home screen or icon badging). On earlier APIs we will have the allow rotation
1164 // setting, on devices with a locked orientation,
1165 return Utilities.isAtLeastO() || !getResources().getBoolean(R.bool.allow_rotation);
1169 public void addToCustomContentPage(View customContent,
1170 CustomContentCallbacks callbacks, String description) {
1171 mWorkspace.addToCustomContentPage(customContent, callbacks, description);
1174 // The custom content needs to offset its content to account for the QSB
1175 public int getTopOffsetForCustomContent() {
1176 return mWorkspace.getPaddingTop();
1180 public Object onRetainNonConfigurationInstance() {
1181 // Flag the loader to stop early before switching
1182 if (mModel.isCurrentCallbacks(this)) {
1183 mModel.stopLoader();
1185 //TODO(hyunyoungs): stop the widgets loader when there is a rotation.
1187 return Boolean.TRUE;
1190 // We can't hide the IME if it was forced open. So don't bother
1192 public void onWindowFocusChanged(boolean hasFocus) {
1193 super.onWindowFocusChanged(hasFocus);
1194 mHasFocus = hasFocus;
1196 if (mLauncherCallbacks != null) {
1197 mLauncherCallbacks.onWindowFocusChanged(hasFocus);
1201 private boolean acceptFilter() {
1202 final InputMethodManager inputManager = (InputMethodManager)
1203 getSystemService(Context.INPUT_METHOD_SERVICE);
1204 return !inputManager.isFullscreenMode();
1208 public boolean onKeyDown(int keyCode, KeyEvent event) {
1209 final int uniChar = event.getUnicodeChar();
1210 final boolean handled = super.onKeyDown(keyCode, event);
1211 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
1212 if (!handled && acceptFilter() && isKeyNotWhitespace) {
1213 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
1215 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
1216 // something usable has been typed - start a search
1217 // the typed text will be retrieved and cleared by
1218 // showSearchDialog()
1219 // If there are multiple keystrokes before the search dialog takes focus,
1220 // onSearchRequested() will be called for every keystroke,
1221 // but it is idempotent, so it's fine.
1222 return onSearchRequested();
1226 // Eat the long press event so the keyboard doesn't come up.
1227 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
1235 public boolean onKeyUp(int keyCode, KeyEvent event) {
1236 if (keyCode == KeyEvent.KEYCODE_MENU) {
1237 // Ignore the menu key if we are currently dragging or are on the custom content screen
1238 if (!isOnCustomContent() && !mDragController.isDragging()) {
1239 // Close any open floating view
1240 AbstractFloatingView.closeAllOpenViews(this);
1242 // Stop resizing any widgets
1243 mWorkspace.exitWidgetResizeMode();
1245 // Show the overview mode if we are on the workspace
1246 if (mState == State.WORKSPACE && !mWorkspace.isInOverviewMode() &&
1247 !mWorkspace.isSwitchingState()) {
1248 mOverviewPanel.requestFocus();
1249 showOverviewMode(true, true /* requestButtonFocus */);
1254 return super.onKeyUp(keyCode, event);
1257 private String getTypedText() {
1258 return mDefaultKeySsb.toString();
1262 public void clearTypedText() {
1263 mDefaultKeySsb.clear();
1264 mDefaultKeySsb.clearSpans();
1265 Selection.setSelection(mDefaultKeySsb, 0);
1269 * Restores the previous state, if it exists.
1271 * @param savedState The previous state.
1273 private void restoreState(Bundle savedState) {
1274 if (savedState == null) {
1278 int stateOrdinal = savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal());
1279 State[] stateValues = State.values();
1280 State state = (stateOrdinal >= 0 && stateOrdinal < stateValues.length)
1281 ? stateValues[stateOrdinal] : State.WORKSPACE;
1282 if (state == State.APPS || state == State.WIDGETS) {
1283 mOnResumeState = state;
1286 PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS);
1287 if (requestArgs != null) {
1288 setWaitingForResult(requestArgs);
1291 mPendingActivityResult = savedState.getParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT);
1295 * Finds all the views we need and configure them properly.
1297 private void setupViews() {
1298 mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
1299 mFocusHandler = mDragLayer.getFocusIndicatorHelper();
1300 mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
1301 mQsbContainer = mDragLayer.findViewById(mDeviceProfile.isVerticalBarLayout()
1302 ? R.id.workspace_blocked_row : R.id.qsb_container);
1303 mWorkspace.initParentViews(mDragLayer);
1305 mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
1306 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
1307 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
1309 // Setup the drag layer
1310 mDragLayer.setup(this, mDragController, mAllAppsController);
1312 // Setup the hotseat
1313 mHotseat = (Hotseat) findViewById(R.id.hotseat);
1314 if (mHotseat != null) {
1315 mHotseat.setOnLongClickListener(this);
1318 // Setup the overview panel
1319 setupOverviewPanel();
1321 // Setup the workspace
1322 mWorkspace.setHapticFeedbackEnabled(false);
1323 mWorkspace.setOnLongClickListener(this);
1324 mWorkspace.setup(mDragController);
1325 // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
1326 // default state, otherwise we will update to the wrong offsets in RTL
1327 mWorkspace.lockWallpaperToDefaultPage();
1328 mWorkspace.bindAndInitFirstWorkspaceScreen(null /* recycled qsb */);
1329 mDragController.addDragListener(mWorkspace);
1331 // Get the search/delete/uninstall bar
1332 mDropTargetBar = (DropTargetBar) mDragLayer.findViewById(R.id.drop_target_bar);
1334 // Setup Apps and Widgets
1335 mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view);
1336 mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view);
1337 if (mLauncherCallbacks != null && mLauncherCallbacks.getAllAppsSearchBarController() != null) {
1338 mAppsView.setSearchBarController(mLauncherCallbacks.getAllAppsSearchBarController());
1340 mAppsView.setSearchBarController(new DefaultAppSearchController());
1343 // Setup the drag controller (drop targets have to be added in reverse order in priority)
1344 mDragController.setMoveTarget(mWorkspace);
1345 mDragController.addDropTarget(mWorkspace);
1346 mDropTargetBar.setup(mDragController);
1348 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
1349 mAllAppsController.setupViews(mAppsView, mHotseat, mWorkspace);
1352 if (TestingUtils.MEMORY_DUMP_ENABLED) {
1353 TestingUtils.addWeightWatcher(this);
1357 private void setupOverviewPanel() {
1358 mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
1360 // Bind wallpaper button actions
1361 View wallpaperButton = findViewById(R.id.wallpaper_button);
1362 new OverviewButtonClickListener(ControlType.WALLPAPER_BUTTON) {
1364 public void handleViewClick(View view) {
1365 onClickWallpaperPicker(view);
1367 }.attachTo(wallpaperButton);
1368 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
1370 // Bind widget button actions
1371 mWidgetsButton = findViewById(R.id.widget_button);
1372 new OverviewButtonClickListener(ControlType.WIDGETS_BUTTON) {
1374 public void handleViewClick(View view) {
1375 onClickAddWidgetButton(view);
1377 }.attachTo(mWidgetsButton);
1378 mWidgetsButton.setOnTouchListener(getHapticFeedbackTouchListener());
1380 // Bind settings actions
1381 View settingsButton = findViewById(R.id.settings_button);
1382 boolean hasSettings = hasSettings();
1384 new OverviewButtonClickListener(ControlType.SETTINGS_BUTTON) {
1386 public void handleViewClick(View view) {
1387 onClickSettingsButton(view);
1389 }.attachTo(settingsButton);
1390 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
1392 settingsButton.setVisibility(View.GONE);
1395 mOverviewPanel.setAlpha(0f);
1399 * Sets the all apps button. This method is called from {@link Hotseat}.
1400 * TODO: Get rid of this.
1402 public void setAllAppsButton(View allAppsButton) {
1403 mAllAppsButton = allAppsButton;
1406 public View getStartViewForAllAppsRevealAnimation() {
1407 return FeatureFlags.NO_ALL_APPS_ICON ? mWorkspace.getPageIndicator() : mAllAppsButton;
1410 public View getWidgetsButton() {
1411 return mWidgetsButton;
1415 * Creates a view representing a shortcut.
1417 * @param info The data structure describing the shortcut.
1419 View createShortcut(ShortcutInfo info) {
1420 return createShortcut((ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1424 * Creates a view representing a shortcut inflated from the specified resource.
1426 * @param parent The group the shortcut belongs to.
1427 * @param info The data structure describing the shortcut.
1429 * @return A View inflated from layoutResId.
1431 public View createShortcut(ViewGroup parent, ShortcutInfo info) {
1432 BubbleTextView favorite = (BubbleTextView) getLayoutInflater().inflate(R.layout.app_icon,
1434 favorite.applyFromShortcutInfo(info);
1435 favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
1436 favorite.setOnClickListener(this);
1437 favorite.setOnFocusChangeListener(mFocusHandler);
1442 * Add a shortcut to the workspace.
1444 * @param data The intent describing the shortcut.
1446 private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
1447 int cellY, PendingRequestArgs args) {
1448 int[] cellXY = mTmpAddItemCellCoordinates;
1449 CellLayout layout = getCellLayout(container, screenId);
1451 if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT ||
1452 args.getPendingIntent().getComponent() == null) {
1456 ShortcutInfo info = null;
1457 if (Utilities.isAtLeastO()) {
1458 info = LauncherAppsCompat.createShortcutInfoFromPinItemRequest(
1459 this, PinItemRequestCompat.getPinItemRequest(data), 0);
1463 // Legacy shortcuts are only supported for primary profile.
1464 info = Process.myUserHandle().equals(args.user)
1465 ? InstallShortcutReceiver.fromShortcutIntent(this, data) : null;
1468 Log.e(TAG, "Unable to parse a valid custom shortcut result");
1470 } else if (!new PackageManagerHelper(this).hasPermissionForActivity(
1471 info.intent, args.getPendingIntent().getComponent().getPackageName())) {
1472 // The app is trying to add a shortcut without sufficient permissions
1473 Log.e(TAG, "Ignoring malicious intent " + info.intent.toUri(0));
1478 final View view = createShortcut(info);
1479 boolean foundCellSpan = false;
1480 // First we check if we already know the exact location where we want to add this item.
1481 if (cellX >= 0 && cellY >= 0) {
1484 foundCellSpan = true;
1486 // If appropriate, either create a folder or add to an existing folder
1487 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
1491 DragObject dragObject = new DragObject();
1492 dragObject.dragInfo = info;
1493 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
1498 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1501 if (!foundCellSpan) {
1502 mWorkspace.onNoCellFound(layout);
1506 getModelWriter().addItemToDatabase(info, container, screenId, cellXY[0], cellXY[1]);
1507 mWorkspace.addInScreen(view, info);
1511 * Add a widget to the workspace.
1513 * @param appWidgetId The app widget id
1515 @Thunk void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo,
1516 AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
1518 if (appWidgetInfo == null) {
1519 appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId);
1522 if (appWidgetInfo.isCustomWidget) {
1523 appWidgetId = LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
1526 LauncherAppWidgetInfo launcherInfo;
1527 launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider);
1528 launcherInfo.spanX = itemInfo.spanX;
1529 launcherInfo.spanY = itemInfo.spanY;
1530 launcherInfo.minSpanX = itemInfo.minSpanX;
1531 launcherInfo.minSpanY = itemInfo.minSpanY;
1532 launcherInfo.user = appWidgetInfo.getUser();
1534 getModelWriter().addItemToDatabase(launcherInfo,
1535 itemInfo.container, itemInfo.screenId, itemInfo.cellX, itemInfo.cellY);
1537 if (hostView == null) {
1538 // Perform actual inflation because we're live
1539 hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
1541 hostView.setVisibility(View.VISIBLE);
1542 prepareAppWidget(hostView, launcherInfo);
1543 mWorkspace.addInScreen(hostView, launcherInfo);
1546 private void prepareAppWidget(AppWidgetHostView hostView, LauncherAppWidgetInfo item) {
1547 hostView.setTag(item);
1548 item.onBindAppWidget(this, hostView);
1549 hostView.setFocusable(true);
1550 hostView.setOnFocusChangeListener(mFocusHandler);
1553 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1555 public void onReceive(Context context, Intent intent) {
1556 final String action = intent.getAction();
1557 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1558 mDragLayer.clearResizeFrame();
1560 // Reset AllApps to its initial state only if we are not in the middle of
1561 // processing a multi-step drop
1562 if (mAppsView != null && mWidgetsView != null && mPendingRequestArgs == null) {
1563 if (!showWorkspace(false)) {
1564 // If we are already on the workspace, then manually reset all apps
1568 mIsResumeFromActionScreenOff = true;
1573 public void updateIconBadges(final Set<PackageUserKey> updatedBadges) {
1574 Runnable r = new Runnable() {
1577 mWorkspace.updateIconBadges(updatedBadges);
1578 mAppsView.updateIconBadges(updatedBadges);
1580 PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(Launcher.this);
1581 if (popup != null) {
1582 popup.updateNotificationHeader(updatedBadges);
1586 if (!waitUntilResume(r)) {
1592 public void onAttachedToWindow() {
1593 super.onAttachedToWindow();
1595 // Listen for broadcasts related to user-presence
1596 final IntentFilter filter = new IntentFilter();
1597 filter.addAction(Intent.ACTION_SCREEN_OFF);
1598 registerReceiver(mReceiver, filter);
1599 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
1602 if (mLauncherCallbacks != null) {
1603 mLauncherCallbacks.onAttachedToWindow();
1608 public void onDetachedFromWindow() {
1609 super.onDetachedFromWindow();
1611 unregisterReceiver(mReceiver);
1615 if (mLauncherCallbacks != null) {
1616 mLauncherCallbacks.onDetachedFromWindow();
1620 public void onWindowVisibilityChanged(int visibility) {
1621 // The following code used to be in onResume, but it turns out onResume is called when
1622 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
1623 // is a more appropriate event to handle
1624 if (visibility == View.VISIBLE) {
1625 if (!mWorkspaceLoading) {
1626 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
1627 // We want to let Launcher draw itself at least once before we force it to build
1628 // layers on all the workspace pages, so that transitioning to Launcher from other
1629 // apps is nice and speedy.
1630 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
1631 private boolean mStarted = false;
1632 public void onDraw() {
1633 if (mStarted) return;
1635 // We delay the layer building a bit in order to give
1636 // other message processing a time to run. In particular
1637 // this avoids a delay in hiding the IME if it was
1638 // currently shown, because doing that may involve
1639 // some communication back with the app.
1640 mWorkspace.postDelayed(mBuildLayersRunnable, 500);
1641 final ViewTreeObserver.OnDrawListener listener = this;
1642 mWorkspace.post(new Runnable() {
1644 if (mWorkspace != null &&
1645 mWorkspace.getViewTreeObserver() != null) {
1646 mWorkspace.getViewTreeObserver().
1647 removeOnDrawListener(listener);
1659 public DragLayer getDragLayer() {
1663 public AllAppsContainerView getAppsView() {
1667 public WidgetsContainerView getWidgetsView() {
1668 return mWidgetsView;
1671 public Workspace getWorkspace() {
1675 public View getQsbContainer() {
1676 return mQsbContainer;
1679 public Hotseat getHotseat() {
1683 public ViewGroup getOverviewPanel() {
1684 return mOverviewPanel;
1687 public DropTargetBar getDropTargetBar() {
1688 return mDropTargetBar;
1691 public LauncherAppWidgetHost getAppWidgetHost() {
1692 return mAppWidgetHost;
1695 public LauncherModel getModel() {
1699 public ModelWriter getModelWriter() {
1700 return mModelWriter;
1703 public SharedPreferences getSharedPrefs() {
1704 return mSharedPrefs;
1708 protected void onNewIntent(Intent intent) {
1710 if (DEBUG_RESUME_TIME) {
1711 startTime = System.currentTimeMillis();
1713 super.onNewIntent(intent);
1715 boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
1716 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
1717 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
1719 // Check this condition before handling isActionMain, as this will get reset.
1720 boolean shouldMoveToDefaultScreen = alreadyOnHome &&
1721 mState == State.WORKSPACE && AbstractFloatingView.getTopOpenView(this) == null;
1723 boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
1725 if (mWorkspace == null) {
1726 // Can be cases where mWorkspace is null, this prevents a NPE
1730 // Note: There should be at most one log per method call. This is enforced implicitly
1731 // by using if-else statements.
1732 UserEventDispatcher ued = getUserEventDispatcher();
1734 // TODO: Log this case.
1735 mWorkspace.exitWidgetResizeMode();
1737 AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(this);
1738 if (topOpenView instanceof PopupContainerWithArrow) {
1739 ued.logActionCommand(Action.Command.HOME_INTENT,
1740 topOpenView.getExtendedTouchView(), ContainerType.DEEPSHORTCUTS);
1741 } else if (topOpenView instanceof Folder) {
1742 ued.logActionCommand(Action.Command.HOME_INTENT,
1743 ((Folder) topOpenView).getFolderIcon(), ContainerType.FOLDER);
1744 } else if (alreadyOnHome) {
1745 ued.logActionCommand(Action.Command.HOME_INTENT,
1746 mWorkspace.getState().containerType, mWorkspace.getCurrentPage());
1749 // In all these cases, only animate if we're already on home
1750 AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome);
1751 exitSpringLoadedDragMode();
1753 // If we are already on home, then just animate back to the workspace,
1754 // otherwise, just wait until onResume to set the state back to Workspace
1755 if (alreadyOnHome) {
1756 showWorkspace(true);
1758 mOnResumeState = State.WORKSPACE;
1761 final View v = getWindow().peekDecorView();
1762 if (v != null && v.getWindowToken() != null) {
1763 InputMethodManager imm = (InputMethodManager) getSystemService(
1764 INPUT_METHOD_SERVICE);
1765 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1768 // Reset the apps view
1769 if (!alreadyOnHome && mAppsView != null) {
1770 mAppsView.scrollToTop();
1773 // Reset the widgets view
1774 if (!alreadyOnHome && mWidgetsView != null) {
1775 mWidgetsView.scrollToTop();
1778 if (mLauncherCallbacks != null) {
1779 mLauncherCallbacks.onHomeIntent();
1782 PinItemDragListener.handleDragRequest(this, intent);
1784 if (mLauncherCallbacks != null) {
1785 mLauncherCallbacks.onNewIntent(intent);
1788 // Defer moving to the default screen until after we callback to the LauncherCallbacks
1789 // as slow logic in the callbacks eat into the time the scroller expects for the snapToPage
1792 boolean callbackAllowsMoveToDefaultScreen = mLauncherCallbacks != null ?
1793 mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
1794 if (shouldMoveToDefaultScreen && !mWorkspace.isTouchActive()
1795 && callbackAllowsMoveToDefaultScreen) {
1797 // We use this flag to suppress noisy callbacks above custom content state
1799 mMoveToDefaultScreenFromNewIntent = true;
1800 mWorkspace.post(new Runnable() {
1803 if (mWorkspace != null) {
1804 mWorkspace.moveToDefaultScreen(true);
1811 if (DEBUG_RESUME_TIME) {
1812 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
1817 public void onRestoreInstanceState(Bundle state) {
1818 super.onRestoreInstanceState(state);
1819 for (int page: mSynchronouslyBoundPages) {
1820 mWorkspace.restoreInstanceStateForChild(page);
1825 protected void onSaveInstanceState(Bundle outState) {
1826 if (mWorkspace.getChildCount() > 0) {
1827 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
1828 mWorkspace.getCurrentPageOffsetFromCustomContent());
1831 super.onSaveInstanceState(outState);
1833 outState.putInt(RUNTIME_STATE, mState.ordinal());
1834 // We close any open folders and shortcut containers since they will not be re-opened,
1835 // and we need to make sure this state is reflected.
1836 AbstractFloatingView.closeAllOpenViews(this, false);
1838 if (mPendingRequestArgs != null) {
1839 outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs);
1841 if (mPendingActivityResult != null) {
1842 outState.putParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT, mPendingActivityResult);
1845 if (mLauncherCallbacks != null) {
1846 mLauncherCallbacks.onSaveInstanceState(outState);
1851 public void onDestroy() {
1854 mWorkspace.removeCallbacks(mBuildLayersRunnable);
1855 mWorkspace.removeFolderListeners();
1857 // Stop callbacks from LauncherModel
1858 // It's possible to receive onDestroy after a new Launcher activity has
1859 // been created. In this case, don't interfere with the new Launcher.
1860 if (mModel.isCurrentCallbacks(this)) {
1861 mModel.stopLoader();
1862 LauncherAppState.getInstance(this).setLauncher(null);
1865 if (mRotationPrefChangeHandler != null) {
1866 mSharedPrefs.unregisterOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
1870 mAppWidgetHost.stopListening();
1871 } catch (NullPointerException ex) {
1872 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
1874 mAppWidgetHost = null;
1876 TextKeyListener.getInstance().release();
1878 ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
1879 .removeAccessibilityStateChangeListener(this);
1881 LauncherAnimUtils.onDestroyActivity();
1883 if (mLauncherCallbacks != null) {
1884 mLauncherCallbacks.onDestroy();
1888 public LauncherAccessibilityDelegate getAccessibilityDelegate() {
1889 return mAccessibilityDelegate;
1892 public DragController getDragController() {
1893 return mDragController;
1897 public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
1898 super.startActivityForResult(intent, requestCode, options);
1902 public void startIntentSenderForResult (IntentSender intent, int requestCode,
1903 Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
1905 super.startIntentSenderForResult(intent, requestCode,
1906 fillInIntent, flagsMask, flagsValues, extraFlags, options);
1907 } catch (IntentSender.SendIntentException e) {
1908 throw new ActivityNotFoundException();
1913 * Indicates that we want global search for this activity by setting the globalSearch
1914 * argument for {@link #startSearch} to true.
1917 public void startSearch(String initialQuery, boolean selectInitialQuery,
1918 Bundle appSearchData, boolean globalSearch) {
1920 if (initialQuery == null) {
1921 // Use any text typed in the launcher as the initial query
1922 initialQuery = getTypedText();
1924 if (appSearchData == null) {
1925 appSearchData = new Bundle();
1926 appSearchData.putString("source", "launcher-search");
1929 if (mLauncherCallbacks == null ||
1930 !mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData)) {
1931 // Starting search from the callbacks failed. Start the default global search.
1932 startGlobalSearch(initialQuery, selectInitialQuery, appSearchData, null);
1935 // We need to show the workspace after starting the search
1936 showWorkspace(true);
1940 * Starts the global search activity. This code is a copied from SearchManager
1942 public void startGlobalSearch(String initialQuery,
1943 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
1944 final SearchManager searchManager =
1945 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
1946 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
1947 if (globalSearchActivity == null) {
1948 Log.w(TAG, "No global search activity found.");
1951 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
1952 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1953 intent.setComponent(globalSearchActivity);
1954 // Make sure that we have a Bundle to put source in
1955 if (appSearchData == null) {
1956 appSearchData = new Bundle();
1958 appSearchData = new Bundle(appSearchData);
1960 // Set source to package name of app that starts global search if not set already.
1961 if (!appSearchData.containsKey("source")) {
1962 appSearchData.putString("source", getPackageName());
1964 intent.putExtra(SearchManager.APP_DATA, appSearchData);
1965 if (!TextUtils.isEmpty(initialQuery)) {
1966 intent.putExtra(SearchManager.QUERY, initialQuery);
1968 if (selectInitialQuery) {
1969 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
1971 intent.setSourceBounds(sourceBounds);
1973 startActivity(intent);
1974 } catch (ActivityNotFoundException ex) {
1975 Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
1979 public boolean isOnCustomContent() {
1980 return mWorkspace.isOnOrMovingToCustomContent();
1984 public boolean onPrepareOptionsMenu(Menu menu) {
1985 super.onPrepareOptionsMenu(menu);
1986 if (mLauncherCallbacks != null) {
1987 return mLauncherCallbacks.onPrepareOptionsMenu(menu);
1993 public boolean onSearchRequested() {
1994 startSearch(null, false, null, true);
1995 // Use a custom animation for launching search
1999 public boolean isWorkspaceLocked() {
2000 return mWorkspaceLoading || mPendingRequestArgs != null;
2003 public boolean isWorkspaceLoading() {
2004 return mWorkspaceLoading;
2007 private void setWorkspaceLoading(boolean value) {
2008 boolean isLocked = isWorkspaceLocked();
2009 mWorkspaceLoading = value;
2010 if (isLocked != isWorkspaceLocked()) {
2011 onWorkspaceLockedChanged();
2015 public void setWaitingForResult(PendingRequestArgs args) {
2016 boolean isLocked = isWorkspaceLocked();
2017 mPendingRequestArgs = args;
2018 if (isLocked != isWorkspaceLocked()) {
2019 onWorkspaceLockedChanged();
2023 protected void onWorkspaceLockedChanged() {
2024 if (mLauncherCallbacks != null) {
2025 mLauncherCallbacks.onWorkspaceLockedChanged();
2029 void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
2030 WidgetAddFlowHandler addFlowHandler) {
2032 Log.d(TAG, "Adding widget from drop");
2034 addAppWidgetImpl(appWidgetId, info, boundWidget, addFlowHandler, 0);
2037 void addAppWidgetImpl(int appWidgetId, ItemInfo info,
2038 AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay) {
2039 if (!addFlowHandler.startConfigActivity(this, appWidgetId, info, REQUEST_CREATE_APPWIDGET)) {
2040 // If the configuration flow was not started, add the widget
2042 Runnable onComplete = new Runnable() {
2045 // Exit spring loaded mode if necessary after adding the widget
2046 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
2050 completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this));
2051 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
2055 protected void moveToCustomContentScreen(boolean animate) {
2056 // Close any folders that may be open.
2057 AbstractFloatingView.closeAllOpenViews(this, animate);
2058 mWorkspace.moveToCustomContentScreen(animate);
2061 public void addPendingItem(PendingAddItemInfo info, long container, long screenId,
2062 int[] cell, int spanX, int spanY) {
2063 info.container = container;
2064 info.screenId = screenId;
2066 info.cellX = cell[0];
2067 info.cellY = cell[1];
2072 switch (info.itemType) {
2073 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2074 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2075 addAppWidgetFromDrop((PendingAddWidgetInfo) info);
2077 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2078 processShortcutFromDrop((PendingAddShortcutInfo) info);
2081 throw new IllegalStateException("Unknown item type: " + info.itemType);
2086 * Process a shortcut drop.
2088 private void processShortcutFromDrop(PendingAddShortcutInfo info) {
2089 Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(info.componentName);
2090 setWaitingForResult(PendingRequestArgs.forIntent(REQUEST_CREATE_SHORTCUT, intent, info));
2091 if (!info.activityInfo.startConfigActivity(this, REQUEST_CREATE_SHORTCUT)) {
2092 handleActivityResult(REQUEST_CREATE_SHORTCUT, RESULT_CANCELED, null);
2097 * Process a widget drop.
2099 private void addAppWidgetFromDrop(PendingAddWidgetInfo info) {
2100 AppWidgetHostView hostView = info.boundWidget;
2102 WidgetAddFlowHandler addFlowHandler = info.getHandler();
2103 if (hostView != null) {
2104 // In the case where we've prebound the widget, we remove it from the DragLayer
2106 Log.d(TAG, "Removing widget view from drag layer and setting boundWidget to null");
2108 getDragLayer().removeView(hostView);
2110 appWidgetId = hostView.getAppWidgetId();
2111 addAppWidgetFromDropImpl(appWidgetId, info, hostView, addFlowHandler);
2113 // Clear the boundWidget so that it doesn't get destroyed.
2114 info.boundWidget = null;
2116 // In this case, we either need to start an activity to get permission to bind
2117 // the widget, or we need to start an activity to configure the widget, or both.
2118 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
2119 Bundle options = info.bindOptions;
2121 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
2122 appWidgetId, info.info, options);
2124 addAppWidgetFromDropImpl(appWidgetId, info, null, addFlowHandler);
2126 addFlowHandler.startBindFlow(this, appWidgetId, info, REQUEST_BIND_APPWIDGET);
2131 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
2133 final FolderInfo folderInfo = new FolderInfo();
2134 folderInfo.title = getText(R.string.folder_name);
2137 getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY);
2140 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo);
2141 mWorkspace.addInScreen(newFolder, folderInfo);
2142 // Force measure the new folder icon
2143 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
2144 parent.getShortcutsAndWidgets().measureChild(newFolder);
2149 * Unbinds the view for the specified item, and removes the item and all its children.
2151 * @param v the view being removed.
2152 * @param itemInfo the {@link ItemInfo} for this view.
2153 * @param deleteFromDb whether or not to delete this item from the db.
2155 public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb) {
2156 if (itemInfo instanceof ShortcutInfo) {
2157 // Remove the shortcut from the folder before removing it from launcher
2158 View folderIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container);
2159 if (folderIcon instanceof FolderIcon) {
2160 ((FolderInfo) folderIcon.getTag()).remove((ShortcutInfo) itemInfo, true);
2162 mWorkspace.removeWorkspaceItem(v);
2165 getModelWriter().deleteItemFromDatabase(itemInfo);
2167 } else if (itemInfo instanceof FolderInfo) {
2168 final FolderInfo folderInfo = (FolderInfo) itemInfo;
2169 if (v instanceof FolderIcon) {
2170 ((FolderIcon) v).removeListeners();
2172 mWorkspace.removeWorkspaceItem(v);
2174 getModelWriter().deleteFolderAndContentsFromDatabase(folderInfo);
2176 } else if (itemInfo instanceof LauncherAppWidgetInfo) {
2177 final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
2178 mWorkspace.removeWorkspaceItem(v);
2180 deleteWidgetInfo(widgetInfo);
2189 * Deletes the widget info and the widget id.
2191 private void deleteWidgetInfo(final LauncherAppWidgetInfo widgetInfo) {
2192 final LauncherAppWidgetHost appWidgetHost = getAppWidgetHost();
2193 if (appWidgetHost != null && !widgetInfo.isCustomWidget() && widgetInfo.isWidgetIdAllocated()) {
2194 // Deleting an app widget ID is a void call but writes to disk before returning
2196 new AsyncTask<Void, Void, Void>() {
2197 public Void doInBackground(Void ... args) {
2198 appWidgetHost.deleteAppWidgetId(widgetInfo.appWidgetId);
2201 }.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
2203 getModelWriter().deleteItemFromDatabase(widgetInfo);
2207 public boolean dispatchKeyEvent(KeyEvent event) {
2208 return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event);
2212 public void onBackPressed() {
2213 if (mLauncherCallbacks != null && mLauncherCallbacks.handleBackPressed()) {
2217 if (mDragController.isDragging()) {
2218 mDragController.cancelDrag();
2222 // Note: There should be at most one log per method call. This is enforced implicitly
2223 // by using if-else statements.
2224 UserEventDispatcher ued = getUserEventDispatcher();
2225 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
2226 if (topView != null) {
2227 if (topView.getActiveTextView() != null) {
2228 topView.getActiveTextView().dispatchBackKey();
2230 if (topView instanceof PopupContainerWithArrow) {
2231 ued.logActionCommand(Action.Command.BACK,
2232 topView.getExtendedTouchView(), ContainerType.DEEPSHORTCUTS);
2233 } else if (topView instanceof Folder) {
2234 ued.logActionCommand(Action.Command.BACK,
2235 ((Folder) topView).getFolderIcon(), ContainerType.FOLDER);
2237 topView.close(true);
2239 } else if (isAppsViewVisible()) {
2240 ued.logActionCommand(Action.Command.BACK, ContainerType.ALLAPPS);
2241 showWorkspace(true);
2242 } else if (isWidgetsViewVisible()) {
2243 ued.logActionCommand(Action.Command.BACK, ContainerType.WIDGETS);
2244 showOverviewMode(true);
2245 } else if (mWorkspace.isInOverviewMode()) {
2246 ued.logActionCommand(Action.Command.BACK, ContainerType.OVERVIEW);
2247 showWorkspace(true);
2249 // TODO: Log this case.
2250 mWorkspace.exitWidgetResizeMode();
2252 // Back button is a no-op here, but give at least some feedback for the button press
2253 mWorkspace.showOutlinesTemporarily();
2258 * Launches the intent referred by the clicked shortcut.
2260 * @param v The view representing the clicked shortcut.
2262 public void onClick(View v) {
2263 // Make sure that rogue clicks don't get through while allapps is launching, or after the
2264 // view has detached (it's possible for this to happen if the view is removed mid touch).
2265 if (v.getWindowToken() == null) {
2269 if (!mWorkspace.isFinishedSwitchingState()) {
2273 if (v instanceof Workspace) {
2274 if (mWorkspace.isInOverviewMode()) {
2275 getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
2276 LauncherLogProto.Action.Direction.NONE,
2277 LauncherLogProto.ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
2278 showWorkspace(true);
2283 if (v instanceof CellLayout) {
2284 if (mWorkspace.isInOverviewMode()) {
2285 int page = mWorkspace.indexOfChild(v);
2286 getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
2287 LauncherLogProto.Action.Direction.NONE,
2288 LauncherLogProto.ContainerType.OVERVIEW, page);
2289 mWorkspace.snapToPageFromOverView(page);
2290 showWorkspace(true);
2295 Object tag = v.getTag();
2296 if (tag instanceof ShortcutInfo) {
2297 onClickAppShortcut(v);
2298 } else if (tag instanceof FolderInfo) {
2299 if (v instanceof FolderIcon) {
2300 onClickFolderIcon(v);
2302 } else if ((FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && v instanceof PageIndicator) ||
2303 (v == mAllAppsButton && mAllAppsButton != null)) {
2304 onClickAllAppsButton(v);
2305 } else if (tag instanceof AppInfo) {
2306 startAppShortcutOrInfoActivity(v);
2307 } else if (tag instanceof LauncherAppWidgetInfo) {
2308 if (v instanceof PendingAppWidgetHostView) {
2309 onClickPendingWidget((PendingAppWidgetHostView) v);
2314 @SuppressLint("ClickableViewAccessibility")
2315 public boolean onTouch(View v, MotionEvent event) {
2320 * Event handler for the app widget view which has not fully restored.
2322 public void onClickPendingWidget(final PendingAppWidgetHostView v) {
2323 if (mIsSafeModeEnabled) {
2324 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2328 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
2329 if (v.isReadyForClickSetup()) {
2330 LauncherAppWidgetProviderInfo appWidgetInfo =
2331 mAppWidgetManager.findProvider(info.providerName, info.user);
2332 if (appWidgetInfo == null) {
2335 WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo);
2337 if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
2338 if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
2339 // This should not happen, as we make sure that an Id is allocated during bind.
2342 addFlowHandler.startBindFlow(this, info.appWidgetId, info,
2343 REQUEST_BIND_PENDING_APPWIDGET);
2345 addFlowHandler.startConfigActivity(this, info, REQUEST_RECONFIGURE_APPWIDGET);
2348 final String packageName = info.providerName.getPackageName();
2349 onClickPendingAppItem(v, packageName, info.installProgress >= 0);
2354 * Event handler for the "grid" button that appears on the home screen, which
2355 * enters all apps mode.
2357 * @param v The view that was clicked.
2359 protected void onClickAllAppsButton(View v) {
2360 if (LOGD) Log.d(TAG, "onClickAllAppsButton");
2361 if (!isAppsViewVisible()) {
2362 getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
2363 ControlType.ALL_APPS_BUTTON);
2364 showAppsView(true /* animated */, true /* updatePredictedApps */,
2365 false /* focusSearchBar */);
2369 protected void onLongClickAllAppsButton(View v) {
2370 if (LOGD) Log.d(TAG, "onLongClickAllAppsButton");
2371 if (!isAppsViewVisible()) {
2372 getUserEventDispatcher().logActionOnControl(Action.Touch.LONGPRESS,
2373 ControlType.ALL_APPS_BUTTON);
2374 showAppsView(true /* animated */,
2375 true /* updatePredictedApps */, true /* focusSearchBar */);
2379 private void onClickPendingAppItem(final View v, final String packageName,
2380 boolean downloadStarted) {
2381 if (downloadStarted) {
2382 // If the download has started, simply direct to the market app.
2383 startMarketIntentForPackage(v, packageName);
2386 new AlertDialog.Builder(this)
2387 .setTitle(R.string.abandoned_promises_title)
2388 .setMessage(R.string.abandoned_promise_explanation)
2389 .setPositiveButton(R.string.abandoned_search, new DialogInterface.OnClickListener() {
2391 public void onClick(DialogInterface dialogInterface, int i) {
2392 startMarketIntentForPackage(v, packageName);
2395 .setNeutralButton(R.string.abandoned_clean_this,
2396 new DialogInterface.OnClickListener() {
2397 public void onClick(DialogInterface dialog, int id) {
2398 final UserHandle user = Process.myUserHandle();
2399 mWorkspace.removeAbandonedPromise(packageName, user);
2405 private void startMarketIntentForPackage(View v, String packageName) {
2406 ItemInfo item = (ItemInfo) v.getTag();
2407 Intent intent = PackageManagerHelper.getMarketIntent(packageName);
2408 boolean success = startActivitySafely(v, intent, item);
2409 if (success && v instanceof BubbleTextView) {
2410 mWaitingForResume = (BubbleTextView) v;
2411 mWaitingForResume.setStayPressed(true);
2416 * Event handler for an app shortcut click.
2418 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
2420 protected void onClickAppShortcut(final View v) {
2421 if (LOGD) Log.d(TAG, "onClickAppShortcut");
2422 Object tag = v.getTag();
2423 if (!(tag instanceof ShortcutInfo)) {
2424 throw new IllegalArgumentException("Input must be a Shortcut");
2428 final ShortcutInfo shortcut = (ShortcutInfo) tag;
2430 if (shortcut.isDisabled != 0) {
2431 if ((shortcut.isDisabled &
2432 ~ShortcutInfo.FLAG_DISABLED_SUSPENDED &
2433 ~ShortcutInfo.FLAG_DISABLED_QUIET_USER) == 0) {
2434 // If the app is only disabled because of the above flags, launch activity anyway.
2435 // Framework will tell the user why the app is suspended.
2437 if (!TextUtils.isEmpty(shortcut.disabledMessage)) {
2438 // Use a message specific to this shortcut, if it has one.
2439 Toast.makeText(this, shortcut.disabledMessage, Toast.LENGTH_SHORT).show();
2442 // Otherwise just use a generic error message.
2443 int error = R.string.activity_not_available;
2444 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
2445 error = R.string.safemode_shortcut_error;
2446 } else if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_BY_PUBLISHER) != 0 ||
2447 (shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_LOCKED_USER) != 0) {
2448 error = R.string.shortcut_not_available;
2450 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
2455 // Check for abandoned promise
2456 if ((v instanceof BubbleTextView) && shortcut.isPromise()) {
2457 String packageName = shortcut.intent.getComponent() != null ?
2458 shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage();
2459 if (!TextUtils.isEmpty(packageName)) {
2460 onClickPendingAppItem(v, packageName,
2461 shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE));
2467 startAppShortcutOrInfoActivity(v);
2470 private void startAppShortcutOrInfoActivity(View v) {
2471 ItemInfo item = (ItemInfo) v.getTag();
2472 Intent intent = item.getIntent();
2473 if (intent == null) {
2474 throw new IllegalArgumentException("Input must have a valid intent");
2476 boolean success = startActivitySafely(v, intent, item);
2477 getUserEventDispatcher().logAppLaunch(v, intent); // TODO for discovered apps b/35802115
2479 if (success && v instanceof BubbleTextView) {
2480 mWaitingForResume = (BubbleTextView) v;
2481 mWaitingForResume.setStayPressed(true);
2486 * Event handler for a folder icon click.
2488 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
2490 protected void onClickFolderIcon(View v) {
2491 if (LOGD) Log.d(TAG, "onClickFolder");
2492 if (!(v instanceof FolderIcon)){
2493 throw new IllegalArgumentException("Input must be a FolderIcon");
2496 Folder folder = ((FolderIcon) v).getFolder();
2497 if (!folder.isOpen() && !folder.isDestroyed()) {
2498 // Open the requested folder
2499 folder.animateOpen();
2504 * Event handler for the (Add) Widgets button that appears after a long press
2505 * on the home screen.
2507 public void onClickAddWidgetButton(View view) {
2508 if (LOGD) Log.d(TAG, "onClickAddWidgetButton");
2509 if (mIsSafeModeEnabled) {
2510 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2512 showWidgetsView(true /* animated */, true /* resetPageToZero */);
2517 * Event handler for the wallpaper picker button that appears after a long press
2518 * on the home screen.
2520 public void onClickWallpaperPicker(View v) {
2521 if (!Utilities.isWallpaperAllowed(this)) {
2522 Toast.makeText(this, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show();
2526 int pageScroll = mWorkspace.getScrollForPage(mWorkspace.getPageNearestToCenterOfScreen());
2527 float offset = mWorkspace.mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
2528 setWaitingForResult(new PendingRequestArgs(new ItemInfo()));
2529 Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER)
2530 .putExtra(Utilities.EXTRA_WALLPAPER_OFFSET, offset);
2532 String pickerPackage = getString(R.string.wallpaper_picker_package);
2533 boolean hasTargetPackage = TextUtils.isEmpty(pickerPackage);
2534 if (!hasTargetPackage) {
2535 intent.setPackage(pickerPackage);
2538 intent.setSourceBounds(getViewBounds(v));
2540 startActivityForResult(intent, REQUEST_PICK_WALLPAPER,
2541 // If there is no target package, use the default intent chooser animation
2542 hasTargetPackage ? getActivityLaunchOptions(v) : null);
2543 } catch (ActivityNotFoundException e) {
2544 setWaitingForResult(null);
2545 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2550 * Event handler for a click on the settings button that appears after a long press
2551 * on the home screen.
2553 public void onClickSettingsButton(View v) {
2554 if (LOGD) Log.d(TAG, "onClickSettingsButton");
2555 Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
2556 .setPackage(getPackageName());
2557 intent.setSourceBounds(getViewBounds(v));
2558 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2559 startActivity(intent, getActivityLaunchOptions(v));
2562 public View.OnTouchListener getHapticFeedbackTouchListener() {
2563 if (mHapticFeedbackTouchListener == null) {
2564 mHapticFeedbackTouchListener = new View.OnTouchListener() {
2565 @SuppressLint("ClickableViewAccessibility")
2567 public boolean onTouch(View v, MotionEvent event) {
2568 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
2569 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2575 return mHapticFeedbackTouchListener;
2579 public void onAccessibilityStateChanged(boolean enabled) {
2580 mDragLayer.onAccessibilityStateChanged(enabled);
2583 public void onDragStarted() {
2584 if (isOnCustomContent()) {
2585 // Custom content screen doesn't participate in drag and drop. If on custom
2586 // content screen, move to default.
2587 moveWorkspaceToDefaultScreen();
2592 * Called when the user stops interacting with the launcher.
2593 * This implies that the user is now on the homescreen and is not doing housekeeping.
2595 protected void onInteractionEnd() {
2596 if (mLauncherCallbacks != null) {
2597 mLauncherCallbacks.onInteractionEnd();
2602 * Called when the user starts interacting with the launcher.
2603 * The possible interactions are:
2605 * - reorder an app shortcut, or a widget
2606 * - open the overview mode.
2607 * This is a good time to stop doing things that only make sense
2608 * when the user is on the homescreen and not doing housekeeping.
2610 protected void onInteractionBegin() {
2611 if (mLauncherCallbacks != null) {
2612 mLauncherCallbacks.onInteractionBegin();
2616 /** Updates the interaction state. */
2617 public void updateInteraction(Workspace.State fromState, Workspace.State toState) {
2618 // Only update the interacting state if we are transitioning to/from a view with an
2620 boolean fromStateWithOverlay = fromState != Workspace.State.NORMAL;
2621 boolean toStateWithOverlay = toState != Workspace.State.NORMAL;
2622 if (toStateWithOverlay) {
2623 onInteractionBegin();
2624 } else if (fromStateWithOverlay) {
2629 private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) {
2631 StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
2633 // Temporarily disable deathPenalty on all default checks. For eg, shortcuts
2634 // containing file Uri's would cause a crash as penaltyDeathOnFileUriExposure
2635 // is enabled by default on NYC.
2636 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll()
2637 .penaltyLog().build());
2639 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
2640 String id = ((ShortcutInfo) info).getDeepShortcutId();
2641 String packageName = intent.getPackage();
2642 DeepShortcutManager.getInstance(this).startShortcut(
2643 packageName, id, intent.getSourceBounds(), optsBundle, info.user);
2645 // Could be launching some bookkeeping activity
2646 startActivity(intent, optsBundle);
2649 StrictMode.setVmPolicy(oldPolicy);
2651 } catch (SecurityException e) {
2652 // Due to legacy reasons, direct call shortcuts require Launchers to have the
2653 // corresponding permission. Show the appropriate permission prompt if that
2655 if (intent.getComponent() == null
2656 && Intent.ACTION_CALL.equals(intent.getAction())
2657 && checkSelfPermission(Manifest.permission.CALL_PHONE) !=
2658 PackageManager.PERMISSION_GRANTED) {
2660 setWaitingForResult(PendingRequestArgs
2661 .forIntent(REQUEST_PERMISSION_CALL_PHONE, intent, info));
2662 requestPermissions(new String[]{Manifest.permission.CALL_PHONE},
2663 REQUEST_PERMISSION_CALL_PHONE);
2665 // No idea why this was thrown.
2671 @TargetApi(Build.VERSION_CODES.M)
2672 public Bundle getActivityLaunchOptions(View v) {
2673 if (Utilities.ATLEAST_MARSHMALLOW) {
2674 int left = 0, top = 0;
2675 int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
2676 if (v instanceof TextView) {
2677 // Launch from center of icon, not entire view
2678 Drawable icon = Workspace.getTextViewIcon((TextView) v);
2680 Rect bounds = icon.getBounds();
2681 left = (width - bounds.width()) / 2;
2682 top = v.getPaddingTop();
2683 width = bounds.width();
2684 height = bounds.height();
2687 return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height).toBundle();
2688 } else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
2689 // On L devices, we use the device default slide-up transition.
2690 // On L MR1 devices, we use a custom version of the slide-up transition which
2691 // doesn't have the delay present in the device default.
2692 return ActivityOptions.makeCustomAnimation(
2693 this, R.anim.task_open_enter, R.anim.no_anim).toBundle();
2698 public Rect getViewBounds(View v) {
2699 int[] pos = new int[2];
2700 v.getLocationOnScreen(pos);
2701 return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
2704 public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
2705 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
2706 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
2709 // Only launch using the new animation if the shortcut has not opted out (this is a
2710 // private contract between launcher and may be ignored in the future).
2711 boolean useLaunchAnimation = (v != null) &&
2712 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
2713 Bundle optsBundle = useLaunchAnimation ? getActivityLaunchOptions(v) : null;
2715 UserHandle user = item == null ? null : item.user;
2718 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2720 intent.setSourceBounds(getViewBounds(v));
2723 if (Utilities.ATLEAST_MARSHMALLOW
2724 && (item instanceof ShortcutInfo)
2725 && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
2726 || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
2727 && !((ShortcutInfo) item).isPromise()) {
2728 // Shortcuts need some special checks due to legacy reasons.
2729 startShortcutIntentSafely(intent, optsBundle, item);
2730 } else if (user == null || user.equals(Process.myUserHandle())) {
2731 // Could be launching some bookkeeping activity
2732 startActivity(intent, optsBundle);
2734 LauncherAppsCompat.getInstance(this).startActivityForProfile(
2735 intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
2738 } catch (ActivityNotFoundException|SecurityException e) {
2739 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2740 Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e);
2746 public boolean dispatchTouchEvent(MotionEvent ev) {
2747 mLastDispatchTouchEventX = ev.getX();
2748 return super.dispatchTouchEvent(ev);
2752 public boolean onLongClick(View v) {
2753 if (!isDraggingEnabled()) return false;
2754 if (isWorkspaceLocked()) return false;
2755 if (mState != State.WORKSPACE) return false;
2757 if ((FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && v instanceof PageIndicator) ||
2758 (v == mAllAppsButton && mAllAppsButton != null)) {
2759 onLongClickAllAppsButton(v);
2764 boolean ignoreLongPressToOverview =
2765 mDeviceProfile.shouldIgnoreLongPressToOverview(mLastDispatchTouchEventX);
2767 if (v instanceof Workspace) {
2768 if (!mWorkspace.isInOverviewMode()) {
2769 if (!mWorkspace.isTouchActive() && !ignoreLongPressToOverview) {
2770 getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
2771 Action.Direction.NONE, ContainerType.WORKSPACE,
2772 mWorkspace.getCurrentPage());
2773 showOverviewMode(true);
2774 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
2775 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
2785 CellLayout.CellInfo longClickCellInfo = null;
2786 View itemUnderLongClick = null;
2787 if (v.getTag() instanceof ItemInfo) {
2788 ItemInfo info = (ItemInfo) v.getTag();
2789 longClickCellInfo = new CellLayout.CellInfo(v, info);
2790 itemUnderLongClick = longClickCellInfo.cell;
2791 mPendingRequestArgs = null;
2794 // The hotseat touch handling does not go through Workspace, and we always allow long press
2795 // on hotseat items.
2796 if (!mDragController.isDragging()) {
2797 if (itemUnderLongClick == null) {
2798 // User long pressed on empty space
2799 if (mWorkspace.isInOverviewMode()) {
2800 mWorkspace.startReordering(v);
2801 getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
2802 Action.Direction.NONE, ContainerType.OVERVIEW);
2804 if (ignoreLongPressToOverview) {
2807 getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
2808 Action.Direction.NONE, ContainerType.WORKSPACE,
2809 mWorkspace.getCurrentPage());
2810 showOverviewMode(true);
2812 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
2813 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
2815 final boolean isAllAppsButton =
2816 !FeatureFlags.NO_ALL_APPS_ICON && isHotseatLayout(v) &&
2817 mDeviceProfile.inv.isAllAppsButtonRank(mHotseat.getOrderInHotseat(
2818 longClickCellInfo.cellX, longClickCellInfo.cellY));
2819 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
2820 // User long pressed on an item
2821 mWorkspace.startDrag(longClickCellInfo, new DragOptions());
2828 boolean isHotseatLayout(View layout) {
2829 // TODO: Remove this method
2830 return mHotseat != null && layout != null &&
2831 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
2835 * Returns the CellLayout of the specified container at the specified screen.
2837 public CellLayout getCellLayout(long container, long screenId) {
2838 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2839 if (mHotseat != null) {
2840 return mHotseat.getLayout();
2845 return mWorkspace.getScreenWithId(screenId);
2850 * For overridden classes.
2852 public boolean isAllAppsVisible() {
2853 return isAppsViewVisible();
2856 public boolean isAppsViewVisible() {
2857 return (mState == State.APPS) || (mOnResumeState == State.APPS);
2860 public boolean isWidgetsViewVisible() {
2861 return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
2865 public void onTrimMemory(int level) {
2866 super.onTrimMemory(level);
2867 if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
2868 // The widget preview db can result in holding onto over
2869 // 3MB of memory for caching which isn't necessary.
2870 SQLiteDatabase.releaseMemory();
2872 // This clears all widget bitmaps from the widget tray
2875 if (mLauncherCallbacks != null) {
2876 mLauncherCallbacks.onTrimMemory(level);
2880 public boolean showWorkspace(boolean animated) {
2881 return showWorkspace(animated, null);
2884 public boolean showWorkspace(boolean animated, Runnable onCompleteRunnable) {
2885 boolean changed = mState != State.WORKSPACE ||
2886 mWorkspace.getState() != Workspace.State.NORMAL;
2887 if (changed || mAllAppsController.isTransitioning()) {
2888 mWorkspace.setVisibility(View.VISIBLE);
2889 mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
2890 Workspace.State.NORMAL, animated, onCompleteRunnable);
2892 // Set focus to the AppsCustomize button
2893 if (mAllAppsButton != null) {
2894 mAllAppsButton.requestFocus();
2898 // Change the state *after* we've called all the transition code
2899 setState(State.WORKSPACE);
2902 // Send an accessibility event to announce the context change
2903 getWindow().getDecorView()
2904 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
2910 * Shows the overview button.
2912 public void showOverviewMode(boolean animated) {
2913 showOverviewMode(animated, false);
2917 * Shows the overview button, and if {@param requestButtonFocus} is set, will force the focus
2918 * onto one of the overview panel buttons.
2920 void showOverviewMode(boolean animated, boolean requestButtonFocus) {
2921 Runnable postAnimRunnable = null;
2922 if (requestButtonFocus) {
2923 postAnimRunnable = new Runnable() {
2926 // Hitting the menu button when in touch mode does not trigger touch mode to
2927 // be disabled, so if requested, force focus on one of the overview panel
2929 mOverviewPanel.requestFocusFromTouch();
2933 mWorkspace.setVisibility(View.VISIBLE);
2934 mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
2935 Workspace.State.OVERVIEW, animated, postAnimRunnable);
2936 setState(State.WORKSPACE);
2938 // If animated from long press, then don't allow any of the controller in the drag
2939 // layer to intercept any remaining touch.
2940 mWorkspace.requestDisallowInterceptTouchEvent(animated);
2943 private void setState(State state) {
2944 this.mState = state;
2945 updateSoftInputMode();
2948 private void updateSoftInputMode() {
2949 if (FeatureFlags.LAUNCHER3_UPDATE_SOFT_INPUT_MODE) {
2951 if (isAppsViewVisible()) {
2952 mode = SOFT_INPUT_MODE_ALL_APPS;
2954 mode = SOFT_INPUT_MODE_DEFAULT;
2956 getWindow().setSoftInputMode(mode);
2961 * Shows the apps view.
2963 public void showAppsView(boolean animated, boolean updatePredictedApps,
2964 boolean focusSearchBar) {
2965 markAppsViewShown();
2966 if (updatePredictedApps) {
2967 tryAndUpdatePredictedApps();
2969 showAppsOrWidgets(State.APPS, animated, focusSearchBar);
2973 * Shows the widgets view.
2975 void showWidgetsView(boolean animated, boolean resetPageToZero) {
2976 if (LOGD) Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero);
2977 if (resetPageToZero) {
2978 mWidgetsView.scrollToTop();
2980 showAppsOrWidgets(State.WIDGETS, animated, false);
2982 mWidgetsView.post(new Runnable() {
2985 mWidgetsView.requestFocus();
2991 * Sets up the transition to show the apps/widgets view.
2993 * @return whether the current from and to state allowed this operation
2995 // TODO: calling method should use the return value so that when {@code false} is returned
2996 // the workspace transition doesn't fall into invalid state.
2997 private boolean showAppsOrWidgets(State toState, boolean animated, boolean focusSearchBar) {
2998 if (!(mState == State.WORKSPACE ||
2999 mState == State.APPS_SPRING_LOADED ||
3000 mState == State.WIDGETS_SPRING_LOADED ||
3001 (mState == State.APPS && mAllAppsController.isTransitioning()))) {
3004 if (toState != State.APPS && toState != State.WIDGETS) {
3008 // This is a safe and supported transition to bypass spring_loaded mode.
3009 if (mExitSpringLoadedModeRunnable != null) {
3010 mHandler.removeCallbacks(mExitSpringLoadedModeRunnable);
3011 mExitSpringLoadedModeRunnable = null;
3014 if (toState == State.APPS) {
3015 mStateTransitionAnimation.startAnimationToAllApps(animated, focusSearchBar);
3017 mStateTransitionAnimation.startAnimationToWidgets(animated);
3020 // Change the state *after* we've called all the transition code
3022 AbstractFloatingView.closeAllOpenViews(this);
3024 // Send an accessibility event to announce the context change
3025 getWindow().getDecorView()
3026 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3031 * Updates the workspace and interaction state on state change, and return the animation to this
3034 public Animator startWorkspaceStateChangeAnimation(Workspace.State toState,
3035 boolean animated, AnimationLayerSet layerViews) {
3036 Workspace.State fromState = mWorkspace.getState();
3037 Animator anim = mWorkspace.setStateWithAnimation(toState, animated, layerViews);
3038 updateInteraction(fromState, toState);
3042 public void enterSpringLoadedDragMode() {
3043 if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name()));
3044 if (isStateSpringLoaded()) {
3048 mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
3049 Workspace.State.SPRING_LOADED, true /* animated */,
3050 null /* onCompleteRunnable */);
3051 setState(State.WORKSPACE_SPRING_LOADED);
3054 public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
3055 final Runnable onCompleteRunnable) {
3056 if (!isStateSpringLoaded()) return;
3058 if (mExitSpringLoadedModeRunnable != null) {
3059 mHandler.removeCallbacks(mExitSpringLoadedModeRunnable);
3061 mExitSpringLoadedModeRunnable = new Runnable() {
3064 if (successfulDrop) {
3065 // TODO(hyunyoungs): verify if this hack is still needed, if not, delete.
3067 // Before we show workspace, hide all apps again because
3068 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
3069 // clean up our state transition functions
3070 mWidgetsView.setVisibility(View.GONE);
3071 showWorkspace(true, onCompleteRunnable);
3073 exitSpringLoadedDragMode();
3075 mExitSpringLoadedModeRunnable = null;
3078 mHandler.postDelayed(mExitSpringLoadedModeRunnable, delay);
3081 boolean isStateSpringLoaded() {
3082 return mState == State.WORKSPACE_SPRING_LOADED || mState == State.APPS_SPRING_LOADED
3083 || mState == State.WIDGETS_SPRING_LOADED;
3086 public void exitSpringLoadedDragMode() {
3087 if (mState == State.APPS_SPRING_LOADED) {
3088 showAppsView(true /* animated */,
3089 false /* updatePredictedApps */, false /* focusSearchBar */);
3090 } else if (mState == State.WIDGETS_SPRING_LOADED) {
3091 showWidgetsView(true, false);
3092 } else if (mState == State.WORKSPACE_SPRING_LOADED) {
3093 showWorkspace(true);
3098 * Updates the set of predicted apps if it hasn't been updated since the last time Launcher was
3101 public void tryAndUpdatePredictedApps() {
3102 if (mLauncherCallbacks != null) {
3103 List<ComponentKey> apps = mLauncherCallbacks.getPredictedApps();
3105 mAppsView.setPredictedApps(apps);
3106 getUserEventDispatcher().setPredictedApps(apps);
3111 void lockAllApps() {
3115 void unlockAllApps() {
3120 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
3121 final boolean result = super.dispatchPopulateAccessibilityEvent(event);
3122 final List<CharSequence> text = event.getText();
3124 // Populate event with a fake title based on the current state.
3125 if (mState == State.APPS) {
3126 text.add(getString(R.string.all_apps_button_label));
3127 } else if (mState == State.WIDGETS) {
3128 text.add(getString(R.string.widget_button_text));
3129 } else if (mWorkspace != null) {
3130 text.add(mWorkspace.getCurrentPageDescription());
3132 text.add(getString(R.string.all_apps_home_button_label));
3138 * If the activity is currently paused, signal that we need to run the passed Runnable
3141 * This needs to be called from incoming places where resources might have been loaded
3142 * while the activity is paused. That is because the Configuration (e.g., rotation) might be
3143 * wrong when we're not running, and if the activity comes back to what the configuration was
3144 * when we were paused, activity is not restarted.
3146 * Implementation of the method from LauncherModel.Callbacks.
3148 * @return {@code true} if we are currently paused. The caller might be able to skip some work
3150 @Thunk boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
3152 if (LOGD) Log.d(TAG, "Deferring update until onResume");
3153 if (deletePreviousRunnables) {
3154 while (mBindOnResumeCallbacks.remove(run)) {
3157 mBindOnResumeCallbacks.add(run);
3164 private boolean waitUntilResume(Runnable run) {
3165 return waitUntilResume(run, false);
3168 public void addOnResumeCallback(Runnable run) {
3169 mOnResumeCallbacks.add(run);
3173 * If the activity is currently paused, signal that we need to re-run the loader
3176 * This needs to be called from incoming places where resources might have been loaded
3177 * while we are paused. That is becaues the Configuration might be wrong
3178 * when we're not running, and if it comes back to what it was when we
3179 * were paused, we are not restarted.
3181 * Implementation of the method from LauncherModel.Callbacks.
3183 * @return true if we are currently paused. The caller might be able to
3184 * skip some work in that case since we will come back again.
3187 public boolean setLoadOnResume() {
3189 if (LOGD) Log.d(TAG, "setLoadOnResume");
3190 mOnResumeNeedsLoad = true;
3198 * Implementation of the method from LauncherModel.Callbacks.
3201 public int getCurrentWorkspaceScreen() {
3202 if (mWorkspace != null) {
3203 return mWorkspace.getCurrentPage();
3210 * Clear any pending bind callbacks. This is called when is loader is planning to
3211 * perform a full rebind from scratch.
3214 public void clearPendingBinds() {
3215 mBindOnResumeCallbacks.clear();
3216 if (mPendingExecutor != null) {
3217 mPendingExecutor.markCompleted();
3218 mPendingExecutor = null;
3223 * Refreshes the shortcuts shown on the workspace.
3225 * Implementation of the method from LauncherModel.Callbacks.
3227 public void startBinding() {
3228 if (LauncherAppState.PROFILE_STARTUP) {
3229 Trace.beginSection("Starting page bind");
3232 AbstractFloatingView.closeAllOpenViews(this);
3234 setWorkspaceLoading(true);
3236 // Clear the workspace because it's going to be rebound
3237 mWorkspace.clearDropTargets();
3238 mWorkspace.removeAllWorkspaceScreens();
3240 if (mHotseat != null) {
3241 mHotseat.resetLayout();
3243 if (LauncherAppState.PROFILE_STARTUP) {
3249 public void bindScreens(ArrayList<Long> orderedScreenIds) {
3250 // Make sure the first screen is always at the start.
3251 if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
3252 orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
3253 orderedScreenIds.remove(Workspace.FIRST_SCREEN_ID);
3254 orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
3255 mModel.updateWorkspaceScreenOrder(this, orderedScreenIds);
3256 } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
3257 // If there are no screens, we need to have an empty screen
3258 mWorkspace.addExtraEmptyScreen();
3260 bindAddScreens(orderedScreenIds);
3262 // Create the custom content page (this call updates mDefaultScreen which calls
3263 // setCurrentPage() so ensure that all pages are added before calling this).
3264 if (hasCustomContentToLeft()) {
3265 mWorkspace.createCustomContentContainer();
3266 populateCustomContentContainer();
3269 // After we have added all the screens, if the wallpaper was locked to the default state,
3270 // then notify to indicate that it can be released and a proper wallpaper offset can be
3271 // computed before the next layout
3272 mWorkspace.unlockWallpaperFromDefaultPageOnNextLayout();
3275 private void bindAddScreens(ArrayList<Long> orderedScreenIds) {
3276 int count = orderedScreenIds.size();
3277 for (int i = 0; i < count; i++) {
3278 long screenId = orderedScreenIds.get(i);
3279 if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) {
3280 // No need to bind the first screen, as its always bound.
3281 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
3286 public void bindAppsAdded(final ArrayList<Long> newScreens,
3287 final ArrayList<ItemInfo> addNotAnimated,
3288 final ArrayList<ItemInfo> addAnimated,
3289 final ArrayList<AppInfo> addedApps) {
3290 Runnable r = new Runnable() {
3292 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
3295 if (waitUntilResume(r)) {
3299 // Add the new screens
3300 if (newScreens != null) {
3301 bindAddScreens(newScreens);
3304 // We add the items without animation on non-visible pages, and with
3305 // animations on the new page (which we will try and snap to).
3306 if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
3307 bindItems(addNotAnimated, 0,
3308 addNotAnimated.size(), false);
3310 if (addAnimated != null && !addAnimated.isEmpty()) {
3311 bindItems(addAnimated, 0,
3312 addAnimated.size(), true);
3315 // Remove the extra empty screen
3316 mWorkspace.removeExtraEmptyScreen(false, false);
3318 if (addedApps != null && mAppsView != null) {
3319 mAppsView.addApps(addedApps);
3324 * Bind the items start-end from the list.
3326 * Implementation of the method from LauncherModel.Callbacks.
3329 public void bindItems(final ArrayList<ItemInfo> items, final int start, final int end,
3330 final boolean forceAnimateIcons) {
3331 Runnable r = new Runnable() {
3333 bindItems(items, start, end, forceAnimateIcons);
3336 if (waitUntilResume(r)) {
3340 // Get the list of added items and intersect them with the set of items here
3341 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
3342 final Collection<Animator> bounceAnims = new ArrayList<Animator>();
3343 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
3344 Workspace workspace = mWorkspace;
3345 long newItemsScreenId = -1;
3346 for (int i = start; i < end; i++) {
3347 final ItemInfo item = items.get(i);
3349 // Short circuit if we are loading dock items for a configuration which has no dock
3350 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3356 switch (item.itemType) {
3357 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3358 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3359 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
3360 ShortcutInfo info = (ShortcutInfo) item;
3361 view = createShortcut(info);
3364 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
3365 view = FolderIcon.fromXml(R.layout.folder_icon, this,
3366 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
3370 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: {
3371 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item;
3372 if (mIsSafeModeEnabled) {
3373 view = new PendingAppWidgetHostView(this, info, mIconCache, true);
3375 LauncherAppWidgetProviderInfo providerInfo =
3376 mAppWidgetManager.getLauncherAppWidgetInfo(info.appWidgetId);
3377 if (providerInfo == null) {
3378 deleteWidgetInfo(info);
3381 view = mAppWidgetHost.createView(this, info.appWidgetId, providerInfo);
3383 prepareAppWidget((AppWidgetHostView) view, info);
3387 throw new RuntimeException("Invalid Item Type");
3391 * Remove colliding items.
3393 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
3394 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
3395 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
3396 View v = cl.getChildAt(item.cellX, item.cellY);
3397 Object tag = v.getTag();
3398 String desc = "Collision while binding workspace item: " + item
3399 + ". Collides with " + tag;
3400 if (ProviderConfig.IS_DOGFOOD_BUILD) {
3401 throw (new RuntimeException(desc));
3404 getModelWriter().deleteItemFromDatabase(item);
3409 workspace.addInScreenFromBind(view, item);
3411 // Animate all the applications up now
3415 bounceAnims.add(createNewAppBounceAnimation(view, i));
3416 newItemsScreenId = item.screenId;
3421 // Animate to the correct page
3422 if (newItemsScreenId > -1) {
3423 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
3424 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
3425 final Runnable startBounceAnimRunnable = new Runnable() {
3427 anim.playTogether(bounceAnims);
3431 if (newItemsScreenId != currentScreenId) {
3432 // We post the animation slightly delayed to prevent slowdowns
3433 // when we are loading right after we return to launcher.
3434 mWorkspace.postDelayed(new Runnable() {
3436 if (mWorkspace != null) {
3437 mWorkspace.snapToPage(newScreenIndex);
3438 mWorkspace.postDelayed(startBounceAnimRunnable,
3439 NEW_APPS_ANIMATION_DELAY);
3442 }, NEW_APPS_PAGE_MOVE_DELAY);
3444 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
3448 workspace.requestLayout();
3452 * Add the views for a widget to the workspace.
3454 * Implementation of the method from LauncherModel.Callbacks.
3456 public void bindAppWidget(final LauncherAppWidgetInfo item) {
3457 Runnable r = new Runnable() {
3459 bindAppWidget(item);
3462 if (waitUntilResume(r)) {
3466 if (mIsSafeModeEnabled) {
3467 PendingAppWidgetHostView view =
3468 new PendingAppWidgetHostView(this, item, mIconCache, true);
3469 prepareAppWidget(view, item);
3470 mWorkspace.addInScreen(view, item);
3471 mWorkspace.requestLayout();
3475 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
3476 if (DEBUG_WIDGETS) {
3477 Log.d(TAG, "bindAppWidget: " + item);
3480 final LauncherAppWidgetProviderInfo appWidgetInfo;
3482 if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
3483 // If the provider is not ready, bind as a pending widget.
3484 appWidgetInfo = null;
3485 } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
3486 // The widget id is not valid. Try to find the widget based on the provider info.
3487 appWidgetInfo = mAppWidgetManager.findProvider(item.providerName, item.user);
3489 appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(item.appWidgetId);
3492 // If the provider is ready, but the width is not yet restored, try to restore it.
3493 if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) &&
3494 (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
3495 if (appWidgetInfo == null) {
3496 if (DEBUG_WIDGETS) {
3497 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
3498 + " belongs to component " + item.providerName
3499 + ", as the provider is null");
3501 getModelWriter().deleteItemFromDatabase(item);
3505 // If we do not have a valid id, try to bind an id.
3506 if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
3507 if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
3508 // Id has not been allocated yet. Allocate a new id.
3509 item.appWidgetId = mAppWidgetHost.allocateAppWidgetId();
3510 item.restoreStatus |= LauncherAppWidgetInfo.FLAG_ID_ALLOCATED;
3512 // Also try to bind the widget. If the bind fails, the user will be shown
3513 // a click to setup UI, which will ask for the bind permission.
3514 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo);
3515 pendingInfo.spanX = item.spanX;
3516 pendingInfo.spanY = item.spanY;
3517 pendingInfo.minSpanX = item.minSpanX;
3518 pendingInfo.minSpanY = item.minSpanY;
3519 Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
3521 boolean isDirectConfig =
3522 item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
3523 if (isDirectConfig && item.bindOptions != null) {
3524 Bundle newOptions = item.bindOptions.getExtras();
3525 if (options != null) {
3526 newOptions.putAll(options);
3528 options = newOptions;
3530 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
3531 item.appWidgetId, appWidgetInfo, options);
3533 // We tried to bind once. If we were not able to bind, we would need to
3534 // go through the permission dialog, which means we cannot skip the config
3536 item.bindOptions = null;
3537 item.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG;
3541 // If the widget has a configure activity, it is still needs to set it up,
3542 // otherwise the widget is ready to go.
3543 item.restoreStatus = (appWidgetInfo.configure == null) || isDirectConfig
3544 ? LauncherAppWidgetInfo.RESTORE_COMPLETED
3545 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
3548 getModelWriter().updateItemInDatabase(item);
3550 } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
3551 && (appWidgetInfo.configure == null)) {
3552 // The widget was marked as UI not ready, but there is no configure activity to
3554 item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
3555 getModelWriter().updateItemInDatabase(item);
3559 final AppWidgetHostView view;
3560 if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
3561 if (DEBUG_WIDGETS) {
3562 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
3563 + appWidgetInfo.provider);
3566 // Verify that we own the widget
3567 if (appWidgetInfo == null) {
3568 FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId);
3569 deleteWidgetInfo(item);
3573 item.minSpanX = appWidgetInfo.minSpanX;
3574 item.minSpanY = appWidgetInfo.minSpanY;
3575 view = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo);
3577 view = new PendingAppWidgetHostView(this, item, mIconCache, false);
3579 prepareAppWidget(view, item);
3580 mWorkspace.addInScreen(view, item);
3581 mWorkspace.requestLayout();
3583 if (DEBUG_WIDGETS) {
3584 Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
3585 + (SystemClock.uptimeMillis()-start) + "ms");
3590 * Restores a pending widget.
3592 * @param appWidgetId The app widget id
3594 private LauncherAppWidgetInfo completeRestoreAppWidget(int appWidgetId, int finalRestoreFlag) {
3595 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
3596 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
3597 Log.e(TAG, "Widget update called, when the widget no longer exists.");
3601 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
3602 info.restoreStatus = finalRestoreFlag;
3604 mWorkspace.reinflateWidgetsIfNecessary();
3605 getModelWriter().updateItemInDatabase(info);
3609 public void onPageBoundSynchronously(int page) {
3610 mSynchronouslyBoundPages.add(page);
3614 public void executeOnNextDraw(ViewOnDrawExecutor executor) {
3615 if (mPendingExecutor != null) {
3616 mPendingExecutor.markCompleted();
3618 mPendingExecutor = executor;
3619 executor.attachTo(this);
3622 public void clearPendingExecutor(ViewOnDrawExecutor executor) {
3623 if (mPendingExecutor == executor) {
3624 mPendingExecutor = null;
3629 public void finishFirstPageBind(final ViewOnDrawExecutor executor) {
3630 Runnable r = new Runnable() {
3632 finishFirstPageBind(executor);
3635 if (waitUntilResume(r)) {
3639 Runnable onComplete = new Runnable() {
3642 if (executor != null) {
3643 executor.onLoadAnimationCompleted();
3647 if (mDragLayer.getAlpha() < 1) {
3648 mDragLayer.animate().alpha(1).withEndAction(onComplete).start();
3655 * Callback saying that there aren't any more items to bind.
3657 * Implementation of the method from LauncherModel.Callbacks.
3659 public void finishBindingItems() {
3660 Runnable r = new Runnable() {
3662 finishBindingItems();
3665 if (waitUntilResume(r)) {
3668 if (LauncherAppState.PROFILE_STARTUP) {
3669 Trace.beginSection("Page bind completed");
3671 mWorkspace.restoreInstanceStateForRemainingPages();
3673 setWorkspaceLoading(false);
3675 if (mPendingActivityResult != null) {
3676 handleActivityResult(mPendingActivityResult.requestCode,
3677 mPendingActivityResult.resultCode, mPendingActivityResult.data);
3678 mPendingActivityResult = null;
3681 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
3683 NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
3685 if (mLauncherCallbacks != null) {
3686 mLauncherCallbacks.finishBindingItems(false);
3688 if (LauncherAppState.PROFILE_STARTUP) {
3693 private boolean canRunNewAppsAnimation() {
3694 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
3695 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
3698 private ValueAnimator createNewAppBounceAnimation(View v, int i) {
3699 ValueAnimator bounceAnim = LauncherAnimUtils.ofViewAlphaAndScale(v, 1, 1, 1);
3700 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
3701 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
3702 bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
3706 public boolean useVerticalBarLayout() {
3707 return mDeviceProfile.isVerticalBarLayout();
3710 public int getSearchBarHeight() {
3711 if (mLauncherCallbacks != null) {
3712 return mLauncherCallbacks.getSearchBarHeight();
3714 return LauncherCallbacks.SEARCH_BAR_HEIGHT_NORMAL;
3718 * A runnable that we can dequeue and re-enqueue when all applications are bound (to prevent
3719 * multiple calls to bind the same list.)
3721 @Thunk ArrayList<AppInfo> mTmpAppsList;
3722 private Runnable mBindAllApplicationsRunnable = new Runnable() {
3724 bindAllApplications(mTmpAppsList);
3725 mTmpAppsList = null;
3730 * Add the icons for all apps.
3732 * Implementation of the method from LauncherModel.Callbacks.
3734 public void bindAllApplications(final ArrayList<AppInfo> apps) {
3735 if (waitUntilResume(mBindAllApplicationsRunnable, true)) {
3736 mTmpAppsList = apps;
3740 if (mAppsView != null) {
3741 mAppsView.setApps(apps);
3743 if (mLauncherCallbacks != null) {
3744 mLauncherCallbacks.bindAllApplications(apps);
3749 * Copies LauncherModel's map of activities to shortcut ids to Launcher's. This is necessary
3750 * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
3753 public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
3754 mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
3758 * A package was updated.
3760 * Implementation of the method from LauncherModel.Callbacks.
3762 public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
3763 Runnable r = new Runnable() {
3765 bindAppsUpdated(apps);
3768 if (waitUntilResume(r)) {
3772 if (mAppsView != null) {
3773 mAppsView.updateApps(apps);
3778 public void bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets) {
3779 Runnable r = new Runnable() {
3781 bindWidgetsRestored(widgets);
3784 if (waitUntilResume(r)) {
3787 mWorkspace.widgetsRestored(widgets);
3791 * Some shortcuts were updated in the background.
3792 * Implementation of the method from LauncherModel.Callbacks.
3794 * @param updated list of shortcuts which have changed.
3795 * @param removed list of shortcuts which were deleted in the background. This can happen when
3796 * an app gets removed from the system or some of its components are no longer
3800 public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated,
3801 final ArrayList<ShortcutInfo> removed, final UserHandle user) {
3802 Runnable r = new Runnable() {
3804 bindShortcutsChanged(updated, removed, user);
3807 if (waitUntilResume(r)) {
3811 if (!updated.isEmpty()) {
3812 mWorkspace.updateShortcuts(updated);
3815 if (!removed.isEmpty()) {
3816 HashSet<ComponentName> removedComponents = new HashSet<>();
3817 HashSet<ShortcutKey> removedDeepShortcuts = new HashSet<>();
3819 for (ShortcutInfo si : removed) {
3820 if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
3821 removedDeepShortcuts.add(ShortcutKey.fromItemInfo(si));
3823 removedComponents.add(si.getTargetComponent());
3827 if (!removedComponents.isEmpty()) {
3828 ItemInfoMatcher matcher = ItemInfoMatcher.ofComponents(removedComponents, user);
3829 mWorkspace.removeItemsByMatcher(matcher);
3830 mDragController.onAppsRemoved(matcher);
3833 if (!removedDeepShortcuts.isEmpty()) {
3834 ItemInfoMatcher matcher = ItemInfoMatcher.ofShortcutKeys(removedDeepShortcuts);
3835 mWorkspace.removeItemsByMatcher(matcher);
3836 mDragController.onAppsRemoved(matcher);
3842 * Update the state of a package, typically related to install state.
3844 * Implementation of the method from LauncherModel.Callbacks.
3847 public void bindRestoreItemsChange(final HashSet<ItemInfo> updates) {
3848 Runnable r = new Runnable() {
3850 bindRestoreItemsChange(updates);
3853 if (waitUntilResume(r)) {
3857 mWorkspace.updateRestoreItems(updates);
3861 * A package was uninstalled/updated. We take both the super set of packageNames
3862 * in addition to specific applications to remove, the reason being that
3863 * this can be called when a package is updated as well. In that scenario,
3864 * we only remove specific components from the workspace and hotseat, where as
3865 * package-removal should clear all items by package name.
3868 public void bindWorkspaceComponentsRemoved(
3869 final HashSet<String> packageNames, final HashSet<ComponentName> components,
3870 final UserHandle user) {
3871 Runnable r = new Runnable() {
3873 bindWorkspaceComponentsRemoved(packageNames, components, user);
3876 if (waitUntilResume(r)) {
3879 if (!packageNames.isEmpty()) {
3880 ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageNames, user);
3881 mWorkspace.removeItemsByMatcher(matcher);
3882 mDragController.onAppsRemoved(matcher);
3885 if (!components.isEmpty()) {
3886 ItemInfoMatcher matcher = ItemInfoMatcher.ofComponents(components, user);
3887 mWorkspace.removeItemsByMatcher(matcher);
3888 mDragController.onAppsRemoved(matcher);
3893 public void bindAppInfosRemoved(final ArrayList<AppInfo> appInfos) {
3894 Runnable r = new Runnable() {
3896 bindAppInfosRemoved(appInfos);
3899 if (waitUntilResume(r)) {
3904 if (mAppsView != null) {
3905 mAppsView.removeApps(appInfos);
3906 tryAndUpdatePredictedApps();
3910 private Runnable mBindAllWidgetsRunnable = new Runnable() {
3912 bindAllWidgets(mAllWidgets);
3917 public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> allWidgets) {
3918 if (waitUntilResume(mBindAllWidgetsRunnable, true)) {
3919 mAllWidgets = allWidgets;
3923 if (mWidgetsView != null && allWidgets != null) {
3924 mWidgetsView.setWidgets(allWidgets);
3928 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
3929 if (topView != null) {
3930 topView.onWidgetsBound();
3934 public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
3935 return mWidgetsView.getWidgetsForPackageUser(packageUserKey);
3939 public void notifyWidgetProvidersChanged() {
3940 if (mWorkspace.getState().shouldUpdateWidget) {
3941 refreshAndBindWidgetsForPackageUser(null);
3946 * @param packageUser if null, refreshes all widgets and shortcuts, otherwise only
3947 * refreshes the widgets and shortcuts associated with the given package/user
3949 public void refreshAndBindWidgetsForPackageUser(@Nullable PackageUserKey packageUser) {
3950 mModel.refreshAndBindWidgetsAndShortcuts(this, mWidgetsView.isEmpty(), packageUser);
3953 public void lockScreenOrientation() {
3954 if (mRotationEnabled) {
3955 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
3959 public void unlockScreenOrientation(boolean immediate) {
3960 if (mRotationEnabled) {
3962 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
3964 mHandler.postDelayed(new Runnable() {
3966 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
3968 }, RESTORE_SCREEN_ORIENTATION_DELAY);
3973 private void markAppsViewShown() {
3974 if (mSharedPrefs.getBoolean(APPS_VIEW_SHOWN, false)) {
3977 mSharedPrefs.edit().putBoolean(APPS_VIEW_SHOWN, true).apply();
3980 private boolean shouldShowDiscoveryBounce() {
3981 if (mState != mState.WORKSPACE) {
3984 if (mLauncherCallbacks != null && mLauncherCallbacks.shouldShowDiscoveryBounce()) {
3987 if (!mIsResumeFromActionScreenOff) {
3990 if (mSharedPrefs.getBoolean(APPS_VIEW_SHOWN, false)) {
3996 protected void moveWorkspaceToDefaultScreen() {
3997 mWorkspace.moveToDefaultScreen(false);
4001 * $ adb shell dumpsys activity com.android.launcher3.Launcher [--all]
4004 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
4005 super.dump(prefix, fd, writer, args);
4007 if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
4008 writer.println(prefix + "Workspace Items");
4009 for (int i = mWorkspace.numCustomPages(); i < mWorkspace.getPageCount(); i++) {
4010 writer.println(prefix + " Homescreen " + i);
4012 ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets();
4013 for (int j = 0; j < layout.getChildCount(); j++) {
4014 Object tag = layout.getChildAt(j).getTag();
4016 writer.println(prefix + " " + tag.toString());
4021 writer.println(prefix + " Hotseat");
4022 ViewGroup layout = mHotseat.getLayout().getShortcutsAndWidgets();
4023 for (int j = 0; j < layout.getChildCount(); j++) {
4024 Object tag = layout.getChildAt(j).getTag();
4026 writer.println(prefix + " " + tag.toString());
4031 FileLog.flushAll(writer);
4032 } catch (Exception e) {
4037 writer.println(prefix + "Misc:");
4038 writer.print(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading);
4039 writer.print(" mPendingRequestArgs=" + mPendingRequestArgs);
4040 writer.println(" mPendingActivityResult=" + mPendingActivityResult);
4042 mModel.dumpState(prefix, fd, writer, args);
4044 if (mLauncherCallbacks != null) {
4045 mLauncherCallbacks.dump(prefix, fd, writer, args);
4050 @TargetApi(Build.VERSION_CODES.N)
4051 public void onProvideKeyboardShortcuts(
4052 List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
4054 ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>();
4055 if (mState == State.WORKSPACE) {
4056 shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label),
4057 KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON));
4059 View currentFocus = getCurrentFocus();
4060 if (new CustomActionsPopup(this, currentFocus).canShow()) {
4061 shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.custom_actions),
4062 KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON));
4064 if (currentFocus instanceof BubbleTextView &&
4065 ((BubbleTextView) currentFocus).hasDeepShortcuts()) {
4066 shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.action_deep_shortcut),
4067 KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON));
4069 if (!shortcutInfos.isEmpty()) {
4070 data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos));
4073 super.onProvideKeyboardShortcuts(data, menu, deviceId);
4077 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
4078 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
4080 case KeyEvent.KEYCODE_A:
4081 if (mState == State.WORKSPACE) {
4082 showAppsView(true, true, false);
4086 case KeyEvent.KEYCODE_S: {
4087 View focusedView = getCurrentFocus();
4088 if (focusedView instanceof BubbleTextView
4089 && focusedView.getTag() instanceof ItemInfo
4090 && mAccessibilityDelegate.performAction(focusedView,
4091 (ItemInfo) focusedView.getTag(),
4092 LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
4093 PopupContainerWithArrow.getOpen(this).requestFocus();
4098 case KeyEvent.KEYCODE_O:
4099 if (new CustomActionsPopup(this, getCurrentFocus()).show()) {
4105 return super.onKeyShortcut(keyCode, event);
4108 public static CustomAppWidget getCustomAppWidget(String name) {
4109 return sCustomAppWidgets.get(name);
4112 public static HashMap<String, CustomAppWidget> getCustomAppWidgets() {
4113 return sCustomAppWidgets;
4116 public static Launcher getLauncher(Context context) {
4117 if (context instanceof Launcher) {
4118 return (Launcher) context;
4120 return ((Launcher) ((ContextWrapper) context).getBaseContext());
4123 private class RotationPrefChangeHandler implements OnSharedPreferenceChangeListener {
4126 public void onSharedPreferenceChanged(
4127 SharedPreferences sharedPreferences, String key) {
4128 if (Utilities.ALLOW_ROTATION_PREFERENCE_KEY.equals(key)) {
4129 // Finish this instance of the activity. When the activity is recreated,
4130 // it will initialize the rotation preference again.