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