• 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.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.AnimatorSet;
23 import android.animation.ObjectAnimator;
24 import android.animation.PropertyValuesHolder;
25 import android.animation.ValueAnimator;
26 import android.app.Activity;
27 import android.app.ActivityManager;
28 import android.app.AlertDialog;
29 import android.app.Dialog;
30 import android.app.SearchManager;
31 import android.app.StatusBarManager;
32 import android.appwidget.AppWidgetManager;
33 import android.appwidget.AppWidgetProviderInfo;
34 import android.content.ActivityNotFoundException;
35 import android.content.BroadcastReceiver;
36 import android.content.ClipData;
37 import android.content.ClipDescription;
38 import android.content.ComponentName;
39 import android.content.ContentResolver;
40 import android.content.Context;
41 import android.content.DialogInterface;
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.Rect;
52 import android.graphics.drawable.Drawable;
53 import android.net.Uri;
54 import android.os.AsyncTask;
55 import android.os.Build;
56 import android.os.Bundle;
57 import android.os.Environment;
58 import android.os.Handler;
59 import android.os.Message;
60 import android.os.SystemClock;
61 import android.os.SystemProperties;
62 import android.provider.Settings;
63 import android.speech.RecognizerIntent;
64 import android.text.Selection;
65 import android.text.SpannableStringBuilder;
66 import android.text.TextUtils;
67 import android.text.method.TextKeyListener;
68 import android.util.Log;
69 import android.view.Display;
70 import android.view.HapticFeedbackConstants;
71 import android.view.KeyEvent;
72 import android.view.LayoutInflater;
73 import android.view.Menu;
74 import android.view.MenuItem;
75 import android.view.MotionEvent;
76 import android.view.Surface;
77 import android.view.View;
78 import android.view.ViewGroup;
79 import android.view.WindowManager;
80 import android.view.View.OnLongClickListener;
81 import android.view.accessibility.AccessibilityEvent;
82 import android.view.animation.AccelerateDecelerateInterpolator;
83 import android.view.animation.AccelerateInterpolator;
84 import android.view.animation.DecelerateInterpolator;
85 import android.view.inputmethod.InputMethodManager;
86 import android.widget.Advanceable;
87 import android.widget.EditText;
88 import android.widget.ImageView;
89 import android.widget.TextView;
90 import android.widget.Toast;
91 
92 import com.android.common.Search;
93 import com.android.launcher.R;
94 import com.android.launcher2.DropTarget.DragObject;
95 
96 import java.io.DataInputStream;
97 import java.io.DataOutputStream;
98 import java.io.FileDescriptor;
99 import java.io.FileNotFoundException;
100 import java.io.IOException;
101 import java.io.PrintWriter;
102 import java.util.ArrayList;
103 import java.util.HashMap;
104 
105 /**
106  * Default launcher application.
107  */
108 public final class Launcher extends Activity
109         implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
110                    AllAppsView.Watcher, View.OnTouchListener {
111     static final String TAG = "Launcher";
112     static final boolean LOGD = false;
113 
114     static final boolean PROFILE_STARTUP = false;
115     static final boolean DEBUG_WIDGETS = false;
116 
117     private static final int MENU_GROUP_WALLPAPER = 1;
118     private static final int MENU_WALLPAPER_SETTINGS = Menu.FIRST + 1;
119     private static final int MENU_MANAGE_APPS = MENU_WALLPAPER_SETTINGS + 1;
120     private static final int MENU_SYSTEM_SETTINGS = MENU_MANAGE_APPS + 1;
121     private static final int MENU_HELP = MENU_SYSTEM_SETTINGS + 1;
122 
123     private static final int REQUEST_CREATE_SHORTCUT = 1;
124     private static final int REQUEST_CREATE_APPWIDGET = 5;
125     private static final int REQUEST_PICK_APPLICATION = 6;
126     private static final int REQUEST_PICK_SHORTCUT = 7;
127     private static final int REQUEST_PICK_APPWIDGET = 9;
128     private static final int REQUEST_PICK_WALLPAPER = 10;
129 
130     static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
131 
132     static final int SCREEN_COUNT = 5;
133     static final int DEFAULT_SCREEN = 2;
134 
135     static final int DIALOG_CREATE_SHORTCUT = 1;
136     static final int DIALOG_RENAME_FOLDER = 2;
137 
138     private static final String PREFERENCES = "launcher.preferences";
139 
140     // Type: int
141     private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
142     // Type: int
143     private static final String RUNTIME_STATE = "launcher.state";
144     // Type: int
145     private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
146     // Type: int
147     private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
148     // Type: int
149     private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
150     // Type: int
151     private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
152     // Type: boolean
153     private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
154     // Type: long
155     private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
156 
157     private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
158 
159     /** The different states that Launcher can be in. */
160     private enum State { WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED };
161     private State mState = State.WORKSPACE;
162     private AnimatorSet mStateAnimation;
163 
164     static final int APPWIDGET_HOST_ID = 1024;
165     private static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
166     private static final int EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT = 600;
167     private static final int SHOW_CLING_DURATION = 550;
168     private static final int DISMISS_CLING_DURATION = 250;
169 
170     private static final Object sLock = new Object();
171     private static int sScreen = DEFAULT_SCREEN;
172 
173     private final BroadcastReceiver mCloseSystemDialogsReceiver
174             = new CloseSystemDialogsIntentReceiver();
175     private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
176 
177     private LayoutInflater mInflater;
178 
179     private DragController mDragController;
180     private Workspace mWorkspace;
181 
182     private AppWidgetManager mAppWidgetManager;
183     private LauncherAppWidgetHost mAppWidgetHost;
184 
185     private ItemInfo mPendingAddInfo = new ItemInfo();
186     private int[] mTmpAddItemCellCoordinates = new int[2];
187 
188     private FolderInfo mFolderInfo;
189 
190     private Hotseat mHotseat;
191     private View mAllAppsButton;
192 
193     private SearchDropTargetBar mSearchDropTargetBar;
194     private AppsCustomizeTabHost mAppsCustomizeTabHost;
195     private AppsCustomizePagedView mAppsCustomizeContent;
196     private boolean mAutoAdvanceRunning = false;
197 
198     private Bundle mSavedState;
199 
200     private SpannableStringBuilder mDefaultKeySsb = null;
201 
202     private boolean mWorkspaceLoading = true;
203 
204     private boolean mPaused = true;
205     private boolean mRestoring;
206     private boolean mWaitingForResult;
207     private boolean mOnResumeNeedsLoad;
208 
209     private Bundle mSavedInstanceState;
210 
211     private LauncherModel mModel;
212     private IconCache mIconCache;
213     private boolean mUserPresent = true;
214     private boolean mVisible = false;
215     private boolean mAttached = false;
216 
217     private static LocaleConfiguration sLocaleConfiguration = null;
218 
219     private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
220 
221     private Intent mAppMarketIntent = null;
222 
223     // Related to the auto-advancing of widgets
224     private final int ADVANCE_MSG = 1;
225     private final int mAdvanceInterval = 20000;
226     private final int mAdvanceStagger = 250;
227     private long mAutoAdvanceSentTime;
228     private long mAutoAdvanceTimeLeft = -1;
229     private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
230         new HashMap<View, AppWidgetProviderInfo>();
231 
232     // Determines how long to wait after a rotation before restoring the screen orientation to
233     // match the sensor state.
234     private final int mRestoreScreenOrientationDelay = 500;
235 
236     // External icons saved in case of resource changes, orientation, etc.
237     private static Drawable.ConstantState[] sGlobalSearchIcon = new Drawable.ConstantState[2];
238     private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2];
239     private static Drawable.ConstantState[] sAppMarketIcon = new Drawable.ConstantState[2];
240 
241     static final ArrayList<String> sDumpLogs = new ArrayList<String>();
242 
243     private DragLayer mDragLayer;
244 
245     private BubbleTextView mWaitingForResume;
246 
247     private Runnable mBuildLayersRunnable = new Runnable() {
248         public void run() {
249             if (mWorkspace != null) {
250                 mWorkspace.buildPageHardwareLayers();
251             }
252         }
253     };
254 
255     private static ArrayList<PendingAddArguments> sPendingAddList
256             = new ArrayList<PendingAddArguments>();
257 
258     private static class PendingAddArguments {
259         int requestCode;
260         Intent intent;
261         long container;
262         int screen;
263         int cellX;
264         int cellY;
265     }
266 
267     @Override
onCreate(Bundle savedInstanceState)268     protected void onCreate(Bundle savedInstanceState) {
269         super.onCreate(savedInstanceState);
270         LauncherApplication app = ((LauncherApplication)getApplication());
271         mModel = app.setLauncher(this);
272         mIconCache = app.getIconCache();
273         mDragController = new DragController(this);
274         mInflater = getLayoutInflater();
275 
276         mAppWidgetManager = AppWidgetManager.getInstance(this);
277         mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
278         mAppWidgetHost.startListening();
279 
280         if (PROFILE_STARTUP) {
281             android.os.Debug.startMethodTracing(
282                     Environment.getExternalStorageDirectory() + "/launcher");
283         }
284 
285         checkForLocaleChange();
286         setContentView(R.layout.launcher);
287         setupViews();
288         showFirstRunWorkspaceCling();
289 
290         registerContentObservers();
291 
292         lockAllApps();
293 
294         mSavedState = savedInstanceState;
295         restoreState(mSavedState);
296 
297         // Update customization drawer _after_ restoring the states
298         if (mAppsCustomizeContent != null) {
299             mAppsCustomizeContent.onPackagesUpdated();
300         }
301 
302         if (PROFILE_STARTUP) {
303             android.os.Debug.stopMethodTracing();
304         }
305 
306         if (!mRestoring) {
307             mModel.startLoader(this, true);
308         }
309 
310         if (!mModel.isAllAppsLoaded()) {
311             ViewGroup appsCustomizeContentParent = (ViewGroup) mAppsCustomizeContent.getParent();
312             mInflater.inflate(R.layout.apps_customize_progressbar, appsCustomizeContentParent);
313         }
314 
315         // For handling default keys
316         mDefaultKeySsb = new SpannableStringBuilder();
317         Selection.setSelection(mDefaultKeySsb, 0);
318 
319         IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
320         registerReceiver(mCloseSystemDialogsReceiver, filter);
321 
322         boolean searchVisible = false;
323         boolean voiceVisible = false;
324         // If we have a saved version of these external icons, we load them up immediately
325         int coi = getCurrentOrientationIndexForGlobalIcons();
326         if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null ||
327                 sAppMarketIcon[coi] == null) {
328             updateAppMarketIcon();
329             searchVisible = updateGlobalSearchIcon();
330             voiceVisible = updateVoiceSearchIcon(searchVisible);
331         }
332         if (sGlobalSearchIcon[coi] != null) {
333              updateGlobalSearchIcon(sGlobalSearchIcon[coi]);
334              searchVisible = true;
335         }
336         if (sVoiceSearchIcon[coi] != null) {
337             updateVoiceSearchIcon(sVoiceSearchIcon[coi]);
338             voiceVisible = true;
339         }
340         if (sAppMarketIcon[coi] != null) {
341             updateAppMarketIcon(sAppMarketIcon[coi]);
342         }
343         mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
344 
345         // On large interfaces, we want the screen to auto-rotate based on the current orientation
346         if (LauncherApplication.isScreenLarge() || Build.TYPE.contentEquals("eng")) {
347             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
348         }
349     }
350 
checkForLocaleChange()351     private void checkForLocaleChange() {
352         if (sLocaleConfiguration == null) {
353             new AsyncTask<Void, Void, LocaleConfiguration>() {
354                 @Override
355                 protected LocaleConfiguration doInBackground(Void... unused) {
356                     LocaleConfiguration localeConfiguration = new LocaleConfiguration();
357                     readConfiguration(Launcher.this, localeConfiguration);
358                     return localeConfiguration;
359                 }
360 
361                 @Override
362                 protected void onPostExecute(LocaleConfiguration result) {
363                     sLocaleConfiguration = result;
364                     checkForLocaleChange();  // recursive, but now with a locale configuration
365                 }
366             }.execute();
367             return;
368         }
369 
370         final Configuration configuration = getResources().getConfiguration();
371 
372         final String previousLocale = sLocaleConfiguration.locale;
373         final String locale = configuration.locale.toString();
374 
375         final int previousMcc = sLocaleConfiguration.mcc;
376         final int mcc = configuration.mcc;
377 
378         final int previousMnc = sLocaleConfiguration.mnc;
379         final int mnc = configuration.mnc;
380 
381         boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;
382 
383         if (localeChanged) {
384             sLocaleConfiguration.locale = locale;
385             sLocaleConfiguration.mcc = mcc;
386             sLocaleConfiguration.mnc = mnc;
387 
388             mIconCache.flush();
389 
390             final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
391             new Thread("WriteLocaleConfiguration") {
392                 @Override
393                 public void run() {
394                     writeConfiguration(Launcher.this, localeConfiguration);
395                 }
396             }.start();
397         }
398     }
399 
400     private static class LocaleConfiguration {
401         public String locale;
402         public int mcc = -1;
403         public int mnc = -1;
404     }
405 
readConfiguration(Context context, LocaleConfiguration configuration)406     private static void readConfiguration(Context context, LocaleConfiguration configuration) {
407         DataInputStream in = null;
408         try {
409             in = new DataInputStream(context.openFileInput(PREFERENCES));
410             configuration.locale = in.readUTF();
411             configuration.mcc = in.readInt();
412             configuration.mnc = in.readInt();
413         } catch (FileNotFoundException e) {
414             // Ignore
415         } catch (IOException e) {
416             // Ignore
417         } finally {
418             if (in != null) {
419                 try {
420                     in.close();
421                 } catch (IOException e) {
422                     // Ignore
423                 }
424             }
425         }
426     }
427 
writeConfiguration(Context context, LocaleConfiguration configuration)428     private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
429         DataOutputStream out = null;
430         try {
431             out = new DataOutputStream(context.openFileOutput(PREFERENCES, MODE_PRIVATE));
432             out.writeUTF(configuration.locale);
433             out.writeInt(configuration.mcc);
434             out.writeInt(configuration.mnc);
435             out.flush();
436         } catch (FileNotFoundException e) {
437             // Ignore
438         } catch (IOException e) {
439             //noinspection ResultOfMethodCallIgnored
440             context.getFileStreamPath(PREFERENCES).delete();
441         } finally {
442             if (out != null) {
443                 try {
444                     out.close();
445                 } catch (IOException e) {
446                     // Ignore
447                 }
448             }
449         }
450     }
451 
getDragLayer()452     public DragLayer getDragLayer() {
453         return mDragLayer;
454     }
455 
getScreen()456     static int getScreen() {
457         synchronized (sLock) {
458             return sScreen;
459         }
460     }
461 
setScreen(int screen)462     static void setScreen(int screen) {
463         synchronized (sLock) {
464             sScreen = screen;
465         }
466     }
467 
468     /**
469      * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
470      * a configuration step, this allows the proper animations to run after other transitions.
471      */
completeAdd(PendingAddArguments args)472     private boolean completeAdd(PendingAddArguments args) {
473         boolean result = false;
474         switch (args.requestCode) {
475             case REQUEST_PICK_APPLICATION:
476                 completeAddApplication(args.intent, args.container, args.screen, args.cellX,
477                         args.cellY);
478                 break;
479             case REQUEST_PICK_SHORTCUT:
480                 processShortcut(args.intent);
481                 break;
482             case REQUEST_CREATE_SHORTCUT:
483                 completeAddShortcut(args.intent, args.container, args.screen, args.cellX,
484                         args.cellY);
485                 result = true;
486                 break;
487             case REQUEST_PICK_APPWIDGET:
488                 addAppWidgetFromPick(args.intent);
489                 break;
490             case REQUEST_CREATE_APPWIDGET:
491                 int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
492                 completeAddAppWidget(appWidgetId, args.container, args.screen);
493                 result = true;
494                 break;
495             case REQUEST_PICK_WALLPAPER:
496                 // We just wanted the activity result here so we can clear mWaitingForResult
497                 break;
498         }
499         // In any situation where we have a multi-step drop, we should reset the add info only after
500         // we complete the drop
501         resetAddInfo();
502         return result;
503     }
504 
505     @Override
onActivityResult(final int requestCode, int resultCode, final Intent data)506     protected void onActivityResult(final int requestCode, int resultCode, final Intent data) {
507         boolean delayExitSpringLoadedMode = false;
508         mWaitingForResult = false;
509 
510         // The pattern used here is that a user PICKs a specific application,
511         // which, depending on the target, might need to CREATE the actual target.
512 
513         // For example, the user would PICK_SHORTCUT for "Music playlist", and we
514         // launch over to the Music app to actually CREATE_SHORTCUT.
515         if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
516             final PendingAddArguments args = new PendingAddArguments();
517             args.requestCode = requestCode;
518             args.intent = data;
519             args.container = mPendingAddInfo.container;
520             args.screen = mPendingAddInfo.screen;
521             args.cellX = mPendingAddInfo.cellX;
522             args.cellY = mPendingAddInfo.cellY;
523 
524             // If the loader is still running, defer the add until it is done.
525             if (isWorkspaceLocked()) {
526                 sPendingAddList.add(args);
527             } else {
528                 delayExitSpringLoadedMode = completeAdd(args);
529             }
530         } else if ((requestCode == REQUEST_PICK_APPWIDGET ||
531                 requestCode == REQUEST_CREATE_APPWIDGET) && resultCode == RESULT_CANCELED) {
532             if (data != null) {
533                 // Clean up the appWidgetId if we canceled
534                 int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
535                 if (appWidgetId != -1) {
536                     mAppWidgetHost.deleteAppWidgetId(appWidgetId);
537                 }
538             }
539         }
540 
541         // Exit spring loaded mode if necessary after cancelling the configuration of a widget
542         exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode);
543     }
544 
545     @Override
onResume()546     protected void onResume() {
547         super.onResume();
548         mPaused = false;
549         if (mRestoring || mOnResumeNeedsLoad) {
550             mWorkspaceLoading = true;
551             mModel.startLoader(this, true);
552             mRestoring = false;
553             mOnResumeNeedsLoad = false;
554         }
555         if (mWaitingForResume != null) {
556             mWaitingForResume.setStayPressed(false);
557         }
558         // When we resume Launcher, a different Activity might be responsible for the app
559         // market intent, so refresh the icon
560         updateAppMarketIcon();
561         if (!mWorkspaceLoading) {
562             mWorkspace.post(mBuildLayersRunnable);
563         }
564         clearTypedText();
565     }
566 
567     @Override
onPause()568     protected void onPause() {
569         super.onPause();
570         mPaused = true;
571         mDragController.cancelDrag();
572     }
573 
574     @Override
onRetainNonConfigurationInstance()575     public Object onRetainNonConfigurationInstance() {
576         // Flag the loader to stop early before switching
577         mModel.stopLoader();
578         if (mAppsCustomizeContent != null) {
579             mAppsCustomizeContent.surrender();
580         }
581         return Boolean.TRUE;
582     }
583 
584     // We can't hide the IME if it was forced open.  So don't bother
585     /*
586     @Override
587     public void onWindowFocusChanged(boolean hasFocus) {
588         super.onWindowFocusChanged(hasFocus);
589 
590         if (hasFocus) {
591             final InputMethodManager inputManager = (InputMethodManager)
592                     getSystemService(Context.INPUT_METHOD_SERVICE);
593             WindowManager.LayoutParams lp = getWindow().getAttributes();
594             inputManager.hideSoftInputFromWindow(lp.token, 0, new android.os.ResultReceiver(new
595                         android.os.Handler()) {
596                         protected void onReceiveResult(int resultCode, Bundle resultData) {
597                             Log.d(TAG, "ResultReceiver got resultCode=" + resultCode);
598                         }
599                     });
600             Log.d(TAG, "called hideSoftInputFromWindow from onWindowFocusChanged");
601         }
602     }
603     */
604 
acceptFilter()605     private boolean acceptFilter() {
606         final InputMethodManager inputManager = (InputMethodManager)
607                 getSystemService(Context.INPUT_METHOD_SERVICE);
608         return !inputManager.isFullscreenMode();
609     }
610 
611     @Override
onKeyDown(int keyCode, KeyEvent event)612     public boolean onKeyDown(int keyCode, KeyEvent event) {
613         final int uniChar = event.getUnicodeChar();
614         final boolean handled = super.onKeyDown(keyCode, event);
615         final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
616         if (!handled && acceptFilter() && isKeyNotWhitespace) {
617             boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
618                     keyCode, event);
619             if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
620                 // something usable has been typed - start a search
621                 // the typed text will be retrieved and cleared by
622                 // showSearchDialog()
623                 // If there are multiple keystrokes before the search dialog takes focus,
624                 // onSearchRequested() will be called for every keystroke,
625                 // but it is idempotent, so it's fine.
626                 return onSearchRequested();
627             }
628         }
629 
630         // Eat the long press event so the keyboard doesn't come up.
631         if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
632             return true;
633         }
634 
635         return handled;
636     }
637 
getTypedText()638     private String getTypedText() {
639         return mDefaultKeySsb.toString();
640     }
641 
clearTypedText()642     private void clearTypedText() {
643         mDefaultKeySsb.clear();
644         mDefaultKeySsb.clearSpans();
645         Selection.setSelection(mDefaultKeySsb, 0);
646     }
647 
648     /**
649      * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
650      * State
651      */
intToState(int stateOrdinal)652     private static State intToState(int stateOrdinal) {
653         State state = State.WORKSPACE;
654         final State[] stateValues = State.values();
655         for (int i = 0; i < stateValues.length; i++) {
656             if (stateValues[i].ordinal() == stateOrdinal) {
657                 state = stateValues[i];
658                 break;
659             }
660         }
661         return state;
662     }
663 
664     /**
665      * Restores the previous state, if it exists.
666      *
667      * @param savedState The previous state.
668      */
restoreState(Bundle savedState)669     private void restoreState(Bundle savedState) {
670         if (savedState == null) {
671             return;
672         }
673 
674         State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
675         if (state == State.APPS_CUSTOMIZE) {
676             showAllApps(false);
677         }
678 
679         final int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
680         if (currentScreen > -1) {
681             mWorkspace.setCurrentPage(currentScreen);
682         }
683 
684         final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
685         final int pendingAddScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
686 
687         if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
688             mPendingAddInfo.container = pendingAddContainer;
689             mPendingAddInfo.screen = pendingAddScreen;
690             mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
691             mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
692             mRestoring = true;
693         }
694 
695         boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
696         if (renameFolder) {
697             long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
698             mFolderInfo = mModel.getFolderById(this, sFolders, id);
699             mRestoring = true;
700         }
701 
702 
703         // Restore the AppsCustomize tab
704         if (mAppsCustomizeTabHost != null) {
705             String curTab = savedState.getString("apps_customize_currentTab");
706             if (curTab != null) {
707                 // We set this directly so that there is no delay before the tab is set
708                 mAppsCustomizeContent.setContentType(
709                         mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
710                 mAppsCustomizeTabHost.setCurrentTabByTag(curTab);
711                 mAppsCustomizeContent.loadAssociatedPages(
712                         mAppsCustomizeContent.getCurrentPage());
713             }
714 
715             int currentIndex = savedState.getInt("apps_customize_currentIndex");
716             mAppsCustomizeContent.restorePageForIndex(currentIndex);
717         }
718     }
719 
720     /**
721      * Finds all the views we need and configure them properly.
722      */
setupViews()723     private void setupViews() {
724         final DragController dragController = mDragController;
725 
726         mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
727         mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
728 
729         // Setup the drag layer
730         mDragLayer.setup(this, dragController);
731 
732         // Setup the hotseat
733         mHotseat = (Hotseat) findViewById(R.id.hotseat);
734         if (mHotseat != null) {
735             mHotseat.setup(this);
736         }
737 
738         // Setup the workspace
739         mWorkspace.setHapticFeedbackEnabled(false);
740         mWorkspace.setOnLongClickListener(this);
741         mWorkspace.setup(dragController);
742         dragController.addDragListener(mWorkspace);
743 
744         // Get the search/delete bar
745         mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.qsb_bar);
746 
747         // Setup AppsCustomize
748         mAppsCustomizeTabHost = (AppsCustomizeTabHost)
749                 findViewById(R.id.apps_customize_pane);
750         mAppsCustomizeContent = (AppsCustomizePagedView)
751                 mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);
752         mAppsCustomizeContent.setup(this, dragController);
753 
754         // Get the all apps button
755         mAllAppsButton = findViewById(R.id.all_apps_button);
756         if (mAllAppsButton != null) {
757             mAllAppsButton.setOnTouchListener(new View.OnTouchListener() {
758                 @Override
759                 public boolean onTouch(View v, MotionEvent event) {
760                     if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
761                         onTouchDownAllAppsButton(v);
762                     }
763                     return false;
764                 }
765             });
766         }
767         // Setup the drag controller (drop targets have to be added in reverse order in priority)
768         dragController.setDragScoller(mWorkspace);
769         dragController.setScrollView(mDragLayer);
770         dragController.setMoveTarget(mWorkspace);
771         dragController.addDropTarget(mWorkspace);
772         if (mSearchDropTargetBar != null) {
773             mSearchDropTargetBar.setup(this, dragController);
774         }
775     }
776 
777     /**
778      * Creates a view representing a shortcut.
779      *
780      * @param info The data structure describing the shortcut.
781      *
782      * @return A View inflated from R.layout.application.
783      */
createShortcut(ShortcutInfo info)784     View createShortcut(ShortcutInfo info) {
785         return createShortcut(R.layout.application,
786                 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
787     }
788 
789     /**
790      * Creates a view representing a shortcut inflated from the specified resource.
791      *
792      * @param layoutResId The id of the XML layout used to create the shortcut.
793      * @param parent The group the shortcut belongs to.
794      * @param info The data structure describing the shortcut.
795      *
796      * @return A View inflated from layoutResId.
797      */
createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info)798     View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
799         BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
800         favorite.applyFromShortcutInfo(info, mIconCache);
801         favorite.setOnClickListener(this);
802         return favorite;
803     }
804 
805     /**
806      * Add an application shortcut to the workspace.
807      *
808      * @param data The intent describing the application.
809      * @param cellInfo The position on screen where to create the shortcut.
810      */
completeAddApplication(Intent data, long container, int screen, int cellX, int cellY)811     void completeAddApplication(Intent data, long container, int screen, int cellX, int cellY) {
812         final int[] cellXY = mTmpAddItemCellCoordinates;
813         final CellLayout layout = getCellLayout(container, screen);
814 
815         // First we check if we already know the exact location where we want to add this item.
816         if (cellX >= 0 && cellY >= 0) {
817             cellXY[0] = cellX;
818             cellXY[1] = cellY;
819         } else if (!layout.findCellForSpan(cellXY, 1, 1)) {
820             showOutOfSpaceMessage();
821             return;
822         }
823 
824         final ShortcutInfo info = mModel.getShortcutInfo(getPackageManager(), data, this);
825 
826         if (info != null) {
827             info.setActivity(data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK |
828                     Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
829             info.container = ItemInfo.NO_ID;
830             mWorkspace.addApplicationShortcut(info, layout, container, screen, cellXY[0], cellXY[1],
831                     isWorkspaceLocked(), cellX, cellY);
832         } else {
833             Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data);
834         }
835     }
836 
837     /**
838      * Add a shortcut to the workspace.
839      *
840      * @param data The intent describing the shortcut.
841      * @param cellInfo The position on screen where to create the shortcut.
842      */
completeAddShortcut(Intent data, long container, int screen, int cellX, int cellY)843     private void completeAddShortcut(Intent data, long container, int screen, int cellX,
844             int cellY) {
845         int[] cellXY = mTmpAddItemCellCoordinates;
846         int[] touchXY = mPendingAddInfo.dropPos;
847         CellLayout layout = getCellLayout(container, screen);
848 
849         boolean foundCellSpan = false;
850 
851         ShortcutInfo info = mModel.infoFromShortcutIntent(this, data, null);
852         final View view = createShortcut(info);
853 
854         // First we check if we already know the exact location where we want to add this item.
855         if (cellX >= 0 && cellY >= 0) {
856             cellXY[0] = cellX;
857             cellXY[1] = cellY;
858             foundCellSpan = true;
859 
860             // If appropriate, either create a folder or add to an existing folder
861             if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY,
862                     true, null,null)) {
863                 return;
864             }
865             DragObject dragObject = new DragObject();
866             dragObject.dragInfo = info;
867             if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, dragObject, true)) {
868                 return;
869             }
870         } else if (touchXY != null) {
871             // when dragging and dropping, just find the closest free spot
872             int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
873             foundCellSpan = (result != null);
874         } else {
875             foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
876         }
877 
878         if (!foundCellSpan) {
879             showOutOfSpaceMessage();
880             return;
881         }
882 
883         LauncherModel.addItemToDatabase(this, info, container, screen, cellXY[0], cellXY[1], false);
884 
885         if (!mRestoring) {
886             mWorkspace.addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1,
887                     isWorkspaceLocked());
888         }
889     }
890 
891     class Padding {
892         int left = 0;
893         int right = 0;
894         int top = 0;
895         int bottom = 0;
896     }
897 
getPaddingForWidget(ComponentName component)898     Padding getPaddingForWidget(ComponentName component) {
899         PackageManager packageManager = getPackageManager();
900         Padding p = new Padding();
901         android.content.pm.ApplicationInfo appInfo;
902 
903         try {
904             appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0);
905         } catch (Exception e) {
906             // if we can't find the package, return 0 padding
907             return p;
908         }
909 
910         if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
911             Resources r = getResources();
912             // The default padding values are private API currently, but will be added in
913             // API level 15. The current values are (8, 8, 8, 8).
914             p.left = r.getDimensionPixelSize(com.android.internal.
915                     R.dimen.default_app_widget_padding_left);
916             p.right = r.getDimensionPixelSize(com.android.internal.
917                     R.dimen.default_app_widget_padding_right);
918             p.top = r.getDimensionPixelSize(com.android.internal.
919                     R.dimen.default_app_widget_padding_top);
920             p.bottom = r.getDimensionPixelSize(com.android.internal.
921                     R.dimen.default_app_widget_padding_bottom);
922         }
923 
924         return p;
925     }
926 
getSpanForWidget(ComponentName component, int minWidth, int minHeight, int[] spanXY)927     int[] getSpanForWidget(ComponentName component, int minWidth, int minHeight, int[] spanXY) {
928         if (spanXY == null) {
929             spanXY = new int[2];
930         }
931 
932         Padding padding = getPaddingForWidget(component);
933         // We want to account for the extra amount of padding that we are adding to the widget
934         // to ensure that it gets the full amount of space that it has requested
935         int requiredWidth = minWidth + padding.left + padding.right;
936         int requiredHeight = minHeight + padding.top + padding.bottom;
937         return CellLayout.rectToCell(getResources(), requiredWidth, requiredHeight, null);
938     }
939 
getSpanForWidget(AppWidgetProviderInfo info, int[] spanXY)940     int[] getSpanForWidget(AppWidgetProviderInfo info, int[] spanXY) {
941         return getSpanForWidget(info.provider, info.minWidth, info.minHeight, spanXY);
942     }
943 
getMinResizeSpanForWidget(AppWidgetProviderInfo info, int[] spanXY)944     int[] getMinResizeSpanForWidget(AppWidgetProviderInfo info, int[] spanXY) {
945         return getSpanForWidget(info.provider, info.minResizeWidth, info.minResizeHeight, spanXY);
946     }
947 
getSpanForWidget(PendingAddWidgetInfo info, int[] spanXY)948     int[] getSpanForWidget(PendingAddWidgetInfo info, int[] spanXY) {
949         return getSpanForWidget(info.componentName, info.minWidth, info.minHeight, spanXY);
950     }
951 
952     /**
953      * Add a widget to the workspace.
954      *
955      * @param appWidgetId The app widget id
956      * @param cellInfo The position on screen where to create the widget.
957      */
completeAddAppWidget(final int appWidgetId, long container, int screen)958     private void completeAddAppWidget(final int appWidgetId, long container, int screen) {
959         AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
960 
961         // Calculate the grid spans needed to fit this widget
962         CellLayout layout = getCellLayout(container, screen);
963 
964         int[] spanXY = getSpanForWidget(appWidgetInfo, null);
965 
966         // Try finding open space on Launcher screen
967         // We have saved the position to which the widget was dragged-- this really only matters
968         // if we are placing widgets on a "spring-loaded" screen
969         int[] cellXY = mTmpAddItemCellCoordinates;
970         int[] touchXY = mPendingAddInfo.dropPos;
971         boolean foundCellSpan = false;
972         if (mPendingAddInfo.cellX >= 0 && mPendingAddInfo.cellY >= 0) {
973             cellXY[0] = mPendingAddInfo.cellX;
974             cellXY[1] = mPendingAddInfo.cellY;
975             foundCellSpan = true;
976         } else if (touchXY != null) {
977             // when dragging and dropping, just find the closest free spot
978             int[] result = layout.findNearestVacantArea(
979                     touchXY[0], touchXY[1], spanXY[0], spanXY[1], cellXY);
980             foundCellSpan = (result != null);
981         } else {
982             foundCellSpan = layout.findCellForSpan(cellXY, spanXY[0], spanXY[1]);
983         }
984 
985         if (!foundCellSpan) {
986             if (appWidgetId != -1) {
987                 // Deleting an app widget ID is a void call but writes to disk before returning
988                 // to the caller...
989                 new Thread("deleteAppWidgetId") {
990                     public void run() {
991                         mAppWidgetHost.deleteAppWidgetId(appWidgetId);
992                     }
993                 }.start();
994             }
995             showOutOfSpaceMessage();
996             return;
997         }
998 
999         // Build Launcher-specific widget info and save to database
1000         LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId);
1001         launcherInfo.spanX = spanXY[0];
1002         launcherInfo.spanY = spanXY[1];
1003 
1004         LauncherModel.addItemToDatabase(this, launcherInfo,
1005                 container, screen, cellXY[0], cellXY[1], false);
1006 
1007         if (!mRestoring) {
1008             // Perform actual inflation because we're live
1009             launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
1010 
1011             launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
1012             launcherInfo.hostView.setTag(launcherInfo);
1013 
1014             mWorkspace.addInScreen(launcherInfo.hostView, container, screen, cellXY[0], cellXY[1],
1015                     launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
1016 
1017             addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1018         }
1019     }
1020 
1021     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1022         @Override
1023         public void onReceive(Context context, Intent intent) {
1024             final String action = intent.getAction();
1025             if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1026                 mUserPresent = false;
1027                 mDragLayer.clearAllResizeFrames();
1028                 updateRunning();
1029 
1030                 // Reset AllApps to its initial state only if we are not in the middle of
1031                 // processing a multi-step drop
1032                 if (mAppsCustomizeTabHost != null && mPendingAddInfo.container == ItemInfo.NO_ID) {
1033                     mAppsCustomizeTabHost.reset();
1034                     showWorkspace(false);
1035                 }
1036             } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1037                 mUserPresent = true;
1038                 updateRunning();
1039             }
1040         }
1041     };
1042 
1043     @Override
onAttachedToWindow()1044     public void onAttachedToWindow() {
1045         super.onAttachedToWindow();
1046 
1047         // Listen for broadcasts related to user-presence
1048         final IntentFilter filter = new IntentFilter();
1049         filter.addAction(Intent.ACTION_SCREEN_OFF);
1050         filter.addAction(Intent.ACTION_USER_PRESENT);
1051         registerReceiver(mReceiver, filter);
1052 
1053         mAttached = true;
1054         mVisible = true;
1055     }
1056 
1057     @Override
onDetachedFromWindow()1058     public void onDetachedFromWindow() {
1059         super.onDetachedFromWindow();
1060         mVisible = false;
1061         mDragLayer.clearAllResizeFrames();
1062 
1063         if (mAttached) {
1064             unregisterReceiver(mReceiver);
1065             mAttached = false;
1066         }
1067         updateRunning();
1068     }
1069 
onWindowVisibilityChanged(int visibility)1070     public void onWindowVisibilityChanged(int visibility) {
1071         mVisible = visibility == View.VISIBLE;
1072         updateRunning();
1073     }
1074 
sendAdvanceMessage(long delay)1075     private void sendAdvanceMessage(long delay) {
1076         mHandler.removeMessages(ADVANCE_MSG);
1077         Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1078         mHandler.sendMessageDelayed(msg, delay);
1079         mAutoAdvanceSentTime = System.currentTimeMillis();
1080     }
1081 
updateRunning()1082     private void updateRunning() {
1083         boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
1084         if (autoAdvanceRunning != mAutoAdvanceRunning) {
1085             mAutoAdvanceRunning = autoAdvanceRunning;
1086             if (autoAdvanceRunning) {
1087                 long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1088                 sendAdvanceMessage(delay);
1089             } else {
1090                 if (!mWidgetsToAdvance.isEmpty()) {
1091                     mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
1092                             (System.currentTimeMillis() - mAutoAdvanceSentTime));
1093                 }
1094                 mHandler.removeMessages(ADVANCE_MSG);
1095                 mHandler.removeMessages(0); // Remove messages sent using postDelayed()
1096             }
1097         }
1098     }
1099 
1100     private final Handler mHandler = new Handler() {
1101         @Override
1102         public void handleMessage(Message msg) {
1103             if (msg.what == ADVANCE_MSG) {
1104                 int i = 0;
1105                 for (View key: mWidgetsToAdvance.keySet()) {
1106                     final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1107                     final int delay = mAdvanceStagger * i;
1108                     if (v instanceof Advanceable) {
1109                        postDelayed(new Runnable() {
1110                            public void run() {
1111                                ((Advanceable) v).advance();
1112                            }
1113                        }, delay);
1114                     }
1115                     i++;
1116                 }
1117                 sendAdvanceMessage(mAdvanceInterval);
1118             }
1119         }
1120     };
1121 
addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo)1122     void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1123         if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
1124         View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1125         if (v instanceof Advanceable) {
1126             mWidgetsToAdvance.put(hostView, appWidgetInfo);
1127             ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
1128             updateRunning();
1129         }
1130     }
1131 
removeWidgetToAutoAdvance(View hostView)1132     void removeWidgetToAutoAdvance(View hostView) {
1133         if (mWidgetsToAdvance.containsKey(hostView)) {
1134             mWidgetsToAdvance.remove(hostView);
1135             updateRunning();
1136         }
1137     }
1138 
removeAppWidget(LauncherAppWidgetInfo launcherInfo)1139     public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1140         removeWidgetToAutoAdvance(launcherInfo.hostView);
1141         launcherInfo.hostView = null;
1142     }
1143 
showOutOfSpaceMessage()1144     void showOutOfSpaceMessage() {
1145         Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
1146     }
1147 
getAppWidgetHost()1148     public LauncherAppWidgetHost getAppWidgetHost() {
1149         return mAppWidgetHost;
1150     }
1151 
getModel()1152     public LauncherModel getModel() {
1153         return mModel;
1154     }
1155 
closeSystemDialogs()1156     void closeSystemDialogs() {
1157         getWindow().closeAllPanels();
1158 
1159         /**
1160          * We should remove this code when we remove all the dialog code.
1161         try {
1162             dismissDialog(DIALOG_CREATE_SHORTCUT);
1163             // Unlock the workspace if the dialog was showing
1164         } catch (Exception e) {
1165             // An exception is thrown if the dialog is not visible, which is fine
1166         }
1167 
1168         try {
1169             dismissDialog(DIALOG_RENAME_FOLDER);
1170             // Unlock the workspace if the dialog was showing
1171         } catch (Exception e) {
1172             // An exception is thrown if the dialog is not visible, which is fine
1173         }
1174          */
1175 
1176         // Whatever we were doing is hereby canceled.
1177         mWaitingForResult = false;
1178     }
1179 
1180     @Override
onNewIntent(Intent intent)1181     protected void onNewIntent(Intent intent) {
1182         super.onNewIntent(intent);
1183 
1184         // Close the menu
1185         if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1186             // also will cancel mWaitingForResult.
1187             closeSystemDialogs();
1188 
1189             boolean alreadyOnHome = ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
1190                         != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
1191 
1192             Folder openFolder = mWorkspace.getOpenFolder();
1193             // In all these cases, only animate if we're already on home
1194             mWorkspace.exitWidgetResizeMode();
1195             if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
1196                     openFolder == null) {
1197                 mWorkspace.moveToDefaultScreen(true);
1198             }
1199 
1200             closeFolder();
1201             exitSpringLoadedDragMode();
1202             showWorkspace(alreadyOnHome);
1203 
1204             final View v = getWindow().peekDecorView();
1205             if (v != null && v.getWindowToken() != null) {
1206                 InputMethodManager imm = (InputMethodManager)getSystemService(
1207                         INPUT_METHOD_SERVICE);
1208                 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1209             }
1210 
1211             // Reset AllApps to its initial state
1212             if (!alreadyOnHome && mAppsCustomizeTabHost != null) {
1213                 mAppsCustomizeTabHost.reset();
1214             }
1215         }
1216     }
1217 
1218     @Override
onRestoreInstanceState(Bundle savedInstanceState)1219     protected void onRestoreInstanceState(Bundle savedInstanceState) {
1220         // Do not call super here
1221         mSavedInstanceState = savedInstanceState;
1222     }
1223 
1224     @Override
onSaveInstanceState(Bundle outState)1225     protected void onSaveInstanceState(Bundle outState) {
1226         outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPage());
1227         super.onSaveInstanceState(outState);
1228 
1229         outState.putInt(RUNTIME_STATE, mState.ordinal());
1230         // We close any open folder since it will not be re-opened, and we need to make sure
1231         // this state is reflected.
1232         closeFolder();
1233 
1234         if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screen > -1 &&
1235                 mWaitingForResult) {
1236             outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
1237             outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screen);
1238             outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
1239             outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
1240         }
1241 
1242         if (mFolderInfo != null && mWaitingForResult) {
1243             outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
1244             outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
1245         }
1246 
1247         // Save the current AppsCustomize tab
1248         if (mAppsCustomizeTabHost != null) {
1249             String currentTabTag = mAppsCustomizeTabHost.getCurrentTabTag();
1250             if (currentTabTag != null) {
1251                 outState.putString("apps_customize_currentTab", currentTabTag);
1252             }
1253             int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
1254             outState.putInt("apps_customize_currentIndex", currentIndex);
1255         }
1256     }
1257 
1258     @Override
onDestroy()1259     public void onDestroy() {
1260         super.onDestroy();
1261 
1262         // Remove all pending runnables
1263         mHandler.removeMessages(ADVANCE_MSG);
1264         mHandler.removeMessages(0);
1265         mWorkspace.removeCallbacks(mBuildLayersRunnable);
1266 
1267         // Stop callbacks from LauncherModel
1268         LauncherApplication app = ((LauncherApplication) getApplication());
1269         mModel.stopLoader();
1270         app.setLauncher(null);
1271 
1272         try {
1273             mAppWidgetHost.stopListening();
1274         } catch (NullPointerException ex) {
1275             Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
1276         }
1277         mAppWidgetHost = null;
1278 
1279         mWidgetsToAdvance.clear();
1280 
1281         TextKeyListener.getInstance().release();
1282 
1283 
1284         unbindWorkspaceAndHotseatItems();
1285 
1286         getContentResolver().unregisterContentObserver(mWidgetObserver);
1287         unregisterReceiver(mCloseSystemDialogsReceiver);
1288 
1289         ((ViewGroup) mWorkspace.getParent()).removeAllViews();
1290         mWorkspace.removeAllViews();
1291         mWorkspace = null;
1292         mDragController = null;
1293 
1294         ValueAnimator.clearAllAnimations();
1295     }
1296 
getDragController()1297     public DragController getDragController() {
1298         return mDragController;
1299     }
1300 
1301     @Override
startActivityForResult(Intent intent, int requestCode)1302     public void startActivityForResult(Intent intent, int requestCode) {
1303         if (requestCode >= 0) mWaitingForResult = true;
1304         super.startActivityForResult(intent, requestCode);
1305     }
1306 
1307     /**
1308      * Indicates that we want global search for this activity by setting the globalSearch
1309      * argument for {@link #startSearch} to true.
1310      */
1311     @Override
startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, boolean globalSearch)1312     public void startSearch(String initialQuery, boolean selectInitialQuery,
1313             Bundle appSearchData, boolean globalSearch) {
1314 
1315         showWorkspace(true);
1316 
1317         if (initialQuery == null) {
1318             // Use any text typed in the launcher as the initial query
1319             initialQuery = getTypedText();
1320         }
1321         if (appSearchData == null) {
1322             appSearchData = new Bundle();
1323             appSearchData.putString(Search.SOURCE, "launcher-search");
1324         }
1325 
1326         final SearchManager searchManager =
1327                 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
1328         searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
1329             appSearchData, globalSearch);
1330     }
1331 
1332     @Override
onCreateOptionsMenu(Menu menu)1333     public boolean onCreateOptionsMenu(Menu menu) {
1334         if (isWorkspaceLocked()) {
1335             return false;
1336         }
1337 
1338         super.onCreateOptionsMenu(menu);
1339 
1340         Intent manageApps = new Intent(Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS);
1341         manageApps.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1342                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1343         Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS);
1344         settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1345                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1346         String helpUrl = getString(R.string.help_url);
1347         Intent help = new Intent(Intent.ACTION_VIEW, Uri.parse(helpUrl));
1348         help.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1349                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1350 
1351         menu.add(MENU_GROUP_WALLPAPER, MENU_WALLPAPER_SETTINGS, 0, R.string.menu_wallpaper)
1352             .setIcon(android.R.drawable.ic_menu_gallery)
1353             .setAlphabeticShortcut('W');
1354         menu.add(0, MENU_MANAGE_APPS, 0, R.string.menu_manage_apps)
1355             .setIcon(android.R.drawable.ic_menu_manage)
1356             .setIntent(manageApps)
1357             .setAlphabeticShortcut('M');
1358         menu.add(0, MENU_SYSTEM_SETTINGS, 0, R.string.menu_settings)
1359             .setIcon(android.R.drawable.ic_menu_preferences)
1360             .setIntent(settings)
1361             .setAlphabeticShortcut('P');
1362         if (!helpUrl.isEmpty()) {
1363             menu.add(0, MENU_HELP, 0, R.string.menu_help)
1364                 .setIcon(android.R.drawable.ic_menu_help)
1365                 .setIntent(help)
1366                 .setAlphabeticShortcut('H');
1367         }
1368         return true;
1369     }
1370 
1371     @Override
onPrepareOptionsMenu(Menu menu)1372     public boolean onPrepareOptionsMenu(Menu menu) {
1373         super.onPrepareOptionsMenu(menu);
1374 
1375         if (mAppsCustomizeTabHost.isTransitioning()) {
1376             return false;
1377         }
1378         boolean allAppsVisible = (mAppsCustomizeTabHost.getVisibility() == View.VISIBLE);
1379         menu.setGroupVisible(MENU_GROUP_WALLPAPER, !allAppsVisible);
1380 
1381         return true;
1382     }
1383 
1384     @Override
onOptionsItemSelected(MenuItem item)1385     public boolean onOptionsItemSelected(MenuItem item) {
1386         switch (item.getItemId()) {
1387         case MENU_WALLPAPER_SETTINGS:
1388             startWallpaper();
1389             return true;
1390         }
1391 
1392         return super.onOptionsItemSelected(item);
1393     }
1394 
1395     @Override
onSearchRequested()1396     public boolean onSearchRequested() {
1397         startSearch(null, false, null, true);
1398         // Use a custom animation for launching search
1399         overridePendingTransition(R.anim.fade_in_fast, R.anim.fade_out_fast);
1400         return true;
1401     }
1402 
isWorkspaceLocked()1403     public boolean isWorkspaceLocked() {
1404         return mWorkspaceLoading || mWaitingForResult;
1405     }
1406 
addItems()1407     private void addItems() {
1408         showWorkspace(true);
1409         showAddDialog();
1410     }
1411 
resetAddInfo()1412     private void resetAddInfo() {
1413         mPendingAddInfo.container = ItemInfo.NO_ID;
1414         mPendingAddInfo.screen = -1;
1415         mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
1416         mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
1417         mPendingAddInfo.dropPos = null;
1418     }
1419 
addAppWidgetFromPick(Intent data)1420     void addAppWidgetFromPick(Intent data) {
1421         // TODO: catch bad widget exception when sent
1422         int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
1423         // TODO: Is this log message meaningful?
1424         if (LOGD) Log.d(TAG, "dumping extras content=" + data.getExtras());
1425         addAppWidgetImpl(appWidgetId, null);
1426     }
1427 
addAppWidgetImpl(int appWidgetId, PendingAddWidgetInfo info)1428     void addAppWidgetImpl(int appWidgetId, PendingAddWidgetInfo info) {
1429         AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
1430 
1431         if (appWidget.configure != null) {
1432             // Launch over to configure widget, if needed
1433             Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
1434             intent.setComponent(appWidget.configure);
1435             intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1436             if (info != null) {
1437                 if (info.mimeType != null && !info.mimeType.isEmpty()) {
1438                     intent.putExtra(
1439                             InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE,
1440                             info.mimeType);
1441 
1442                     final String mimeType = info.mimeType;
1443                     final ClipData clipData = (ClipData) info.configurationData;
1444                     final ClipDescription clipDesc = clipData.getDescription();
1445                     for (int i = 0; i < clipDesc.getMimeTypeCount(); ++i) {
1446                         if (clipDesc.getMimeType(i).equals(mimeType)) {
1447                             final ClipData.Item item = clipData.getItemAt(i);
1448                             final CharSequence stringData = item.getText();
1449                             final Uri uriData = item.getUri();
1450                             final Intent intentData = item.getIntent();
1451                             final String key =
1452                                 InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA;
1453                             if (uriData != null) {
1454                                 intent.putExtra(key, uriData);
1455                             } else if (intentData != null) {
1456                                 intent.putExtra(key, intentData);
1457                             } else if (stringData != null) {
1458                                 intent.putExtra(key, stringData);
1459                             }
1460                             break;
1461                         }
1462                     }
1463                 }
1464             }
1465 
1466             startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
1467         } else {
1468             // Otherwise just add it
1469             completeAddAppWidget(appWidgetId, info.container, info.screen);
1470 
1471             // Exit spring loaded mode if necessary after adding the widget
1472             exitSpringLoadedDragModeDelayed(true, false);
1473         }
1474     }
1475 
1476     /**
1477      * Process a shortcut drop.
1478      *
1479      * @param componentName The name of the component
1480      * @param screen The screen where it should be added
1481      * @param cell The cell it should be added to, optional
1482      * @param position The location on the screen where it was dropped, optional
1483      */
processShortcutFromDrop(ComponentName componentName, long container, int screen, int[] cell, int[] loc)1484     void processShortcutFromDrop(ComponentName componentName, long container, int screen,
1485             int[] cell, int[] loc) {
1486         resetAddInfo();
1487         mPendingAddInfo.container = container;
1488         mPendingAddInfo.screen = screen;
1489         mPendingAddInfo.dropPos = loc;
1490 
1491         if (cell != null) {
1492             mPendingAddInfo.cellX = cell[0];
1493             mPendingAddInfo.cellY = cell[1];
1494         }
1495 
1496         Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
1497         createShortcutIntent.setComponent(componentName);
1498         processShortcut(createShortcutIntent);
1499     }
1500 
1501     /**
1502      * Process a widget drop.
1503      *
1504      * @param info The PendingAppWidgetInfo of the widget being added.
1505      * @param screen The screen where it should be added
1506      * @param cell The cell it should be added to, optional
1507      * @param position The location on the screen where it was dropped, optional
1508      */
addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, int screen, int[] cell, int[] loc)1509     void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, int screen,
1510             int[] cell, int[] loc) {
1511         resetAddInfo();
1512         mPendingAddInfo.container = info.container = container;
1513         mPendingAddInfo.screen = info.screen = screen;
1514         mPendingAddInfo.dropPos = loc;
1515         if (cell != null) {
1516             mPendingAddInfo.cellX = cell[0];
1517             mPendingAddInfo.cellY = cell[1];
1518         }
1519 
1520         int appWidgetId = getAppWidgetHost().allocateAppWidgetId();
1521         AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, info.componentName);
1522         addAppWidgetImpl(appWidgetId, info);
1523     }
1524 
processShortcut(Intent intent)1525     void processShortcut(Intent intent) {
1526         // Handle case where user selected "Applications"
1527         String applicationName = getResources().getString(R.string.group_applications);
1528         String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
1529 
1530         if (applicationName != null && applicationName.equals(shortcutName)) {
1531             Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
1532             mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
1533 
1534             Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
1535             pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
1536             pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application));
1537             startActivityForResultSafely(pickIntent, REQUEST_PICK_APPLICATION);
1538         } else {
1539             startActivityForResultSafely(intent, REQUEST_CREATE_SHORTCUT);
1540         }
1541     }
1542 
processWallpaper(Intent intent)1543     void processWallpaper(Intent intent) {
1544         startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
1545     }
1546 
addFolder(CellLayout layout, long container, final int screen, int cellX, int cellY)1547     FolderIcon addFolder(CellLayout layout, long container, final int screen, int cellX,
1548             int cellY) {
1549         final FolderInfo folderInfo = new FolderInfo();
1550         folderInfo.title = getText(R.string.folder_name);
1551 
1552         // Update the model
1553         LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screen, cellX, cellY,
1554                 false);
1555         sFolders.put(folderInfo.id, folderInfo);
1556 
1557         // Create the view
1558         FolderIcon newFolder =
1559             FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
1560         mWorkspace.addInScreen(newFolder, container, screen, cellX, cellY, 1, 1,
1561                 isWorkspaceLocked());
1562         return newFolder;
1563     }
1564 
removeFolder(FolderInfo folder)1565     void removeFolder(FolderInfo folder) {
1566         sFolders.remove(folder.id);
1567     }
1568 
showNotifications()1569     private void showNotifications() {
1570         final StatusBarManager statusBar = (StatusBarManager) getSystemService(STATUS_BAR_SERVICE);
1571         if (statusBar != null) {
1572             statusBar.expand();
1573         }
1574     }
1575 
startWallpaper()1576     private void startWallpaper() {
1577         showWorkspace(true);
1578         final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
1579         Intent chooser = Intent.createChooser(pickWallpaper,
1580                 getText(R.string.chooser_wallpaper));
1581         // NOTE: Adds a configure option to the chooser if the wallpaper supports it
1582         //       Removed in Eclair MR1
1583 //        WallpaperManager wm = (WallpaperManager)
1584 //                getSystemService(Context.WALLPAPER_SERVICE);
1585 //        WallpaperInfo wi = wm.getWallpaperInfo();
1586 //        if (wi != null && wi.getSettingsActivity() != null) {
1587 //            LabeledIntent li = new LabeledIntent(getPackageName(),
1588 //                    R.string.configure_wallpaper, 0);
1589 //            li.setClassName(wi.getPackageName(), wi.getSettingsActivity());
1590 //            chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { li });
1591 //        }
1592         startActivityForResult(chooser, REQUEST_PICK_WALLPAPER);
1593     }
1594 
1595     /**
1596      * Registers various content observers. The current implementation registers
1597      * only a favorites observer to keep track of the favorites applications.
1598      */
registerContentObservers()1599     private void registerContentObservers() {
1600         ContentResolver resolver = getContentResolver();
1601         resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
1602                 true, mWidgetObserver);
1603     }
1604 
1605     @Override
dispatchKeyEvent(KeyEvent event)1606     public boolean dispatchKeyEvent(KeyEvent event) {
1607         if (event.getAction() == KeyEvent.ACTION_DOWN) {
1608             switch (event.getKeyCode()) {
1609                 case KeyEvent.KEYCODE_HOME:
1610                     return true;
1611                 case KeyEvent.KEYCODE_VOLUME_DOWN:
1612                     if (SystemProperties.getInt("debug.launcher2.dumpstate", 0) != 0) {
1613                         dumpState();
1614                         return true;
1615                     }
1616                     break;
1617             }
1618         } else if (event.getAction() == KeyEvent.ACTION_UP) {
1619             switch (event.getKeyCode()) {
1620                 case KeyEvent.KEYCODE_HOME:
1621                     return true;
1622             }
1623         }
1624 
1625         return super.dispatchKeyEvent(event);
1626     }
1627 
1628     @Override
onBackPressed()1629     public void onBackPressed() {
1630         if (mState == State.APPS_CUSTOMIZE) {
1631             showWorkspace(true);
1632         } else if (mWorkspace.getOpenFolder() != null) {
1633             Folder openFolder = mWorkspace.getOpenFolder();
1634             if (openFolder.isEditingName()) {
1635                 openFolder.dismissEditingName();
1636             } else {
1637                 closeFolder();
1638             }
1639         } else {
1640             mWorkspace.exitWidgetResizeMode();
1641 
1642             // Back button is a no-op here, but give at least some feedback for the button press
1643             mWorkspace.showOutlinesTemporarily();
1644         }
1645     }
1646 
1647     /**
1648      * Re-listen when widgets are reset.
1649      */
onAppWidgetReset()1650     private void onAppWidgetReset() {
1651         if (mAppWidgetHost != null) {
1652             mAppWidgetHost.startListening();
1653         }
1654     }
1655 
1656     /**
1657      * Go through the and disconnect any of the callbacks in the drawables and the views or we
1658      * leak the previous Home screen on orientation change.
1659      */
unbindWorkspaceAndHotseatItems()1660     private void unbindWorkspaceAndHotseatItems() {
1661         if (mModel != null) {
1662             mModel.unbindWorkspaceItems();
1663         }
1664     }
1665 
1666     /**
1667      * Launches the intent referred by the clicked shortcut.
1668      *
1669      * @param v The view representing the clicked shortcut.
1670      */
onClick(View v)1671     public void onClick(View v) {
1672         // Make sure that rogue clicks don't get through while allapps is launching, or after the
1673         // view has detached (it's possible for this to happen if the view is removed mid touch).
1674         if (v.getWindowToken() == null) {
1675             return;
1676         }
1677 
1678         if (mWorkspace.isSwitchingState()) {
1679             return;
1680         }
1681 
1682         Object tag = v.getTag();
1683         if (tag instanceof ShortcutInfo) {
1684             // Open shortcut
1685             final Intent intent = ((ShortcutInfo) tag).intent;
1686             int[] pos = new int[2];
1687             v.getLocationOnScreen(pos);
1688             intent.setSourceBounds(new Rect(pos[0], pos[1],
1689                     pos[0] + v.getWidth(), pos[1] + v.getHeight()));
1690             boolean success = startActivitySafely(intent, tag);
1691 
1692             if (success && v instanceof BubbleTextView) {
1693                 mWaitingForResume = (BubbleTextView) v;
1694                 mWaitingForResume.setStayPressed(true);
1695             }
1696         } else if (tag instanceof FolderInfo) {
1697             if (v instanceof FolderIcon) {
1698                 FolderIcon fi = (FolderIcon) v;
1699                 handleFolderClick(fi);
1700             }
1701         } else if (v == mAllAppsButton) {
1702             if (mState == State.APPS_CUSTOMIZE) {
1703                 showWorkspace(true);
1704             } else {
1705                 onClickAllAppsButton(v);
1706             }
1707         }
1708     }
1709 
onTouch(View v, MotionEvent event)1710     public boolean onTouch(View v, MotionEvent event) {
1711         // this is an intercepted event being forwarded from mWorkspace;
1712         // clicking anywhere on the workspace causes the customization drawer to slide down
1713         showWorkspace(true);
1714         return false;
1715     }
1716 
1717     /**
1718      * Event handler for the search button
1719      *
1720      * @param v The view that was clicked.
1721      */
onClickSearchButton(View v)1722     public void onClickSearchButton(View v) {
1723         onSearchRequested();
1724     }
1725 
1726     /**
1727      * Event handler for the voice button
1728      *
1729      * @param v The view that was clicked.
1730      */
onClickVoiceButton(View v)1731     public void onClickVoiceButton(View v) {
1732         startVoiceSearch();
1733     }
1734 
startVoiceSearch()1735     private void startVoiceSearch() {
1736         Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
1737         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1738         startActivity(intent);
1739     }
1740 
1741     /**
1742      * Event handler for the "grid" button that appears on the home screen, which
1743      * enters all apps mode.
1744      *
1745      * @param v The view that was clicked.
1746      */
onClickAllAppsButton(View v)1747     public void onClickAllAppsButton(View v) {
1748         showAllApps(true);
1749     }
1750 
onTouchDownAllAppsButton(View v)1751     public void onTouchDownAllAppsButton(View v) {
1752         // Provide the same haptic feedback that the system offers for virtual keys.
1753         v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
1754     }
1755 
onClickAppMarketButton(View v)1756     public void onClickAppMarketButton(View v) {
1757         if (mAppMarketIntent != null) {
1758             startActivitySafely(mAppMarketIntent, "app market");
1759         }
1760     }
1761 
startApplicationDetailsActivity(ComponentName componentName)1762     void startApplicationDetailsActivity(ComponentName componentName) {
1763         String packageName = componentName.getPackageName();
1764         Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
1765                 Uri.fromParts("package", packageName, null));
1766         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1767         startActivity(intent);
1768     }
1769 
startApplicationUninstallActivity(ApplicationInfo appInfo)1770     void startApplicationUninstallActivity(ApplicationInfo appInfo) {
1771         if ((appInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) == 0) {
1772             // System applications cannot be installed. For now, show a toast explaining that.
1773             // We may give them the option of disabling apps this way.
1774             int messageId = R.string.uninstall_system_app_text;
1775             Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
1776         } else {
1777             String packageName = appInfo.componentName.getPackageName();
1778             String className = appInfo.componentName.getClassName();
1779             Intent intent = new Intent(
1780                     Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
1781             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1782                     Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1783             startActivity(intent);
1784         }
1785     }
1786 
startActivitySafely(Intent intent, Object tag)1787     boolean startActivitySafely(Intent intent, Object tag) {
1788         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1789         try {
1790             startActivity(intent);
1791             return true;
1792         } catch (ActivityNotFoundException e) {
1793             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
1794             Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
1795         } catch (SecurityException e) {
1796             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
1797             Log.e(TAG, "Launcher does not have the permission to launch " + intent +
1798                     ". Make sure to create a MAIN intent-filter for the corresponding activity " +
1799                     "or use the exported attribute for this activity. "
1800                     + "tag="+ tag + " intent=" + intent, e);
1801         }
1802         return false;
1803     }
1804 
startActivityForResultSafely(Intent intent, int requestCode)1805     void startActivityForResultSafely(Intent intent, int requestCode) {
1806         try {
1807             startActivityForResult(intent, requestCode);
1808         } catch (ActivityNotFoundException e) {
1809             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
1810         } catch (SecurityException e) {
1811             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
1812             Log.e(TAG, "Launcher does not have the permission to launch " + intent +
1813                     ". Make sure to create a MAIN intent-filter for the corresponding activity " +
1814                     "or use the exported attribute for this activity.", e);
1815         }
1816     }
1817 
handleFolderClick(FolderIcon folderIcon)1818     private void handleFolderClick(FolderIcon folderIcon) {
1819         final FolderInfo info = folderIcon.mInfo;
1820         Folder openFolder = mWorkspace.getFolderForTag(info);
1821 
1822         // If the folder info reports that the associated folder is open, then verify that
1823         // it is actually opened. There have been a few instances where this gets out of sync.
1824         if (info.opened && openFolder == null) {
1825             Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
1826                     + info.screen + " (" + info.cellX + ", " + info.cellY + ")");
1827             info.opened = false;
1828         }
1829 
1830         if (!info.opened) {
1831             // Close any open folder
1832             closeFolder();
1833             // Open the requested folder
1834             openFolder(folderIcon);
1835         } else {
1836             // Find the open folder...
1837             int folderScreen;
1838             if (openFolder != null) {
1839                 folderScreen = mWorkspace.getPageForView(openFolder);
1840                 // .. and close it
1841                 closeFolder(openFolder);
1842                 if (folderScreen != mWorkspace.getCurrentPage()) {
1843                     // Close any folder open on the current screen
1844                     closeFolder();
1845                     // Pull the folder onto this screen
1846                     openFolder(folderIcon);
1847                 }
1848             }
1849         }
1850     }
1851 
growAndFadeOutFolderIcon(FolderIcon fi)1852     private void growAndFadeOutFolderIcon(FolderIcon fi) {
1853         if (fi == null) return;
1854         PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
1855         PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
1856         PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
1857 
1858         FolderInfo info = (FolderInfo) fi.getTag();
1859         if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1860             CellLayout cl = (CellLayout) fi.getParent().getParent();
1861             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
1862             cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
1863         }
1864 
1865         ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(fi, alpha, scaleX, scaleY);
1866         oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
1867         oa.start();
1868     }
1869 
shrinkAndFadeInFolderIcon(FolderIcon fi)1870     private void shrinkAndFadeInFolderIcon(FolderIcon fi) {
1871         if (fi == null) return;
1872         PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
1873         PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
1874         PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
1875 
1876         FolderInfo info = (FolderInfo) fi.getTag();
1877         CellLayout cl = null;
1878         if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1879             cl = (CellLayout) fi.getParent().getParent();
1880         }
1881 
1882         final CellLayout layout = cl;
1883         ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(fi, alpha, scaleX, scaleY);
1884         oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
1885         oa.addListener(new AnimatorListenerAdapter() {
1886             @Override
1887             public void onAnimationEnd(Animator animation) {
1888                 if (layout != null) {
1889                     layout.clearFolderLeaveBehind();
1890                 }
1891             }
1892         });
1893         oa.start();
1894     }
1895 
1896     /**
1897      * Opens the user folder described by the specified tag. The opening of the folder
1898      * is animated relative to the specified View. If the View is null, no animation
1899      * is played.
1900      *
1901      * @param folderInfo The FolderInfo describing the folder to open.
1902      */
openFolder(FolderIcon folderIcon)1903     public void openFolder(FolderIcon folderIcon) {
1904         Folder folder = folderIcon.mFolder;
1905         FolderInfo info = folder.mInfo;
1906 
1907         growAndFadeOutFolderIcon(folderIcon);
1908         info.opened = true;
1909 
1910         // Just verify that the folder hasn't already been added to the DragLayer.
1911         // There was a one-off crash where the folder had a parent already.
1912         if (folder.getParent() == null) {
1913             mDragLayer.addView(folder);
1914             mDragController.addDropTarget((DropTarget) folder);
1915         } else {
1916             Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
1917                     folder.getParent() + ").");
1918         }
1919         folder.animateOpen();
1920     }
1921 
closeFolder()1922     public void closeFolder() {
1923         Folder folder = mWorkspace.getOpenFolder();
1924         if (folder != null) {
1925             if (folder.isEditingName()) {
1926                 folder.dismissEditingName();
1927             }
1928             closeFolder(folder);
1929 
1930             // Dismiss the folder cling
1931             dismissFolderCling(null);
1932         }
1933     }
1934 
closeFolder(Folder folder)1935     void closeFolder(Folder folder) {
1936         folder.getInfo().opened = false;
1937 
1938         ViewGroup parent = (ViewGroup) folder.getParent().getParent();
1939         if (parent != null) {
1940             FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
1941             shrinkAndFadeInFolderIcon(fi);
1942         }
1943         folder.animateClosed();
1944     }
1945 
onLongClick(View v)1946     public boolean onLongClick(View v) {
1947         if (mState != State.WORKSPACE) {
1948             return false;
1949         }
1950 
1951         if (isWorkspaceLocked()) {
1952             return false;
1953         }
1954 
1955         if (!(v instanceof CellLayout)) {
1956             v = (View) v.getParent().getParent();
1957         }
1958 
1959         resetAddInfo();
1960         CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
1961         // This happens when long clicking an item with the dpad/trackball
1962         if (longClickCellInfo == null) {
1963             return true;
1964         }
1965 
1966         // The hotseat touch handling does not go through Workspace, and we always allow long press
1967         // on hotseat items.
1968         final View itemUnderLongClick = longClickCellInfo.cell;
1969         boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();
1970         if (allowLongPress && !mDragController.isDragging()) {
1971             if (itemUnderLongClick == null) {
1972                 // User long pressed on empty space
1973                 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
1974                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
1975                 startWallpaper();
1976             } else {
1977                 if (!(itemUnderLongClick instanceof Folder)) {
1978                     // User long pressed on an item
1979                     mWorkspace.startDrag(longClickCellInfo);
1980                 }
1981             }
1982         }
1983         return true;
1984     }
1985 
isHotseatLayout(View layout)1986     boolean isHotseatLayout(View layout) {
1987         return mHotseat != null && layout != null &&
1988                 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
1989     }
getHotseat()1990     Hotseat getHotseat() {
1991         return mHotseat;
1992     }
1993 
1994     /**
1995      * Returns the CellLayout of the specified container at the specified screen.
1996      */
getCellLayout(long container, int screen)1997     CellLayout getCellLayout(long container, int screen) {
1998         if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1999             if (mHotseat != null) {
2000                 return mHotseat.getLayout();
2001             } else {
2002                 return null;
2003             }
2004         } else {
2005             return (CellLayout) mWorkspace.getChildAt(screen);
2006         }
2007     }
2008 
getWorkspace()2009     Workspace getWorkspace() {
2010         return mWorkspace;
2011     }
2012 
2013     @Override
onCreateDialog(int id)2014     protected Dialog onCreateDialog(int id) {
2015         switch (id) {
2016             case DIALOG_CREATE_SHORTCUT:
2017                 return new CreateShortcut().createDialog();
2018             case DIALOG_RENAME_FOLDER:
2019                 return new RenameFolder().createDialog();
2020         }
2021 
2022         return super.onCreateDialog(id);
2023     }
2024 
2025     @Override
onPrepareDialog(int id, Dialog dialog)2026     protected void onPrepareDialog(int id, Dialog dialog) {
2027         switch (id) {
2028             case DIALOG_CREATE_SHORTCUT:
2029                 break;
2030             case DIALOG_RENAME_FOLDER:
2031                 if (mFolderInfo != null) {
2032                     EditText input = (EditText) dialog.findViewById(R.id.folder_name);
2033                     final CharSequence text = mFolderInfo.title;
2034                     input.setText(text);
2035                     input.setSelection(0, text.length());
2036                 }
2037                 break;
2038         }
2039     }
2040 
showRenameDialog(FolderInfo info)2041     void showRenameDialog(FolderInfo info) {
2042         mFolderInfo = info;
2043         mWaitingForResult = true;
2044         showDialog(DIALOG_RENAME_FOLDER);
2045     }
2046 
showAddDialog()2047     private void showAddDialog() {
2048         resetAddInfo();
2049         mPendingAddInfo.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
2050         mPendingAddInfo.screen = mWorkspace.getCurrentPage();
2051         mWaitingForResult = true;
2052         showDialog(DIALOG_CREATE_SHORTCUT);
2053     }
2054 
2055     private class RenameFolder {
2056         private EditText mInput;
2057 
createDialog()2058         Dialog createDialog() {
2059             final View layout = View.inflate(Launcher.this, R.layout.rename_folder, null);
2060             mInput = (EditText) layout.findViewById(R.id.folder_name);
2061 
2062             AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
2063             builder.setIcon(0);
2064             builder.setTitle(getString(R.string.rename_folder_title));
2065             builder.setCancelable(true);
2066             builder.setOnCancelListener(new Dialog.OnCancelListener() {
2067                 public void onCancel(DialogInterface dialog) {
2068                     cleanup();
2069                 }
2070             });
2071             builder.setNegativeButton(getString(R.string.cancel_action),
2072                 new Dialog.OnClickListener() {
2073                     public void onClick(DialogInterface dialog, int which) {
2074                         cleanup();
2075                     }
2076                 }
2077             );
2078             builder.setPositiveButton(getString(R.string.rename_action),
2079                 new Dialog.OnClickListener() {
2080                     public void onClick(DialogInterface dialog, int which) {
2081                         changeFolderName();
2082                     }
2083                 }
2084             );
2085             builder.setView(layout);
2086 
2087             final AlertDialog dialog = builder.create();
2088             dialog.setOnShowListener(new DialogInterface.OnShowListener() {
2089                 public void onShow(DialogInterface dialog) {
2090                     mWaitingForResult = true;
2091                     mInput.requestFocus();
2092                     InputMethodManager inputManager = (InputMethodManager)
2093                             getSystemService(Context.INPUT_METHOD_SERVICE);
2094                     inputManager.showSoftInput(mInput, 0);
2095                 }
2096             });
2097 
2098             return dialog;
2099         }
2100 
changeFolderName()2101         private void changeFolderName() {
2102             final String name = mInput.getText().toString();
2103             if (!TextUtils.isEmpty(name)) {
2104                 // Make sure we have the right folder info
2105                 mFolderInfo = sFolders.get(mFolderInfo.id);
2106                 mFolderInfo.title = name;
2107                 LauncherModel.updateItemInDatabase(Launcher.this, mFolderInfo);
2108 
2109                 if (mWorkspaceLoading) {
2110                     lockAllApps();
2111                     mModel.startLoader(Launcher.this, false);
2112                 } else {
2113                     final FolderIcon folderIcon = (FolderIcon)
2114                             mWorkspace.getViewForTag(mFolderInfo);
2115                     if (folderIcon != null) {
2116                         // TODO: At some point we'll probably want some version of setting
2117                         // the text for a folder icon.
2118                         //folderIcon.setText(name);
2119                         getWorkspace().requestLayout();
2120                     } else {
2121                         lockAllApps();
2122                         mWorkspaceLoading = true;
2123                         mModel.startLoader(Launcher.this, false);
2124                     }
2125                 }
2126             }
2127             cleanup();
2128         }
2129 
cleanup()2130         private void cleanup() {
2131             dismissDialog(DIALOG_RENAME_FOLDER);
2132             mWaitingForResult = false;
2133             mFolderInfo = null;
2134         }
2135     }
2136 
2137     // Now a part of LauncherModel.Callbacks. Used to reorder loading steps.
isAllAppsVisible()2138     public boolean isAllAppsVisible() {
2139         return (mState == State.APPS_CUSTOMIZE);
2140     }
2141 
2142     // AllAppsView.Watcher
zoomed(float zoom)2143     public void zoomed(float zoom) {
2144         if (zoom == 1.0f) {
2145             mWorkspace.setVisibility(View.GONE);
2146         }
2147     }
2148 
2149     /**
2150      * Helper method for the cameraZoomIn/cameraZoomOut animations
2151      * @param view The view being animated
2152      * @param state The state that we are moving in or out of (eg. APPS_CUSTOMIZE)
2153      * @param scaleFactor The scale factor used for the zoom
2154      */
setPivotsForZoom(View view, State state, float scaleFactor)2155     private void setPivotsForZoom(View view, State state, float scaleFactor) {
2156         view.setPivotX(view.getWidth() / 2.0f);
2157         view.setPivotY(view.getHeight() / 2.0f);
2158     }
2159 
updateWallpaperVisibility(boolean visible)2160     void updateWallpaperVisibility(boolean visible) {
2161         int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
2162         int curflags = getWindow().getAttributes().flags
2163                 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
2164         if (wpflags != curflags) {
2165             getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
2166         }
2167     }
2168 
2169     /**
2170      * Zoom the camera out from the workspace to reveal 'toView'.
2171      * Assumes that the view to show is anchored at either the very top or very bottom
2172      * of the screen.
2173      * @param toState The state to zoom out to. Must be APPS_CUSTOMIZE.
2174      */
cameraZoomOut(State toState, boolean animated, final boolean springLoaded)2175     private void cameraZoomOut(State toState, boolean animated, final boolean springLoaded) {
2176         final Resources res = getResources();
2177         final Launcher instance = this;
2178 
2179         final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime);
2180         final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime);
2181         final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
2182         final View toView = mAppsCustomizeTabHost;
2183         final int startDelay =
2184                 res.getInteger(R.integer.config_workspaceAppsCustomizeAnimationStagger);
2185 
2186         setPivotsForZoom(toView, toState, scale);
2187 
2188         // Shrink workspaces away if going to AppsCustomize from workspace
2189         mWorkspace.changeState(Workspace.State.SMALL, animated);
2190 
2191         if (animated) {
2192             final ValueAnimator scaleAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
2193             scaleAnim.setInterpolator(new Workspace.ZoomOutInterpolator());
2194             scaleAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
2195                 public void onAnimationUpdate(float a, float b) {
2196                     ((View) toView.getParent()).invalidate();
2197                     toView.fastInvalidate();
2198                     toView.setFastScaleX(a * scale + b * 1f);
2199                     toView.setFastScaleY(a * scale + b * 1f);
2200                 }
2201             });
2202 
2203             toView.setVisibility(View.VISIBLE);
2204             toView.setFastAlpha(0f);
2205             ValueAnimator alphaAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(fadeDuration);
2206             alphaAnim.setInterpolator(new DecelerateInterpolator(1.5f));
2207             alphaAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
2208                 public void onAnimationUpdate(float a, float b) {
2209                     // don't need to invalidate because we do so above
2210                     toView.setFastAlpha(a * 0f + b * 1f);
2211                 }
2212             });
2213             alphaAnim.setStartDelay(startDelay);
2214             alphaAnim.start();
2215 
2216             if (toView instanceof LauncherTransitionable) {
2217                 ((LauncherTransitionable) toView).onLauncherTransitionStart(instance, scaleAnim,
2218                         false);
2219             }
2220             scaleAnim.addListener(new AnimatorListenerAdapter() {
2221                 boolean animationCancelled = false;
2222 
2223                 @Override
2224                 public void onAnimationStart(Animator animation) {
2225                     updateWallpaperVisibility(true);
2226                     // Prepare the position
2227                     toView.setTranslationX(0.0f);
2228                     toView.setTranslationY(0.0f);
2229                     toView.setVisibility(View.VISIBLE);
2230                     toView.bringToFront();
2231                 }
2232                 @Override
2233                 public void onAnimationEnd(Animator animation) {
2234                     // If we don't set the final scale values here, if this animation is cancelled
2235                     // it will have the wrong scale value and subsequent cameraPan animations will
2236                     // not fix that
2237                     toView.setScaleX(1.0f);
2238                     toView.setScaleY(1.0f);
2239                     if (toView instanceof LauncherTransitionable) {
2240                         ((LauncherTransitionable) toView).onLauncherTransitionEnd(instance,
2241                                 scaleAnim, false);
2242                     }
2243 
2244                     if (!springLoaded && !LauncherApplication.isScreenLarge()) {
2245                         // Hide the workspace scrollbar
2246                         mWorkspace.hideScrollingIndicator(true);
2247                         mWorkspace.hideDockDivider(true);
2248                     }
2249                     if (!animationCancelled) {
2250                         updateWallpaperVisibility(false);
2251                     }
2252                 }
2253 
2254                 @Override
2255                 public void onAnimationCancel(Animator animation) {
2256                     animationCancelled = true;
2257                 }
2258             });
2259 
2260             // toView should appear right at the end of the workspace shrink animation
2261 
2262             if (mStateAnimation != null) mStateAnimation.cancel();
2263             mStateAnimation = new AnimatorSet();
2264             mStateAnimation.play(scaleAnim).after(startDelay);
2265             mStateAnimation.start();
2266         } else {
2267             toView.setTranslationX(0.0f);
2268             toView.setTranslationY(0.0f);
2269             toView.setScaleX(1.0f);
2270             toView.setScaleY(1.0f);
2271             toView.setVisibility(View.VISIBLE);
2272             toView.bringToFront();
2273             if (toView instanceof LauncherTransitionable) {
2274                 ((LauncherTransitionable) toView).onLauncherTransitionStart(instance, null, false);
2275                 ((LauncherTransitionable) toView).onLauncherTransitionEnd(instance, null, false);
2276 
2277                 if (!springLoaded && !LauncherApplication.isScreenLarge()) {
2278                     // Hide the workspace scrollbar
2279                     mWorkspace.hideScrollingIndicator(true);
2280                     mWorkspace.hideDockDivider(true);
2281                 }
2282             }
2283             updateWallpaperVisibility(false);
2284         }
2285     }
2286 
2287     /**
2288      * Zoom the camera back into the workspace, hiding 'fromView'.
2289      * This is the opposite of cameraZoomOut.
2290      * @param fromState The current state (must be APPS_CUSTOMIZE).
2291      * @param animated If true, the transition will be animated.
2292      */
cameraZoomIn(State fromState, boolean animated, final boolean springLoaded)2293     private void cameraZoomIn(State fromState, boolean animated, final boolean springLoaded) {
2294         Resources res = getResources();
2295         final Launcher instance = this;
2296 
2297         final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime);
2298         final float scaleFactor = (float)
2299                 res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
2300         final View fromView = mAppsCustomizeTabHost;
2301 
2302         setPivotsForZoom(fromView, fromState, scaleFactor);
2303         updateWallpaperVisibility(true);
2304         showHotseat(animated);
2305         if (animated) {
2306             if (mStateAnimation != null) mStateAnimation.cancel();
2307             mStateAnimation = new AnimatorSet();
2308 
2309             final float oldScaleX = fromView.getScaleX();
2310             final float oldScaleY = fromView.getScaleY();
2311 
2312             ValueAnimator scaleAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
2313             scaleAnim.setInterpolator(new Workspace.ZoomInInterpolator());
2314             scaleAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
2315                 public void onAnimationUpdate(float a, float b) {
2316                     ((View)fromView.getParent()).fastInvalidate();
2317                     fromView.setFastScaleX(a * oldScaleX + b * scaleFactor);
2318                     fromView.setFastScaleY(a * oldScaleY + b * scaleFactor);
2319                 }
2320             });
2321             final ValueAnimator alphaAnim = ValueAnimator.ofFloat(0f, 1f);
2322             alphaAnim.setDuration(res.getInteger(R.integer.config_appsCustomizeFadeOutTime));
2323             alphaAnim.setInterpolator(new AccelerateDecelerateInterpolator());
2324             alphaAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
2325                 public void onAnimationUpdate(float a, float b) {
2326                     // don't need to invalidate because we do so above
2327                     fromView.setFastAlpha(a * 1f + b * 0f);
2328                 }
2329             });
2330             if (fromView instanceof LauncherTransitionable) {
2331                 ((LauncherTransitionable) fromView).onLauncherTransitionStart(instance, alphaAnim,
2332                         true);
2333             }
2334             alphaAnim.addListener(new AnimatorListenerAdapter() {
2335                 @Override
2336                 public void onAnimationEnd(Animator animation) {
2337                     updateWallpaperVisibility(true);
2338                     fromView.setVisibility(View.GONE);
2339                     if (fromView instanceof LauncherTransitionable) {
2340                         ((LauncherTransitionable) fromView).onLauncherTransitionEnd(instance,
2341                                 alphaAnim, true);
2342                     }
2343                     mWorkspace.hideScrollingIndicator(false);
2344                 }
2345             });
2346 
2347             mStateAnimation.playTogether(scaleAnim, alphaAnim);
2348             mStateAnimation.start();
2349         } else {
2350             fromView.setVisibility(View.GONE);
2351             if (fromView instanceof LauncherTransitionable) {
2352                 ((LauncherTransitionable) fromView).onLauncherTransitionStart(instance, null, true);
2353                 ((LauncherTransitionable) fromView).onLauncherTransitionEnd(instance, null, true);
2354             }
2355         }
2356     }
2357 
showWorkspace(boolean animated)2358     void showWorkspace(boolean animated) {
2359         Resources res = getResources();
2360         int stagger = res.getInteger(R.integer.config_appsCustomizeWorkspaceAnimationStagger);
2361 
2362         mWorkspace.changeState(Workspace.State.NORMAL, animated, stagger);
2363         if (mState == State.APPS_CUSTOMIZE) {
2364             closeAllApps(animated);
2365         }
2366 
2367         mWorkspace.showDockDivider(!animated);
2368         mWorkspace.flashScrollingIndicator();
2369 
2370         // Change the state *after* we've called all the transition code
2371         mState = State.WORKSPACE;
2372 
2373         // Resume the auto-advance of widgets
2374         mUserPresent = true;
2375         updateRunning();
2376 
2377         // send an accessibility event to announce the context change
2378         getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
2379     }
2380 
enterSpringLoadedDragMode()2381     void enterSpringLoadedDragMode() {
2382         if (mState == State.APPS_CUSTOMIZE) {
2383             mWorkspace.changeState(Workspace.State.SPRING_LOADED);
2384             cameraZoomIn(State.APPS_CUSTOMIZE, true, true);
2385             mState = State.APPS_CUSTOMIZE_SPRING_LOADED;
2386         }
2387     }
2388 
exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay)2389     void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay) {
2390         if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return;
2391 
2392         mHandler.postDelayed(new Runnable() {
2393             @Override
2394             public void run() {
2395                 if (successfulDrop) {
2396                     // Before we show workspace, hide all apps again because
2397                     // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
2398                     // clean up our state transition functions
2399                     mAppsCustomizeTabHost.setVisibility(View.GONE);
2400                     mSearchDropTargetBar.showSearchBar(true);
2401                     showWorkspace(true);
2402                 } else {
2403                     exitSpringLoadedDragMode();
2404                 }
2405             }
2406         }, (extendedDelay ?
2407                 EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT :
2408                 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT));
2409     }
exitSpringLoadedDragMode()2410     void exitSpringLoadedDragMode() {
2411         if (mState == State.APPS_CUSTOMIZE_SPRING_LOADED) {
2412             cameraZoomOut(State.APPS_CUSTOMIZE, true, true);
2413             mState = State.APPS_CUSTOMIZE;
2414         }
2415         // Otherwise, we are not in spring loaded mode, so don't do anything.
2416     }
2417 
isAllAppsCustomizeOpen()2418     public boolean isAllAppsCustomizeOpen() {
2419         return mState == State.APPS_CUSTOMIZE;
2420     }
2421 
2422     /**
2423      * Shows the hotseat area.
2424      */
showHotseat(boolean animated)2425     void showHotseat(boolean animated) {
2426         if (!LauncherApplication.isScreenLarge()) {
2427             if (animated) {
2428                 int duration = mSearchDropTargetBar.getTransitionInDuration();
2429                 mHotseat.animate().alpha(1f).setDuration(duration);
2430             } else {
2431                 mHotseat.setAlpha(1f);
2432             }
2433         }
2434     }
2435 
2436     /**
2437      * Hides the hotseat area.
2438      */
hideHotseat(boolean animated)2439     void hideHotseat(boolean animated) {
2440         if (!LauncherApplication.isScreenLarge()) {
2441             if (animated) {
2442                 int duration = mSearchDropTargetBar.getTransitionOutDuration();
2443                 mHotseat.animate().alpha(0f).setDuration(duration);
2444             } else {
2445                 mHotseat.setAlpha(0f);
2446             }
2447         }
2448     }
2449 
showAllApps(boolean animated)2450     void showAllApps(boolean animated) {
2451         if (mState != State.WORKSPACE) return;
2452 
2453         cameraZoomOut(State.APPS_CUSTOMIZE, animated, false);
2454         mAppsCustomizeTabHost.requestFocus();
2455 
2456         // Hide the search bar and hotseat
2457         mSearchDropTargetBar.hideSearchBar(animated);
2458 
2459         // Change the state *after* we've called all the transition code
2460         mState = State.APPS_CUSTOMIZE;
2461 
2462         // Pause the auto-advance of widgets until we are out of AllApps
2463         mUserPresent = false;
2464         updateRunning();
2465         closeFolder();
2466 
2467         // Send an accessibility event to announce the context change
2468         getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
2469     }
2470 
2471     /**
2472      * Things to test when changing this code.
2473      *   - Home from workspace
2474      *          - from center screen
2475      *          - from other screens
2476      *   - Home from all apps
2477      *          - from center screen
2478      *          - from other screens
2479      *   - Back from all apps
2480      *          - from center screen
2481      *          - from other screens
2482      *   - Launch app from workspace and quit
2483      *          - with back
2484      *          - with home
2485      *   - Launch app from all apps and quit
2486      *          - with back
2487      *          - with home
2488      *   - Go to a screen that's not the default, then all
2489      *     apps, and launch and app, and go back
2490      *          - with back
2491      *          -with home
2492      *   - On workspace, long press power and go back
2493      *          - with back
2494      *          - with home
2495      *   - On all apps, long press power and go back
2496      *          - with back
2497      *          - with home
2498      *   - On workspace, power off
2499      *   - On all apps, power off
2500      *   - Launch an app and turn off the screen while in that app
2501      *          - Go back with home key
2502      *          - Go back with back key  TODO: make this not go to workspace
2503      *          - From all apps
2504      *          - From workspace
2505      *   - Enter and exit car mode (becuase it causes an extra configuration changed)
2506      *          - From all apps
2507      *          - From the center workspace
2508      *          - From another workspace
2509      */
closeAllApps(boolean animated)2510     void closeAllApps(boolean animated) {
2511         if (mState == State.APPS_CUSTOMIZE || mState == State.APPS_CUSTOMIZE_SPRING_LOADED) {
2512             mWorkspace.setVisibility(View.VISIBLE);
2513             cameraZoomIn(State.APPS_CUSTOMIZE, animated, false);
2514 
2515             // Show the search bar and hotseat
2516             mSearchDropTargetBar.showSearchBar(animated);
2517 
2518             // Set focus to the AppsCustomize button
2519             if (mAllAppsButton != null) {
2520                 mAllAppsButton.requestFocus();
2521             }
2522         }
2523     }
2524 
lockAllApps()2525     void lockAllApps() {
2526         // TODO
2527     }
2528 
unlockAllApps()2529     void unlockAllApps() {
2530         // TODO
2531     }
2532 
2533     /**
2534      * Add an item from all apps or customize onto the given workspace screen.
2535      * If layout is null, add to the current screen.
2536      */
addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout)2537     void addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout) {
2538         if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) {
2539             showOutOfSpaceMessage();
2540         } else {
2541             layout.animateDrop();
2542         }
2543     }
2544 
2545     /** Maps the current orientation to an index for referencing orientation correct global icons */
getCurrentOrientationIndexForGlobalIcons()2546     private int getCurrentOrientationIndexForGlobalIcons() {
2547         // default - 0, landscape - 1
2548         switch (getResources().getConfiguration().orientation) {
2549         case Configuration.ORIENTATION_LANDSCAPE:
2550             return 1;
2551         default:
2552             return 0;
2553         }
2554     }
2555 
getExternalPackageToolbarIcon(ComponentName activityName)2556     private Drawable getExternalPackageToolbarIcon(ComponentName activityName) {
2557         try {
2558             PackageManager packageManager = getPackageManager();
2559             // Look for the toolbar icon specified in the activity meta-data
2560             Bundle metaData = packageManager.getActivityInfo(
2561                     activityName, PackageManager.GET_META_DATA).metaData;
2562             if (metaData != null) {
2563                 int iconResId = metaData.getInt(TOOLBAR_ICON_METADATA_NAME);
2564                 if (iconResId != 0) {
2565                     Resources res = packageManager.getResourcesForActivity(activityName);
2566                     return res.getDrawable(iconResId);
2567                 }
2568             }
2569         } catch (NameNotFoundException e) {
2570             // This can happen if the activity defines an invalid drawable
2571             Log.w(TAG, "Failed to load toolbar icon; " + activityName.flattenToShortString() +
2572                     " not found", e);
2573         } catch (Resources.NotFoundException nfe) {
2574             // This can happen if the activity defines an invalid drawable
2575             Log.w(TAG, "Failed to load toolbar icon from " + activityName.flattenToShortString(),
2576                     nfe);
2577         }
2578         return null;
2579     }
2580 
2581     // if successful in getting icon, return it; otherwise, set button to use default drawable
updateTextButtonWithIconFromExternalActivity( int buttonId, ComponentName activityName, int fallbackDrawableId)2582     private Drawable.ConstantState updateTextButtonWithIconFromExternalActivity(
2583             int buttonId, ComponentName activityName, int fallbackDrawableId) {
2584         TextView button = (TextView) findViewById(buttonId);
2585         Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName);
2586         Resources r = getResources();
2587         int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width);
2588         int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height);
2589 
2590         // If we were unable to find the icon via the meta-data, use a generic one
2591         if (toolbarIcon == null) {
2592             toolbarIcon = r.getDrawable(fallbackDrawableId);
2593             toolbarIcon.setBounds(0, 0, w, h);
2594             button.setCompoundDrawables(toolbarIcon, null, null, null);
2595             return null;
2596         } else {
2597             toolbarIcon.setBounds(0, 0, w, h);
2598             button.setCompoundDrawables(toolbarIcon, null, null, null);
2599             return toolbarIcon.getConstantState();
2600         }
2601     }
2602 
2603     // if successful in getting icon, return it; otherwise, set button to use default drawable
updateButtonWithIconFromExternalActivity( int buttonId, ComponentName activityName, int fallbackDrawableId)2604     private Drawable.ConstantState updateButtonWithIconFromExternalActivity(
2605             int buttonId, ComponentName activityName, int fallbackDrawableId) {
2606         ImageView button = (ImageView) findViewById(buttonId);
2607         Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName);
2608 
2609         if (button != null) {
2610             // If we were unable to find the icon via the meta-data, use a
2611             // generic one
2612             if (toolbarIcon == null) {
2613                 button.setImageResource(fallbackDrawableId);
2614             } else {
2615                 button.setImageDrawable(toolbarIcon);
2616             }
2617         }
2618 
2619         return toolbarIcon != null ? toolbarIcon.getConstantState() : null;
2620 
2621     }
2622 
updateTextButtonWithDrawable(int buttonId, Drawable.ConstantState d)2623     private void updateTextButtonWithDrawable(int buttonId, Drawable.ConstantState d) {
2624         TextView button = (TextView) findViewById(buttonId);
2625         button.setCompoundDrawables(d.newDrawable(getResources()), null, null, null);
2626     }
2627 
updateButtonWithDrawable(int buttonId, Drawable.ConstantState d)2628     private void updateButtonWithDrawable(int buttonId, Drawable.ConstantState d) {
2629         ImageView button = (ImageView) findViewById(buttonId);
2630         button.setImageDrawable(d.newDrawable(getResources()));
2631     }
2632 
updateGlobalSearchIcon()2633     private boolean updateGlobalSearchIcon() {
2634         final View searchButtonContainer = findViewById(R.id.search_button_container);
2635         final ImageView searchButton = (ImageView) findViewById(R.id.search_button);
2636         final View searchDivider = findViewById(R.id.search_divider);
2637         final View voiceButtonContainer = findViewById(R.id.voice_button_container);
2638         final View voiceButton = findViewById(R.id.voice_button);
2639 
2640         final SearchManager searchManager =
2641                 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2642         ComponentName activityName = searchManager.getGlobalSearchActivity();
2643         if (activityName != null) {
2644             int coi = getCurrentOrientationIndexForGlobalIcons();
2645             sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
2646                     R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo);
2647             if (searchDivider != null) searchDivider.setVisibility(View.VISIBLE);
2648             if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.VISIBLE);
2649             searchButton.setVisibility(View.VISIBLE);
2650             return true;
2651         } else {
2652             // We disable both search and voice search when there is no global search provider
2653             if (searchDivider != null) searchDivider.setVisibility(View.GONE);
2654             if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.GONE);
2655             if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
2656             searchButton.setVisibility(View.GONE);
2657             voiceButton.setVisibility(View.GONE);
2658             return false;
2659         }
2660     }
2661 
updateGlobalSearchIcon(Drawable.ConstantState d)2662     private void updateGlobalSearchIcon(Drawable.ConstantState d) {
2663         updateButtonWithDrawable(R.id.search_button, d);
2664     }
2665 
updateVoiceSearchIcon(boolean searchVisible)2666     private boolean updateVoiceSearchIcon(boolean searchVisible) {
2667         final View searchDivider = findViewById(R.id.search_divider);
2668         final View voiceButtonContainer = findViewById(R.id.voice_button_container);
2669         final View voiceButton = findViewById(R.id.voice_button);
2670 
2671         // We only show/update the voice search icon if the search icon is enabled as well
2672         Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
2673         ComponentName activityName = intent.resolveActivity(getPackageManager());
2674         if (searchVisible && activityName != null) {
2675             int coi = getCurrentOrientationIndexForGlobalIcons();
2676             sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
2677                     R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo);
2678             if (searchDivider != null) searchDivider.setVisibility(View.VISIBLE);
2679             if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.VISIBLE);
2680             voiceButton.setVisibility(View.VISIBLE);
2681             return true;
2682         } else {
2683             if (searchDivider != null) searchDivider.setVisibility(View.GONE);
2684             if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
2685             voiceButton.setVisibility(View.GONE);
2686             return false;
2687         }
2688     }
2689 
updateVoiceSearchIcon(Drawable.ConstantState d)2690     private void updateVoiceSearchIcon(Drawable.ConstantState d) {
2691         updateButtonWithDrawable(R.id.voice_button, d);
2692     }
2693 
2694     /**
2695      * Sets the app market icon
2696      */
updateAppMarketIcon()2697     private void updateAppMarketIcon() {
2698         final View marketButton = findViewById(R.id.market_button);
2699         Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET);
2700         // Find the app market activity by resolving an intent.
2701         // (If multiple app markets are installed, it will return the ResolverActivity.)
2702         ComponentName activityName = intent.resolveActivity(getPackageManager());
2703         if (activityName != null) {
2704             int coi = getCurrentOrientationIndexForGlobalIcons();
2705             mAppMarketIntent = intent;
2706             sAppMarketIcon[coi] = updateTextButtonWithIconFromExternalActivity(
2707                     R.id.market_button, activityName, R.drawable.ic_launcher_market_holo);
2708             marketButton.setVisibility(View.VISIBLE);
2709         } else {
2710             // We should hide and disable the view so that we don't try and restore the visibility
2711             // of it when we swap between drag & normal states from IconDropTarget subclasses.
2712             marketButton.setVisibility(View.GONE);
2713             marketButton.setEnabled(false);
2714         }
2715     }
2716 
updateAppMarketIcon(Drawable.ConstantState d)2717     private void updateAppMarketIcon(Drawable.ConstantState d) {
2718         updateTextButtonWithDrawable(R.id.market_button, d);
2719     }
2720 
2721     /**
2722      * Displays the shortcut creation dialog and launches, if necessary, the
2723      * appropriate activity.
2724      */
2725     private class CreateShortcut implements DialogInterface.OnClickListener,
2726             DialogInterface.OnCancelListener, DialogInterface.OnDismissListener,
2727             DialogInterface.OnShowListener {
2728 
2729         private AddAdapter mAdapter;
2730 
createDialog()2731         Dialog createDialog() {
2732             mAdapter = new AddAdapter(Launcher.this);
2733 
2734             final AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this,
2735                     AlertDialog.THEME_HOLO_DARK);
2736             builder.setAdapter(mAdapter, this);
2737 
2738             AlertDialog dialog = builder.create();
2739             dialog.setOnCancelListener(this);
2740             dialog.setOnDismissListener(this);
2741             dialog.setOnShowListener(this);
2742 
2743             return dialog;
2744         }
2745 
onCancel(DialogInterface dialog)2746         public void onCancel(DialogInterface dialog) {
2747             mWaitingForResult = false;
2748             cleanup();
2749         }
2750 
onDismiss(DialogInterface dialog)2751         public void onDismiss(DialogInterface dialog) {
2752             mWaitingForResult = false;
2753             cleanup();
2754         }
2755 
cleanup()2756         private void cleanup() {
2757             try {
2758                 dismissDialog(DIALOG_CREATE_SHORTCUT);
2759             } catch (Exception e) {
2760                 // An exception is thrown if the dialog is not visible, which is fine
2761             }
2762         }
2763 
2764         /**
2765          * Handle the action clicked in the "Add to home" dialog.
2766          */
onClick(DialogInterface dialog, int which)2767         public void onClick(DialogInterface dialog, int which) {
2768             cleanup();
2769 
2770             AddAdapter.ListItem item = (AddAdapter.ListItem) mAdapter.getItem(which);
2771             switch (item.actionTag) {
2772                 case AddAdapter.ITEM_APPLICATION: {
2773                     if (mAppsCustomizeTabHost != null) {
2774                         mAppsCustomizeTabHost.selectAppsTab();
2775                     }
2776                     showAllApps(true);
2777                     break;
2778                 }
2779                 case AddAdapter.ITEM_APPWIDGET: {
2780                     if (mAppsCustomizeTabHost != null) {
2781                         mAppsCustomizeTabHost.selectWidgetsTab();
2782                     }
2783                     showAllApps(true);
2784                     break;
2785                 }
2786                 case AddAdapter.ITEM_WALLPAPER: {
2787                     startWallpaper();
2788                     break;
2789                 }
2790             }
2791         }
2792 
onShow(DialogInterface dialog)2793         public void onShow(DialogInterface dialog) {
2794             mWaitingForResult = true;
2795         }
2796     }
2797 
2798     /**
2799      * Receives notifications when system dialogs are to be closed.
2800      */
2801     private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
2802         @Override
onReceive(Context context, Intent intent)2803         public void onReceive(Context context, Intent intent) {
2804             closeSystemDialogs();
2805         }
2806     }
2807 
2808     /**
2809      * Receives notifications whenever the appwidgets are reset.
2810      */
2811     private class AppWidgetResetObserver extends ContentObserver {
AppWidgetResetObserver()2812         public AppWidgetResetObserver() {
2813             super(new Handler());
2814         }
2815 
2816         @Override
onChange(boolean selfChange)2817         public void onChange(boolean selfChange) {
2818             onAppWidgetReset();
2819         }
2820     }
2821 
2822     /**
2823      * If the activity is currently paused, signal that we need to re-run the loader
2824      * in onResume.
2825      *
2826      * This needs to be called from incoming places where resources might have been loaded
2827      * while we are paused.  That is becaues the Configuration might be wrong
2828      * when we're not running, and if it comes back to what it was when we
2829      * were paused, we are not restarted.
2830      *
2831      * Implementation of the method from LauncherModel.Callbacks.
2832      *
2833      * @return true if we are currently paused.  The caller might be able to
2834      * skip some work in that case since we will come back again.
2835      */
setLoadOnResume()2836     public boolean setLoadOnResume() {
2837         if (mPaused) {
2838             Log.i(TAG, "setLoadOnResume");
2839             mOnResumeNeedsLoad = true;
2840             return true;
2841         } else {
2842             return false;
2843         }
2844     }
2845 
2846     /**
2847      * Implementation of the method from LauncherModel.Callbacks.
2848      */
getCurrentWorkspaceScreen()2849     public int getCurrentWorkspaceScreen() {
2850         if (mWorkspace != null) {
2851             return mWorkspace.getCurrentPage();
2852         } else {
2853             return SCREEN_COUNT / 2;
2854         }
2855     }
2856 
2857 
2858     /**
2859      * Refreshes the shortcuts shown on the workspace.
2860      *
2861      * Implementation of the method from LauncherModel.Callbacks.
2862      */
startBinding()2863     public void startBinding() {
2864         final Workspace workspace = mWorkspace;
2865 
2866         mWorkspace.clearDropTargets();
2867         int count = workspace.getChildCount();
2868         for (int i = 0; i < count; i++) {
2869             // Use removeAllViewsInLayout() to avoid an extra requestLayout() and invalidate().
2870             final CellLayout layoutParent = (CellLayout) workspace.getChildAt(i);
2871             layoutParent.removeAllViewsInLayout();
2872         }
2873         if (mHotseat != null) {
2874             mHotseat.resetLayout();
2875         }
2876     }
2877 
2878     /**
2879      * Bind the items start-end from the list.
2880      *
2881      * Implementation of the method from LauncherModel.Callbacks.
2882      */
bindItems(ArrayList<ItemInfo> shortcuts, int start, int end)2883     public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end) {
2884         setLoadOnResume();
2885 
2886         final Workspace workspace = mWorkspace;
2887         for (int i=start; i<end; i++) {
2888             final ItemInfo item = shortcuts.get(i);
2889 
2890             // Short circuit if we are loading dock items for a configuration which has no dock
2891             if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2892                     mHotseat == null) {
2893                 continue;
2894             }
2895 
2896             switch (item.itemType) {
2897                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
2898                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2899                     View shortcut = createShortcut((ShortcutInfo)item);
2900                     workspace.addInScreen(shortcut, item.container, item.screen, item.cellX,
2901                             item.cellY, 1, 1, false);
2902                     break;
2903                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2904                     FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
2905                             (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
2906                             (FolderInfo) item, mIconCache);
2907                     workspace.addInScreen(newFolder, item.container, item.screen, item.cellX,
2908                             item.cellY, 1, 1, false);
2909                     break;
2910             }
2911         }
2912         workspace.requestLayout();
2913     }
2914 
2915     /**
2916      * Implementation of the method from LauncherModel.Callbacks.
2917      */
bindFolders(HashMap<Long, FolderInfo> folders)2918     public void bindFolders(HashMap<Long, FolderInfo> folders) {
2919         setLoadOnResume();
2920         sFolders.clear();
2921         sFolders.putAll(folders);
2922     }
2923 
2924     /**
2925      * Add the views for a widget to the workspace.
2926      *
2927      * Implementation of the method from LauncherModel.Callbacks.
2928      */
bindAppWidget(LauncherAppWidgetInfo item)2929     public void bindAppWidget(LauncherAppWidgetInfo item) {
2930         setLoadOnResume();
2931 
2932         final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
2933         if (DEBUG_WIDGETS) {
2934             Log.d(TAG, "bindAppWidget: " + item);
2935         }
2936         final Workspace workspace = mWorkspace;
2937 
2938         final int appWidgetId = item.appWidgetId;
2939         final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
2940         if (DEBUG_WIDGETS) {
2941             Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider);
2942         }
2943 
2944         item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
2945 
2946         item.hostView.setAppWidget(appWidgetId, appWidgetInfo);
2947         item.hostView.setTag(item);
2948 
2949         workspace.addInScreen(item.hostView, item.container, item.screen, item.cellX,
2950                 item.cellY, item.spanX, item.spanY, false);
2951 
2952         addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
2953 
2954         workspace.requestLayout();
2955 
2956         if (DEBUG_WIDGETS) {
2957             Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
2958                     + (SystemClock.uptimeMillis()-start) + "ms");
2959         }
2960     }
2961 
2962     /**
2963      * Callback saying that there aren't any more items to bind.
2964      *
2965      * Implementation of the method from LauncherModel.Callbacks.
2966      */
finishBindingItems()2967     public void finishBindingItems() {
2968         setLoadOnResume();
2969 
2970         if (mSavedState != null) {
2971             if (!mWorkspace.hasFocus()) {
2972                 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
2973             }
2974             mSavedState = null;
2975         }
2976 
2977         if (mSavedInstanceState != null) {
2978             super.onRestoreInstanceState(mSavedInstanceState);
2979             mSavedInstanceState = null;
2980         }
2981 
2982         mWorkspaceLoading = false;
2983 
2984         // If we received the result of any pending adds while the loader was running (e.g. the
2985         // widget configuration forced an orientation change), process them now.
2986         for (int i = 0; i < sPendingAddList.size(); i++) {
2987             completeAdd(sPendingAddList.get(i));
2988         }
2989         sPendingAddList.clear();
2990 
2991         // Update the market app icon as necessary (the other icons will be managed in response to
2992         // package changes in bindSearchablesChanged()
2993         updateAppMarketIcon();
2994 
2995         mWorkspace.post(mBuildLayersRunnable);
2996     }
2997 
2998     @Override
bindSearchablesChanged()2999     public void bindSearchablesChanged() {
3000         boolean searchVisible = updateGlobalSearchIcon();
3001         boolean voiceVisible = updateVoiceSearchIcon(searchVisible);
3002         mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
3003     }
3004 
3005     /**
3006      * Add the icons for all apps.
3007      *
3008      * Implementation of the method from LauncherModel.Callbacks.
3009      */
bindAllApplications(final ArrayList<ApplicationInfo> apps)3010     public void bindAllApplications(final ArrayList<ApplicationInfo> apps) {
3011         // Remove the progress bar entirely; we could also make it GONE
3012         // but better to remove it since we know it's not going to be used
3013         View progressBar = mAppsCustomizeTabHost.
3014             findViewById(R.id.apps_customize_progress_bar);
3015         if (progressBar != null) {
3016             ((ViewGroup)progressBar.getParent()).removeView(progressBar);
3017         }
3018         // We just post the call to setApps so the user sees the progress bar
3019         // disappear-- otherwise, it just looks like the progress bar froze
3020         // which doesn't look great
3021         mAppsCustomizeTabHost.post(new Runnable() {
3022             public void run() {
3023                 if (mAppsCustomizeContent != null) {
3024                     mAppsCustomizeContent.setApps(apps);
3025                 }
3026             }
3027         });
3028     }
3029 
3030     /**
3031      * A package was installed.
3032      *
3033      * Implementation of the method from LauncherModel.Callbacks.
3034      */
bindAppsAdded(ArrayList<ApplicationInfo> apps)3035     public void bindAppsAdded(ArrayList<ApplicationInfo> apps) {
3036         setLoadOnResume();
3037         removeDialog(DIALOG_CREATE_SHORTCUT);
3038 
3039         if (mAppsCustomizeContent != null) {
3040             mAppsCustomizeContent.addApps(apps);
3041         }
3042     }
3043 
3044     /**
3045      * A package was updated.
3046      *
3047      * Implementation of the method from LauncherModel.Callbacks.
3048      */
bindAppsUpdated(ArrayList<ApplicationInfo> apps)3049     public void bindAppsUpdated(ArrayList<ApplicationInfo> apps) {
3050         setLoadOnResume();
3051         removeDialog(DIALOG_CREATE_SHORTCUT);
3052         if (mWorkspace != null) {
3053             mWorkspace.updateShortcuts(apps);
3054         }
3055 
3056         if (mAppsCustomizeContent != null) {
3057             mAppsCustomizeContent.updateApps(apps);
3058         }
3059     }
3060 
3061     /**
3062      * A package was uninstalled.
3063      *
3064      * Implementation of the method from LauncherModel.Callbacks.
3065      */
bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent)3066     public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent) {
3067         removeDialog(DIALOG_CREATE_SHORTCUT);
3068         if (permanent) {
3069             mWorkspace.removeItems(apps);
3070         }
3071 
3072         if (mAppsCustomizeContent != null) {
3073             mAppsCustomizeContent.removeApps(apps);
3074         }
3075 
3076         // Notify the drag controller
3077         mDragController.onAppsRemoved(apps, this);
3078     }
3079 
3080     /**
3081      * A number of packages were updated.
3082      */
bindPackagesUpdated()3083     public void bindPackagesUpdated() {
3084         if (mAppsCustomizeContent != null) {
3085             mAppsCustomizeContent.onPackagesUpdated();
3086         }
3087     }
3088 
mapConfigurationOriActivityInfoOri(int configOri)3089     private int mapConfigurationOriActivityInfoOri(int configOri) {
3090         final Display d = getWindowManager().getDefaultDisplay();
3091         int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
3092         switch (d.getRotation()) {
3093         case Surface.ROTATION_0:
3094         case Surface.ROTATION_180:
3095             // We are currently in the same basic orientation as the natural orientation
3096             naturalOri = configOri;
3097             break;
3098         case Surface.ROTATION_90:
3099         case Surface.ROTATION_270:
3100             // We are currently in the other basic orientation to the natural orientation
3101             naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
3102                     Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
3103             break;
3104         }
3105 
3106         int[] oriMap = {
3107                 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
3108                 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
3109                 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
3110                 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
3111         };
3112         // Since the map starts at portrait, we need to offset if this device's natural orientation
3113         // is landscape.
3114         int indexOffset = 0;
3115         if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
3116             indexOffset = 1;
3117         }
3118         return oriMap[(d.getRotation() + indexOffset) % 4];
3119     }
3120 
lockScreenOrientationOnLargeUI()3121     public void lockScreenOrientationOnLargeUI() {
3122         if (LauncherApplication.isScreenLarge()) {
3123             setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
3124                     .getConfiguration().orientation));
3125         }
3126     }
unlockScreenOrientationOnLargeUI()3127     public void unlockScreenOrientationOnLargeUI() {
3128         if (LauncherApplication.isScreenLarge()) {
3129             mHandler.postDelayed(new Runnable() {
3130                 public void run() {
3131                     setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
3132                 }
3133             }, mRestoreScreenOrientationDelay);
3134         }
3135     }
3136 
3137     /* Cling related */
3138     private static final String PREFS_KEY = "com.android.launcher2.prefs";
isClingsEnabled()3139     private boolean isClingsEnabled() {
3140         // TEMPORARY: DISABLE CLINGS ON LARGE UI
3141         if (LauncherApplication.isScreenLarge()) return false;
3142         // disable clings when running in a test harness
3143         if(ActivityManager.isRunningInTestHarness()) return false;
3144 
3145         return true;
3146     }
initCling(int clingId, int[] positionData, boolean animate, int delay)3147     private Cling initCling(int clingId, int[] positionData, boolean animate, int delay) {
3148         Cling cling = (Cling) findViewById(clingId);
3149         if (cling != null) {
3150             cling.init(this, positionData);
3151             cling.setVisibility(View.VISIBLE);
3152             cling.setLayerType(View.LAYER_TYPE_HARDWARE, null);
3153             if (animate) {
3154                 cling.buildLayer();
3155                 cling.setAlpha(0f);
3156                 cling.animate()
3157                     .alpha(1f)
3158                     .setInterpolator(new AccelerateInterpolator())
3159                     .setDuration(SHOW_CLING_DURATION)
3160                     .setStartDelay(delay)
3161                     .start();
3162             } else {
3163                 cling.setAlpha(1f);
3164             }
3165         }
3166         return cling;
3167     }
dismissCling(final Cling cling, final String flag, int duration)3168     private void dismissCling(final Cling cling, final String flag, int duration) {
3169         if (cling != null) {
3170             ObjectAnimator anim = ObjectAnimator.ofFloat(cling, "alpha", 0f);
3171             anim.setDuration(duration);
3172             anim.addListener(new AnimatorListenerAdapter() {
3173                 public void onAnimationEnd(Animator animation) {
3174                     cling.setVisibility(View.GONE);
3175                     cling.cleanup();
3176                     SharedPreferences prefs =
3177                         getSharedPreferences("com.android.launcher2.prefs", Context.MODE_PRIVATE);
3178                     SharedPreferences.Editor editor = prefs.edit();
3179                     editor.putBoolean(flag, true);
3180                     editor.commit();
3181                 };
3182             });
3183             anim.start();
3184         }
3185     }
removeCling(int id)3186     private void removeCling(int id) {
3187         final View cling = findViewById(id);
3188         if (cling != null) {
3189             final ViewGroup parent = (ViewGroup) cling.getParent();
3190             parent.post(new Runnable() {
3191                 @Override
3192                 public void run() {
3193                     parent.removeView(cling);
3194                 }
3195             });
3196         }
3197     }
showFirstRunWorkspaceCling()3198     public void showFirstRunWorkspaceCling() {
3199         // Enable the clings only if they have not been dismissed before
3200         SharedPreferences prefs =
3201             getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE);
3202         if (isClingsEnabled() && !prefs.getBoolean(Cling.WORKSPACE_CLING_DISMISSED_KEY, false)) {
3203             initCling(R.id.workspace_cling, null, false, 0);
3204         } else {
3205             removeCling(R.id.workspace_cling);
3206         }
3207     }
showFirstRunAllAppsCling(int[] position)3208     public void showFirstRunAllAppsCling(int[] position) {
3209         // Enable the clings only if they have not been dismissed before
3210         SharedPreferences prefs =
3211             getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE);
3212         if (isClingsEnabled() && !prefs.getBoolean(Cling.ALLAPPS_CLING_DISMISSED_KEY, false)) {
3213             initCling(R.id.all_apps_cling, position, true, 0);
3214         } else {
3215             removeCling(R.id.all_apps_cling);
3216         }
3217     }
showFirstRunFoldersCling()3218     public Cling showFirstRunFoldersCling() {
3219         // Enable the clings only if they have not been dismissed before
3220         SharedPreferences prefs =
3221             getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE);
3222         Cling cling = null;
3223         if (isClingsEnabled() && !prefs.getBoolean(Cling.FOLDER_CLING_DISMISSED_KEY, false)) {
3224             cling = initCling(R.id.folder_cling, null, true, 0);
3225         } else {
3226             removeCling(R.id.folder_cling);
3227         }
3228         return cling;
3229     }
isFolderClingVisible()3230     public boolean isFolderClingVisible() {
3231         Cling cling = (Cling) findViewById(R.id.folder_cling);
3232         if (cling != null) {
3233             return cling.getVisibility() == View.VISIBLE;
3234         }
3235         return false;
3236     }
dismissWorkspaceCling(View v)3237     public void dismissWorkspaceCling(View v) {
3238         Cling cling = (Cling) findViewById(R.id.workspace_cling);
3239         dismissCling(cling, Cling.WORKSPACE_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION);
3240     }
dismissAllAppsCling(View v)3241     public void dismissAllAppsCling(View v) {
3242         Cling cling = (Cling) findViewById(R.id.all_apps_cling);
3243         dismissCling(cling, Cling.ALLAPPS_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION);
3244     }
dismissFolderCling(View v)3245     public void dismissFolderCling(View v) {
3246         Cling cling = (Cling) findViewById(R.id.folder_cling);
3247         dismissCling(cling, Cling.FOLDER_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION);
3248     }
3249 
3250     /**
3251      * Prints out out state for debugging.
3252      */
dumpState()3253     public void dumpState() {
3254         Log.d(TAG, "BEGIN launcher2 dump state for launcher " + this);
3255         Log.d(TAG, "mSavedState=" + mSavedState);
3256         Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
3257         Log.d(TAG, "mRestoring=" + mRestoring);
3258         Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
3259         Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
3260         Log.d(TAG, "sFolders.size=" + sFolders.size());
3261         mModel.dumpState();
3262 
3263         if (mAppsCustomizeContent != null) {
3264             mAppsCustomizeContent.dumpState();
3265         }
3266         Log.d(TAG, "END launcher2 dump state");
3267     }
3268 
3269     @Override
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)3270     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
3271         super.dump(prefix, fd, writer, args);
3272         writer.println(" ");
3273         writer.println("Debug logs: ");
3274         for (int i = 0; i < sDumpLogs.size(); i++) {
3275             writer.println("  " + sDumpLogs.get(i));
3276         }
3277     }
3278 }
3279 
3280 interface LauncherTransitionable {
onLauncherTransitionStart(Launcher l, Animator animation, boolean toWorkspace)3281     void onLauncherTransitionStart(Launcher l, Animator animation, boolean toWorkspace);
onLauncherTransitionEnd(Launcher l, Animator animation, boolean toWorkspace)3282     void onLauncherTransitionEnd(Launcher l, Animator animation, boolean toWorkspace);
3283 }
3284