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