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