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