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