OSDN Git Service

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