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