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