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