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 
17 package com.android.launcher3;
18 
19 import android.Manifest;
20 import android.animation.Animator;
21 import android.animation.AnimatorSet;
22 import android.animation.ValueAnimator;
23 import android.annotation.SuppressLint;
24 import android.annotation.TargetApi;
25 import android.app.ActivityOptions;
26 import android.app.AlertDialog;
27 import android.app.SearchManager;
28 import android.appwidget.AppWidgetHostView;
29 import android.appwidget.AppWidgetManager;
30 import android.content.ActivityNotFoundException;
31 import android.content.BroadcastReceiver;
32 import android.content.ComponentCallbacks2;
33 import android.content.ComponentName;
34 import android.content.Context;
35 import android.content.ContextWrapper;
36 import android.content.DialogInterface;
37 import android.content.Intent;
38 import android.content.IntentFilter;
39 import android.content.IntentSender;
40 import android.content.SharedPreferences;
41 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
42 import android.content.pm.ActivityInfo;
43 import android.content.pm.PackageManager;
44 import android.database.sqlite.SQLiteDatabase;
45 import android.graphics.Point;
46 import android.graphics.Rect;
47 import android.graphics.drawable.Drawable;
48 import android.os.AsyncTask;
49 import android.os.Build;
50 import android.os.Bundle;
51 import android.os.Handler;
52 import android.os.Process;
53 import android.os.StrictMode;
54 import android.os.SystemClock;
55 import android.os.Trace;
56 import android.os.UserHandle;
57 import android.support.annotation.Nullable;
58 import android.text.Selection;
59 import android.text.SpannableStringBuilder;
60 import android.text.TextUtils;
61 import android.text.method.TextKeyListener;
62 import android.util.Log;
63 import android.view.Display;
64 import android.view.HapticFeedbackConstants;
65 import android.view.KeyEvent;
66 import android.view.KeyboardShortcutGroup;
67 import android.view.KeyboardShortcutInfo;
68 import android.view.Menu;
69 import android.view.MotionEvent;
70 import android.view.View;
71 import android.view.View.OnLongClickListener;
72 import android.view.ViewGroup;
73 import android.view.ViewTreeObserver;
74 import android.view.WindowManager;
75 import android.view.accessibility.AccessibilityEvent;
76 import android.view.accessibility.AccessibilityManager;
77 import android.view.animation.OvershootInterpolator;
78 import android.view.inputmethod.InputMethodManager;
79 import android.widget.TextView;
80 import android.widget.Toast;
81 
82 import com.android.launcher3.DropTarget.DragObject;
83 import com.android.launcher3.LauncherSettings.Favorites;
84 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
85 import com.android.launcher3.allapps.AllAppsContainerView;
86 import com.android.launcher3.allapps.AllAppsTransitionController;
87 import com.android.launcher3.allapps.DefaultAppSearchController;
88 import com.android.launcher3.anim.AnimationLayerSet;
89 import com.android.launcher3.compat.AppWidgetManagerCompat;
90 import com.android.launcher3.compat.LauncherAppsCompat;
91 import com.android.launcher3.compat.PinItemRequestCompat;
92 import com.android.launcher3.config.FeatureFlags;
93 import com.android.launcher3.config.ProviderConfig;
94 import com.android.launcher3.dragndrop.DragController;
95 import com.android.launcher3.dragndrop.DragLayer;
96 import com.android.launcher3.dragndrop.DragOptions;
97 import com.android.launcher3.dragndrop.DragView;
98 import com.android.launcher3.dragndrop.PinItemDragListener;
99 import com.android.launcher3.dynamicui.ExtractedColors;
100 import com.android.launcher3.folder.Folder;
101 import com.android.launcher3.folder.FolderIcon;
102 import com.android.launcher3.keyboard.CustomActionsPopup;
103 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
104 import com.android.launcher3.logging.FileLog;
105 import com.android.launcher3.logging.UserEventDispatcher;
106 import com.android.launcher3.model.ModelWriter;
107 import com.android.launcher3.model.PackageItemInfo;
108 import com.android.launcher3.model.WidgetItem;
109 import com.android.launcher3.notification.NotificationListener;
110 import com.android.launcher3.pageindicators.PageIndicator;
111 import com.android.launcher3.popup.PopupContainerWithArrow;
112 import com.android.launcher3.popup.PopupDataProvider;
113 import com.android.launcher3.shortcuts.DeepShortcutManager;
114 import com.android.launcher3.shortcuts.ShortcutKey;
115 import com.android.launcher3.userevent.nano.LauncherLogProto;
116 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
117 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
118 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
119 import com.android.launcher3.util.ActivityResultInfo;
120 import com.android.launcher3.util.ComponentKey;
121 import com.android.launcher3.util.ItemInfoMatcher;
122 import com.android.launcher3.util.MultiHashMap;
123 import com.android.launcher3.util.PackageManagerHelper;
124 import com.android.launcher3.util.PackageUserKey;
125 import com.android.launcher3.util.PendingRequestArgs;
126 import com.android.launcher3.util.TestingUtils;
127 import com.android.launcher3.util.Thunk;
128 import com.android.launcher3.util.ViewOnDrawExecutor;
129 import com.android.launcher3.widget.PendingAddShortcutInfo;
130 import com.android.launcher3.widget.PendingAddWidgetInfo;
131 import com.android.launcher3.widget.WidgetAddFlowHandler;
132 import com.android.launcher3.widget.WidgetHostViewLoader;
133 import com.android.launcher3.widget.WidgetsContainerView;
134 
135 import java.io.FileDescriptor;
136 import java.io.PrintWriter;
137 import java.util.ArrayList;
138 import java.util.Collection;
139 import java.util.HashMap;
140 import java.util.HashSet;
141 import java.util.List;
142 import java.util.Set;
143 
144 /**
145  * Default launcher application.
146  */
147 public 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 
setOrientation()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
onCreate(Bundle savedInstanceState)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
findViewById(int id)462     public View findViewById(int id) {
463         return mLauncherView.findViewById(id);
464     }
465 
466     @Override
onExtractedColorsChanged()467     public void onExtractedColorsChanged() {
468         loadExtractedColorsAndColorItems();
469     }
470 
471     @Override
onAppWidgetHostReset()472     public void onAppWidgetHostReset() {
473         if (mAppWidgetHost != null) {
474             mAppWidgetHost.startListening();
475         }
476     }
477 
loadExtractedColorsAndColorItems()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      */
activateLightSystemBars(boolean isLight, boolean statusBar, boolean navBar)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 
onPostCreate(Bundle savedInstanceState)529     public void onPostCreate(Bundle savedInstanceState) {
530         super.onPostCreate(savedInstanceState);
531         if (mLauncherCallbacks != null) {
532             mLauncherCallbacks.onPostCreate(savedInstanceState);
533         }
534     }
535 
onInsetsChanged(Rect insets)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      */
setLauncherOverlay(LauncherOverlay overlay)544     public void setLauncherOverlay(LauncherOverlay overlay) {
545         if (overlay != null) {
546             overlay.setOverlayCallbacks(new LauncherOverlayCallbacksImpl());
547         }
548         mWorkspace.setLauncherOverlay(overlay);
549     }
550 
setLauncherCallbacks(LauncherCallbacks callbacks)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
onLauncherProviderChanged()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 */
hasCustomContentToLeft()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      */
populateCustomContentContainer()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      */
invalidateHasCustomContentToLeft()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 
isDraggingEnabled()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 
getViewIdForItem(ItemInfo info)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 
getPopupDataProvider()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      */
completeAdd( int requestCode, Intent intent, int appWidgetId, PendingRequestArgs info)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 
handleActivityResult( final int requestCode, final int resultCode, final Intent data)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
onActivityResult( final int requestCode, final int resultCode, final Intent data)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 */
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)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      */
ensurePendingDropLayoutExists(long screenId)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 
completeTwoStageWidgetDrop( final int resultCode, final int appWidgetId, final PendingRequestArgs requestArgs)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
onStop()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
onStart()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
onResume()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
onPause()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.
onShow(boolean fromResume)1096         public void onShow(boolean fromResume);
1097 
1098         // Custom content is completely hidden
onHide()1099         public void onHide();
1100 
1101         // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
onScrollProgressChanged(float progress)1102         public void onScrollProgressChanged(float progress);
1103 
1104         // Indicates whether the user is allowed to scroll away from the custom content.
isScrollingAllowed()1105         boolean isScrollingAllowed();
1106     }
1107 
1108     public interface LauncherOverlay {
1109 
1110         /**
1111          * Touch interaction leading to overscroll has begun
1112          */
onScrollInteractionBegin()1113         public void onScrollInteractionBegin();
1114 
1115         /**
1116          * Touch interaction related to overscroll has ended
1117          */
onScrollInteractionEnd()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          */
onScrollChange(float progress, boolean rtl)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          */
setOverlayCallbacks(LauncherOverlayCallbacks callbacks)1130         public void setOverlayCallbacks(LauncherOverlayCallbacks callbacks);
1131     }
1132 
1133     public interface LauncherSearchCallbacks {
1134         /**
1135          * Called when the search overlay is shown.
1136          */
onSearchOverlayOpened()1137         public void onSearchOverlayOpened();
1138 
1139         /**
1140          * Called when the search overlay is dismissed.
1141          */
onSearchOverlayClosed()1142         public void onSearchOverlayClosed();
1143     }
1144 
1145     public interface LauncherOverlayCallbacks {
onScrollChanged(float progress)1146         public void onScrollChanged(float progress);
1147     }
1148 
1149     class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
1150 
onScrollChanged(float progress)1151         public void onScrollChanged(float progress) {
1152             if (mWorkspace != null) {
1153                 mWorkspace.onOverlayScrollChanged(progress);
1154             }
1155         }
1156     }
1157 
hasSettings()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 
addToCustomContentPage(View customContent, CustomContentCallbacks callbacks, String description)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
getTopOffsetForCustomContent()1175     public int getTopOffsetForCustomContent() {
1176         return mWorkspace.getPaddingTop();
1177     }
1178 
1179     @Override
onRetainNonConfigurationInstance()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
onWindowFocusChanged(boolean hasFocus)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 
acceptFilter()1201     private boolean acceptFilter() {
1202         final InputMethodManager inputManager = (InputMethodManager)
1203                 getSystemService(Context.INPUT_METHOD_SERVICE);
1204         return !inputManager.isFullscreenMode();
1205     }
1206 
1207     @Override
onKeyDown(int keyCode, KeyEvent event)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
onKeyUp(int keyCode, KeyEvent event)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 
getTypedText()1257     private String getTypedText() {
1258         return mDefaultKeySsb.toString();
1259     }
1260 
1261     @Override
clearTypedText()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      */
restoreState(Bundle savedState)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      */
setupViews()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 
setupOverviewPanel()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      */
setAllAppsButton(View allAppsButton)1402     public void setAllAppsButton(View allAppsButton) {
1403         mAllAppsButton = allAppsButton;
1404     }
1405 
getStartViewForAllAppsRevealAnimation()1406     public View getStartViewForAllAppsRevealAnimation() {
1407         return FeatureFlags.NO_ALL_APPS_ICON ? mWorkspace.getPageIndicator() : mAllAppsButton;
1408     }
1409 
getWidgetsButton()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      */
createShortcut(ShortcutInfo info)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      */
createShortcut(ViewGroup parent, ShortcutInfo info)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      */
completeAddShortcut(Intent data, long container, long screenId, int cellX, int cellY, PendingRequestArgs args)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      */
completeAddAppWidget(int appWidgetId, ItemInfo itemInfo, AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo)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 
prepareAppWidget(AppWidgetHostView hostView, LauncherAppWidgetInfo item)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 
updateIconBadges(final Set<PackageUserKey> updatedBadges)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
onAttachedToWindow()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
onDetachedFromWindow()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 
onWindowVisibilityChanged(int visibility)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 
getDragLayer()1659     public DragLayer getDragLayer() {
1660         return mDragLayer;
1661     }
1662 
getAppsView()1663     public AllAppsContainerView getAppsView() {
1664         return mAppsView;
1665     }
1666 
getWidgetsView()1667     public WidgetsContainerView getWidgetsView() {
1668         return mWidgetsView;
1669     }
1670 
getWorkspace()1671     public Workspace getWorkspace() {
1672         return mWorkspace;
1673     }
1674 
getQsbContainer()1675     public View getQsbContainer() {
1676         return mQsbContainer;
1677     }
1678 
getHotseat()1679     public Hotseat getHotseat() {
1680         return mHotseat;
1681     }
1682 
getOverviewPanel()1683     public ViewGroup getOverviewPanel() {
1684         return mOverviewPanel;
1685     }
1686 
getDropTargetBar()1687     public DropTargetBar getDropTargetBar() {
1688         return mDropTargetBar;
1689     }
1690 
getAppWidgetHost()1691     public LauncherAppWidgetHost getAppWidgetHost() {
1692         return mAppWidgetHost;
1693     }
1694 
getModel()1695     public LauncherModel getModel() {
1696         return mModel;
1697     }
1698 
getModelWriter()1699     public ModelWriter getModelWriter() {
1700         return mModelWriter;
1701     }
1702 
getSharedPrefs()1703     public SharedPreferences getSharedPrefs() {
1704         return mSharedPrefs;
1705     }
1706 
1707     @Override
onNewIntent(Intent intent)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
onRestoreInstanceState(Bundle state)1817     public void onRestoreInstanceState(Bundle state) {
1818         super.onRestoreInstanceState(state);
1819         for (int page: mSynchronouslyBoundPages) {
1820             mWorkspace.restoreInstanceStateForChild(page);
1821         }
1822     }
1823 
1824     @Override
onSaveInstanceState(Bundle outState)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
onDestroy()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 
getAccessibilityDelegate()1888     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
1889         return mAccessibilityDelegate;
1890     }
1891 
getDragController()1892     public DragController getDragController() {
1893         return mDragController;
1894     }
1895 
1896     @Override
startActivityForResult(Intent intent, int requestCode, Bundle options)1897     public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
1898         super.startActivityForResult(intent, requestCode, options);
1899     }
1900 
1901     @Override
startIntentSenderForResult(IntentSender intent, int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options)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
startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, boolean globalSearch)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      */
startGlobalSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds)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 
isOnCustomContent()1979     public boolean isOnCustomContent() {
1980         return mWorkspace.isOnOrMovingToCustomContent();
1981     }
1982 
1983     @Override
onPrepareOptionsMenu(Menu menu)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
onSearchRequested()1993     public boolean onSearchRequested() {
1994         startSearch(null, false, null, true);
1995         // Use a custom animation for launching search
1996         return true;
1997     }
1998 
isWorkspaceLocked()1999     public boolean isWorkspaceLocked() {
2000         return mWorkspaceLoading || mPendingRequestArgs != null;
2001     }
2002 
isWorkspaceLoading()2003     public boolean isWorkspaceLoading() {
2004         return mWorkspaceLoading;
2005     }
2006 
setWorkspaceLoading(boolean value)2007     private void setWorkspaceLoading(boolean value) {
2008         boolean isLocked = isWorkspaceLocked();
2009         mWorkspaceLoading = value;
2010         if (isLocked != isWorkspaceLocked()) {
2011             onWorkspaceLockedChanged();
2012         }
2013     }
2014 
setWaitingForResult(PendingRequestArgs args)2015     public void setWaitingForResult(PendingRequestArgs args) {
2016         boolean isLocked = isWorkspaceLocked();
2017         mPendingRequestArgs = args;
2018         if (isLocked != isWorkspaceLocked()) {
2019             onWorkspaceLockedChanged();
2020         }
2021     }
2022 
onWorkspaceLockedChanged()2023     protected void onWorkspaceLockedChanged() {
2024         if (mLauncherCallbacks != null) {
2025             mLauncherCallbacks.onWorkspaceLockedChanged();
2026         }
2027     }
2028 
addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler)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 
addAppWidgetImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay)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 
moveToCustomContentScreen(boolean animate)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 
addPendingItem(PendingAddItemInfo info, long container, long screenId, int[] cell, int spanX, int spanY)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      */
processShortcutFromDrop(PendingAddShortcutInfo info)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      */
addAppWidgetFromDrop(PendingAddWidgetInfo info)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 
addFolder(CellLayout layout, long container, final long screenId, int cellX, int cellY)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      */
removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb)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      */
deleteWidgetInfo(final LauncherAppWidgetInfo widgetInfo)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
dispatchKeyEvent(KeyEvent event)2207     public boolean dispatchKeyEvent(KeyEvent event) {
2208         return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event);
2209     }
2210 
2211     @Override
onBackPressed()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      */
onClick(View v)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")
onTouch(View v, MotionEvent event)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      */
onClickPendingWidget(final PendingAppWidgetHostView v)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      */
onClickAllAppsButton(View v)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 
onLongClickAllAppsButton(View v)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 
onClickPendingAppItem(final View v, final String packageName, boolean downloadStarted)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 
startMarketIntentForPackage(View v, String packageName)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      */
onClickAppShortcut(final View v)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 
startAppShortcutOrInfoActivity(View v)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      */
onClickFolderIcon(View v)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      */
onClickAddWidgetButton(View view)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      */
onClickWallpaperPicker(View v)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      */
onClickSettingsButton(View v)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 
getHapticFeedbackTouchListener()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
onAccessibilityStateChanged(boolean enabled)2579     public void onAccessibilityStateChanged(boolean enabled) {
2580         mDragLayer.onAccessibilityStateChanged(enabled);
2581     }
2582 
onDragStarted()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      */
onInteractionEnd()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      */
onInteractionBegin()2610     protected void onInteractionBegin() {
2611         if (mLauncherCallbacks != null) {
2612             mLauncherCallbacks.onInteractionBegin();
2613         }
2614     }
2615 
2616     /** Updates the interaction state. */
updateInteraction(Workspace.State fromState, Workspace.State toState)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 
startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info)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)
getActivityLaunchOptions(View v)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 
getViewBounds(View v)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 
startActivitySafely(View v, Intent intent, ItemInfo item)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
dispatchTouchEvent(MotionEvent ev)2746     public boolean dispatchTouchEvent(MotionEvent ev) {
2747         mLastDispatchTouchEventX = ev.getX();
2748         return super.dispatchTouchEvent(ev);
2749     }
2750 
2751     @Override
onLongClick(View v)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 
isHotseatLayout(View layout)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      */
getCellLayout(long container, long screenId)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      */
isAllAppsVisible()2852     public boolean isAllAppsVisible() {
2853         return isAppsViewVisible();
2854     }
2855 
isAppsViewVisible()2856     public boolean isAppsViewVisible() {
2857         return (mState == State.APPS) || (mOnResumeState == State.APPS);
2858     }
2859 
isWidgetsViewVisible()2860     public boolean isWidgetsViewVisible() {
2861         return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
2862     }
2863 
2864     @Override
onTrimMemory(int level)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 
showWorkspace(boolean animated)2880     public boolean showWorkspace(boolean animated) {
2881         return showWorkspace(animated, null);
2882     }
2883 
showWorkspace(boolean animated, Runnable onCompleteRunnable)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      */
showOverviewMode(boolean animated)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      */
showOverviewMode(boolean animated, boolean requestButtonFocus)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 
setState(State state)2943     private void setState(State state) {
2944         this.mState = state;
2945         updateSoftInputMode();
2946     }
2947 
updateSoftInputMode()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      */
showAppsView(boolean animated, boolean updatePredictedApps, boolean focusSearchBar)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      */
showWidgetsView(boolean animated, boolean resetPageToZero)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.
showAppsOrWidgets(State toState, boolean animated, boolean focusSearchBar)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      */
startWorkspaceStateChangeAnimation(Workspace.State toState, boolean animated, AnimationLayerSet layerViews)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 
enterSpringLoadedDragMode()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 
exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay, final Runnable onCompleteRunnable)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 
isStateSpringLoaded()3081     boolean isStateSpringLoaded() {
3082         return mState == State.WORKSPACE_SPRING_LOADED || mState == State.APPS_SPRING_LOADED
3083                 || mState == State.WIDGETS_SPRING_LOADED;
3084     }
3085 
exitSpringLoadedDragMode()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      */
tryAndUpdatePredictedApps()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 
lockAllApps()3111     void lockAllApps() {
3112         // TODO
3113     }
3114 
unlockAllApps()3115     void unlockAllApps() {
3116         // TODO
3117     }
3118 
3119     @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)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      */
waitUntilResume(Runnable run, boolean deletePreviousRunnables)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 
waitUntilResume(Runnable run)3164     private boolean waitUntilResume(Runnable run) {
3165         return waitUntilResume(run, false);
3166     }
3167 
addOnResumeCallback(Runnable run)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
setLoadOnResume()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
getCurrentWorkspaceScreen()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
clearPendingBinds()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      */
startBinding()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
bindScreens(ArrayList<Long> orderedScreenIds)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 
bindAddScreens(ArrayList<Long> orderedScreenIds)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 
bindAppsAdded(final ArrayList<Long> newScreens, final ArrayList<ItemInfo> addNotAnimated, final ArrayList<ItemInfo> addAnimated, final ArrayList<AppInfo> addedApps)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
bindItems(final ArrayList<ItemInfo> items, final int start, final int end, final boolean forceAnimateIcons)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      */
bindAppWidget(final LauncherAppWidgetInfo item)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      */
completeRestoreAppWidget(int appWidgetId, int finalRestoreFlag)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 
onPageBoundSynchronously(int page)3609     public void onPageBoundSynchronously(int page) {
3610         mSynchronouslyBoundPages.add(page);
3611     }
3612 
3613     @Override
executeOnNextDraw(ViewOnDrawExecutor executor)3614     public void executeOnNextDraw(ViewOnDrawExecutor executor) {
3615         if (mPendingExecutor != null) {
3616             mPendingExecutor.markCompleted();
3617         }
3618         mPendingExecutor = executor;
3619         executor.attachTo(this);
3620     }
3621 
clearPendingExecutor(ViewOnDrawExecutor executor)3622     public void clearPendingExecutor(ViewOnDrawExecutor executor) {
3623         if (mPendingExecutor == executor) {
3624             mPendingExecutor = null;
3625         }
3626     }
3627 
3628     @Override
finishFirstPageBind(final ViewOnDrawExecutor executor)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      */
finishBindingItems()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 
canRunNewAppsAnimation()3693     private boolean canRunNewAppsAnimation() {
3694         long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
3695         return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
3696     }
3697 
createNewAppBounceAnimation(View v, int i)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 
useVerticalBarLayout()3706     public boolean useVerticalBarLayout() {
3707         return mDeviceProfile.isVerticalBarLayout();
3708     }
3709 
getSearchBarHeight()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      */
bindAllApplications(final ArrayList<AppInfo> apps)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
bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy)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      */
bindAppsUpdated(final ArrayList<AppInfo> apps)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
bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets)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
bindShortcutsChanged(final ArrayList<ShortcutInfo> updated, final ArrayList<ShortcutInfo> removed, final UserHandle user)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
bindRestoreItemsChange(final HashSet<ItemInfo> updates)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
bindWorkspaceComponentsRemoved( final HashSet<String> packageNames, final HashSet<ComponentName> components, final UserHandle user)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
bindAppInfosRemoved(final ArrayList<AppInfo> appInfos)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
bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> allWidgets)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 
getWidgetsForPackageUser(PackageUserKey packageUserKey)3934     public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
3935         return mWidgetsView.getWidgetsForPackageUser(packageUserKey);
3936     }
3937 
3938     @Override
notifyWidgetProvidersChanged()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      */
refreshAndBindWidgetsForPackageUser(@ullable PackageUserKey packageUser)3949     public void refreshAndBindWidgetsForPackageUser(@Nullable PackageUserKey packageUser) {
3950         mModel.refreshAndBindWidgetsAndShortcuts(this, mWidgetsView.isEmpty(), packageUser);
3951     }
3952 
lockScreenOrientation()3953     public void lockScreenOrientation() {
3954         if (mRotationEnabled) {
3955             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
3956         }
3957     }
3958 
unlockScreenOrientation(boolean immediate)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 
markAppsViewShown()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 
shouldShowDiscoveryBounce()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 
moveWorkspaceToDefaultScreen()3996     protected void moveWorkspaceToDefaultScreen() {
3997         mWorkspace.moveToDefaultScreen(false);
3998     }
3999 
4000     /**
4001      * $ adb shell dumpsys activity com.android.launcher3.Launcher [--all]
4002      */
4003     @Override
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)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)
onProvideKeyboardShortcuts( List<KeyboardShortcutGroup> data, Menu menu, int deviceId)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
onKeyShortcut(int keyCode, KeyEvent event)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 
getCustomAppWidget(String name)4108     public static CustomAppWidget getCustomAppWidget(String name) {
4109         return sCustomAppWidgets.get(name);
4110     }
4111 
getCustomAppWidgets()4112     public static HashMap<String, CustomAppWidget> getCustomAppWidgets() {
4113         return sCustomAppWidgets;
4114     }
4115 
getLauncher(Context context)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
onSharedPreferenceChanged( SharedPreferences sharedPreferences, String key)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