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