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