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