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