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