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