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