1 
2 /*
3  * Copyright (C) 2008 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.launcher2;
19 
20 import android.accounts.Account;
21 import android.accounts.AccountManager;
22 import android.animation.Animator;
23 import android.animation.AnimatorListenerAdapter;
24 import android.animation.AnimatorSet;
25 import android.animation.ObjectAnimator;
26 import android.animation.PropertyValuesHolder;
27 import android.animation.ValueAnimator;
28 import android.animation.ValueAnimator.AnimatorUpdateListener;
29 import android.app.Activity;
30 import android.app.ActivityManager;
31 import android.app.ActivityOptions;
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.ContentResolver;
41 import android.content.Context;
42 import android.content.Intent;
43 import android.content.IntentFilter;
44 import android.content.SharedPreferences;
45 import android.content.pm.ActivityInfo;
46 import android.content.pm.LauncherApps;
47 import android.content.pm.PackageManager;
48 import android.content.pm.PackageManager.NameNotFoundException;
49 import android.content.res.Configuration;
50 import android.content.res.Resources;
51 import android.database.ContentObserver;
52 import android.graphics.Bitmap;
53 import android.graphics.Canvas;
54 import android.graphics.PorterDuff;
55 import android.graphics.Rect;
56 import android.graphics.drawable.Drawable;
57 import android.net.Uri;
58 import android.os.AsyncTask;
59 import android.os.Bundle;
60 import android.os.Environment;
61 import android.os.Handler;
62 import android.os.Message;
63 import android.os.StrictMode;
64 import android.os.SystemClock;
65 import android.os.UserHandle;
66 import android.os.UserManager;
67 import android.provider.Settings;
68 import android.speech.RecognizerIntent;
69 import android.text.Selection;
70 import android.text.SpannableStringBuilder;
71 import android.text.TextUtils;
72 import android.text.method.TextKeyListener;
73 import android.util.Log;
74 import android.view.Display;
75 import android.view.HapticFeedbackConstants;
76 import android.view.KeyEvent;
77 import android.view.LayoutInflater;
78 import android.view.Menu;
79 import android.view.MenuItem;
80 import android.view.MotionEvent;
81 import android.view.Surface;
82 import android.view.View;
83 import android.view.View.OnLongClickListener;
84 import android.view.ViewGroup;
85 import android.view.ViewTreeObserver;
86 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
87 import android.view.WindowManager;
88 import android.view.accessibility.AccessibilityEvent;
89 import android.view.animation.AccelerateDecelerateInterpolator;
90 import android.view.animation.AccelerateInterpolator;
91 import android.view.animation.DecelerateInterpolator;
92 import android.view.inputmethod.InputMethodManager;
93 import android.widget.Advanceable;
94 import android.widget.ImageView;
95 import android.widget.TextView;
96 import android.widget.Toast;
97 
98 import com.android.common.Search;
99 import com.android.launcher.R;
100 import com.android.launcher2.DropTarget.DragObject;
101 
102 import java.io.DataInputStream;
103 import java.io.DataOutputStream;
104 import java.io.FileDescriptor;
105 import java.io.FileNotFoundException;
106 import java.io.IOException;
107 import java.io.PrintWriter;
108 import java.util.ArrayList;
109 import java.util.Collection;
110 import java.util.Collections;
111 import java.util.Comparator;
112 import java.util.HashMap;
113 import java.util.HashSet;
114 import java.util.List;
115 import java.util.Set;
116 
117 /**
118  * Default launcher application.
119  */
120 public final class Launcher extends Activity
121         implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
122                    View.OnTouchListener {
123     static final String TAG = "Launcher";
124     static final boolean LOGD = false;
125 
126     static final boolean PROFILE_STARTUP = false;
127     static final boolean DEBUG_WIDGETS = false;
128     static final boolean DEBUG_STRICT_MODE = false;
129     static final boolean DEBUG_RESUME_TIME = false;
130 
131     private static final int MENU_GROUP_WALLPAPER = 1;
132     private static final int MENU_WALLPAPER_SETTINGS = Menu.FIRST + 1;
133     private static final int MENU_MANAGE_APPS = MENU_WALLPAPER_SETTINGS + 1;
134     private static final int MENU_SYSTEM_SETTINGS = MENU_MANAGE_APPS + 1;
135     private static final int MENU_HELP = MENU_SYSTEM_SETTINGS + 1;
136 
137     private static final int REQUEST_CREATE_SHORTCUT = 1;
138     private static final int REQUEST_CREATE_APPWIDGET = 5;
139     private static final int REQUEST_PICK_APPLICATION = 6;
140     private static final int REQUEST_PICK_SHORTCUT = 7;
141     private static final int REQUEST_PICK_APPWIDGET = 9;
142     private static final int REQUEST_PICK_WALLPAPER = 10;
143 
144     private static final int REQUEST_BIND_APPWIDGET = 11;
145 
146     static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
147 
148     static final int SCREEN_COUNT = 5;
149     static final int DEFAULT_SCREEN = 2;
150 
151     private static final String PREFERENCES = "launcher.preferences";
152     // To turn on these properties, type
153     // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
154     static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
155     static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
156 
157     // The Intent extra that defines whether to ignore the launch animation
158     static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
159             "com.android.launcher.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
160 
161     // Type: int
162     private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
163     // Type: int
164     private static final String RUNTIME_STATE = "launcher.state";
165     // Type: int
166     private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
167     // Type: int
168     private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
169     // Type: int
170     private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
171     // Type: int
172     private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
173     // Type: boolean
174     private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
175     // Type: long
176     private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
177     // Type: int
178     private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
179     // Type: int
180     private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
181     // Type: parcelable
182     private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
183     // Type: parcelable
184     private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
185 
186     private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
187     private static final String TOOLBAR_SEARCH_ICON_METADATA_NAME =
188             "com.android.launcher.toolbar_search_icon";
189     private static final String TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME =
190             "com.android.launcher.toolbar_voice_search_icon";
191 
192     /** The different states that Launcher can be in. */
193     private enum State { NONE, WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED };
194     private State mState = State.WORKSPACE;
195     private AnimatorSet mStateAnimation;
196     private AnimatorSet mDividerAnimator;
197 
198     static final int APPWIDGET_HOST_ID = 1024;
199     private static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
200     private static final int EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT = 600;
201     private static final int SHOW_CLING_DURATION = 550;
202     private static final int DISMISS_CLING_DURATION = 250;
203 
204     private static final Object sLock = new Object();
205     private static int sScreen = DEFAULT_SCREEN;
206 
207     // How long to wait before the new-shortcut animation automatically pans the workspace
208     private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 10;
209 
210     private final BroadcastReceiver mCloseSystemDialogsReceiver
211             = new CloseSystemDialogsIntentReceiver();
212     private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
213 
214     private LayoutInflater mInflater;
215 
216     private Workspace mWorkspace;
217     private View mQsbDivider;
218     private View mDockDivider;
219     private View mLauncherView;
220     private DragLayer mDragLayer;
221     private DragController mDragController;
222 
223     private AppWidgetManager mAppWidgetManager;
224     private LauncherAppWidgetHost mAppWidgetHost;
225 
226     private ItemInfo mPendingAddInfo = new ItemInfo();
227     private AppWidgetProviderInfo mPendingAddWidgetInfo;
228     private int mPendingAddWidgetId = -1;
229 
230     private int[] mTmpAddItemCellCoordinates = new int[2];
231 
232     private FolderInfo mFolderInfo;
233 
234     private Hotseat mHotseat;
235     private View mAllAppsButton;
236 
237     private SearchDropTargetBar mSearchDropTargetBar;
238     private AppsCustomizeTabHost mAppsCustomizeTabHost;
239     private AppsCustomizePagedView mAppsCustomizeContent;
240     private boolean mAutoAdvanceRunning = false;
241 
242     private Bundle mSavedState;
243     // We set the state in both onCreate and then onNewIntent in some cases, which causes both
244     // scroll issues (because the workspace may not have been measured yet) and extra work.
245     // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
246     private State mOnResumeState = State.NONE;
247 
248     private SpannableStringBuilder mDefaultKeySsb = null;
249 
250     private boolean mWorkspaceLoading = true;
251 
252     private boolean mPaused = true;
253     private boolean mRestoring;
254     private boolean mWaitingForResult;
255     private boolean mOnResumeNeedsLoad;
256 
257     private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
258 
259     // Keep track of whether the user has left launcher
260     private static boolean sPausedFromUserAction = false;
261 
262     private Bundle mSavedInstanceState;
263 
264     private LauncherModel mModel;
265     private IconCache mIconCache;
266     private boolean mUserPresent = true;
267     private boolean mVisible = false;
268     private boolean mAttached = false;
269 
270     private static LocaleConfiguration sLocaleConfiguration = null;
271 
272     private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
273 
274     private Intent mAppMarketIntent = null;
275 
276     // Related to the auto-advancing of widgets
277     private final int ADVANCE_MSG = 1;
278     private final int mAdvanceInterval = 20000;
279     private final int mAdvanceStagger = 250;
280     private long mAutoAdvanceSentTime;
281     private long mAutoAdvanceTimeLeft = -1;
282     private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
283         new HashMap<View, AppWidgetProviderInfo>();
284 
285     // Determines how long to wait after a rotation before restoring the screen orientation to
286     // match the sensor state.
287     private final int mRestoreScreenOrientationDelay = 500;
288 
289     // External icons saved in case of resource changes, orientation, etc.
290     private static Drawable.ConstantState[] sGlobalSearchIcon = new Drawable.ConstantState[2];
291     private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2];
292     private static Drawable.ConstantState[] sAppMarketIcon = new Drawable.ConstantState[2];
293 
294     private Drawable mWorkspaceBackgroundDrawable;
295 
296     private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
297 
298     static final ArrayList<String> sDumpLogs = new ArrayList<String>();
299 
300     // We only want to get the SharedPreferences once since it does an FS stat each time we get
301     // it from the context.
302     private SharedPreferences mSharedPrefs;
303 
304     // Holds the page that we need to animate to, and the icon views that we need to animate up
305     // when we scroll to that page on resume.
306     private int mNewShortcutAnimatePage = -1;
307     private ArrayList<View> mNewShortcutAnimateViews = new ArrayList<View>();
308     private ImageView mFolderIconImageView;
309     private Bitmap mFolderIconBitmap;
310     private Canvas mFolderIconCanvas;
311     private Rect mRectForFolderAnimation = new Rect();
312 
313     private BubbleTextView mWaitingForResume;
314 
315     private HideFromAccessibilityHelper mHideFromAccessibilityHelper
316         = new HideFromAccessibilityHelper();
317 
318     private Runnable mBuildLayersRunnable = new Runnable() {
319         public void run() {
320             if (mWorkspace != null) {
321                 mWorkspace.buildPageHardwareLayers();
322             }
323         }
324     };
325 
326     private static ArrayList<PendingAddArguments> sPendingAddList
327             = new ArrayList<PendingAddArguments>();
328 
329     private static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY);
330 
331     private static class PendingAddArguments {
332         int requestCode;
333         Intent intent;
334         long container;
335         int screen;
336         int cellX;
337         int cellY;
338     }
339 
isPropertyEnabled(String propertyName)340     private static boolean isPropertyEnabled(String propertyName) {
341         return Log.isLoggable(propertyName, Log.VERBOSE);
342     }
343 
344     @Override
onCreate(Bundle savedInstanceState)345     protected void onCreate(Bundle savedInstanceState) {
346         if (DEBUG_STRICT_MODE) {
347             StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
348                     .detectDiskReads()
349                     .detectDiskWrites()
350                     .detectNetwork()   // or .detectAll() for all detectable problems
351                     .penaltyLog()
352                     .build());
353             StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
354                     .detectLeakedSqlLiteObjects()
355                     .detectLeakedClosableObjects()
356                     .penaltyLog()
357                     .penaltyDeath()
358                     .build());
359         }
360 
361         super.onCreate(savedInstanceState);
362         LauncherApplication app = ((LauncherApplication)getApplication());
363         mSharedPrefs = getSharedPreferences(LauncherApplication.getSharedPreferencesKey(),
364                 Context.MODE_PRIVATE);
365         mModel = app.setLauncher(this);
366         mIconCache = app.getIconCache();
367         mDragController = new DragController(this);
368         mInflater = getLayoutInflater();
369 
370         mAppWidgetManager = AppWidgetManager.getInstance(this);
371         mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
372         mAppWidgetHost.startListening();
373 
374         // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
375         // this also ensures that any synchronous binding below doesn't re-trigger another
376         // LauncherModel load.
377         mPaused = false;
378 
379         if (PROFILE_STARTUP) {
380             android.os.Debug.startMethodTracing(
381                     Environment.getExternalStorageDirectory() + "/launcher");
382         }
383 
384         checkForLocaleChange();
385         setContentView(R.layout.launcher);
386         setupViews();
387         showFirstRunWorkspaceCling();
388 
389         registerContentObservers();
390 
391         lockAllApps();
392 
393         mSavedState = savedInstanceState;
394         restoreState(mSavedState);
395 
396         // Update customization drawer _after_ restoring the states
397         if (mAppsCustomizeContent != null) {
398             mAppsCustomizeContent.onPackagesUpdated(
399                 LauncherModel.getSortedWidgetsAndShortcuts(this));
400         }
401 
402         if (PROFILE_STARTUP) {
403             android.os.Debug.stopMethodTracing();
404         }
405 
406         if (!mRestoring) {
407             if (sPausedFromUserAction) {
408                 // If the user leaves launcher, then we should just load items asynchronously when
409                 // they return.
410                 mModel.startLoader(true, -1);
411             } else {
412                 // We only load the page synchronously if the user rotates (or triggers a
413                 // configuration change) while launcher is in the foreground
414                 mModel.startLoader(true, mWorkspace.getCurrentPage());
415             }
416         }
417 
418         if (!mModel.isAllAppsLoaded()) {
419             ViewGroup appsCustomizeContentParent = (ViewGroup) mAppsCustomizeContent.getParent();
420             mInflater.inflate(R.layout.apps_customize_progressbar, appsCustomizeContentParent);
421         }
422 
423         // For handling default keys
424         mDefaultKeySsb = new SpannableStringBuilder();
425         Selection.setSelection(mDefaultKeySsb, 0);
426 
427         IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
428         registerReceiver(mCloseSystemDialogsReceiver, filter);
429 
430         updateGlobalIcons();
431 
432         // On large interfaces, we want the screen to auto-rotate based on the current orientation
433         unlockScreenOrientation(true);
434     }
435 
onUserLeaveHint()436     protected void onUserLeaveHint() {
437         super.onUserLeaveHint();
438         sPausedFromUserAction = true;
439     }
440 
updateGlobalIcons()441     private void updateGlobalIcons() {
442         boolean searchVisible = false;
443         boolean voiceVisible = false;
444         // If we have a saved version of these external icons, we load them up immediately
445         int coi = getCurrentOrientationIndexForGlobalIcons();
446         if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null ||
447                 sAppMarketIcon[coi] == null) {
448             updateAppMarketIcon();
449             searchVisible = updateGlobalSearchIcon();
450             voiceVisible = updateVoiceSearchIcon(searchVisible);
451         }
452         if (sGlobalSearchIcon[coi] != null) {
453              updateGlobalSearchIcon(sGlobalSearchIcon[coi]);
454              searchVisible = true;
455         }
456         if (sVoiceSearchIcon[coi] != null) {
457             updateVoiceSearchIcon(sVoiceSearchIcon[coi]);
458             voiceVisible = true;
459         }
460         if (sAppMarketIcon[coi] != null) {
461             updateAppMarketIcon(sAppMarketIcon[coi]);
462         }
463         if (mSearchDropTargetBar != null) {
464             mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
465         }
466     }
467 
checkForLocaleChange()468     private void checkForLocaleChange() {
469         if (sLocaleConfiguration == null) {
470             new AsyncTask<Void, Void, LocaleConfiguration>() {
471                 @Override
472                 protected LocaleConfiguration doInBackground(Void... unused) {
473                     LocaleConfiguration localeConfiguration = new LocaleConfiguration();
474                     readConfiguration(Launcher.this, localeConfiguration);
475                     return localeConfiguration;
476                 }
477 
478                 @Override
479                 protected void onPostExecute(LocaleConfiguration result) {
480                     sLocaleConfiguration = result;
481                     checkForLocaleChange();  // recursive, but now with a locale configuration
482                 }
483             }.execute();
484             return;
485         }
486 
487         final Configuration configuration = getResources().getConfiguration();
488 
489         final String previousLocale = sLocaleConfiguration.locale;
490         final String locale = configuration.locale.toString();
491 
492         final int previousMcc = sLocaleConfiguration.mcc;
493         final int mcc = configuration.mcc;
494 
495         final int previousMnc = sLocaleConfiguration.mnc;
496         final int mnc = configuration.mnc;
497 
498         boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;
499 
500         if (localeChanged) {
501             sLocaleConfiguration.locale = locale;
502             sLocaleConfiguration.mcc = mcc;
503             sLocaleConfiguration.mnc = mnc;
504 
505             mIconCache.flush();
506 
507             final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
508             new Thread("WriteLocaleConfiguration") {
509                 @Override
510                 public void run() {
511                     writeConfiguration(Launcher.this, localeConfiguration);
512                 }
513             }.start();
514         }
515     }
516 
517     private static class LocaleConfiguration {
518         public String locale;
519         public int mcc = -1;
520         public int mnc = -1;
521     }
522 
readConfiguration(Context context, LocaleConfiguration configuration)523     private static void readConfiguration(Context context, LocaleConfiguration configuration) {
524         DataInputStream in = null;
525         try {
526             in = new DataInputStream(context.openFileInput(PREFERENCES));
527             configuration.locale = in.readUTF();
528             configuration.mcc = in.readInt();
529             configuration.mnc = in.readInt();
530         } catch (FileNotFoundException e) {
531             // Ignore
532         } catch (IOException e) {
533             // Ignore
534         } finally {
535             if (in != null) {
536                 try {
537                     in.close();
538                 } catch (IOException e) {
539                     // Ignore
540                 }
541             }
542         }
543     }
544 
writeConfiguration(Context context, LocaleConfiguration configuration)545     private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
546         DataOutputStream out = null;
547         try {
548             out = new DataOutputStream(context.openFileOutput(PREFERENCES, MODE_PRIVATE));
549             out.writeUTF(configuration.locale);
550             out.writeInt(configuration.mcc);
551             out.writeInt(configuration.mnc);
552             out.flush();
553         } catch (FileNotFoundException e) {
554             // Ignore
555         } catch (IOException e) {
556             //noinspection ResultOfMethodCallIgnored
557             context.getFileStreamPath(PREFERENCES).delete();
558         } finally {
559             if (out != null) {
560                 try {
561                     out.close();
562                 } catch (IOException e) {
563                     // Ignore
564                 }
565             }
566         }
567     }
568 
getDragLayer()569     public DragLayer getDragLayer() {
570         return mDragLayer;
571     }
572 
isDraggingEnabled()573     boolean isDraggingEnabled() {
574         // We prevent dragging when we are loading the workspace as it is possible to pick up a view
575         // that is subsequently removed from the workspace in startBinding().
576         return !mModel.isLoadingWorkspace();
577     }
578 
getScreen()579     static int getScreen() {
580         synchronized (sLock) {
581             return sScreen;
582         }
583     }
584 
setScreen(int screen)585     static void setScreen(int screen) {
586         synchronized (sLock) {
587             sScreen = screen;
588         }
589     }
590 
591     /**
592      * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
593      * a configuration step, this allows the proper animations to run after other transitions.
594      */
completeAdd(PendingAddArguments args)595     private boolean completeAdd(PendingAddArguments args) {
596         boolean result = false;
597         switch (args.requestCode) {
598             case REQUEST_PICK_APPLICATION:
599                 completeAddApplication(args.intent, args.container, args.screen, args.cellX,
600                         args.cellY);
601                 break;
602             case REQUEST_PICK_SHORTCUT:
603                 processShortcut(args.intent);
604                 break;
605             case REQUEST_CREATE_SHORTCUT:
606                 completeAddShortcut(args.intent, args.container, args.screen, args.cellX,
607                         args.cellY);
608                 result = true;
609                 break;
610             case REQUEST_CREATE_APPWIDGET:
611                 int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
612                 completeAddAppWidget(appWidgetId, args.container, args.screen, null, null);
613                 result = true;
614                 break;
615             case REQUEST_PICK_WALLPAPER:
616                 // We just wanted the activity result here so we can clear mWaitingForResult
617                 break;
618         }
619         // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
620         // if you turned the screen off and then back while in All Apps, Launcher would not
621         // return to the workspace. Clearing mAddInfo.container here fixes this issue
622         resetAddInfo();
623         return result;
624     }
625 
626     @Override
onActivityResult( final int requestCode, final int resultCode, final Intent data)627     protected void onActivityResult(
628             final int requestCode, final int resultCode, final Intent data) {
629 
630         int pendingAddWidgetId = mPendingAddWidgetId;
631         mPendingAddWidgetId = -1;
632 
633         if (requestCode == REQUEST_BIND_APPWIDGET) {
634             int appWidgetId = data != null ?
635                     data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
636             if (resultCode == RESULT_CANCELED) {
637                 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
638             } else if (resultCode == RESULT_OK) {
639                 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null, mPendingAddWidgetInfo);
640             }
641             return;
642         }
643         boolean delayExitSpringLoadedMode = false;
644         boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
645                 requestCode == REQUEST_CREATE_APPWIDGET);
646         mWaitingForResult = false;
647 
648         // We have special handling for widgets
649         if (isWidgetDrop) {
650             final int appWidgetId;
651             int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
652                     : -1;
653             if (widgetId < 0) {
654                 appWidgetId = pendingAddWidgetId;
655             } else {
656                 appWidgetId = widgetId;
657             }
658 
659             if (appWidgetId < 0) {
660                 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not returned from the \\" +
661                         "widget configuration activity.");
662                 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
663             } else {
664                 completeTwoStageWidgetDrop(resultCode, appWidgetId);
665             }
666             return;
667         }
668 
669         // The pattern used here is that a user PICKs a specific application,
670         // which, depending on the target, might need to CREATE the actual target.
671 
672         // For example, the user would PICK_SHORTCUT for "Music playlist", and we
673         // launch over to the Music app to actually CREATE_SHORTCUT.
674         if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
675             final PendingAddArguments args = new PendingAddArguments();
676             args.requestCode = requestCode;
677             args.intent = data;
678             args.container = mPendingAddInfo.container;
679             args.screen = mPendingAddInfo.screen;
680             args.cellX = mPendingAddInfo.cellX;
681             args.cellY = mPendingAddInfo.cellY;
682             if (isWorkspaceLocked()) {
683                 sPendingAddList.add(args);
684             } else {
685                 delayExitSpringLoadedMode = completeAdd(args);
686             }
687         }
688         mDragLayer.clearAnimatedView();
689         // Exit spring loaded mode if necessary after cancelling the configuration of a widget
690         exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode,
691                 null);
692     }
693 
completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId)694     private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
695         CellLayout cellLayout =
696                 (CellLayout) mWorkspace.getChildAt(mPendingAddInfo.screen);
697         Runnable onCompleteRunnable = null;
698         int animationType = 0;
699 
700         AppWidgetHostView boundWidget = null;
701         if (resultCode == RESULT_OK) {
702             animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
703             final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
704                     mPendingAddWidgetInfo);
705             boundWidget = layout;
706             onCompleteRunnable = new Runnable() {
707                 @Override
708                 public void run() {
709                     completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
710                             mPendingAddInfo.screen, layout, null);
711                     exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
712                             null);
713                 }
714             };
715         } else if (resultCode == RESULT_CANCELED) {
716             mAppWidgetHost.deleteAppWidgetId(appWidgetId);
717             animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
718             onCompleteRunnable = new Runnable() {
719                 @Override
720                 public void run() {
721                     exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
722                             null);
723                 }
724             };
725         }
726         if (mDragLayer.getAnimatedView() != null) {
727             mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
728                     (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
729                     animationType, boundWidget, true);
730         } else {
731             // The animated view may be null in the case of a rotation during widget configuration
732             onCompleteRunnable.run();
733         }
734     }
735 
736     @Override
onStop()737     protected void onStop() {
738         super.onStop();
739         FirstFrameAnimatorHelper.setIsVisible(false);
740     }
741 
742     @Override
onStart()743     protected void onStart() {
744         super.onStart();
745         FirstFrameAnimatorHelper.setIsVisible(true);
746     }
747 
748     @Override
onResume()749     protected void onResume() {
750         long startTime = 0;
751         if (DEBUG_RESUME_TIME) {
752             startTime = System.currentTimeMillis();
753         }
754         super.onResume();
755 
756         // Restore the previous launcher state
757         if (mOnResumeState == State.WORKSPACE) {
758             showWorkspace(false);
759         } else if (mOnResumeState == State.APPS_CUSTOMIZE) {
760             showAllApps(false);
761         }
762         mOnResumeState = State.NONE;
763 
764         // Background was set to gradient in onPause(), restore to black if in all apps.
765         setWorkspaceBackground(mState == State.WORKSPACE);
766 
767         // Process any items that were added while Launcher was away
768         InstallShortcutReceiver.flushInstallQueue(this);
769 
770         mPaused = false;
771         sPausedFromUserAction = false;
772         if (mRestoring || mOnResumeNeedsLoad) {
773             mWorkspaceLoading = true;
774             mModel.startLoader(true, -1);
775             mRestoring = false;
776             mOnResumeNeedsLoad = false;
777         }
778         if (mOnResumeCallbacks.size() > 0) {
779             // We might have postponed some bind calls until onResume (see waitUntilResume) --
780             // execute them here
781             long startTimeCallbacks = 0;
782             if (DEBUG_RESUME_TIME) {
783                 startTimeCallbacks = System.currentTimeMillis();
784             }
785 
786             if (mAppsCustomizeContent != null) {
787                 mAppsCustomizeContent.setBulkBind(true);
788             }
789             for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
790                 mOnResumeCallbacks.get(i).run();
791             }
792             if (mAppsCustomizeContent != null) {
793                 mAppsCustomizeContent.setBulkBind(false);
794             }
795             mOnResumeCallbacks.clear();
796             if (DEBUG_RESUME_TIME) {
797                 Log.d(TAG, "Time spent processing callbacks in onResume: " +
798                     (System.currentTimeMillis() - startTimeCallbacks));
799             }
800         }
801 
802         // Reset the pressed state of icons that were locked in the press state while activities
803         // were launching
804         if (mWaitingForResume != null) {
805             // Resets the previous workspace icon press state
806             mWaitingForResume.setStayPressed(false);
807         }
808         if (mAppsCustomizeContent != null) {
809             // Resets the previous all apps icon press state
810             mAppsCustomizeContent.resetDrawableState();
811         }
812         // It is possible that widgets can receive updates while launcher is not in the foreground.
813         // Consequently, the widgets will be inflated in the orientation of the foreground activity
814         // (framework issue). On resuming, we ensure that any widgets are inflated for the current
815         // orientation.
816         getWorkspace().reinflateWidgetsIfNecessary();
817 
818         // Again, as with the above scenario, it's possible that one or more of the global icons
819         // were updated in the wrong orientation.
820         updateGlobalIcons();
821         if (DEBUG_RESUME_TIME) {
822             Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
823         }
824     }
825 
826     @Override
onPause()827     protected void onPause() {
828         // NOTE: We want all transitions from launcher to act as if the wallpaper were enabled
829         // to be consistent.  So re-enable the flag here, and we will re-disable it as necessary
830         // when Launcher resumes and we are still in AllApps.
831         updateWallpaperVisibility(true);
832 
833         super.onPause();
834         mPaused = true;
835         mDragController.cancelDrag();
836         mDragController.resetLastGestureUpTime();
837     }
838 
839     @Override
onRetainNonConfigurationInstance()840     public Object onRetainNonConfigurationInstance() {
841         // Flag the loader to stop early before switching
842         mModel.stopLoader();
843         if (mAppsCustomizeContent != null) {
844             mAppsCustomizeContent.surrender();
845         }
846         return Boolean.TRUE;
847     }
848 
849     // We can't hide the IME if it was forced open.  So don't bother
850     /*
851     @Override
852     public void onWindowFocusChanged(boolean hasFocus) {
853         super.onWindowFocusChanged(hasFocus);
854 
855         if (hasFocus) {
856             final InputMethodManager inputManager = (InputMethodManager)
857                     getSystemService(Context.INPUT_METHOD_SERVICE);
858             WindowManager.LayoutParams lp = getWindow().getAttributes();
859             inputManager.hideSoftInputFromWindow(lp.token, 0, new android.os.ResultReceiver(new
860                         android.os.Handler()) {
861                         protected void onReceiveResult(int resultCode, Bundle resultData) {
862                             Log.d(TAG, "ResultReceiver got resultCode=" + resultCode);
863                         }
864                     });
865             Log.d(TAG, "called hideSoftInputFromWindow from onWindowFocusChanged");
866         }
867     }
868     */
869 
acceptFilter()870     private boolean acceptFilter() {
871         final InputMethodManager inputManager = (InputMethodManager)
872                 getSystemService(Context.INPUT_METHOD_SERVICE);
873         return !inputManager.isFullscreenMode();
874     }
875 
876     @Override
onKeyDown(int keyCode, KeyEvent event)877     public boolean onKeyDown(int keyCode, KeyEvent event) {
878         final int uniChar = event.getUnicodeChar();
879         final boolean handled = super.onKeyDown(keyCode, event);
880         final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
881         if (!handled && acceptFilter() && isKeyNotWhitespace) {
882             boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
883                     keyCode, event);
884             if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
885                 // something usable has been typed - start a search
886                 // the typed text will be retrieved and cleared by
887                 // showSearchDialog()
888                 // If there are multiple keystrokes before the search dialog takes focus,
889                 // onSearchRequested() will be called for every keystroke,
890                 // but it is idempotent, so it's fine.
891                 return onSearchRequested();
892             }
893         }
894 
895         // Eat the long press event so the keyboard doesn't come up.
896         if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
897             return true;
898         }
899 
900         return handled;
901     }
902 
getTypedText()903     private String getTypedText() {
904         return mDefaultKeySsb.toString();
905     }
906 
clearTypedText()907     private void clearTypedText() {
908         mDefaultKeySsb.clear();
909         mDefaultKeySsb.clearSpans();
910         Selection.setSelection(mDefaultKeySsb, 0);
911     }
912 
913     /**
914      * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
915      * State
916      */
intToState(int stateOrdinal)917     private static State intToState(int stateOrdinal) {
918         State state = State.WORKSPACE;
919         final State[] stateValues = State.values();
920         for (int i = 0; i < stateValues.length; i++) {
921             if (stateValues[i].ordinal() == stateOrdinal) {
922                 state = stateValues[i];
923                 break;
924             }
925         }
926         return state;
927     }
928 
929     /**
930      * Restores the previous state, if it exists.
931      *
932      * @param savedState The previous state.
933      */
restoreState(Bundle savedState)934     private void restoreState(Bundle savedState) {
935         if (savedState == null) {
936             return;
937         }
938 
939         State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
940         if (state == State.APPS_CUSTOMIZE) {
941             mOnResumeState = State.APPS_CUSTOMIZE;
942         }
943 
944         int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
945         if (currentScreen > -1) {
946             mWorkspace.setCurrentPage(currentScreen);
947         }
948 
949         final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
950         final int pendingAddScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
951 
952         if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
953             mPendingAddInfo.container = pendingAddContainer;
954             mPendingAddInfo.screen = pendingAddScreen;
955             mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
956             mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
957             mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
958             mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
959             mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
960             mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
961             mWaitingForResult = true;
962             mRestoring = true;
963         }
964 
965 
966         boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
967         if (renameFolder) {
968             long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
969             mFolderInfo = mModel.getFolderById(this, sFolders, id);
970             mRestoring = true;
971         }
972 
973 
974         // Restore the AppsCustomize tab
975         if (mAppsCustomizeTabHost != null) {
976             String curTab = savedState.getString("apps_customize_currentTab");
977             if (curTab != null) {
978                 mAppsCustomizeTabHost.setContentTypeImmediate(
979                         mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
980                 mAppsCustomizeContent.loadAssociatedPages(
981                         mAppsCustomizeContent.getCurrentPage());
982             }
983 
984             int currentIndex = savedState.getInt("apps_customize_currentIndex");
985             mAppsCustomizeContent.restorePageForIndex(currentIndex);
986         }
987     }
988 
989     /**
990      * Finds all the views we need and configure them properly.
991      */
setupViews()992     private void setupViews() {
993         final DragController dragController = mDragController;
994 
995         mLauncherView = findViewById(R.id.launcher);
996         mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
997         mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
998         mQsbDivider = findViewById(R.id.qsb_divider);
999         mDockDivider = findViewById(R.id.dock_divider);
1000 
1001         mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1002         mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
1003 
1004         // Setup the drag layer
1005         mDragLayer.setup(this, dragController);
1006 
1007         // Setup the hotseat
1008         mHotseat = (Hotseat) findViewById(R.id.hotseat);
1009         if (mHotseat != null) {
1010             mHotseat.setup(this);
1011         }
1012 
1013         // Setup the workspace
1014         mWorkspace.setHapticFeedbackEnabled(false);
1015         mWorkspace.setOnLongClickListener(this);
1016         mWorkspace.setup(dragController);
1017         dragController.addDragListener(mWorkspace);
1018 
1019         // Get the search/delete bar
1020         mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.qsb_bar);
1021 
1022         // Setup AppsCustomize
1023         mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane);
1024         mAppsCustomizeContent = (AppsCustomizePagedView)
1025                 mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);
1026         mAppsCustomizeContent.setup(this, dragController);
1027 
1028         // Setup the drag controller (drop targets have to be added in reverse order in priority)
1029         dragController.setDragScoller(mWorkspace);
1030         dragController.setScrollView(mDragLayer);
1031         dragController.setMoveTarget(mWorkspace);
1032         dragController.addDropTarget(mWorkspace);
1033         if (mSearchDropTargetBar != null) {
1034             mSearchDropTargetBar.setup(this, dragController);
1035         }
1036     }
1037 
1038     /**
1039      * Creates a view representing a shortcut.
1040      *
1041      * @param info The data structure describing the shortcut.
1042      *
1043      * @return A View inflated from R.layout.application.
1044      */
createShortcut(ShortcutInfo info)1045     View createShortcut(ShortcutInfo info) {
1046         return createShortcut(R.layout.application,
1047                 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1048     }
1049 
1050     /**
1051      * Creates a view representing a shortcut inflated from the specified resource.
1052      *
1053      * @param layoutResId The id of the XML layout used to create the shortcut.
1054      * @param parent The group the shortcut belongs to.
1055      * @param info The data structure describing the shortcut.
1056      *
1057      * @return A View inflated from layoutResId.
1058      */
createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info)1059     View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
1060         BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
1061         favorite.applyFromShortcutInfo(info, mIconCache);
1062         favorite.setOnClickListener(this);
1063         return favorite;
1064     }
1065 
1066     /**
1067      * Add an application shortcut to the workspace.
1068      *
1069      * @param data The intent describing the application.
1070      * @param cellInfo The position on screen where to create the shortcut.
1071      */
completeAddApplication(Intent data, long container, int screen, int cellX, int cellY)1072     void completeAddApplication(Intent data, long container, int screen, int cellX, int cellY) {
1073         final int[] cellXY = mTmpAddItemCellCoordinates;
1074         final CellLayout layout = getCellLayout(container, screen);
1075 
1076         // First we check if we already know the exact location where we want to add this item.
1077         if (cellX >= 0 && cellY >= 0) {
1078             cellXY[0] = cellX;
1079             cellXY[1] = cellY;
1080         } else if (!layout.findCellForSpan(cellXY, 1, 1)) {
1081             showOutOfSpaceMessage(isHotseatLayout(layout));
1082             return;
1083         }
1084 
1085         final ShortcutInfo info = mModel.getShortcutInfo(getPackageManager(), data,
1086                 android.os.Process.myUserHandle(), this);
1087 
1088         if (info != null) {
1089             // Necessary flags are added when the activity is launched via
1090             // LauncherApps
1091             info.setActivity(data);
1092             info.container = ItemInfo.NO_ID;
1093             mWorkspace.addApplicationShortcut(info, layout, container, screen, cellXY[0], cellXY[1],
1094                     isWorkspaceLocked(), cellX, cellY);
1095         } else {
1096             Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data);
1097         }
1098     }
1099 
1100     /**
1101      * Add a shortcut to the workspace.
1102      *
1103      * @param data The intent describing the shortcut.
1104      * @param cellInfo The position on screen where to create the shortcut.
1105      */
completeAddShortcut(Intent data, long container, int screen, int cellX, int cellY)1106     private void completeAddShortcut(Intent data, long container, int screen, int cellX,
1107             int cellY) {
1108         int[] cellXY = mTmpAddItemCellCoordinates;
1109         int[] touchXY = mPendingAddInfo.dropPos;
1110         CellLayout layout = getCellLayout(container, screen);
1111 
1112         boolean foundCellSpan = false;
1113 
1114         ShortcutInfo info = mModel.infoFromShortcutIntent(this, data, null);
1115         if (info == null) {
1116             return;
1117         }
1118         final View view = createShortcut(info);
1119 
1120         // First we check if we already know the exact location where we want to add this item.
1121         if (cellX >= 0 && cellY >= 0) {
1122             cellXY[0] = cellX;
1123             cellXY[1] = cellY;
1124             foundCellSpan = true;
1125 
1126             // If appropriate, either create a folder or add to an existing folder
1127             if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
1128                     true, null,null)) {
1129                 return;
1130             }
1131             DragObject dragObject = new DragObject();
1132             dragObject.dragInfo = info;
1133             if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
1134                     true)) {
1135                 return;
1136             }
1137         } else if (touchXY != null) {
1138             // when dragging and dropping, just find the closest free spot
1139             int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
1140             foundCellSpan = (result != null);
1141         } else {
1142             foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1143         }
1144 
1145         if (!foundCellSpan) {
1146             showOutOfSpaceMessage(isHotseatLayout(layout));
1147             return;
1148         }
1149 
1150         LauncherModel.addItemToDatabase(this, info, container, screen, cellXY[0], cellXY[1], false);
1151 
1152         if (!mRestoring) {
1153             mWorkspace.addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1,
1154                     isWorkspaceLocked());
1155         }
1156     }
1157 
getSpanForWidget(Context context, ComponentName component, int minWidth, int minHeight)1158     static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
1159             int minHeight) {
1160         Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null);
1161         // We want to account for the extra amount of padding that we are adding to the widget
1162         // to ensure that it gets the full amount of space that it has requested
1163         int requiredWidth = minWidth + padding.left + padding.right;
1164         int requiredHeight = minHeight + padding.top + padding.bottom;
1165         return CellLayout.rectToCell(context.getResources(), requiredWidth, requiredHeight, null);
1166     }
1167 
getSpanForWidget(Context context, AppWidgetProviderInfo info)1168     static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) {
1169         return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight);
1170     }
1171 
getMinSpanForWidget(Context context, AppWidgetProviderInfo info)1172     static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) {
1173         return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight);
1174     }
1175 
getSpanForWidget(Context context, PendingAddWidgetInfo info)1176     static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) {
1177         return getSpanForWidget(context, info.componentName, info.info.minWidth,
1178                 info.info.minHeight);
1179     }
1180 
getMinSpanForWidget(Context context, PendingAddWidgetInfo info)1181     static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) {
1182         return getSpanForWidget(context, info.componentName, info.info.minResizeWidth,
1183                 info.info.minResizeHeight);
1184     }
1185 
1186     /**
1187      * Add a widget to the workspace.
1188      *
1189      * @param appWidgetId The app widget id
1190      * @param cellInfo The position on screen where to create the widget.
1191      */
completeAddAppWidget(final int appWidgetId, long container, int screen, AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo)1192     private void completeAddAppWidget(final int appWidgetId, long container, int screen,
1193             AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo) {
1194         if (appWidgetInfo == null) {
1195             appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
1196         }
1197 
1198         // Calculate the grid spans needed to fit this widget
1199         CellLayout layout = getCellLayout(container, screen);
1200 
1201         int[] minSpanXY = getMinSpanForWidget(this, appWidgetInfo);
1202         int[] spanXY = getSpanForWidget(this, appWidgetInfo);
1203 
1204         // Try finding open space on Launcher screen
1205         // We have saved the position to which the widget was dragged-- this really only matters
1206         // if we are placing widgets on a "spring-loaded" screen
1207         int[] cellXY = mTmpAddItemCellCoordinates;
1208         int[] touchXY = mPendingAddInfo.dropPos;
1209         int[] finalSpan = new int[2];
1210         boolean foundCellSpan = false;
1211         if (mPendingAddInfo.cellX >= 0 && mPendingAddInfo.cellY >= 0) {
1212             cellXY[0] = mPendingAddInfo.cellX;
1213             cellXY[1] = mPendingAddInfo.cellY;
1214             spanXY[0] = mPendingAddInfo.spanX;
1215             spanXY[1] = mPendingAddInfo.spanY;
1216             foundCellSpan = true;
1217         } else if (touchXY != null) {
1218             // when dragging and dropping, just find the closest free spot
1219             int[] result = layout.findNearestVacantArea(
1220                     touchXY[0], touchXY[1], minSpanXY[0], minSpanXY[1], spanXY[0],
1221                     spanXY[1], cellXY, finalSpan);
1222             spanXY[0] = finalSpan[0];
1223             spanXY[1] = finalSpan[1];
1224             foundCellSpan = (result != null);
1225         } else {
1226             foundCellSpan = layout.findCellForSpan(cellXY, minSpanXY[0], minSpanXY[1]);
1227         }
1228 
1229         if (!foundCellSpan) {
1230             if (appWidgetId != -1) {
1231                 // Deleting an app widget ID is a void call but writes to disk before returning
1232                 // to the caller...
1233                 new Thread("deleteAppWidgetId") {
1234                     public void run() {
1235                         mAppWidgetHost.deleteAppWidgetId(appWidgetId);
1236                     }
1237                 }.start();
1238             }
1239             showOutOfSpaceMessage(isHotseatLayout(layout));
1240             return;
1241         }
1242 
1243         // Build Launcher-specific widget info and save to database
1244         LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId,
1245                 appWidgetInfo.provider);
1246         launcherInfo.spanX = spanXY[0];
1247         launcherInfo.spanY = spanXY[1];
1248         launcherInfo.minSpanX = mPendingAddInfo.minSpanX;
1249         launcherInfo.minSpanY = mPendingAddInfo.minSpanY;
1250         launcherInfo.user = appWidgetInfo.getProfile();
1251 
1252         LauncherModel.addItemToDatabase(this, launcherInfo,
1253                 container, screen, cellXY[0], cellXY[1], false);
1254 
1255         if (!mRestoring) {
1256             if (hostView == null) {
1257                 // Perform actual inflation because we're live
1258                 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
1259                 launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
1260             } else {
1261                 // The AppWidgetHostView has already been inflated and instantiated
1262                 launcherInfo.hostView = hostView;
1263             }
1264 
1265             launcherInfo.hostView.setTag(launcherInfo);
1266             launcherInfo.hostView.setVisibility(View.VISIBLE);
1267             launcherInfo.notifyWidgetSizeChanged(this);
1268 
1269             mWorkspace.addInScreen(launcherInfo.hostView, container, screen, cellXY[0], cellXY[1],
1270                     launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
1271 
1272             addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1273         }
1274         resetAddInfo();
1275     }
1276 
1277     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1278         @Override
1279         public void onReceive(Context context, Intent intent) {
1280             final String action = intent.getAction();
1281             if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1282                 mUserPresent = false;
1283                 mDragLayer.clearAllResizeFrames();
1284                 updateRunning();
1285 
1286                 // Reset AllApps to its initial state only if we are not in the middle of
1287                 // processing a multi-step drop
1288                 if (mAppsCustomizeTabHost != null && mPendingAddInfo.container == ItemInfo.NO_ID) {
1289                     mAppsCustomizeTabHost.reset();
1290                     showWorkspace(false);
1291                 }
1292             } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1293                 mUserPresent = true;
1294                 updateRunning();
1295             } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
1296                     || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
1297                 getModel().forceReload();
1298             }
1299         }
1300     };
1301 
1302     @Override
onAttachedToWindow()1303     public void onAttachedToWindow() {
1304         super.onAttachedToWindow();
1305 
1306         // Listen for broadcasts related to user-presence
1307         final IntentFilter filter = new IntentFilter();
1308         filter.addAction(Intent.ACTION_SCREEN_OFF);
1309         filter.addAction(Intent.ACTION_USER_PRESENT);
1310         filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
1311         filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
1312         registerReceiver(mReceiver, filter);
1313         FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
1314         mAttached = true;
1315         mVisible = true;
1316     }
1317 
1318     @Override
onDetachedFromWindow()1319     public void onDetachedFromWindow() {
1320         super.onDetachedFromWindow();
1321         mVisible = false;
1322 
1323         if (mAttached) {
1324             unregisterReceiver(mReceiver);
1325             mAttached = false;
1326         }
1327         updateRunning();
1328     }
1329 
onWindowVisibilityChanged(int visibility)1330     public void onWindowVisibilityChanged(int visibility) {
1331         mVisible = visibility == View.VISIBLE;
1332         updateRunning();
1333         // The following code used to be in onResume, but it turns out onResume is called when
1334         // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
1335         // is a more appropriate event to handle
1336         if (mVisible) {
1337             mAppsCustomizeTabHost.onWindowVisible();
1338             if (!mWorkspaceLoading) {
1339                 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
1340                 // We want to let Launcher draw itself at least once before we force it to build
1341                 // layers on all the workspace pages, so that transitioning to Launcher from other
1342                 // apps is nice and speedy.
1343                 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
1344                     private boolean mStarted = false;
1345                     public void onDraw() {
1346                         if (mStarted) return;
1347                         mStarted = true;
1348                         // We delay the layer building a bit in order to give
1349                         // other message processing a time to run.  In particular
1350                         // this avoids a delay in hiding the IME if it was
1351                         // currently shown, because doing that may involve
1352                         // some communication back with the app.
1353                         mWorkspace.postDelayed(mBuildLayersRunnable, 500);
1354                         final ViewTreeObserver.OnDrawListener listener = this;
1355                         mWorkspace.post(new Runnable() {
1356                                 public void run() {
1357                                     if (mWorkspace != null &&
1358                                             mWorkspace.getViewTreeObserver() != null) {
1359                                         mWorkspace.getViewTreeObserver().
1360                                                 removeOnDrawListener(listener);
1361                                     }
1362                                 }
1363                             });
1364                         return;
1365                     }
1366                 });
1367             }
1368             // When Launcher comes back to foreground, a different Activity might be responsible for
1369             // the app market intent, so refresh the icon
1370             updateAppMarketIcon();
1371             clearTypedText();
1372         }
1373     }
1374 
sendAdvanceMessage(long delay)1375     private void sendAdvanceMessage(long delay) {
1376         mHandler.removeMessages(ADVANCE_MSG);
1377         Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1378         mHandler.sendMessageDelayed(msg, delay);
1379         mAutoAdvanceSentTime = System.currentTimeMillis();
1380     }
1381 
updateRunning()1382     private void updateRunning() {
1383         boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
1384         if (autoAdvanceRunning != mAutoAdvanceRunning) {
1385             mAutoAdvanceRunning = autoAdvanceRunning;
1386             if (autoAdvanceRunning) {
1387                 long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1388                 sendAdvanceMessage(delay);
1389             } else {
1390                 if (!mWidgetsToAdvance.isEmpty()) {
1391                     mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
1392                             (System.currentTimeMillis() - mAutoAdvanceSentTime));
1393                 }
1394                 mHandler.removeMessages(ADVANCE_MSG);
1395                 mHandler.removeMessages(0); // Remove messages sent using postDelayed()
1396             }
1397         }
1398     }
1399 
1400     private final Handler mHandler = new Handler() {
1401         @Override
1402         public void handleMessage(Message msg) {
1403             if (msg.what == ADVANCE_MSG) {
1404                 int i = 0;
1405                 for (View key: mWidgetsToAdvance.keySet()) {
1406                     final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1407                     final int delay = mAdvanceStagger * i;
1408                     if (v instanceof Advanceable) {
1409                        postDelayed(new Runnable() {
1410                            public void run() {
1411                                ((Advanceable) v).advance();
1412                            }
1413                        }, delay);
1414                     }
1415                     i++;
1416                 }
1417                 sendAdvanceMessage(mAdvanceInterval);
1418             }
1419         }
1420     };
1421 
addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo)1422     void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1423         if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
1424         View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1425         if (v instanceof Advanceable) {
1426             mWidgetsToAdvance.put(hostView, appWidgetInfo);
1427             ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
1428             updateRunning();
1429         }
1430     }
1431 
removeWidgetToAutoAdvance(View hostView)1432     void removeWidgetToAutoAdvance(View hostView) {
1433         if (mWidgetsToAdvance.containsKey(hostView)) {
1434             mWidgetsToAdvance.remove(hostView);
1435             updateRunning();
1436         }
1437     }
1438 
removeAppWidget(LauncherAppWidgetInfo launcherInfo)1439     public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1440         removeWidgetToAutoAdvance(launcherInfo.hostView);
1441         launcherInfo.hostView = null;
1442     }
1443 
showOutOfSpaceMessage(boolean isHotseatLayout)1444     void showOutOfSpaceMessage(boolean isHotseatLayout) {
1445         int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
1446         Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
1447     }
1448 
getAppWidgetHost()1449     public LauncherAppWidgetHost getAppWidgetHost() {
1450         return mAppWidgetHost;
1451     }
1452 
getModel()1453     public LauncherModel getModel() {
1454         return mModel;
1455     }
1456 
closeSystemDialogs()1457     void closeSystemDialogs() {
1458         getWindow().closeAllPanels();
1459 
1460         // Whatever we were doing is hereby canceled.
1461         mWaitingForResult = false;
1462     }
1463 
1464     @Override
onNewIntent(Intent intent)1465     protected void onNewIntent(Intent intent) {
1466         long startTime = 0;
1467         if (DEBUG_RESUME_TIME) {
1468             startTime = System.currentTimeMillis();
1469         }
1470         super.onNewIntent(intent);
1471 
1472         // Close the menu
1473         if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1474             // also will cancel mWaitingForResult.
1475             closeSystemDialogs();
1476 
1477             final boolean alreadyOnHome =
1478                     ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
1479                         != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
1480 
1481             Runnable processIntent = new Runnable() {
1482                 public void run() {
1483                     if (mWorkspace == null) {
1484                         // Can be cases where mWorkspace is null, this prevents a NPE
1485                         return;
1486                     }
1487                     Folder openFolder = mWorkspace.getOpenFolder();
1488                     // In all these cases, only animate if we're already on home
1489                     mWorkspace.exitWidgetResizeMode();
1490                     if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
1491                             openFolder == null) {
1492                         mWorkspace.moveToDefaultScreen(true);
1493                     }
1494 
1495                     closeFolder();
1496                     exitSpringLoadedDragMode();
1497 
1498                     // If we are already on home, then just animate back to the workspace,
1499                     // otherwise, just wait until onResume to set the state back to Workspace
1500                     if (alreadyOnHome) {
1501                         showWorkspace(true);
1502                     } else {
1503                         mOnResumeState = State.WORKSPACE;
1504                     }
1505 
1506                     final View v = getWindow().peekDecorView();
1507                     if (v != null && v.getWindowToken() != null) {
1508                         InputMethodManager imm = (InputMethodManager)getSystemService(
1509                                 INPUT_METHOD_SERVICE);
1510                         imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1511                     }
1512 
1513                     // Reset AllApps to its initial state
1514                     if (!alreadyOnHome && mAppsCustomizeTabHost != null) {
1515                         mAppsCustomizeTabHost.reset();
1516                     }
1517                 }
1518             };
1519 
1520             if (alreadyOnHome && !mWorkspace.hasWindowFocus()) {
1521                 // Delay processing of the intent to allow the status bar animation to finish
1522                 // first in order to avoid janky animations.
1523                 mWorkspace.postDelayed(processIntent, 350);
1524             } else {
1525                 // Process the intent immediately.
1526                 processIntent.run();
1527             }
1528 
1529         }
1530         if (DEBUG_RESUME_TIME) {
1531             Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
1532         }
1533     }
1534 
1535     @Override
onRestoreInstanceState(Bundle state)1536     public void onRestoreInstanceState(Bundle state) {
1537         super.onRestoreInstanceState(state);
1538         for (int page: mSynchronouslyBoundPages) {
1539             mWorkspace.restoreInstanceStateForChild(page);
1540         }
1541     }
1542 
1543     @Override
onSaveInstanceState(Bundle outState)1544     protected void onSaveInstanceState(Bundle outState) {
1545         outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getNextPage());
1546         super.onSaveInstanceState(outState);
1547 
1548         outState.putInt(RUNTIME_STATE, mState.ordinal());
1549         // We close any open folder since it will not be re-opened, and we need to make sure
1550         // this state is reflected.
1551         closeFolder();
1552 
1553         if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screen > -1 &&
1554                 mWaitingForResult) {
1555             outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
1556             outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screen);
1557             outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
1558             outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
1559             outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
1560             outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
1561             outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
1562             outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
1563         }
1564 
1565         if (mFolderInfo != null && mWaitingForResult) {
1566             outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
1567             outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
1568         }
1569 
1570         // Save the current AppsCustomize tab
1571         if (mAppsCustomizeTabHost != null) {
1572             String currentTabTag = mAppsCustomizeTabHost.getCurrentTabTag();
1573             if (currentTabTag != null) {
1574                 outState.putString("apps_customize_currentTab", currentTabTag);
1575             }
1576             int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
1577             outState.putInt("apps_customize_currentIndex", currentIndex);
1578         }
1579     }
1580 
1581     @Override
onDestroy()1582     public void onDestroy() {
1583         super.onDestroy();
1584 
1585         // Remove all pending runnables
1586         mHandler.removeMessages(ADVANCE_MSG);
1587         mHandler.removeMessages(0);
1588         mWorkspace.removeCallbacks(mBuildLayersRunnable);
1589 
1590         // Stop callbacks from LauncherModel
1591         LauncherApplication app = ((LauncherApplication) getApplication());
1592         mModel.stopLoader();
1593         app.setLauncher(null);
1594 
1595         try {
1596             mAppWidgetHost.stopListening();
1597         } catch (NullPointerException ex) {
1598             Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
1599         }
1600         mAppWidgetHost = null;
1601 
1602         mWidgetsToAdvance.clear();
1603 
1604         TextKeyListener.getInstance().release();
1605 
1606         // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
1607         // to prevent leaking Launcher activities on orientation change.
1608         if (mModel != null) {
1609             mModel.unbindItemInfosAndClearQueuedBindRunnables();
1610         }
1611 
1612         getContentResolver().unregisterContentObserver(mWidgetObserver);
1613         unregisterReceiver(mCloseSystemDialogsReceiver);
1614 
1615         mDragLayer.clearAllResizeFrames();
1616         ((ViewGroup) mWorkspace.getParent()).removeAllViews();
1617         mWorkspace.removeAllViews();
1618         mWorkspace = null;
1619         mDragController = null;
1620 
1621         LauncherAnimUtils.onDestroyActivity();
1622     }
1623 
getDragController()1624     public DragController getDragController() {
1625         return mDragController;
1626     }
1627 
1628     @Override
startActivityForResult(Intent intent, int requestCode)1629     public void startActivityForResult(Intent intent, int requestCode) {
1630         if (requestCode >= 0) mWaitingForResult = true;
1631         super.startActivityForResult(intent, requestCode);
1632     }
1633 
1634     /**
1635      * Indicates that we want global search for this activity by setting the globalSearch
1636      * argument for {@link #startSearch} to true.
1637      */
1638     @Override
startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, boolean globalSearch)1639     public void startSearch(String initialQuery, boolean selectInitialQuery,
1640             Bundle appSearchData, boolean globalSearch) {
1641 
1642         showWorkspace(true);
1643 
1644         if (initialQuery == null) {
1645             // Use any text typed in the launcher as the initial query
1646             initialQuery = getTypedText();
1647         }
1648         if (appSearchData == null) {
1649             appSearchData = new Bundle();
1650             appSearchData.putString(Search.SOURCE, "launcher-search");
1651         }
1652         Rect sourceBounds = new Rect();
1653         if (mSearchDropTargetBar != null) {
1654             sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
1655         }
1656 
1657         startGlobalSearch(initialQuery, selectInitialQuery,
1658             appSearchData, sourceBounds);
1659     }
1660 
1661     /**
1662      * Starts the global search activity. This code is a copied from SearchManager
1663      */
startGlobalSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds)1664     public void startGlobalSearch(String initialQuery,
1665             boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
1666         final SearchManager searchManager =
1667             (SearchManager) getSystemService(Context.SEARCH_SERVICE);
1668         ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
1669         if (globalSearchActivity == null) {
1670             Log.w(TAG, "No global search activity found.");
1671             return;
1672         }
1673         Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
1674         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1675         intent.setComponent(globalSearchActivity);
1676         // Make sure that we have a Bundle to put source in
1677         if (appSearchData == null) {
1678             appSearchData = new Bundle();
1679         } else {
1680             appSearchData = new Bundle(appSearchData);
1681         }
1682         // Set source to package name of app that starts global search, if not set already.
1683         if (!appSearchData.containsKey("source")) {
1684             appSearchData.putString("source", getPackageName());
1685         }
1686         intent.putExtra(SearchManager.APP_DATA, appSearchData);
1687         if (!TextUtils.isEmpty(initialQuery)) {
1688             intent.putExtra(SearchManager.QUERY, initialQuery);
1689         }
1690         if (selectInitialQuery) {
1691             intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
1692         }
1693         intent.setSourceBounds(sourceBounds);
1694         try {
1695             startActivity(intent);
1696         } catch (ActivityNotFoundException ex) {
1697             Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
1698         }
1699     }
1700 
1701     @Override
onCreateOptionsMenu(Menu menu)1702     public boolean onCreateOptionsMenu(Menu menu) {
1703         if (isWorkspaceLocked()) {
1704             return false;
1705         }
1706 
1707         super.onCreateOptionsMenu(menu);
1708 
1709         Intent manageApps = new Intent(Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS);
1710         manageApps.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1711                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1712         Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS);
1713         settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1714                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1715         String helpUrl = getString(R.string.help_url);
1716         Intent help = new Intent(Intent.ACTION_VIEW, Uri.parse(helpUrl));
1717         help.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1718                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1719 
1720         menu.add(MENU_GROUP_WALLPAPER, MENU_WALLPAPER_SETTINGS, 0, R.string.menu_wallpaper)
1721             .setIcon(android.R.drawable.ic_menu_gallery)
1722             .setAlphabeticShortcut('W');
1723         menu.add(0, MENU_MANAGE_APPS, 0, R.string.menu_manage_apps)
1724             .setIcon(android.R.drawable.ic_menu_manage)
1725             .setIntent(manageApps)
1726             .setAlphabeticShortcut('M');
1727         menu.add(0, MENU_SYSTEM_SETTINGS, 0, R.string.menu_settings)
1728             .setIcon(android.R.drawable.ic_menu_preferences)
1729             .setIntent(settings)
1730             .setAlphabeticShortcut('P');
1731         if (!helpUrl.isEmpty()) {
1732             menu.add(0, MENU_HELP, 0, R.string.menu_help)
1733                 .setIcon(android.R.drawable.ic_menu_help)
1734                 .setIntent(help)
1735                 .setAlphabeticShortcut('H');
1736         }
1737         return true;
1738     }
1739 
1740     @Override
onPrepareOptionsMenu(Menu menu)1741     public boolean onPrepareOptionsMenu(Menu menu) {
1742         super.onPrepareOptionsMenu(menu);
1743 
1744         if (mAppsCustomizeTabHost.isTransitioning()) {
1745             return false;
1746         }
1747         boolean allAppsVisible = (mAppsCustomizeTabHost.getVisibility() == View.VISIBLE);
1748         menu.setGroupVisible(MENU_GROUP_WALLPAPER, !allAppsVisible);
1749 
1750         return true;
1751     }
1752 
1753     @Override
onOptionsItemSelected(MenuItem item)1754     public boolean onOptionsItemSelected(MenuItem item) {
1755         switch (item.getItemId()) {
1756         case MENU_WALLPAPER_SETTINGS:
1757             startWallpaper();
1758             return true;
1759         }
1760 
1761         return super.onOptionsItemSelected(item);
1762     }
1763 
1764     @Override
onSearchRequested()1765     public boolean onSearchRequested() {
1766         startSearch(null, false, null, true);
1767         // Use a custom animation for launching search
1768         return true;
1769     }
1770 
isWorkspaceLocked()1771     public boolean isWorkspaceLocked() {
1772         return mWorkspaceLoading || mWaitingForResult;
1773     }
1774 
resetAddInfo()1775     private void resetAddInfo() {
1776         mPendingAddInfo.container = ItemInfo.NO_ID;
1777         mPendingAddInfo.screen = -1;
1778         mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
1779         mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
1780         mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
1781         mPendingAddInfo.dropPos = null;
1782     }
1783 
addAppWidgetImpl(final int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget, AppWidgetProviderInfo appWidgetInfo)1784     void addAppWidgetImpl(final int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
1785             AppWidgetProviderInfo appWidgetInfo) {
1786         if (appWidgetInfo.configure != null) {
1787             mPendingAddWidgetInfo = appWidgetInfo;
1788             mPendingAddWidgetId = appWidgetId;
1789 
1790             // Launch over to configure widget, if needed
1791             startAppWidgetConfigureActivitySafely(appWidgetId);
1792         } else {
1793             // Otherwise just add it
1794             completeAddAppWidget(appWidgetId, info.container, info.screen, boundWidget,
1795                     appWidgetInfo);
1796             // Exit spring loaded mode if necessary after adding the widget
1797             exitSpringLoadedDragModeDelayed(true, false, null);
1798         }
1799     }
1800 
1801     /**
1802      * Process a shortcut drop.
1803      *
1804      * @param componentName The name of the component
1805      * @param screen The screen where it should be added
1806      * @param cell The cell it should be added to, optional
1807      * @param position The location on the screen where it was dropped, optional
1808      */
processShortcutFromDrop(ComponentName componentName, long container, int screen, int[] cell, int[] loc)1809     void processShortcutFromDrop(ComponentName componentName, long container, int screen,
1810             int[] cell, int[] loc) {
1811         resetAddInfo();
1812         mPendingAddInfo.container = container;
1813         mPendingAddInfo.screen = screen;
1814         mPendingAddInfo.dropPos = loc;
1815 
1816         if (cell != null) {
1817             mPendingAddInfo.cellX = cell[0];
1818             mPendingAddInfo.cellY = cell[1];
1819         }
1820 
1821         Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
1822         createShortcutIntent.setComponent(componentName);
1823         processShortcut(createShortcutIntent);
1824     }
1825 
1826     /**
1827      * Process a widget drop.
1828      *
1829      * @param info The PendingAppWidgetInfo of the widget being added.
1830      * @param screen The screen where it should be added
1831      * @param cell The cell it should be added to, optional
1832      * @param position The location on the screen where it was dropped, optional
1833      */
addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, int screen, int[] cell, int[] span, int[] loc)1834     void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, int screen,
1835             int[] cell, int[] span, int[] loc) {
1836         resetAddInfo();
1837         mPendingAddInfo.container = info.container = container;
1838         mPendingAddInfo.screen = info.screen = screen;
1839         mPendingAddInfo.dropPos = loc;
1840         mPendingAddInfo.minSpanX = info.minSpanX;
1841         mPendingAddInfo.minSpanY = info.minSpanY;
1842 
1843         if (cell != null) {
1844             mPendingAddInfo.cellX = cell[0];
1845             mPendingAddInfo.cellY = cell[1];
1846         }
1847         if (span != null) {
1848             mPendingAddInfo.spanX = span[0];
1849             mPendingAddInfo.spanY = span[1];
1850         }
1851 
1852         AppWidgetHostView hostView = info.boundWidget;
1853         int appWidgetId;
1854         if (hostView != null) {
1855             appWidgetId = hostView.getAppWidgetId();
1856             addAppWidgetImpl(appWidgetId, info, hostView, info.info);
1857         } else {
1858             // In this case, we either need to start an activity to get permission to bind
1859             // the widget, or we need to start an activity to configure the widget, or both.
1860             appWidgetId = getAppWidgetHost().allocateAppWidgetId();
1861 
1862             boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
1863                     info.info.getProfile(), info.componentName, info.bindOptions);
1864             if (success) {
1865                 addAppWidgetImpl(appWidgetId, info, null, info.info);
1866             } else {
1867                 mPendingAddWidgetInfo = info.info;
1868                 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
1869                 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1870                 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
1871                 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE,
1872                         info.info.getProfile());
1873                 // TODO: we need to make sure that this accounts for the options bundle.
1874                 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
1875                 startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
1876             }
1877         }
1878     }
1879 
processShortcut(Intent intent)1880     void processShortcut(Intent intent) {
1881         // Handle case where user selected "Applications"
1882         String applicationName = getResources().getString(R.string.group_applications);
1883         String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
1884 
1885         if (applicationName != null && applicationName.equals(shortcutName)) {
1886             Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
1887             mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
1888 
1889             Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
1890             pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
1891             pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application));
1892             startActivityForResultSafely(pickIntent, REQUEST_PICK_APPLICATION);
1893         } else {
1894             startActivityForResultSafely(intent, REQUEST_CREATE_SHORTCUT);
1895         }
1896     }
1897 
processWallpaper(Intent intent)1898     void processWallpaper(Intent intent) {
1899         startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
1900     }
1901 
addFolder(CellLayout layout, long container, final int screen, int cellX, int cellY)1902     FolderIcon addFolder(CellLayout layout, long container, final int screen, int cellX,
1903             int cellY) {
1904         final FolderInfo folderInfo = new FolderInfo();
1905         folderInfo.title = getText(R.string.folder_name);
1906 
1907         // Update the model
1908         LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screen, cellX, cellY,
1909                 false);
1910         sFolders.put(folderInfo.id, folderInfo);
1911 
1912         // Create the view
1913         FolderIcon newFolder =
1914             FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
1915         mWorkspace.addInScreen(newFolder, container, screen, cellX, cellY, 1, 1,
1916                 isWorkspaceLocked());
1917         return newFolder;
1918     }
1919 
removeFolder(FolderInfo folder)1920     void removeFolder(FolderInfo folder) {
1921         sFolders.remove(folder.id);
1922     }
1923 
startWallpaper()1924     private void startWallpaper() {
1925         showWorkspace(true);
1926         final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
1927         Intent chooser = Intent.createChooser(pickWallpaper,
1928                 getText(R.string.chooser_wallpaper));
1929         // NOTE: Adds a configure option to the chooser if the wallpaper supports it
1930         //       Removed in Eclair MR1
1931 //        WallpaperManager wm = (WallpaperManager)
1932 //                getSystemService(Context.WALLPAPER_SERVICE);
1933 //        WallpaperInfo wi = wm.getWallpaperInfo();
1934 //        if (wi != null && wi.getSettingsActivity() != null) {
1935 //            LabeledIntent li = new LabeledIntent(getPackageName(),
1936 //                    R.string.configure_wallpaper, 0);
1937 //            li.setClassName(wi.getPackageName(), wi.getSettingsActivity());
1938 //            chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { li });
1939 //        }
1940         startActivityForResult(chooser, REQUEST_PICK_WALLPAPER);
1941     }
1942 
1943     /**
1944      * Registers various content observers. The current implementation registers
1945      * only a favorites observer to keep track of the favorites applications.
1946      */
registerContentObservers()1947     private void registerContentObservers() {
1948         ContentResolver resolver = getContentResolver();
1949         resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
1950                 true, mWidgetObserver);
1951     }
1952 
1953     @Override
dispatchKeyEvent(KeyEvent event)1954     public boolean dispatchKeyEvent(KeyEvent event) {
1955         if (event.getAction() == KeyEvent.ACTION_DOWN) {
1956             switch (event.getKeyCode()) {
1957                 case KeyEvent.KEYCODE_HOME:
1958                     return true;
1959                 case KeyEvent.KEYCODE_VOLUME_DOWN:
1960                     if (isPropertyEnabled(DUMP_STATE_PROPERTY)) {
1961                         dumpState();
1962                         return true;
1963                     }
1964                     break;
1965             }
1966         } else if (event.getAction() == KeyEvent.ACTION_UP) {
1967             switch (event.getKeyCode()) {
1968                 case KeyEvent.KEYCODE_HOME:
1969                     return true;
1970             }
1971         }
1972 
1973         return super.dispatchKeyEvent(event);
1974     }
1975 
1976     @Override
onBackPressed()1977     public void onBackPressed() {
1978         if (isAllAppsVisible()) {
1979             showWorkspace(true);
1980         } else if (mWorkspace.getOpenFolder() != null) {
1981             Folder openFolder = mWorkspace.getOpenFolder();
1982             if (openFolder.isEditingName()) {
1983                 openFolder.dismissEditingName();
1984             } else {
1985                 closeFolder();
1986             }
1987         } else {
1988             mWorkspace.exitWidgetResizeMode();
1989 
1990             // Back button is a no-op here, but give at least some feedback for the button press
1991             mWorkspace.showOutlinesTemporarily();
1992         }
1993     }
1994 
1995     /**
1996      * Re-listen when widgets are reset.
1997      */
onAppWidgetReset()1998     private void onAppWidgetReset() {
1999         if (mAppWidgetHost != null) {
2000             mAppWidgetHost.startListening();
2001         }
2002     }
2003 
2004     /**
2005      * Launches the intent referred by the clicked shortcut.
2006      *
2007      * @param v The view representing the clicked shortcut.
2008      */
onClick(View v)2009     public void onClick(View v) {
2010         // Make sure that rogue clicks don't get through while allapps is launching, or after the
2011         // view has detached (it's possible for this to happen if the view is removed mid touch).
2012         if (v.getWindowToken() == null) {
2013             return;
2014         }
2015 
2016         if (!mWorkspace.isFinishedSwitchingState()) {
2017             return;
2018         }
2019 
2020         Object tag = v.getTag();
2021         if (tag instanceof ShortcutInfo) {
2022             // Open shortcut
2023             final Intent intent = ((ShortcutInfo) tag).intent;
2024             int[] pos = new int[2];
2025             v.getLocationOnScreen(pos);
2026             intent.setSourceBounds(new Rect(pos[0], pos[1],
2027                     pos[0] + v.getWidth(), pos[1] + v.getHeight()));
2028 
2029             boolean success = startActivitySafely(v, intent, tag);
2030 
2031             if (success && v instanceof BubbleTextView) {
2032                 mWaitingForResume = (BubbleTextView) v;
2033                 mWaitingForResume.setStayPressed(true);
2034             }
2035         } else if (tag instanceof FolderInfo) {
2036             if (v instanceof FolderIcon) {
2037                 FolderIcon fi = (FolderIcon) v;
2038                 handleFolderClick(fi);
2039             }
2040         } else if (v == mAllAppsButton) {
2041             if (isAllAppsVisible()) {
2042                 showWorkspace(true);
2043             } else {
2044                 onClickAllAppsButton(v);
2045             }
2046         }
2047     }
2048 
onTouch(View v, MotionEvent event)2049     public boolean onTouch(View v, MotionEvent event) {
2050         // this is an intercepted event being forwarded from mWorkspace;
2051         // clicking anywhere on the workspace causes the customization drawer to slide down
2052         showWorkspace(true);
2053         return false;
2054     }
2055 
2056     /**
2057      * Event handler for the search button
2058      *
2059      * @param v The view that was clicked.
2060      */
onClickSearchButton(View v)2061     public void onClickSearchButton(View v) {
2062         v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2063 
2064         onSearchRequested();
2065     }
2066 
2067     /**
2068      * Event handler for the voice button
2069      *
2070      * @param v The view that was clicked.
2071      */
onClickVoiceButton(View v)2072     public void onClickVoiceButton(View v) {
2073         v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2074 
2075         try {
2076             final SearchManager searchManager =
2077                     (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2078             ComponentName activityName = searchManager.getGlobalSearchActivity();
2079             Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
2080             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2081             if (activityName != null) {
2082                 intent.setPackage(activityName.getPackageName());
2083             }
2084             startActivity(null, intent, "onClickVoiceButton");
2085         } catch (ActivityNotFoundException e) {
2086             Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
2087             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2088             startActivitySafely(null, intent, "onClickVoiceButton");
2089         }
2090     }
2091 
2092     /**
2093      * Event handler for the "grid" button that appears on the home screen, which
2094      * enters all apps mode.
2095      *
2096      * @param v The view that was clicked.
2097      */
onClickAllAppsButton(View v)2098     public void onClickAllAppsButton(View v) {
2099         showAllApps(true);
2100     }
2101 
onTouchDownAllAppsButton(View v)2102     public void onTouchDownAllAppsButton(View v) {
2103         // Provide the same haptic feedback that the system offers for virtual keys.
2104         v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2105     }
2106 
onClickAppMarketButton(View v)2107     public void onClickAppMarketButton(View v) {
2108         if (mAppMarketIntent != null) {
2109             startActivitySafely(v, mAppMarketIntent, "app market");
2110         } else {
2111             Log.e(TAG, "Invalid app market intent.");
2112         }
2113     }
2114 
startApplicationDetailsActivity(ComponentName componentName, UserHandle user)2115     void startApplicationDetailsActivity(ComponentName componentName, UserHandle user) {
2116         LauncherApps launcherApps = (LauncherApps) getSystemService(Context.LAUNCHER_APPS_SERVICE);
2117         try {
2118             launcherApps.startAppDetailsActivity(componentName, user, null, null);
2119         } catch (SecurityException e) {
2120             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2121             Log.e(TAG, "Launcher does not have permission to launch settings");
2122         } catch (ActivityNotFoundException e) {
2123             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2124             Log.e(TAG, "Unable to launch settings");
2125         }
2126     }
2127 
startApplicationUninstallActivity(ApplicationInfo appInfo, UserHandle user)2128     void startApplicationUninstallActivity(ApplicationInfo appInfo, UserHandle user) {
2129         if ((appInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) == 0) {
2130             // System applications cannot be installed. For now, show a toast explaining that.
2131             // We may give them the option of disabling apps this way.
2132             int messageId = R.string.uninstall_system_app_text;
2133             Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
2134         } else {
2135             String packageName = appInfo.componentName.getPackageName();
2136             String className = appInfo.componentName.getClassName();
2137             Intent intent = new Intent(
2138                     Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
2139             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
2140                     Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2141             if (user != null) {
2142                 intent.putExtra(Intent.EXTRA_USER, user);
2143             }
2144             startActivity(intent);
2145         }
2146     }
2147 
startActivity(View v, Intent intent, Object tag)2148     boolean startActivity(View v, Intent intent, Object tag) {
2149         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2150 
2151         try {
2152             // Only launch using the new animation if the shortcut has not opted out (this is a
2153             // private contract between launcher and may be ignored in the future).
2154             boolean useLaunchAnimation = (v != null) &&
2155                     !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
2156             UserHandle user = (UserHandle) intent.getParcelableExtra(ApplicationInfo.EXTRA_PROFILE);
2157             LauncherApps launcherApps = (LauncherApps)
2158                     this.getSystemService(Context.LAUNCHER_APPS_SERVICE);
2159             if (useLaunchAnimation) {
2160                 ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
2161                         v.getMeasuredWidth(), v.getMeasuredHeight());
2162                 if (user == null || user.equals(android.os.Process.myUserHandle())) {
2163                     // Could be launching some bookkeeping activity
2164                     startActivity(intent, opts.toBundle());
2165                 } else {
2166                     launcherApps.startMainActivity(intent.getComponent(), user,
2167                             intent.getSourceBounds(),
2168                             opts.toBundle());
2169                 }
2170             } else {
2171                 if (user == null || user.equals(android.os.Process.myUserHandle())) {
2172                     startActivity(intent);
2173                 } else {
2174                     launcherApps.startMainActivity(intent.getComponent(), user,
2175                             intent.getSourceBounds(), null);
2176                 }
2177             }
2178             return true;
2179         } catch (SecurityException e) {
2180             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2181             Log.e(TAG, "Launcher does not have the permission to launch " + intent +
2182                     ". Make sure to create a MAIN intent-filter for the corresponding activity " +
2183                     "or use the exported attribute for this activity. "
2184                     + "tag="+ tag + " intent=" + intent, e);
2185         }
2186         return false;
2187     }
2188 
startActivitySafely(View v, Intent intent, Object tag)2189     boolean startActivitySafely(View v, Intent intent, Object tag) {
2190         boolean success = false;
2191         try {
2192             success = startActivity(v, intent, tag);
2193         } catch (ActivityNotFoundException e) {
2194             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2195             Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
2196         }
2197         return success;
2198     }
2199 
startAppWidgetConfigureActivitySafely(int appWidgetId)2200     void startAppWidgetConfigureActivitySafely(int appWidgetId) {
2201         try {
2202             mAppWidgetHost.startAppWidgetConfigureActivityForResult(this, appWidgetId, 0,
2203                     REQUEST_CREATE_APPWIDGET, null);
2204         } catch (ActivityNotFoundException e) {
2205             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2206         }
2207     }
2208 
startActivityForResultSafely(Intent intent, int requestCode)2209     void startActivityForResultSafely(Intent intent, int requestCode) {
2210         try {
2211             startActivityForResult(intent, requestCode);
2212         } catch (ActivityNotFoundException e) {
2213             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2214         } catch (SecurityException e) {
2215             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2216             Log.e(TAG, "Launcher does not have the permission to launch " + intent +
2217                     ". Make sure to create a MAIN intent-filter for the corresponding activity " +
2218                     "or use the exported attribute for this activity.", e);
2219         }
2220     }
2221 
handleFolderClick(FolderIcon folderIcon)2222     private void handleFolderClick(FolderIcon folderIcon) {
2223         final FolderInfo info = folderIcon.getFolderInfo();
2224         Folder openFolder = mWorkspace.getFolderForTag(info);
2225 
2226         // If the folder info reports that the associated folder is open, then verify that
2227         // it is actually opened. There have been a few instances where this gets out of sync.
2228         if (info.opened && openFolder == null) {
2229             Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
2230                     + info.screen + " (" + info.cellX + ", " + info.cellY + ")");
2231             info.opened = false;
2232         }
2233 
2234         if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
2235             // Close any open folder
2236             closeFolder();
2237             // Open the requested folder
2238             openFolder(folderIcon);
2239         } else {
2240             // Find the open folder...
2241             int folderScreen;
2242             if (openFolder != null) {
2243                 folderScreen = mWorkspace.getPageForView(openFolder);
2244                 // .. and close it
2245                 closeFolder(openFolder);
2246                 if (folderScreen != mWorkspace.getCurrentPage()) {
2247                     // Close any folder open on the current screen
2248                     closeFolder();
2249                     // Pull the folder onto this screen
2250                     openFolder(folderIcon);
2251                 }
2252             }
2253         }
2254     }
2255 
2256     /**
2257      * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
2258      * in the DragLayer in the exact absolute location of the original FolderIcon.
2259      */
copyFolderIconToImage(FolderIcon fi)2260     private void copyFolderIconToImage(FolderIcon fi) {
2261         final int width = fi.getMeasuredWidth();
2262         final int height = fi.getMeasuredHeight();
2263 
2264         // Lazy load ImageView, Bitmap and Canvas
2265         if (mFolderIconImageView == null) {
2266             mFolderIconImageView = new ImageView(this);
2267         }
2268         if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
2269                 mFolderIconBitmap.getHeight() != height) {
2270             mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
2271             mFolderIconCanvas = new Canvas(mFolderIconBitmap);
2272         }
2273 
2274         DragLayer.LayoutParams lp;
2275         if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
2276             lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
2277         } else {
2278             lp = new DragLayer.LayoutParams(width, height);
2279         }
2280 
2281         // The layout from which the folder is being opened may be scaled, adjust the starting
2282         // view size by this scale factor.
2283         float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
2284         lp.customPosition = true;
2285         lp.x = mRectForFolderAnimation.left;
2286         lp.y = mRectForFolderAnimation.top;
2287         lp.width = (int) (scale * width);
2288         lp.height = (int) (scale * height);
2289 
2290         mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
2291         fi.draw(mFolderIconCanvas);
2292         mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
2293         if (fi.getFolder() != null) {
2294             mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
2295             mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
2296         }
2297         // Just in case this image view is still in the drag layer from a previous animation,
2298         // we remove it and re-add it.
2299         if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
2300             mDragLayer.removeView(mFolderIconImageView);
2301         }
2302         mDragLayer.addView(mFolderIconImageView, lp);
2303         if (fi.getFolder() != null) {
2304             fi.getFolder().bringToFront();
2305         }
2306     }
2307 
growAndFadeOutFolderIcon(FolderIcon fi)2308     private void growAndFadeOutFolderIcon(FolderIcon fi) {
2309         if (fi == null) return;
2310         PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
2311         PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
2312         PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
2313 
2314         FolderInfo info = (FolderInfo) fi.getTag();
2315         if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2316             CellLayout cl = (CellLayout) fi.getParent().getParent();
2317             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
2318             cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
2319         }
2320 
2321         // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
2322         copyFolderIconToImage(fi);
2323         fi.setVisibility(View.INVISIBLE);
2324 
2325         ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
2326                 scaleX, scaleY);
2327         oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
2328         oa.start();
2329     }
2330 
shrinkAndFadeInFolderIcon(final FolderIcon fi)2331     private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
2332         if (fi == null) return;
2333         PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
2334         PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
2335         PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
2336 
2337         final CellLayout cl = (CellLayout) fi.getParent().getParent();
2338 
2339         // We remove and re-draw the FolderIcon in-case it has changed
2340         mDragLayer.removeView(mFolderIconImageView);
2341         copyFolderIconToImage(fi);
2342         ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
2343                 scaleX, scaleY);
2344         oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
2345         oa.addListener(new AnimatorListenerAdapter() {
2346             @Override
2347             public void onAnimationEnd(Animator animation) {
2348                 if (cl != null) {
2349                     cl.clearFolderLeaveBehind();
2350                     // Remove the ImageView copy of the FolderIcon and make the original visible.
2351                     mDragLayer.removeView(mFolderIconImageView);
2352                     fi.setVisibility(View.VISIBLE);
2353                 }
2354             }
2355         });
2356         oa.start();
2357     }
2358 
2359     /**
2360      * Opens the user folder described by the specified tag. The opening of the folder
2361      * is animated relative to the specified View. If the View is null, no animation
2362      * is played.
2363      *
2364      * @param folderInfo The FolderInfo describing the folder to open.
2365      */
openFolder(FolderIcon folderIcon)2366     public void openFolder(FolderIcon folderIcon) {
2367         Folder folder = folderIcon.getFolder();
2368         FolderInfo info = folder.mInfo;
2369 
2370         info.opened = true;
2371 
2372         // Just verify that the folder hasn't already been added to the DragLayer.
2373         // There was a one-off crash where the folder had a parent already.
2374         if (folder.getParent() == null) {
2375             mDragLayer.addView(folder);
2376             mDragController.addDropTarget((DropTarget) folder);
2377         } else {
2378             Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
2379                     folder.getParent() + ").");
2380         }
2381         folder.animateOpen();
2382         growAndFadeOutFolderIcon(folderIcon);
2383 
2384         // Notify the accessibility manager that this folder "window" has appeared and occluded
2385         // the workspace items
2386         folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
2387         getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
2388     }
2389 
closeFolder()2390     public void closeFolder() {
2391         Folder folder = mWorkspace.getOpenFolder();
2392         if (folder != null) {
2393             if (folder.isEditingName()) {
2394                 folder.dismissEditingName();
2395             }
2396             closeFolder(folder);
2397 
2398             // Dismiss the folder cling
2399             dismissFolderCling(null);
2400         }
2401     }
2402 
closeFolder(Folder folder)2403     void closeFolder(Folder folder) {
2404         folder.getInfo().opened = false;
2405 
2406         ViewGroup parent = (ViewGroup) folder.getParent().getParent();
2407         if (parent != null) {
2408             FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
2409             shrinkAndFadeInFolderIcon(fi);
2410         }
2411         folder.animateClosed();
2412 
2413         // Notify the accessibility manager that this folder "window" has disappeard and no
2414         // longer occludeds the workspace items
2415         getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
2416     }
2417 
onLongClick(View v)2418     public boolean onLongClick(View v) {
2419         if (!isDraggingEnabled()) return false;
2420         if (isWorkspaceLocked()) return false;
2421         if (mState != State.WORKSPACE) return false;
2422 
2423         if (!(v instanceof CellLayout)) {
2424             v = (View) v.getParent().getParent();
2425         }
2426 
2427         resetAddInfo();
2428         CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
2429         // This happens when long clicking an item with the dpad/trackball
2430         if (longClickCellInfo == null) {
2431             return true;
2432         }
2433 
2434         // The hotseat touch handling does not go through Workspace, and we always allow long press
2435         // on hotseat items.
2436         final View itemUnderLongClick = longClickCellInfo.cell;
2437         boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();
2438         if (allowLongPress && !mDragController.isDragging()) {
2439             if (itemUnderLongClick == null) {
2440                 // User long pressed on empty space
2441                 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
2442                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
2443                 startWallpaper();
2444             } else {
2445                 if (!(itemUnderLongClick instanceof Folder)) {
2446                     // User long pressed on an item
2447                     mWorkspace.startDrag(longClickCellInfo);
2448                 }
2449             }
2450         }
2451         return true;
2452     }
2453 
isHotseatLayout(View layout)2454     boolean isHotseatLayout(View layout) {
2455         return mHotseat != null && layout != null &&
2456                 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
2457     }
getHotseat()2458     Hotseat getHotseat() {
2459         return mHotseat;
2460     }
getSearchBar()2461     SearchDropTargetBar getSearchBar() {
2462         return mSearchDropTargetBar;
2463     }
2464 
2465     /**
2466      * Returns the CellLayout of the specified container at the specified screen.
2467      */
getCellLayout(long container, int screen)2468     CellLayout getCellLayout(long container, int screen) {
2469         if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2470             if (mHotseat != null) {
2471                 return mHotseat.getLayout();
2472             } else {
2473                 return null;
2474             }
2475         } else {
2476             return (CellLayout) mWorkspace.getChildAt(screen);
2477         }
2478     }
2479 
getWorkspace()2480     Workspace getWorkspace() {
2481         return mWorkspace;
2482     }
2483 
2484     // Now a part of LauncherModel.Callbacks. Used to reorder loading steps.
2485     @Override
isAllAppsVisible()2486     public boolean isAllAppsVisible() {
2487         return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE);
2488     }
2489 
2490     @Override
isAllAppsButtonRank(int rank)2491     public boolean isAllAppsButtonRank(int rank) {
2492         return mHotseat.isAllAppsButtonRank(rank);
2493     }
2494 
2495     /**
2496      * Helper method for the cameraZoomIn/cameraZoomOut animations
2497      * @param view The view being animated
2498      * @param scaleFactor The scale factor used for the zoom
2499      */
setPivotsForZoom(View view, float scaleFactor)2500     private void setPivotsForZoom(View view, float scaleFactor) {
2501         view.setPivotX(view.getWidth() / 2.0f);
2502         view.setPivotY(view.getHeight() / 2.0f);
2503     }
2504 
disableWallpaperIfInAllApps()2505     void disableWallpaperIfInAllApps() {
2506         // Only disable it if we are in all apps
2507         if (isAllAppsVisible()) {
2508             if (mAppsCustomizeTabHost != null &&
2509                     !mAppsCustomizeTabHost.isTransitioning()) {
2510                 updateWallpaperVisibility(false);
2511             }
2512         }
2513     }
2514 
setWorkspaceBackground(boolean workspace)2515     private void setWorkspaceBackground(boolean workspace) {
2516         mLauncherView.setBackground(workspace ?
2517                 mWorkspaceBackgroundDrawable : null);
2518     }
2519 
updateWallpaperVisibility(boolean visible)2520     void updateWallpaperVisibility(boolean visible) {
2521         int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
2522         int curflags = getWindow().getAttributes().flags
2523                 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
2524         if (wpflags != curflags) {
2525             getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
2526         }
2527         setWorkspaceBackground(visible);
2528     }
2529 
dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace)2530     private void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) {
2531         if (v instanceof LauncherTransitionable) {
2532             ((LauncherTransitionable) v).onLauncherTransitionPrepare(this, animated, toWorkspace);
2533         }
2534     }
2535 
dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace)2536     private void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
2537         if (v instanceof LauncherTransitionable) {
2538             ((LauncherTransitionable) v).onLauncherTransitionStart(this, animated, toWorkspace);
2539         }
2540 
2541         // Update the workspace transition step as well
2542         dispatchOnLauncherTransitionStep(v, 0f);
2543     }
2544 
dispatchOnLauncherTransitionStep(View v, float t)2545     private void dispatchOnLauncherTransitionStep(View v, float t) {
2546         if (v instanceof LauncherTransitionable) {
2547             ((LauncherTransitionable) v).onLauncherTransitionStep(this, t);
2548         }
2549     }
2550 
dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace)2551     private void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
2552         if (v instanceof LauncherTransitionable) {
2553             ((LauncherTransitionable) v).onLauncherTransitionEnd(this, animated, toWorkspace);
2554         }
2555 
2556         // Update the workspace transition step as well
2557         dispatchOnLauncherTransitionStep(v, 1f);
2558     }
2559 
2560     /**
2561      * Things to test when changing the following seven functions.
2562      *   - Home from workspace
2563      *          - from center screen
2564      *          - from other screens
2565      *   - Home from all apps
2566      *          - from center screen
2567      *          - from other screens
2568      *   - Back from all apps
2569      *          - from center screen
2570      *          - from other screens
2571      *   - Launch app from workspace and quit
2572      *          - with back
2573      *          - with home
2574      *   - Launch app from all apps and quit
2575      *          - with back
2576      *          - with home
2577      *   - Go to a screen that's not the default, then all
2578      *     apps, and launch and app, and go back
2579      *          - with back
2580      *          -with home
2581      *   - On workspace, long press power and go back
2582      *          - with back
2583      *          - with home
2584      *   - On all apps, long press power and go back
2585      *          - with back
2586      *          - with home
2587      *   - On workspace, power off
2588      *   - On all apps, power off
2589      *   - Launch an app and turn off the screen while in that app
2590      *          - Go back with home key
2591      *          - Go back with back key  TODO: make this not go to workspace
2592      *          - From all apps
2593      *          - From workspace
2594      *   - Enter and exit car mode (becuase it causes an extra configuration changed)
2595      *          - From all apps
2596      *          - From the center workspace
2597      *          - From another workspace
2598      */
2599 
2600     /**
2601      * Zoom the camera out from the workspace to reveal 'toView'.
2602      * Assumes that the view to show is anchored at either the very top or very bottom
2603      * of the screen.
2604      */
showAppsCustomizeHelper(final boolean animated, final boolean springLoaded)2605     private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded) {
2606         if (mStateAnimation != null) {
2607             mStateAnimation.setDuration(0);
2608             mStateAnimation.cancel();
2609             mStateAnimation = null;
2610         }
2611         final Resources res = getResources();
2612 
2613         final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime);
2614         final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime);
2615         final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
2616         final View fromView = mWorkspace;
2617         final AppsCustomizeTabHost toView = mAppsCustomizeTabHost;
2618         final int startDelay =
2619                 res.getInteger(R.integer.config_workspaceAppsCustomizeAnimationStagger);
2620 
2621         setPivotsForZoom(toView, scale);
2622 
2623         // Shrink workspaces away if going to AppsCustomize from workspace
2624         Animator workspaceAnim =
2625                 mWorkspace.getChangeStateAnimation(Workspace.State.SMALL, animated);
2626 
2627         if (animated) {
2628             toView.setScaleX(scale);
2629             toView.setScaleY(scale);
2630             final LauncherViewPropertyAnimator scaleAnim = new LauncherViewPropertyAnimator(toView);
2631             scaleAnim.
2632                 scaleX(1f).scaleY(1f).
2633                 setDuration(duration).
2634                 setInterpolator(new Workspace.ZoomOutInterpolator());
2635 
2636             toView.setVisibility(View.VISIBLE);
2637             toView.setAlpha(0f);
2638             final ObjectAnimator alphaAnim = LauncherAnimUtils
2639                 .ofFloat(toView, "alpha", 0f, 1f)
2640                 .setDuration(fadeDuration);
2641             alphaAnim.setInterpolator(new DecelerateInterpolator(1.5f));
2642             alphaAnim.addUpdateListener(new AnimatorUpdateListener() {
2643                 @Override
2644                 public void onAnimationUpdate(ValueAnimator animation) {
2645                     if (animation == null) {
2646                         throw new RuntimeException("animation is null");
2647                     }
2648                     float t = (Float) animation.getAnimatedValue();
2649                     dispatchOnLauncherTransitionStep(fromView, t);
2650                     dispatchOnLauncherTransitionStep(toView, t);
2651                 }
2652             });
2653 
2654             // toView should appear right at the end of the workspace shrink
2655             // animation
2656             mStateAnimation = LauncherAnimUtils.createAnimatorSet();
2657             mStateAnimation.play(scaleAnim).after(startDelay);
2658             mStateAnimation.play(alphaAnim).after(startDelay);
2659 
2660             mStateAnimation.addListener(new AnimatorListenerAdapter() {
2661                 boolean animationCancelled = false;
2662 
2663                 @Override
2664                 public void onAnimationStart(Animator animation) {
2665                     updateWallpaperVisibility(true);
2666                     // Prepare the position
2667                     toView.setTranslationX(0.0f);
2668                     toView.setTranslationY(0.0f);
2669                     toView.setVisibility(View.VISIBLE);
2670                     toView.bringToFront();
2671                 }
2672                 @Override
2673                 public void onAnimationEnd(Animator animation) {
2674                     dispatchOnLauncherTransitionEnd(fromView, animated, false);
2675                     dispatchOnLauncherTransitionEnd(toView, animated, false);
2676 
2677                     if (mWorkspace != null && !springLoaded && !LauncherApplication.isScreenLarge()) {
2678                         // Hide the workspace scrollbar
2679                         mWorkspace.hideScrollingIndicator(true);
2680                         hideDockDivider();
2681                     }
2682                     if (!animationCancelled) {
2683                         updateWallpaperVisibility(false);
2684                     }
2685 
2686                     // Hide the search bar
2687                     if (mSearchDropTargetBar != null) {
2688                         mSearchDropTargetBar.hideSearchBar(false);
2689                     }
2690                 }
2691 
2692                 @Override
2693                 public void onAnimationCancel(Animator animation) {
2694                     animationCancelled = true;
2695                 }
2696             });
2697 
2698             if (workspaceAnim != null) {
2699                 mStateAnimation.play(workspaceAnim);
2700             }
2701 
2702             boolean delayAnim = false;
2703 
2704             dispatchOnLauncherTransitionPrepare(fromView, animated, false);
2705             dispatchOnLauncherTransitionPrepare(toView, animated, false);
2706 
2707             // If any of the objects being animated haven't been measured/laid out
2708             // yet, delay the animation until we get a layout pass
2709             if ((((LauncherTransitionable) toView).getContent().getMeasuredWidth() == 0) ||
2710                     (mWorkspace.getMeasuredWidth() == 0) ||
2711                     (toView.getMeasuredWidth() == 0)) {
2712                 delayAnim = true;
2713             }
2714 
2715             final AnimatorSet stateAnimation = mStateAnimation;
2716             final Runnable startAnimRunnable = new Runnable() {
2717                 public void run() {
2718                     // Check that mStateAnimation hasn't changed while
2719                     // we waited for a layout/draw pass
2720                     if (mStateAnimation != stateAnimation)
2721                         return;
2722                     setPivotsForZoom(toView, scale);
2723                     dispatchOnLauncherTransitionStart(fromView, animated, false);
2724                     dispatchOnLauncherTransitionStart(toView, animated, false);
2725                     LauncherAnimUtils.startAnimationAfterNextDraw(mStateAnimation, toView);
2726                 }
2727             };
2728             if (delayAnim) {
2729                 final ViewTreeObserver observer = toView.getViewTreeObserver();
2730                 observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
2731                         public void onGlobalLayout() {
2732                             startAnimRunnable.run();
2733                             toView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
2734                         }
2735                     });
2736             } else {
2737                 startAnimRunnable.run();
2738             }
2739         } else {
2740             toView.setTranslationX(0.0f);
2741             toView.setTranslationY(0.0f);
2742             toView.setScaleX(1.0f);
2743             toView.setScaleY(1.0f);
2744             toView.setVisibility(View.VISIBLE);
2745             toView.bringToFront();
2746 
2747             if (!springLoaded && !LauncherApplication.isScreenLarge()) {
2748                 // Hide the workspace scrollbar
2749                 mWorkspace.hideScrollingIndicator(true);
2750                 hideDockDivider();
2751 
2752                 // Hide the search bar
2753                 if (mSearchDropTargetBar != null) {
2754                     mSearchDropTargetBar.hideSearchBar(false);
2755                 }
2756             }
2757             dispatchOnLauncherTransitionPrepare(fromView, animated, false);
2758             dispatchOnLauncherTransitionStart(fromView, animated, false);
2759             dispatchOnLauncherTransitionEnd(fromView, animated, false);
2760             dispatchOnLauncherTransitionPrepare(toView, animated, false);
2761             dispatchOnLauncherTransitionStart(toView, animated, false);
2762             dispatchOnLauncherTransitionEnd(toView, animated, false);
2763             updateWallpaperVisibility(false);
2764         }
2765     }
2766 
2767     /**
2768      * Zoom the camera back into the workspace, hiding 'fromView'.
2769      * This is the opposite of showAppsCustomizeHelper.
2770      * @param animated If true, the transition will be animated.
2771      */
hideAppsCustomizeHelper(State toState, final boolean animated, final boolean springLoaded, final Runnable onCompleteRunnable)2772     private void hideAppsCustomizeHelper(State toState, final boolean animated,
2773             final boolean springLoaded, final Runnable onCompleteRunnable) {
2774 
2775         if (mStateAnimation != null) {
2776             mStateAnimation.setDuration(0);
2777             mStateAnimation.cancel();
2778             mStateAnimation = null;
2779         }
2780         Resources res = getResources();
2781 
2782         final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime);
2783         final int fadeOutDuration =
2784                 res.getInteger(R.integer.config_appsCustomizeFadeOutTime);
2785         final float scaleFactor = (float)
2786                 res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
2787         final View fromView = mAppsCustomizeTabHost;
2788         final View toView = mWorkspace;
2789         Animator workspaceAnim = null;
2790 
2791         if (toState == State.WORKSPACE) {
2792             int stagger = res.getInteger(R.integer.config_appsCustomizeWorkspaceAnimationStagger);
2793             workspaceAnim = mWorkspace.getChangeStateAnimation(
2794                     Workspace.State.NORMAL, animated, stagger);
2795         } else if (toState == State.APPS_CUSTOMIZE_SPRING_LOADED) {
2796             workspaceAnim = mWorkspace.getChangeStateAnimation(
2797                     Workspace.State.SPRING_LOADED, animated);
2798         }
2799 
2800         setPivotsForZoom(fromView, scaleFactor);
2801         updateWallpaperVisibility(true);
2802         showHotseat(animated);
2803         if (animated) {
2804             final LauncherViewPropertyAnimator scaleAnim =
2805                     new LauncherViewPropertyAnimator(fromView);
2806             scaleAnim.
2807                 scaleX(scaleFactor).scaleY(scaleFactor).
2808                 setDuration(duration).
2809                 setInterpolator(new Workspace.ZoomInInterpolator());
2810 
2811             final ObjectAnimator alphaAnim = LauncherAnimUtils
2812                 .ofFloat(fromView, "alpha", 1f, 0f)
2813                 .setDuration(fadeOutDuration);
2814             alphaAnim.setInterpolator(new AccelerateDecelerateInterpolator());
2815             alphaAnim.addUpdateListener(new AnimatorUpdateListener() {
2816                 @Override
2817                 public void onAnimationUpdate(ValueAnimator animation) {
2818                     float t = 1f - (Float) animation.getAnimatedValue();
2819                     dispatchOnLauncherTransitionStep(fromView, t);
2820                     dispatchOnLauncherTransitionStep(toView, t);
2821                 }
2822             });
2823 
2824             mStateAnimation = LauncherAnimUtils.createAnimatorSet();
2825 
2826             dispatchOnLauncherTransitionPrepare(fromView, animated, true);
2827             dispatchOnLauncherTransitionPrepare(toView, animated, true);
2828             mAppsCustomizeContent.pauseScrolling();
2829 
2830             mStateAnimation.addListener(new AnimatorListenerAdapter() {
2831                 @Override
2832                 public void onAnimationEnd(Animator animation) {
2833                     updateWallpaperVisibility(true);
2834                     fromView.setVisibility(View.GONE);
2835                     dispatchOnLauncherTransitionEnd(fromView, animated, true);
2836                     dispatchOnLauncherTransitionEnd(toView, animated, true);
2837                     if (mWorkspace != null) {
2838                         mWorkspace.hideScrollingIndicator(false);
2839                     }
2840                     if (onCompleteRunnable != null) {
2841                         onCompleteRunnable.run();
2842                     }
2843                     mAppsCustomizeContent.updateCurrentPageScroll();
2844                     mAppsCustomizeContent.resumeScrolling();
2845                 }
2846             });
2847 
2848             mStateAnimation.playTogether(scaleAnim, alphaAnim);
2849             if (workspaceAnim != null) {
2850                 mStateAnimation.play(workspaceAnim);
2851             }
2852             dispatchOnLauncherTransitionStart(fromView, animated, true);
2853             dispatchOnLauncherTransitionStart(toView, animated, true);
2854             LauncherAnimUtils.startAnimationAfterNextDraw(mStateAnimation, toView);
2855         } else {
2856             fromView.setVisibility(View.GONE);
2857             dispatchOnLauncherTransitionPrepare(fromView, animated, true);
2858             dispatchOnLauncherTransitionStart(fromView, animated, true);
2859             dispatchOnLauncherTransitionEnd(fromView, animated, true);
2860             dispatchOnLauncherTransitionPrepare(toView, animated, true);
2861             dispatchOnLauncherTransitionStart(toView, animated, true);
2862             dispatchOnLauncherTransitionEnd(toView, animated, true);
2863             mWorkspace.hideScrollingIndicator(false);
2864         }
2865     }
2866 
2867     @Override
onTrimMemory(int level)2868     public void onTrimMemory(int level) {
2869         super.onTrimMemory(level);
2870         if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
2871             mAppsCustomizeTabHost.onTrimMemory();
2872         }
2873     }
2874 
2875     @Override
onWindowFocusChanged(boolean hasFocus)2876     public void onWindowFocusChanged(boolean hasFocus) {
2877         if (!hasFocus) {
2878             // When another window occludes launcher (like the notification shade, or recents),
2879             // ensure that we enable the wallpaper flag so that transitions are done correctly.
2880             updateWallpaperVisibility(true);
2881         } else {
2882             // When launcher has focus again, disable the wallpaper if we are in AllApps
2883             mWorkspace.postDelayed(new Runnable() {
2884                 @Override
2885                 public void run() {
2886                     disableWallpaperIfInAllApps();
2887                 }
2888             }, 500);
2889         }
2890     }
2891 
showWorkspace(boolean animated)2892     void showWorkspace(boolean animated) {
2893         showWorkspace(animated, null);
2894     }
2895 
showWorkspace(boolean animated, Runnable onCompleteRunnable)2896     void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
2897         if (mState != State.WORKSPACE) {
2898             boolean wasInSpringLoadedMode = (mState == State.APPS_CUSTOMIZE_SPRING_LOADED);
2899             mWorkspace.setVisibility(View.VISIBLE);
2900             hideAppsCustomizeHelper(State.WORKSPACE, animated, false, onCompleteRunnable);
2901 
2902             // Show the search bar (only animate if we were showing the drop target bar in spring
2903             // loaded mode)
2904             if (mSearchDropTargetBar != null) {
2905                 mSearchDropTargetBar.showSearchBar(wasInSpringLoadedMode);
2906             }
2907 
2908             // We only need to animate in the dock divider if we're going from spring loaded mode
2909             showDockDivider(animated && wasInSpringLoadedMode);
2910 
2911             // Set focus to the AppsCustomize button
2912             if (mAllAppsButton != null) {
2913                 mAllAppsButton.requestFocus();
2914             }
2915         }
2916 
2917         mWorkspace.flashScrollingIndicator(animated);
2918 
2919         // Change the state *after* we've called all the transition code
2920         mState = State.WORKSPACE;
2921 
2922         // Resume the auto-advance of widgets
2923         mUserPresent = true;
2924         updateRunning();
2925 
2926         // Send an accessibility event to announce the context change
2927         getWindow().getDecorView()
2928                 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
2929     }
2930 
showAllApps(boolean animated)2931     void showAllApps(boolean animated) {
2932         if (mState != State.WORKSPACE) return;
2933 
2934         showAppsCustomizeHelper(animated, false);
2935         mAppsCustomizeTabHost.requestFocus();
2936 
2937         // Change the state *after* we've called all the transition code
2938         mState = State.APPS_CUSTOMIZE;
2939 
2940         // Pause the auto-advance of widgets until we are out of AllApps
2941         mUserPresent = false;
2942         updateRunning();
2943         closeFolder();
2944 
2945         // Send an accessibility event to announce the context change
2946         getWindow().getDecorView()
2947                 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
2948     }
2949 
enterSpringLoadedDragMode()2950     void enterSpringLoadedDragMode() {
2951         if (isAllAppsVisible()) {
2952             hideAppsCustomizeHelper(State.APPS_CUSTOMIZE_SPRING_LOADED, true, true, null);
2953             hideDockDivider();
2954             mState = State.APPS_CUSTOMIZE_SPRING_LOADED;
2955         }
2956     }
2957 
exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay, final Runnable onCompleteRunnable)2958     void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay,
2959             final Runnable onCompleteRunnable) {
2960         if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return;
2961 
2962         mHandler.postDelayed(new Runnable() {
2963             @Override
2964             public void run() {
2965                 if (successfulDrop) {
2966                     // Before we show workspace, hide all apps again because
2967                     // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
2968                     // clean up our state transition functions
2969                     mAppsCustomizeTabHost.setVisibility(View.GONE);
2970                     showWorkspace(true, onCompleteRunnable);
2971                 } else {
2972                     exitSpringLoadedDragMode();
2973                 }
2974             }
2975         }, (extendedDelay ?
2976                 EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT :
2977                 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT));
2978     }
2979 
exitSpringLoadedDragMode()2980     void exitSpringLoadedDragMode() {
2981         if (mState == State.APPS_CUSTOMIZE_SPRING_LOADED) {
2982             final boolean animated = true;
2983             final boolean springLoaded = true;
2984             showAppsCustomizeHelper(animated, springLoaded);
2985             mState = State.APPS_CUSTOMIZE;
2986         }
2987         // Otherwise, we are not in spring loaded mode, so don't do anything.
2988     }
2989 
hideDockDivider()2990     void hideDockDivider() {
2991         if (mQsbDivider != null && mDockDivider != null) {
2992             mQsbDivider.setVisibility(View.INVISIBLE);
2993             mDockDivider.setVisibility(View.INVISIBLE);
2994         }
2995     }
2996 
showDockDivider(boolean animated)2997     void showDockDivider(boolean animated) {
2998         if (mQsbDivider != null && mDockDivider != null) {
2999             mQsbDivider.setVisibility(View.VISIBLE);
3000             mDockDivider.setVisibility(View.VISIBLE);
3001             if (mDividerAnimator != null) {
3002                 mDividerAnimator.cancel();
3003                 mQsbDivider.setAlpha(1f);
3004                 mDockDivider.setAlpha(1f);
3005                 mDividerAnimator = null;
3006             }
3007             if (animated) {
3008                 mDividerAnimator = LauncherAnimUtils.createAnimatorSet();
3009                 mDividerAnimator.playTogether(LauncherAnimUtils.ofFloat(mQsbDivider, "alpha", 1f),
3010                         LauncherAnimUtils.ofFloat(mDockDivider, "alpha", 1f));
3011                 int duration = 0;
3012                 if (mSearchDropTargetBar != null) {
3013                     duration = mSearchDropTargetBar.getTransitionInDuration();
3014                 }
3015                 mDividerAnimator.setDuration(duration);
3016                 mDividerAnimator.start();
3017             }
3018         }
3019     }
3020 
lockAllApps()3021     void lockAllApps() {
3022         // TODO
3023     }
3024 
unlockAllApps()3025     void unlockAllApps() {
3026         // TODO
3027     }
3028 
3029     /**
3030      * Shows the hotseat area.
3031      */
showHotseat(boolean animated)3032     void showHotseat(boolean animated) {
3033         if (!LauncherApplication.isScreenLarge()) {
3034             if (animated) {
3035                 if (mHotseat.getAlpha() != 1f) {
3036                     int duration = 0;
3037                     if (mSearchDropTargetBar != null) {
3038                         duration = mSearchDropTargetBar.getTransitionInDuration();
3039                     }
3040                     mHotseat.animate().alpha(1f).setDuration(duration);
3041                 }
3042             } else {
3043                 mHotseat.setAlpha(1f);
3044             }
3045         }
3046     }
3047 
3048     /**
3049      * Hides the hotseat area.
3050      */
hideHotseat(boolean animated)3051     void hideHotseat(boolean animated) {
3052         if (!LauncherApplication.isScreenLarge()) {
3053             if (animated) {
3054                 if (mHotseat.getAlpha() != 0f) {
3055                     int duration = 0;
3056                     if (mSearchDropTargetBar != null) {
3057                         duration = mSearchDropTargetBar.getTransitionOutDuration();
3058                     }
3059                     mHotseat.animate().alpha(0f).setDuration(duration);
3060                 }
3061             } else {
3062                 mHotseat.setAlpha(0f);
3063             }
3064         }
3065     }
3066 
3067     /**
3068      * Add an item from all apps or customize onto the given workspace screen.
3069      * If layout is null, add to the current screen.
3070      */
addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout)3071     void addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout) {
3072         if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) {
3073             showOutOfSpaceMessage(isHotseatLayout(layout));
3074         }
3075     }
3076 
3077     /** Maps the current orientation to an index for referencing orientation correct global icons */
getCurrentOrientationIndexForGlobalIcons()3078     private int getCurrentOrientationIndexForGlobalIcons() {
3079         // default - 0, landscape - 1
3080         switch (getResources().getConfiguration().orientation) {
3081         case Configuration.ORIENTATION_LANDSCAPE:
3082             return 1;
3083         default:
3084             return 0;
3085         }
3086     }
3087 
getExternalPackageToolbarIcon(ComponentName activityName, String resourceName)3088     private Drawable getExternalPackageToolbarIcon(ComponentName activityName, String resourceName) {
3089         try {
3090             PackageManager packageManager = getPackageManager();
3091             // Look for the toolbar icon specified in the activity meta-data
3092             Bundle metaData = packageManager.getActivityInfo(
3093                     activityName, PackageManager.GET_META_DATA).metaData;
3094             if (metaData != null) {
3095                 int iconResId = metaData.getInt(resourceName);
3096                 if (iconResId != 0) {
3097                     Resources res = packageManager.getResourcesForActivity(activityName);
3098                     return res.getDrawable(iconResId);
3099                 }
3100             }
3101         } catch (NameNotFoundException e) {
3102             // This can happen if the activity defines an invalid drawable
3103             Log.w(TAG, "Failed to load toolbar icon; " + activityName.flattenToShortString() +
3104                     " not found", e);
3105         } catch (Resources.NotFoundException nfe) {
3106             // This can happen if the activity defines an invalid drawable
3107             Log.w(TAG, "Failed to load toolbar icon from " + activityName.flattenToShortString(),
3108                     nfe);
3109         }
3110         return null;
3111     }
3112 
3113     // if successful in getting icon, return it; otherwise, set button to use default drawable
updateTextButtonWithIconFromExternalActivity( int buttonId, ComponentName activityName, int fallbackDrawableId, String toolbarResourceName)3114     private Drawable.ConstantState updateTextButtonWithIconFromExternalActivity(
3115             int buttonId, ComponentName activityName, int fallbackDrawableId,
3116             String toolbarResourceName) {
3117         Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName);
3118         Resources r = getResources();
3119         int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width);
3120         int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height);
3121 
3122         TextView button = (TextView) findViewById(buttonId);
3123         // If we were unable to find the icon via the meta-data, use a generic one
3124         if (toolbarIcon == null) {
3125             toolbarIcon = r.getDrawable(fallbackDrawableId);
3126             toolbarIcon.setBounds(0, 0, w, h);
3127             if (button != null) {
3128                 button.setCompoundDrawables(toolbarIcon, null, null, null);
3129             }
3130             return null;
3131         } else {
3132             toolbarIcon.setBounds(0, 0, w, h);
3133             if (button != null) {
3134                 button.setCompoundDrawables(toolbarIcon, null, null, null);
3135             }
3136             return toolbarIcon.getConstantState();
3137         }
3138     }
3139 
3140     // if successful in getting icon, return it; otherwise, set button to use default drawable
updateButtonWithIconFromExternalActivity( int buttonId, ComponentName activityName, int fallbackDrawableId, String toolbarResourceName)3141     private Drawable.ConstantState updateButtonWithIconFromExternalActivity(
3142             int buttonId, ComponentName activityName, int fallbackDrawableId,
3143             String toolbarResourceName) {
3144         ImageView button = (ImageView) findViewById(buttonId);
3145         Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName);
3146 
3147         if (button != null) {
3148             // If we were unable to find the icon via the meta-data, use a
3149             // generic one
3150             if (toolbarIcon == null) {
3151                 button.setImageResource(fallbackDrawableId);
3152             } else {
3153                 button.setImageDrawable(toolbarIcon);
3154             }
3155         }
3156 
3157         return toolbarIcon != null ? toolbarIcon.getConstantState() : null;
3158 
3159     }
3160 
updateTextButtonWithDrawable(int buttonId, Drawable d)3161     private void updateTextButtonWithDrawable(int buttonId, Drawable d) {
3162         TextView button = (TextView) findViewById(buttonId);
3163         button.setCompoundDrawables(d, null, null, null);
3164     }
3165 
updateButtonWithDrawable(int buttonId, Drawable.ConstantState d)3166     private void updateButtonWithDrawable(int buttonId, Drawable.ConstantState d) {
3167         ImageView button = (ImageView) findViewById(buttonId);
3168         button.setImageDrawable(d.newDrawable(getResources()));
3169     }
3170 
invalidatePressedFocusedStates(View container, View button)3171     private void invalidatePressedFocusedStates(View container, View button) {
3172         if (container instanceof HolographicLinearLayout) {
3173             HolographicLinearLayout layout = (HolographicLinearLayout) container;
3174             layout.invalidatePressedFocusedStates();
3175         } else if (button instanceof HolographicImageView) {
3176             HolographicImageView view = (HolographicImageView) button;
3177             view.invalidatePressedFocusedStates();
3178         }
3179     }
3180 
updateGlobalSearchIcon()3181     private boolean updateGlobalSearchIcon() {
3182         final View searchButtonContainer = findViewById(R.id.search_button_container);
3183         final ImageView searchButton = (ImageView) findViewById(R.id.search_button);
3184         final View voiceButtonContainer = findViewById(R.id.voice_button_container);
3185         final View voiceButton = findViewById(R.id.voice_button);
3186         final View voiceButtonProxy = findViewById(R.id.voice_button_proxy);
3187 
3188         final SearchManager searchManager =
3189                 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
3190         ComponentName activityName = searchManager.getGlobalSearchActivity();
3191         if (activityName != null) {
3192             int coi = getCurrentOrientationIndexForGlobalIcons();
3193             sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
3194                     R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo,
3195                     TOOLBAR_SEARCH_ICON_METADATA_NAME);
3196             if (sGlobalSearchIcon[coi] == null) {
3197                 sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
3198                         R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo,
3199                         TOOLBAR_ICON_METADATA_NAME);
3200             }
3201 
3202             if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.VISIBLE);
3203             searchButton.setVisibility(View.VISIBLE);
3204             invalidatePressedFocusedStates(searchButtonContainer, searchButton);
3205             return true;
3206         } else {
3207             // We disable both search and voice search when there is no global search provider
3208             if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.GONE);
3209             if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
3210             searchButton.setVisibility(View.GONE);
3211             voiceButton.setVisibility(View.GONE);
3212             if (voiceButtonProxy != null) {
3213                 voiceButtonProxy.setVisibility(View.GONE);
3214             }
3215             return false;
3216         }
3217     }
3218 
updateGlobalSearchIcon(Drawable.ConstantState d)3219     private void updateGlobalSearchIcon(Drawable.ConstantState d) {
3220         final View searchButtonContainer = findViewById(R.id.search_button_container);
3221         final View searchButton = (ImageView) findViewById(R.id.search_button);
3222         updateButtonWithDrawable(R.id.search_button, d);
3223         invalidatePressedFocusedStates(searchButtonContainer, searchButton);
3224     }
3225 
updateVoiceSearchIcon(boolean searchVisible)3226     private boolean updateVoiceSearchIcon(boolean searchVisible) {
3227         final View voiceButtonContainer = findViewById(R.id.voice_button_container);
3228         final View voiceButton = findViewById(R.id.voice_button);
3229         final View voiceButtonProxy = findViewById(R.id.voice_button_proxy);
3230 
3231         // We only show/update the voice search icon if the search icon is enabled as well
3232         final SearchManager searchManager =
3233                 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
3234         ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
3235 
3236         ComponentName activityName = null;
3237         if (globalSearchActivity != null) {
3238             // Check if the global search activity handles voice search
3239             Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
3240             intent.setPackage(globalSearchActivity.getPackageName());
3241             activityName = intent.resolveActivity(getPackageManager());
3242         }
3243 
3244         if (activityName == null) {
3245             // Fallback: check if an activity other than the global search activity
3246             // resolves this
3247             Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
3248             activityName = intent.resolveActivity(getPackageManager());
3249         }
3250         if (searchVisible && activityName != null) {
3251             int coi = getCurrentOrientationIndexForGlobalIcons();
3252             sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
3253                     R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo,
3254                     TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME);
3255             if (sVoiceSearchIcon[coi] == null) {
3256                 sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
3257                         R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo,
3258                         TOOLBAR_ICON_METADATA_NAME);
3259             }
3260             if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.VISIBLE);
3261             voiceButton.setVisibility(View.VISIBLE);
3262             if (voiceButtonProxy != null) {
3263                 voiceButtonProxy.setVisibility(View.VISIBLE);
3264             }
3265             invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
3266             return true;
3267         } else {
3268             if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
3269             voiceButton.setVisibility(View.GONE);
3270             if (voiceButtonProxy != null) {
3271                 voiceButtonProxy.setVisibility(View.GONE);
3272             }
3273             return false;
3274         }
3275     }
3276 
updateVoiceSearchIcon(Drawable.ConstantState d)3277     private void updateVoiceSearchIcon(Drawable.ConstantState d) {
3278         final View voiceButtonContainer = findViewById(R.id.voice_button_container);
3279         final View voiceButton = findViewById(R.id.voice_button);
3280         updateButtonWithDrawable(R.id.voice_button, d);
3281         invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
3282     }
3283 
3284     /**
3285      * Sets the app market icon
3286      */
updateAppMarketIcon()3287     private void updateAppMarketIcon() {
3288         final View marketButton = findViewById(R.id.market_button);
3289         Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET);
3290         // Find the app market activity by resolving an intent.
3291         // (If multiple app markets are installed, it will return the ResolverActivity.)
3292         ComponentName activityName = intent.resolveActivity(getPackageManager());
3293         if (activityName != null) {
3294             int coi = getCurrentOrientationIndexForGlobalIcons();
3295             mAppMarketIntent = intent;
3296             sAppMarketIcon[coi] = updateTextButtonWithIconFromExternalActivity(
3297                     R.id.market_button, activityName, R.drawable.ic_launcher_market_holo,
3298                     TOOLBAR_ICON_METADATA_NAME);
3299             marketButton.setVisibility(View.VISIBLE);
3300         } else {
3301             // We should hide and disable the view so that we don't try and restore the visibility
3302             // of it when we swap between drag & normal states from IconDropTarget subclasses.
3303             marketButton.setVisibility(View.GONE);
3304             marketButton.setEnabled(false);
3305         }
3306     }
3307 
updateAppMarketIcon(Drawable.ConstantState d)3308     private void updateAppMarketIcon(Drawable.ConstantState d) {
3309         // Ensure that the new drawable we are creating has the approprate toolbar icon bounds
3310         Resources r = getResources();
3311         Drawable marketIconDrawable = d.newDrawable(r);
3312         int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width);
3313         int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height);
3314         marketIconDrawable.setBounds(0, 0, w, h);
3315 
3316         updateTextButtonWithDrawable(R.id.market_button, marketIconDrawable);
3317     }
3318 
3319     @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)3320     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
3321         final boolean result = super.dispatchPopulateAccessibilityEvent(event);
3322         final List<CharSequence> text = event.getText();
3323         text.clear();
3324         // Populate event with a fake title based on the current state.
3325         if (mState == State.APPS_CUSTOMIZE) {
3326             text.add(getString(R.string.all_apps_button_label));
3327         } else {
3328             text.add(getString(R.string.all_apps_home_button_label));
3329         }
3330         return result;
3331     }
3332 
3333     /**
3334      * Receives notifications when system dialogs are to be closed.
3335      */
3336     private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
3337         @Override
onReceive(Context context, Intent intent)3338         public void onReceive(Context context, Intent intent) {
3339             closeSystemDialogs();
3340         }
3341     }
3342 
3343     /**
3344      * Receives notifications whenever the appwidgets are reset.
3345      */
3346     private class AppWidgetResetObserver extends ContentObserver {
AppWidgetResetObserver()3347         public AppWidgetResetObserver() {
3348             super(new Handler());
3349         }
3350 
3351         @Override
onChange(boolean selfChange)3352         public void onChange(boolean selfChange) {
3353             onAppWidgetReset();
3354         }
3355     }
3356 
3357     /**
3358      * If the activity is currently paused, signal that we need to run the passed Runnable
3359      * in onResume.
3360      *
3361      * This needs to be called from incoming places where resources might have been loaded
3362      * while we are paused.  That is becaues the Configuration might be wrong
3363      * when we're not running, and if it comes back to what it was when we
3364      * were paused, we are not restarted.
3365      *
3366      * Implementation of the method from LauncherModel.Callbacks.
3367      *
3368      * @return true if we are currently paused.  The caller might be able to
3369      * skip some work in that case since we will come back again.
3370      */
waitUntilResume(Runnable run, boolean deletePreviousRunnables)3371     private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
3372         if (mPaused) {
3373             Log.i(TAG, "Deferring update until onResume");
3374             if (deletePreviousRunnables) {
3375                 while (mOnResumeCallbacks.remove(run)) {
3376                 }
3377             }
3378             mOnResumeCallbacks.add(run);
3379             return true;
3380         } else {
3381             return false;
3382         }
3383     }
3384 
waitUntilResume(Runnable run)3385     private boolean waitUntilResume(Runnable run) {
3386         return waitUntilResume(run, false);
3387     }
3388 
3389     /**
3390      * If the activity is currently paused, signal that we need to re-run the loader
3391      * in onResume.
3392      *
3393      * This needs to be called from incoming places where resources might have been loaded
3394      * while we are paused.  That is becaues the Configuration might be wrong
3395      * when we're not running, and if it comes back to what it was when we
3396      * were paused, we are not restarted.
3397      *
3398      * Implementation of the method from LauncherModel.Callbacks.
3399      *
3400      * @return true if we are currently paused.  The caller might be able to
3401      * skip some work in that case since we will come back again.
3402      */
setLoadOnResume()3403     public boolean setLoadOnResume() {
3404         if (mPaused) {
3405             Log.i(TAG, "setLoadOnResume");
3406             mOnResumeNeedsLoad = true;
3407             return true;
3408         } else {
3409             return false;
3410         }
3411     }
3412 
3413     /**
3414      * Implementation of the method from LauncherModel.Callbacks.
3415      */
getCurrentWorkspaceScreen()3416     public int getCurrentWorkspaceScreen() {
3417         if (mWorkspace != null) {
3418             return mWorkspace.getCurrentPage();
3419         } else {
3420             return SCREEN_COUNT / 2;
3421         }
3422     }
3423 
3424     /**
3425      * Refreshes the shortcuts shown on the workspace.
3426      *
3427      * Implementation of the method from LauncherModel.Callbacks.
3428      */
startBinding()3429     public void startBinding() {
3430         // If we're starting binding all over again, clear any bind calls we'd postponed in
3431         // the past (see waitUntilResume) -- we don't need them since we're starting binding
3432         // from scratch again
3433         mOnResumeCallbacks.clear();
3434 
3435         final Workspace workspace = mWorkspace;
3436         mNewShortcutAnimatePage = -1;
3437         mNewShortcutAnimateViews.clear();
3438         mWorkspace.clearDropTargets();
3439         int count = workspace.getChildCount();
3440         for (int i = 0; i < count; i++) {
3441             // Use removeAllViewsInLayout() to avoid an extra requestLayout() and invalidate().
3442             final CellLayout layoutParent = (CellLayout) workspace.getChildAt(i);
3443             layoutParent.removeAllViewsInLayout();
3444         }
3445         mWidgetsToAdvance.clear();
3446         if (mHotseat != null) {
3447             mHotseat.resetLayout();
3448         }
3449     }
3450 
3451     /**
3452      * Bind the items start-end from the list.
3453      *
3454      * Implementation of the method from LauncherModel.Callbacks.
3455      */
bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end)3456     public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end) {
3457         if (waitUntilResume(new Runnable() {
3458                 public void run() {
3459                     bindItems(shortcuts, start, end);
3460                 }
3461             })) {
3462             return;
3463         }
3464 
3465         // Get the list of added shortcuts and intersect them with the set of shortcuts here
3466         Set<String> newApps = new HashSet<String>();
3467         newApps = mSharedPrefs.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, newApps);
3468 
3469         Workspace workspace = mWorkspace;
3470         for (int i = start; i < end; i++) {
3471             final ItemInfo item = shortcuts.get(i);
3472 
3473             // Short circuit if we are loading dock items for a configuration which has no dock
3474             if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3475                     mHotseat == null) {
3476                 continue;
3477             }
3478 
3479             switch (item.itemType) {
3480                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3481                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3482                     ShortcutInfo info = (ShortcutInfo) item;
3483                     String uri = info.intent.toUri(0).toString();
3484                     View shortcut = createShortcut(info);
3485                     workspace.addInScreen(shortcut, item.container, item.screen, item.cellX,
3486                             item.cellY, 1, 1, false);
3487                     boolean animateIconUp = false;
3488                     synchronized (newApps) {
3489                         if (newApps.contains(uri)) {
3490                             animateIconUp = newApps.remove(uri);
3491                         }
3492                     }
3493                     if (animateIconUp) {
3494                         // Prepare the view to be animated up
3495                         shortcut.setAlpha(0f);
3496                         shortcut.setScaleX(0f);
3497                         shortcut.setScaleY(0f);
3498                         mNewShortcutAnimatePage = item.screen;
3499                         if (!mNewShortcutAnimateViews.contains(shortcut)) {
3500                             mNewShortcutAnimateViews.add(shortcut);
3501                         }
3502                     }
3503                     break;
3504                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3505                     FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
3506                             (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
3507                             (FolderInfo) item, mIconCache);
3508                     workspace.addInScreen(newFolder, item.container, item.screen, item.cellX,
3509                             item.cellY, 1, 1, false);
3510                     break;
3511             }
3512         }
3513 
3514         workspace.requestLayout();
3515     }
3516 
3517     /**
3518      * Implementation of the method from LauncherModel.Callbacks.
3519      */
bindFolders(final HashMap<Long, FolderInfo> folders)3520     public void bindFolders(final HashMap<Long, FolderInfo> folders) {
3521         if (waitUntilResume(new Runnable() {
3522                 public void run() {
3523                     bindFolders(folders);
3524                 }
3525             })) {
3526             return;
3527         }
3528         sFolders.clear();
3529         sFolders.putAll(folders);
3530     }
3531 
3532     /**
3533      * Add the views for a widget to the workspace.
3534      *
3535      * Implementation of the method from LauncherModel.Callbacks.
3536      */
bindAppWidget(final LauncherAppWidgetInfo item)3537     public void bindAppWidget(final LauncherAppWidgetInfo item) {
3538         if (waitUntilResume(new Runnable() {
3539                 public void run() {
3540                     bindAppWidget(item);
3541                 }
3542             })) {
3543             return;
3544         }
3545 
3546         final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
3547         if (DEBUG_WIDGETS) {
3548             Log.d(TAG, "bindAppWidget: " + item);
3549         }
3550         final Workspace workspace = mWorkspace;
3551 
3552         final int appWidgetId = item.appWidgetId;
3553         final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
3554         if (DEBUG_WIDGETS) {
3555             Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider);
3556         }
3557 
3558         item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
3559 
3560         item.hostView.setTag(item);
3561         item.onBindAppWidget(this);
3562 
3563         workspace.addInScreen(item.hostView, item.container, item.screen, item.cellX,
3564                 item.cellY, item.spanX, item.spanY, false);
3565         addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
3566 
3567         workspace.requestLayout();
3568 
3569         if (DEBUG_WIDGETS) {
3570             Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
3571                     + (SystemClock.uptimeMillis()-start) + "ms");
3572         }
3573     }
3574 
onPageBoundSynchronously(int page)3575     public void onPageBoundSynchronously(int page) {
3576         mSynchronouslyBoundPages.add(page);
3577     }
3578 
3579     /**
3580      * Callback saying that there aren't any more items to bind.
3581      *
3582      * Implementation of the method from LauncherModel.Callbacks.
3583      */
finishBindingItems()3584     public void finishBindingItems() {
3585         if (waitUntilResume(new Runnable() {
3586                 public void run() {
3587                     finishBindingItems();
3588                 }
3589             })) {
3590             return;
3591         }
3592         if (mSavedState != null) {
3593             if (!mWorkspace.hasFocus()) {
3594                 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
3595             }
3596             mSavedState = null;
3597         }
3598 
3599         mWorkspace.restoreInstanceStateForRemainingPages();
3600 
3601         // If we received the result of any pending adds while the loader was running (e.g. the
3602         // widget configuration forced an orientation change), process them now.
3603         for (int i = 0; i < sPendingAddList.size(); i++) {
3604             completeAdd(sPendingAddList.get(i));
3605         }
3606         sPendingAddList.clear();
3607 
3608         // Update the market app icon as necessary (the other icons will be managed in response to
3609         // package changes in bindSearchablesChanged()
3610         updateAppMarketIcon();
3611 
3612         // Animate up any icons as necessary
3613         if (mVisible || mWorkspaceLoading) {
3614             Runnable newAppsRunnable = new Runnable() {
3615                 @Override
3616                 public void run() {
3617                     runNewAppsAnimation(false);
3618                 }
3619             };
3620 
3621             boolean willSnapPage = mNewShortcutAnimatePage > -1 &&
3622                     mNewShortcutAnimatePage != mWorkspace.getCurrentPage();
3623             if (canRunNewAppsAnimation()) {
3624                 // If the user has not interacted recently, then either snap to the new page to show
3625                 // the new-apps animation or just run them if they are to appear on the current page
3626                 if (willSnapPage) {
3627                     mWorkspace.snapToPage(mNewShortcutAnimatePage, newAppsRunnable);
3628                 } else {
3629                     runNewAppsAnimation(false);
3630                 }
3631             } else {
3632                 // If the user has interacted recently, then just add the items in place if they
3633                 // are on another page (or just normally if they are added to the current page)
3634                 runNewAppsAnimation(willSnapPage);
3635             }
3636         }
3637 
3638         mWorkspaceLoading = false;
3639     }
3640 
canRunNewAppsAnimation()3641     private boolean canRunNewAppsAnimation() {
3642         long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
3643         return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
3644     }
3645 
3646     /**
3647      * Runs a new animation that scales up icons that were added while Launcher was in the
3648      * background.
3649      *
3650      * @param immediate whether to run the animation or show the results immediately
3651      */
runNewAppsAnimation(boolean immediate)3652     private void runNewAppsAnimation(boolean immediate) {
3653         AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
3654         Collection<Animator> bounceAnims = new ArrayList<Animator>();
3655 
3656         // Order these new views spatially so that they animate in order
3657         Collections.sort(mNewShortcutAnimateViews, new Comparator<View>() {
3658             @Override
3659             public int compare(View a, View b) {
3660                 CellLayout.LayoutParams alp = (CellLayout.LayoutParams) a.getLayoutParams();
3661                 CellLayout.LayoutParams blp = (CellLayout.LayoutParams) b.getLayoutParams();
3662                 int cellCountX = LauncherModel.getCellCountX();
3663                 return (alp.cellY * cellCountX + alp.cellX) - (blp.cellY * cellCountX + blp.cellX);
3664             }
3665         });
3666 
3667         // Animate each of the views in place (or show them immediately if requested)
3668         if (immediate) {
3669             for (View v : mNewShortcutAnimateViews) {
3670                 v.setAlpha(1f);
3671                 v.setScaleX(1f);
3672                 v.setScaleY(1f);
3673             }
3674         } else {
3675             for (int i = 0; i < mNewShortcutAnimateViews.size(); ++i) {
3676                 View v = mNewShortcutAnimateViews.get(i);
3677                 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
3678                         PropertyValuesHolder.ofFloat("alpha", 1f),
3679                         PropertyValuesHolder.ofFloat("scaleX", 1f),
3680                         PropertyValuesHolder.ofFloat("scaleY", 1f));
3681                 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
3682                 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
3683                 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
3684                 bounceAnims.add(bounceAnim);
3685             }
3686             anim.playTogether(bounceAnims);
3687             anim.addListener(new AnimatorListenerAdapter() {
3688                 @Override
3689                 public void onAnimationEnd(Animator animation) {
3690                     if (mWorkspace != null) {
3691                         mWorkspace.postDelayed(mBuildLayersRunnable, 500);
3692                     }
3693                 }
3694             });
3695             anim.start();
3696         }
3697 
3698         // Clean up
3699         mNewShortcutAnimatePage = -1;
3700         mNewShortcutAnimateViews.clear();
3701         new Thread("clearNewAppsThread") {
3702             public void run() {
3703                 mSharedPrefs.edit()
3704                             .putInt(InstallShortcutReceiver.NEW_APPS_PAGE_KEY, -1)
3705                             .putStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, null)
3706                             .commit();
3707             }
3708         }.start();
3709     }
3710 
3711     @Override
bindSearchablesChanged()3712     public void bindSearchablesChanged() {
3713         boolean searchVisible = updateGlobalSearchIcon();
3714         boolean voiceVisible = updateVoiceSearchIcon(searchVisible);
3715         if (mSearchDropTargetBar != null) {
3716             mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
3717         }
3718     }
3719 
3720     /**
3721      * Add the icons for all apps.
3722      *
3723      * Implementation of the method from LauncherModel.Callbacks.
3724      */
bindAllApplications(final ArrayList<ApplicationInfo> apps)3725     public void bindAllApplications(final ArrayList<ApplicationInfo> apps) {
3726         Runnable setAllAppsRunnable = new Runnable() {
3727             public void run() {
3728                 if (mAppsCustomizeContent != null) {
3729                     mAppsCustomizeContent.setApps(apps);
3730                 }
3731             }
3732         };
3733 
3734         // Remove the progress bar entirely; we could also make it GONE
3735         // but better to remove it since we know it's not going to be used
3736         View progressBar = mAppsCustomizeTabHost.
3737             findViewById(R.id.apps_customize_progress_bar);
3738         if (progressBar != null) {
3739             ((ViewGroup)progressBar.getParent()).removeView(progressBar);
3740 
3741             // We just post the call to setApps so the user sees the progress bar
3742             // disappear-- otherwise, it just looks like the progress bar froze
3743             // which doesn't look great
3744             mAppsCustomizeTabHost.post(setAllAppsRunnable);
3745         } else {
3746             // If we did not initialize the spinner in onCreate, then we can directly set the
3747             // list of applications without waiting for any progress bars views to be hidden.
3748             setAllAppsRunnable.run();
3749         }
3750     }
3751 
3752     /**
3753      * A package was installed.
3754      *
3755      * Implementation of the method from LauncherModel.Callbacks.
3756      */
bindAppsAdded(final ArrayList<ApplicationInfo> apps)3757     public void bindAppsAdded(final ArrayList<ApplicationInfo> apps) {
3758         if (waitUntilResume(new Runnable() {
3759                 public void run() {
3760                     bindAppsAdded(apps);
3761                 }
3762             })) {
3763             return;
3764         }
3765 
3766 
3767         if (mAppsCustomizeContent != null) {
3768             mAppsCustomizeContent.addApps(apps);
3769         }
3770     }
3771 
3772     /**
3773      * A package was updated.
3774      *
3775      * Implementation of the method from LauncherModel.Callbacks.
3776      */
bindAppsUpdated(final ArrayList<ApplicationInfo> apps)3777     public void bindAppsUpdated(final ArrayList<ApplicationInfo> apps) {
3778         if (waitUntilResume(new Runnable() {
3779                 public void run() {
3780                     bindAppsUpdated(apps);
3781                 }
3782             })) {
3783             return;
3784         }
3785 
3786         if (mWorkspace != null) {
3787             mWorkspace.updateShortcuts(apps);
3788         }
3789 
3790         if (mAppsCustomizeContent != null) {
3791             mAppsCustomizeContent.updateApps(apps);
3792         }
3793     }
3794 
3795     /**
3796      * A package was uninstalled.  We take both the super set of packageNames
3797      * in addition to specific applications to remove, the reason being that
3798      * this can be called when a package is updated as well.  In that scenario,
3799      * we only remove specific components from the workspace, where as
3800      * package-removal should clear all items by package name.
3801      *
3802      * Implementation of the method from LauncherModel.Callbacks.
3803      */
bindComponentsRemoved(final ArrayList<String> packageNames, final ArrayList<ApplicationInfo> appInfos, final boolean matchPackageNamesOnly, final UserHandle user)3804     public void bindComponentsRemoved(final ArrayList<String> packageNames,
3805                                       final ArrayList<ApplicationInfo> appInfos,
3806             final boolean matchPackageNamesOnly, final UserHandle user) {
3807         if (waitUntilResume(new Runnable() {
3808             public void run() {
3809                 bindComponentsRemoved(packageNames, appInfos, matchPackageNamesOnly, user);
3810             }
3811         })) {
3812             return;
3813         }
3814 
3815         if (matchPackageNamesOnly) {
3816             mWorkspace.removeItemsByPackageName(packageNames, user);
3817         } else {
3818             mWorkspace.removeItemsByApplicationInfo(appInfos, user);
3819         }
3820 
3821         if (mAppsCustomizeContent != null) {
3822             mAppsCustomizeContent.removeApps(appInfos);
3823         }
3824 
3825         // Notify the drag controller
3826         mDragController.onAppsRemoved(appInfos, this);
3827     }
3828 
3829     /**
3830      * A number of packages were updated.
3831      */
3832 
3833     private ArrayList<Object> mWidgetsAndShortcuts;
3834     private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
3835             public void run() {
3836                 bindPackagesUpdated(mWidgetsAndShortcuts);
3837                 mWidgetsAndShortcuts = null;
3838             }
3839         };
3840 
bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts)3841     public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
3842         if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
3843             mWidgetsAndShortcuts = widgetsAndShortcuts;
3844             return;
3845         }
3846 
3847         if (mAppsCustomizeContent != null) {
3848             mAppsCustomizeContent.onPackagesUpdated(widgetsAndShortcuts);
3849         }
3850     }
3851 
mapConfigurationOriActivityInfoOri(int configOri)3852     private int mapConfigurationOriActivityInfoOri(int configOri) {
3853         final Display d = getWindowManager().getDefaultDisplay();
3854         int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
3855         switch (d.getRotation()) {
3856         case Surface.ROTATION_0:
3857         case Surface.ROTATION_180:
3858             // We are currently in the same basic orientation as the natural orientation
3859             naturalOri = configOri;
3860             break;
3861         case Surface.ROTATION_90:
3862         case Surface.ROTATION_270:
3863             // We are currently in the other basic orientation to the natural orientation
3864             naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
3865                     Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
3866             break;
3867         }
3868 
3869         int[] oriMap = {
3870                 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
3871                 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
3872                 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
3873                 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
3874         };
3875         // Since the map starts at portrait, we need to offset if this device's natural orientation
3876         // is landscape.
3877         int indexOffset = 0;
3878         if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
3879             indexOffset = 1;
3880         }
3881         return oriMap[(d.getRotation() + indexOffset) % 4];
3882     }
3883 
isRotationEnabled()3884     public boolean isRotationEnabled() {
3885         boolean enableRotation = sForceEnableRotation ||
3886                 getResources().getBoolean(R.bool.allow_rotation);
3887         return enableRotation;
3888     }
lockScreenOrientation()3889     public void lockScreenOrientation() {
3890         if (isRotationEnabled()) {
3891             setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
3892                     .getConfiguration().orientation));
3893         }
3894     }
unlockScreenOrientation(boolean immediate)3895     public void unlockScreenOrientation(boolean immediate) {
3896         if (isRotationEnabled()) {
3897             if (immediate) {
3898                 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
3899             } else {
3900                 mHandler.postDelayed(new Runnable() {
3901                     public void run() {
3902                         setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
3903                     }
3904                 }, mRestoreScreenOrientationDelay);
3905             }
3906         }
3907     }
3908 
3909     /* Cling related */
isClingsEnabled()3910     private boolean isClingsEnabled() {
3911         // disable clings when running in a test harness
3912         if(ActivityManager.isRunningInTestHarness()) return false;
3913 
3914         // Restricted secondary users (child mode) will potentially have very few apps
3915         // seeded when they start up for the first time. Clings won't work well with that
3916         boolean supportsLimitedUsers =
3917                 android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
3918         Account[] accounts = AccountManager.get(this).getAccounts();
3919         if (supportsLimitedUsers && accounts.length == 0) {
3920             UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
3921             Bundle restrictions = um.getUserRestrictions();
3922             if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
3923                return false;
3924             }
3925         }
3926         // Check if the system has requested skipping of first-use hints.
3927         if (Settings.Secure.getInt(getContentResolver(),
3928                 Settings.Secure.SKIP_FIRST_USE_HINTS, 0) == 1) {
3929             return false;
3930         }
3931         return true;
3932     }
3933 
initCling(int clingId, int[] positionData, boolean animate, int delay)3934     private Cling initCling(int clingId, int[] positionData, boolean animate, int delay) {
3935         final Cling cling = (Cling) findViewById(clingId);
3936         if (cling != null) {
3937             cling.init(this, positionData);
3938             cling.setVisibility(View.VISIBLE);
3939             cling.setLayerType(View.LAYER_TYPE_HARDWARE, null);
3940             if (animate) {
3941                 cling.buildLayer();
3942                 cling.setAlpha(0f);
3943                 cling.animate()
3944                     .alpha(1f)
3945                     .setInterpolator(new AccelerateInterpolator())
3946                     .setDuration(SHOW_CLING_DURATION)
3947                     .setStartDelay(delay)
3948                     .start();
3949             } else {
3950                 cling.setAlpha(1f);
3951             }
3952             cling.setFocusableInTouchMode(true);
3953             cling.post(new Runnable() {
3954                 public void run() {
3955                     cling.setFocusable(true);
3956                     cling.requestFocus();
3957                 }
3958             });
3959             mHideFromAccessibilityHelper.setImportantForAccessibilityToNo(
3960                     mDragLayer, clingId == R.id.all_apps_cling);
3961         }
3962         return cling;
3963     }
3964 
dismissCling(final Cling cling, final String flag, int duration)3965     private void dismissCling(final Cling cling, final String flag, int duration) {
3966         // To catch cases where siblings of top-level views are made invisible, just check whether
3967         // the cling is directly set to GONE before dismissing it.
3968         if (cling != null && cling.getVisibility() != View.GONE) {
3969             ObjectAnimator anim = LauncherAnimUtils.ofFloat(cling, "alpha", 0f);
3970             anim.setDuration(duration);
3971             anim.addListener(new AnimatorListenerAdapter() {
3972                 public void onAnimationEnd(Animator animation) {
3973                     cling.setVisibility(View.GONE);
3974                     cling.cleanup();
3975                     // We should update the shared preferences on a background thread
3976                     new Thread("dismissClingThread") {
3977                         public void run() {
3978                             SharedPreferences.Editor editor = mSharedPrefs.edit();
3979                             editor.putBoolean(flag, true);
3980                             editor.commit();
3981                         }
3982                     }.start();
3983                 };
3984             });
3985             anim.start();
3986             mHideFromAccessibilityHelper.restoreImportantForAccessibility(mDragLayer);
3987         }
3988     }
3989 
removeCling(int id)3990     private void removeCling(int id) {
3991         final View cling = findViewById(id);
3992         if (cling != null) {
3993             final ViewGroup parent = (ViewGroup) cling.getParent();
3994             parent.post(new Runnable() {
3995                 @Override
3996                 public void run() {
3997                     parent.removeView(cling);
3998                 }
3999             });
4000             mHideFromAccessibilityHelper.restoreImportantForAccessibility(mDragLayer);
4001         }
4002     }
4003 
skipCustomClingIfNoAccounts()4004     private boolean skipCustomClingIfNoAccounts() {
4005         Cling cling = (Cling) findViewById(R.id.workspace_cling);
4006         boolean customCling = cling.getDrawIdentifier().equals("workspace_custom");
4007         if (customCling) {
4008             AccountManager am = AccountManager.get(this);
4009             Account[] accounts = am.getAccountsByType("com.google");
4010             return accounts.length == 0;
4011         }
4012         return false;
4013     }
4014 
showFirstRunWorkspaceCling()4015     public void showFirstRunWorkspaceCling() {
4016         // Enable the clings only if they have not been dismissed before
4017         if (isClingsEnabled() &&
4018                 !mSharedPrefs.getBoolean(Cling.WORKSPACE_CLING_DISMISSED_KEY, false) &&
4019                 !skipCustomClingIfNoAccounts() ) {
4020             // If we're not using the default workspace layout, replace workspace cling
4021             // with a custom workspace cling (usually specified in an overlay)
4022             // For now, only do this on tablets
4023             if (mSharedPrefs.getInt(LauncherProvider.DEFAULT_WORKSPACE_RESOURCE_ID, 0) != 0 &&
4024                     getResources().getBoolean(R.bool.config_useCustomClings)) {
4025                 // Use a custom cling
4026                 View cling = findViewById(R.id.workspace_cling);
4027                 ViewGroup clingParent = (ViewGroup) cling.getParent();
4028                 int clingIndex = clingParent.indexOfChild(cling);
4029                 clingParent.removeViewAt(clingIndex);
4030                 View customCling = mInflater.inflate(R.layout.custom_workspace_cling, clingParent, false);
4031                 clingParent.addView(customCling, clingIndex);
4032                 customCling.setId(R.id.workspace_cling);
4033             }
4034             initCling(R.id.workspace_cling, null, false, 0);
4035         } else {
4036             removeCling(R.id.workspace_cling);
4037         }
4038     }
showFirstRunAllAppsCling(int[] position)4039     public void showFirstRunAllAppsCling(int[] position) {
4040         // Enable the clings only if they have not been dismissed before
4041         if (isClingsEnabled() &&
4042                 !mSharedPrefs.getBoolean(Cling.ALLAPPS_CLING_DISMISSED_KEY, false)) {
4043             initCling(R.id.all_apps_cling, position, true, 0);
4044         } else {
4045             removeCling(R.id.all_apps_cling);
4046         }
4047     }
showFirstRunFoldersCling()4048     public Cling showFirstRunFoldersCling() {
4049         // Enable the clings only if they have not been dismissed before
4050         if (isClingsEnabled() &&
4051                 !mSharedPrefs.getBoolean(Cling.FOLDER_CLING_DISMISSED_KEY, false)) {
4052             return initCling(R.id.folder_cling, null, true, 0);
4053         } else {
4054             removeCling(R.id.folder_cling);
4055             return null;
4056         }
4057     }
isFolderClingVisible()4058     public boolean isFolderClingVisible() {
4059         Cling cling = (Cling) findViewById(R.id.folder_cling);
4060         if (cling != null) {
4061             return cling.getVisibility() == View.VISIBLE;
4062         }
4063         return false;
4064     }
dismissWorkspaceCling(View v)4065     public void dismissWorkspaceCling(View v) {
4066         Cling cling = (Cling) findViewById(R.id.workspace_cling);
4067         dismissCling(cling, Cling.WORKSPACE_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION);
4068     }
dismissAllAppsCling(View v)4069     public void dismissAllAppsCling(View v) {
4070         Cling cling = (Cling) findViewById(R.id.all_apps_cling);
4071         dismissCling(cling, Cling.ALLAPPS_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION);
4072     }
dismissFolderCling(View v)4073     public void dismissFolderCling(View v) {
4074         Cling cling = (Cling) findViewById(R.id.folder_cling);
4075         dismissCling(cling, Cling.FOLDER_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION);
4076     }
4077 
4078     /**
4079      * Prints out out state for debugging.
4080      */
dumpState()4081     public void dumpState() {
4082         Log.d(TAG, "BEGIN launcher2 dump state for launcher " + this);
4083         Log.d(TAG, "mSavedState=" + mSavedState);
4084         Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
4085         Log.d(TAG, "mRestoring=" + mRestoring);
4086         Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
4087         Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
4088         Log.d(TAG, "sFolders.size=" + sFolders.size());
4089         mModel.dumpState();
4090 
4091         if (mAppsCustomizeContent != null) {
4092             mAppsCustomizeContent.dumpState();
4093         }
4094         Log.d(TAG, "END launcher2 dump state");
4095     }
4096 
4097     @Override
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)4098     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
4099         super.dump(prefix, fd, writer, args);
4100         writer.println(" ");
4101         writer.println("Debug logs: ");
4102         for (int i = 0; i < sDumpLogs.size(); i++) {
4103             writer.println("  " + sDumpLogs.get(i));
4104         }
4105     }
4106 
dumpDebugLogsToConsole()4107     public static void dumpDebugLogsToConsole() {
4108         Log.d(TAG, "");
4109         Log.d(TAG, "*********************");
4110         Log.d(TAG, "Launcher debug logs: ");
4111         for (int i = 0; i < sDumpLogs.size(); i++) {
4112             Log.d(TAG, "  " + sDumpLogs.get(i));
4113         }
4114         Log.d(TAG, "*********************");
4115         Log.d(TAG, "");
4116     }
4117 }
4118 
4119 interface LauncherTransitionable {
getContent()4120     View getContent();
onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace)4121     void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace)4122     void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
onLauncherTransitionStep(Launcher l, float t)4123     void onLauncherTransitionStep(Launcher l, float t);
onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace)4124     void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
4125 }
4126