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