OSDN Git Service

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