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