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