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