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