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