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