OSDN Git Service

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