OSDN Git Service

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