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