OSDN Git Service

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