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