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