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