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