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