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