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