OSDN Git Service

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