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