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