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