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