OSDN Git Service

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