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