Launcher.java revision 815ba2d2eabded5ffee53fba668d63ca9173e6fd
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 com.android.common.Search;
21import com.android.launcher.R;
22import com.android.launcher2.CustomizePagedView.CustomizationType;
23import com.android.launcher2.Workspace.ShrinkState;
24
25import android.animation.Animator;
26import android.animation.AnimatorListenerAdapter;
27import android.animation.AnimatorSet;
28import android.animation.ObjectAnimator;
29import android.animation.PropertyValuesHolder;
30import android.animation.ValueAnimator;
31import android.app.Activity;
32import android.app.AlertDialog;
33import android.app.Dialog;
34import android.app.SearchManager;
35import android.app.StatusBarManager;
36import android.app.WallpaperManager;
37import android.appwidget.AppWidgetManager;
38import android.appwidget.AppWidgetProviderInfo;
39import android.content.ActivityNotFoundException;
40import android.content.BroadcastReceiver;
41import android.content.ClipData;
42import android.content.ClipDescription;
43import android.content.ComponentName;
44import android.content.ContentResolver;
45import android.content.Context;
46import android.content.DialogInterface;
47import android.content.Intent;
48import android.content.IntentFilter;
49import android.content.Intent.ShortcutIconResource;
50import android.content.pm.ActivityInfo;
51import android.content.pm.PackageManager;
52import android.content.pm.ResolveInfo;
53import android.content.pm.PackageManager.NameNotFoundException;
54import android.content.res.Configuration;
55import android.content.res.Resources;
56import android.content.res.TypedArray;
57import android.database.ContentObserver;
58import android.graphics.Bitmap;
59import android.graphics.Canvas;
60import android.graphics.Rect;
61import android.graphics.drawable.ColorDrawable;
62import android.graphics.drawable.Drawable;
63import android.net.Uri;
64import android.os.AsyncTask;
65import android.os.Bundle;
66import android.os.Environment;
67import android.os.Handler;
68import android.os.Message;
69import android.os.Parcelable;
70import android.os.SystemClock;
71import android.os.SystemProperties;
72import android.provider.LiveFolders;
73import android.provider.Settings;
74import android.speech.RecognizerIntent;
75import android.text.Selection;
76import android.text.SpannableStringBuilder;
77import android.text.TextUtils;
78import android.text.method.TextKeyListener;
79import android.util.Log;
80import android.view.Display;
81import android.view.HapticFeedbackConstants;
82import android.view.KeyEvent;
83import android.view.LayoutInflater;
84import android.view.Menu;
85import android.view.MenuItem;
86import android.view.MotionEvent;
87import android.view.View;
88import android.view.ViewGroup;
89import android.view.WindowManager;
90import android.view.View.OnLongClickListener;
91import android.view.accessibility.AccessibilityEvent;
92import android.view.inputmethod.InputMethodManager;
93import android.widget.Advanceable;
94import android.widget.EditText;
95import android.widget.ImageView;
96import android.widget.LinearLayout;
97import android.widget.PopupWindow;
98import android.widget.TabHost;
99import android.widget.TabWidget;
100import android.widget.TextView;
101import android.widget.Toast;
102import android.widget.TabHost.OnTabChangeListener;
103import android.widget.TabHost.TabContentFactory;
104
105import java.io.DataInputStream;
106import java.io.DataOutputStream;
107import java.io.FileNotFoundException;
108import java.io.IOException;
109import java.util.ArrayList;
110import java.util.HashMap;
111import java.util.List;
112
113
114/**
115 * Default launcher application.
116 */
117public final class Launcher extends Activity
118        implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
119                   AllAppsView.Watcher, View.OnTouchListener {
120    static final String TAG = "Launcher";
121    static final boolean LOGD = false;
122
123    static final boolean PROFILE_STARTUP = false;
124    static final boolean DEBUG_WIDGETS = false;
125    static final boolean DEBUG_USER_INTERFACE = false;
126
127    private static final int MENU_GROUP_ADD = 1;
128    private static final int MENU_GROUP_WALLPAPER = MENU_GROUP_ADD + 1;
129
130    private static final int MENU_ADD = Menu.FIRST + 1;
131    private static final int MENU_MANAGE_APPS = MENU_ADD + 1;
132    private static final int MENU_WALLPAPER_SETTINGS = MENU_MANAGE_APPS + 1;
133    private static final int MENU_SEARCH = MENU_WALLPAPER_SETTINGS + 1;
134    private static final int MENU_NOTIFICATIONS = MENU_SEARCH + 1;
135    private static final int MENU_SETTINGS = MENU_NOTIFICATIONS + 1;
136
137    private static final int REQUEST_CREATE_SHORTCUT = 1;
138    private static final int REQUEST_CREATE_LIVE_FOLDER = 4;
139    private static final int REQUEST_CREATE_APPWIDGET = 5;
140    private static final int REQUEST_PICK_APPLICATION = 6;
141    private static final int REQUEST_PICK_SHORTCUT = 7;
142    private static final int REQUEST_PICK_LIVE_FOLDER = 8;
143    private static final int REQUEST_PICK_APPWIDGET = 9;
144    private static final int REQUEST_PICK_WALLPAPER = 10;
145
146    static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
147
148    static final int SCREEN_COUNT = 5;
149    static final int DEFAULT_SCREEN = 2;
150
151    static final int DIALOG_CREATE_SHORTCUT = 1;
152    static final int DIALOG_RENAME_FOLDER = 2;
153
154    private static final String PREFERENCES = "launcher.preferences";
155
156    // Type: int
157    private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
158    // Type: int
159    private static final String RUNTIME_STATE = "launcher.state";
160    // Type: long
161    private static final String RUNTIME_STATE_USER_FOLDERS = "launcher.user_folder";
162    // Type: int
163    private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
164    // Type: int
165    private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cellX";
166    // Type: int
167    private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cellY";
168    // Type: boolean
169    private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
170    // Type: long
171    private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
172
173    // tags for the customization tabs
174    private static final String WIDGETS_TAG = "widgets";
175    private static final String APPLICATIONS_TAG = "applications";
176    private static final String SHORTCUTS_TAG = "shortcuts";
177    private static final String WALLPAPERS_TAG = "wallpapers";
178
179    private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
180
181    /** The different states that Launcher can be in. */
182    private enum State { WORKSPACE, ALL_APPS, CUSTOMIZE, OVERVIEW,
183        CUSTOMIZE_SPRING_LOADED, ALL_APPS_SPRING_LOADED };
184    private State mState = State.WORKSPACE;
185    private AnimatorSet mStateAnimation;
186
187    static final int APPWIDGET_HOST_ID = 1024;
188
189    private static final Object sLock = new Object();
190    private static int sScreen = DEFAULT_SCREEN;
191
192    private final BroadcastReceiver mCloseSystemDialogsReceiver
193            = new CloseSystemDialogsIntentReceiver();
194    private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
195
196    private LayoutInflater mInflater;
197
198    private DragController mDragController;
199    private Workspace mWorkspace;
200
201    private AppWidgetManager mAppWidgetManager;
202    private LauncherAppWidgetHost mAppWidgetHost;
203
204    private int mAddScreen = -1;
205    private int mAddIntersectCellX = -1;
206    private int mAddIntersectCellY = -1;
207    private int[] mAddDropPosition;
208    private int[] mTmpAddItemCellCoordinates = new int[2];
209
210    private FolderInfo mFolderInfo;
211
212    private DeleteZone mDeleteZone;
213    private HandleView mHandleView;
214    private AllAppsView mAllAppsGrid;
215    private TabHost mHomeCustomizationDrawer;
216    private boolean mAutoAdvanceRunning = false;
217
218    private AllAppsPagedView mAllAppsPagedView = null;
219    private CustomizePagedView mCustomizePagedView = null;
220
221    private Bundle mSavedState;
222
223    private SpannableStringBuilder mDefaultKeySsb = null;
224
225    private boolean mWorkspaceLoading = true;
226
227    private boolean mPaused = true;
228    private boolean mRestoring;
229    private boolean mWaitingForResult;
230    private boolean mOnResumeNeedsLoad;
231
232    private Bundle mSavedInstanceState;
233
234    private LauncherModel mModel;
235    private IconCache mIconCache;
236    private boolean mUserPresent = true;
237    private boolean mVisible = false;
238    private boolean mAttached = false;
239
240    private static LocaleConfiguration sLocaleConfiguration = null;
241
242    private ArrayList<ItemInfo> mDesktopItems = new ArrayList<ItemInfo>();
243
244    private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
245
246    private ImageView mPreviousView;
247    private ImageView mNextView;
248
249    // Hotseats (quick-launch icons next to AllApps)
250    private String[] mHotseatConfig = null;
251    private Intent[] mHotseats = null;
252    private Drawable[] mHotseatIcons = null;
253    private CharSequence[] mHotseatLabels = null;
254
255    private Intent mAppMarketIntent = null;
256
257    // Related to the auto-advancing of widgets
258    private final int ADVANCE_MSG = 1;
259    private final int mAdvanceInterval = 20000;
260    private final int mAdvanceStagger = 250;
261    private long mAutoAdvanceSentTime;
262    private long mAutoAdvanceTimeLeft = -1;
263    private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
264        new HashMap<View, AppWidgetProviderInfo>();
265
266    // External icons saved in case of resource changes, orientation, etc.
267    private static Drawable.ConstantState sGlobalSearchIcon;
268    private static Drawable.ConstantState sVoiceSearchIcon;
269    private static Drawable.ConstantState sAppMarketIcon;
270
271    private CustomizationType getCustomizeFilterForTabTag(String tag) {
272        if (tag.equals(WIDGETS_TAG)) {
273            return CustomizationType.WidgetCustomization;
274        } else if (tag.equals(APPLICATIONS_TAG)) {
275            return CustomizationType.ApplicationCustomization;
276        } else if (tag.equals(WALLPAPERS_TAG)) {
277            return CustomizePagedView.CustomizationType.WallpaperCustomization;
278        } else if (tag.equals(SHORTCUTS_TAG)) {
279            return CustomizePagedView.CustomizationType.ShortcutCustomization;
280        }
281        return CustomizationType.WidgetCustomization;
282    }
283
284    @Override
285    protected void onCreate(Bundle savedInstanceState) {
286        super.onCreate(savedInstanceState);
287
288        if (LauncherApplication.isInPlaceRotationEnabled()) {
289            // hide the status bar (temporary until we get the status bar design figured out)
290            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
291            this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
292        }
293
294        LauncherApplication app = ((LauncherApplication)getApplication());
295        mModel = app.setLauncher(this);
296        mIconCache = app.getIconCache();
297        mDragController = new DragController(this);
298        mInflater = getLayoutInflater();
299
300        mAppWidgetManager = AppWidgetManager.getInstance(this);
301        mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
302        mAppWidgetHost.startListening();
303
304        if (PROFILE_STARTUP) {
305            android.os.Debug.startMethodTracing(
306                    Environment.getExternalStorageDirectory() + "/launcher");
307        }
308
309        loadHotseats();
310        checkForLocaleChange();
311        setWallpaperDimension();
312        setContentView(R.layout.launcher);
313        mHomeCustomizationDrawer = (TabHost) findViewById(R.id.customization_drawer);
314        if (mHomeCustomizationDrawer != null) {
315            mHomeCustomizationDrawer.setup();
316
317            // share the same customization workspace across all the tabs
318            mCustomizePagedView = (CustomizePagedView) mInflater.inflate(
319                    R.layout.customization_drawer, mHomeCustomizationDrawer, false);
320            TabContentFactory contentFactory = new TabContentFactory() {
321                public View createTabContent(String tag) {
322                    return mCustomizePagedView;
323                }
324            };
325
326
327            TextView tabView;
328            TabWidget tabWidget = (TabWidget)
329                    mHomeCustomizationDrawer.findViewById(com.android.internal.R.id.tabs);
330
331            tabView = (TextView) mInflater.inflate(R.layout.tab_widget_indicator, tabWidget, false);
332            tabView.setText(getString(R.string.widgets_tab_label));
333            mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(WIDGETS_TAG)
334                    .setIndicator(tabView).setContent(contentFactory));
335            tabView = (TextView) mInflater.inflate(R.layout.tab_widget_indicator, tabWidget, false);
336            tabView.setText(getString(R.string.applications_tab_label));
337            mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(APPLICATIONS_TAG)
338                    .setIndicator(tabView).setContent(contentFactory));
339            tabView = (TextView) mInflater.inflate(R.layout.tab_widget_indicator, tabWidget, false);
340            tabView.setText(getString(R.string.wallpapers_tab_label));
341            mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(WALLPAPERS_TAG)
342                    .setIndicator(tabView).setContent(contentFactory));
343            tabView = (TextView) mInflater.inflate(R.layout.tab_widget_indicator, tabWidget, false);
344            tabView.setText(getString(R.string.shortcuts_tab_label));
345            mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(SHORTCUTS_TAG)
346                    .setIndicator(tabView).setContent(contentFactory));
347            mHomeCustomizationDrawer.setOnTabChangedListener(new OnTabChangeListener() {
348                public void onTabChanged(String tabId) {
349                    final CustomizePagedView.CustomizationType newType =
350                        getCustomizeFilterForTabTag(tabId);
351                    if (newType != mCustomizePagedView.getCustomizationFilter()) {
352                        // animate the changing of the tab content by fading pages in and out
353                        final Resources res = getResources();
354                        final int duration = res.getInteger(R.integer.config_tabTransitionTime);
355                        final float alpha = mCustomizePagedView.getAlpha();
356                        ValueAnimator alphaAnim = ObjectAnimator.ofFloat(mCustomizePagedView,
357                                "alpha", alpha, 0.0f);
358                        alphaAnim.setDuration(duration);
359                        alphaAnim.addListener(new AnimatorListenerAdapter() {
360                            @Override
361                            public void onAnimationEnd(Animator animation) {
362                                String tag = mHomeCustomizationDrawer.getCurrentTabTag();
363                                mCustomizePagedView.setCustomizationFilter(newType);
364
365                                final float alpha = mCustomizePagedView.getAlpha();
366                                ValueAnimator alphaAnim = ObjectAnimator.ofFloat(
367                                        mCustomizePagedView, "alpha", alpha, 1.0f);
368                                alphaAnim.setDuration(duration);
369                                alphaAnim.start();
370                            }
371                        });
372                        alphaAnim.start();
373                    }
374                }
375            });
376        }
377        setupViews();
378
379        registerContentObservers();
380
381        lockAllApps();
382
383        mSavedState = savedInstanceState;
384        restoreState(mSavedState);
385
386        // Update customization drawer _after_ restoring the states
387        if (mCustomizePagedView != null) {
388            mCustomizePagedView.update();
389        }
390
391        if (PROFILE_STARTUP) {
392            android.os.Debug.stopMethodTracing();
393        }
394
395        if (!mRestoring) {
396            mModel.startLoader(this, true);
397        }
398
399        // For handling default keys
400        mDefaultKeySsb = new SpannableStringBuilder();
401        Selection.setSelection(mDefaultKeySsb, 0);
402
403        IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
404        registerReceiver(mCloseSystemDialogsReceiver, filter);
405
406        // If we have a saved version of these external icons, we load them up immediately
407        if (LauncherApplication.isScreenXLarge()) {
408            if (sGlobalSearchIcon != null) {
409                updateGlobalSearchIcon(sGlobalSearchIcon);
410            }
411            if (sVoiceSearchIcon != null) {
412                updateVoiceSearchIcon(sVoiceSearchIcon);
413            }
414            if (sAppMarketIcon != null) {
415                updateAppMarketIcon(sAppMarketIcon);
416            }
417        }
418    }
419
420    @Override
421    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
422        super.dispatchPopulateAccessibilityEvent(event);
423
424        // we want to take over text population so it is context dependent
425        event.getText().clear();
426        if (mState == State.ALL_APPS) {
427            event.getText().add(getString(R.string.all_apps_button_label));
428        } else if (mState == State.WORKSPACE) {
429            event.getText().add(getString(R.string.all_apps_home_button_label));
430        }
431
432        return true;
433    }
434
435    private void checkForLocaleChange() {
436        if (sLocaleConfiguration == null) {
437            new AsyncTask<Void, Void, LocaleConfiguration>() {
438                @Override
439                protected LocaleConfiguration doInBackground(Void... unused) {
440                    LocaleConfiguration localeConfiguration = new LocaleConfiguration();
441                    readConfiguration(Launcher.this, localeConfiguration);
442                    return localeConfiguration;
443                }
444
445                @Override
446                protected void onPostExecute(LocaleConfiguration result) {
447                    sLocaleConfiguration = result;
448                    checkForLocaleChange();  // recursive, but now with a locale configuration
449                }
450            }.execute();
451            return;
452        }
453
454        final Configuration configuration = getResources().getConfiguration();
455
456        final String previousLocale = sLocaleConfiguration.locale;
457        final String locale = configuration.locale.toString();
458
459        final int previousMcc = sLocaleConfiguration.mcc;
460        final int mcc = configuration.mcc;
461
462        final int previousMnc = sLocaleConfiguration.mnc;
463        final int mnc = configuration.mnc;
464
465        boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;
466
467        if (localeChanged) {
468            sLocaleConfiguration.locale = locale;
469            sLocaleConfiguration.mcc = mcc;
470            sLocaleConfiguration.mnc = mnc;
471
472            mIconCache.flush();
473            loadHotseats();
474
475            final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
476            new Thread("WriteLocaleConfiguration") {
477                @Override
478                public void run() {
479                    writeConfiguration(Launcher.this, localeConfiguration);
480                }
481            }.start();
482        }
483    }
484
485    private static class LocaleConfiguration {
486        public String locale;
487        public int mcc = -1;
488        public int mnc = -1;
489    }
490
491    private static void readConfiguration(Context context, LocaleConfiguration configuration) {
492        DataInputStream in = null;
493        try {
494            in = new DataInputStream(context.openFileInput(PREFERENCES));
495            configuration.locale = in.readUTF();
496            configuration.mcc = in.readInt();
497            configuration.mnc = in.readInt();
498        } catch (FileNotFoundException e) {
499            // Ignore
500        } catch (IOException e) {
501            // Ignore
502        } finally {
503            if (in != null) {
504                try {
505                    in.close();
506                } catch (IOException e) {
507                    // Ignore
508                }
509            }
510        }
511    }
512
513    private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
514        DataOutputStream out = null;
515        try {
516            out = new DataOutputStream(context.openFileOutput(PREFERENCES, MODE_PRIVATE));
517            out.writeUTF(configuration.locale);
518            out.writeInt(configuration.mcc);
519            out.writeInt(configuration.mnc);
520            out.flush();
521        } catch (FileNotFoundException e) {
522            // Ignore
523        } catch (IOException e) {
524            //noinspection ResultOfMethodCallIgnored
525            context.getFileStreamPath(PREFERENCES).delete();
526        } finally {
527            if (out != null) {
528                try {
529                    out.close();
530                } catch (IOException e) {
531                    // Ignore
532                }
533            }
534        }
535    }
536
537    static int getScreen() {
538        synchronized (sLock) {
539            return sScreen;
540        }
541    }
542
543    static void setScreen(int screen) {
544        synchronized (sLock) {
545            sScreen = screen;
546        }
547    }
548
549    private void setWallpaperDimension() {
550        WallpaperManager wpm = (WallpaperManager)getSystemService(WALLPAPER_SERVICE);
551
552        Display display = getWindowManager().getDefaultDisplay();
553        // TODO: Put back when we decide about scrolling the wallpaper
554        // boolean isPortrait = display.getWidth() < display.getHeight();
555        // final int width = isPortrait ? display.getWidth() : display.getHeight();
556        // final int height = isPortrait ? display.getHeight() : display.getWidth();
557        wpm.suggestDesiredDimensions(Math.max(display.getWidth(), display.getHeight()),
558                Math.max(display.getWidth(), display.getHeight()));
559    }
560
561    // Note: This doesn't do all the client-id magic that BrowserProvider does
562    // in Browser. (http://b/2425179)
563    private Uri getDefaultBrowserUri() {
564        String url = getString(R.string.default_browser_url);
565        if (url.indexOf("{CID}") != -1) {
566            url = url.replace("{CID}", "android-google");
567        }
568        return Uri.parse(url);
569    }
570
571    // Load the Intent templates from arrays.xml to populate the hotseats. For
572    // each Intent, if it resolves to a single app, use that as the launch
573    // intent & use that app's label as the contentDescription. Otherwise,
574    // retain the ResolveActivity so the user can pick an app.
575    private void loadHotseats() {
576        if (mHotseatConfig == null) {
577            mHotseatConfig = getResources().getStringArray(R.array.hotseats);
578            if (mHotseatConfig.length > 0) {
579                mHotseats = new Intent[mHotseatConfig.length];
580                mHotseatLabels = new CharSequence[mHotseatConfig.length];
581                mHotseatIcons = new Drawable[mHotseatConfig.length];
582            } else {
583                mHotseats = null;
584                mHotseatIcons = null;
585                mHotseatLabels = null;
586            }
587
588            TypedArray hotseatIconDrawables = getResources().obtainTypedArray(R.array.hotseat_icons);
589            for (int i=0; i<mHotseatConfig.length; i++) {
590                // load icon for this slot; currently unrelated to the actual activity
591                try {
592                    mHotseatIcons[i] = hotseatIconDrawables.getDrawable(i);
593                } catch (ArrayIndexOutOfBoundsException ex) {
594                    Log.w(TAG, "Missing hotseat_icons array item #" + i);
595                    mHotseatIcons[i] = null;
596                }
597            }
598            hotseatIconDrawables.recycle();
599        }
600
601        PackageManager pm = getPackageManager();
602        for (int i=0; i<mHotseatConfig.length; i++) {
603            Intent intent = null;
604            if (mHotseatConfig[i].equals("*BROWSER*")) {
605                // magic value meaning "launch user's default web browser"
606                // replace it with a generic web request so we can see if there is indeed a default
607                String defaultUri = getString(R.string.default_browser_url);
608                intent = new Intent(
609                        Intent.ACTION_VIEW,
610                        ((defaultUri != null)
611                            ? Uri.parse(defaultUri)
612                            : getDefaultBrowserUri())
613                    ).addCategory(Intent.CATEGORY_BROWSABLE);
614                // note: if the user launches this without a default set, she
615                // will always be taken to the default URL above; this is
616                // unavoidable as we must specify a valid URL in order for the
617                // chooser to appear, and once the user selects something, that
618                // URL is unavoidably sent to the chosen app.
619            } else {
620                try {
621                    intent = Intent.parseUri(mHotseatConfig[i], 0);
622                } catch (java.net.URISyntaxException ex) {
623                    Log.w(TAG, "Invalid hotseat intent: " + mHotseatConfig[i]);
624                    // bogus; leave intent=null
625                }
626            }
627
628            if (intent == null) {
629                mHotseats[i] = null;
630                mHotseatLabels[i] = getText(R.string.activity_not_found);
631                continue;
632            }
633
634            if (LOGD) {
635                Log.d(TAG, "loadHotseats: hotseat " + i
636                    + " initial intent=["
637                    + intent.toUri(Intent.URI_INTENT_SCHEME)
638                    + "]");
639            }
640
641            ResolveInfo bestMatch = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
642            List<ResolveInfo> allMatches = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
643            if (LOGD) {
644                Log.d(TAG, "Best match for intent: " + bestMatch);
645                Log.d(TAG, "All matches: ");
646                for (ResolveInfo ri : allMatches) {
647                    Log.d(TAG, "  --> " + ri);
648                }
649            }
650            // did this resolve to a single app, or the resolver?
651            if (allMatches.size() == 0 || bestMatch == null) {
652                // can't find any activity to handle this. let's leave the
653                // intent as-is and let Launcher show a toast when it fails
654                // to launch.
655                mHotseats[i] = intent;
656
657                // set accessibility text to "Not installed"
658                mHotseatLabels[i] = getText(R.string.activity_not_found);
659            } else {
660                boolean found = false;
661                for (ResolveInfo ri : allMatches) {
662                    if (bestMatch.activityInfo.name.equals(ri.activityInfo.name)
663                        && bestMatch.activityInfo.applicationInfo.packageName
664                            .equals(ri.activityInfo.applicationInfo.packageName)) {
665                        found = true;
666                        break;
667                    }
668                }
669
670                if (!found) {
671                    if (LOGD) Log.d(TAG, "Multiple options, no default yet");
672                    // the bestMatch is probably the ResolveActivity, meaning the
673                    // user has not yet selected a default
674                    // so: we'll keep the original intent for now
675                    mHotseats[i] = intent;
676
677                    // set the accessibility text to "Select shortcut"
678                    mHotseatLabels[i] = getText(R.string.title_select_shortcut);
679                } else {
680                    // we have an app!
681                    // now reconstruct the intent to launch it through the front
682                    // door
683                    ComponentName com = new ComponentName(
684                        bestMatch.activityInfo.applicationInfo.packageName,
685                        bestMatch.activityInfo.name);
686                    mHotseats[i] = new Intent(Intent.ACTION_MAIN).setComponent(com);
687
688                    // load the app label for accessibility
689                    mHotseatLabels[i] = bestMatch.activityInfo.loadLabel(pm);
690                }
691            }
692
693            if (LOGD) {
694                Log.d(TAG, "loadHotseats: hotseat " + i
695                    + " final intent=["
696                    + ((mHotseats[i] == null)
697                        ? "null"
698                        : mHotseats[i].toUri(Intent.URI_INTENT_SCHEME))
699                    + "] label=[" + mHotseatLabels[i]
700                    + "]"
701                    );
702            }
703        }
704    }
705
706    @Override
707    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
708        mWaitingForResult = false;
709
710        // The pattern used here is that a user PICKs a specific application,
711        // which, depending on the target, might need to CREATE the actual target.
712
713        // For example, the user would PICK_SHORTCUT for "Music playlist", and we
714        // launch over to the Music app to actually CREATE_SHORTCUT.
715
716        if (resultCode == RESULT_OK && mAddScreen != -1) {
717            switch (requestCode) {
718                case REQUEST_PICK_APPLICATION:
719                    completeAddApplication(
720                            this, data, mAddScreen, mAddIntersectCellX, mAddIntersectCellY);
721                    break;
722                case REQUEST_PICK_SHORTCUT:
723                    processShortcut(data);
724                    break;
725                case REQUEST_CREATE_SHORTCUT:
726                    completeAddShortcut(data, mAddScreen, mAddIntersectCellX, mAddIntersectCellY);
727                    break;
728                case REQUEST_PICK_LIVE_FOLDER:
729                    addLiveFolder(data);
730                    break;
731                case REQUEST_CREATE_LIVE_FOLDER:
732                    completeAddLiveFolder(data, mAddScreen, mAddIntersectCellX, mAddIntersectCellY);
733                    break;
734                case REQUEST_PICK_APPWIDGET:
735                    addAppWidgetFromPick(data);
736                    break;
737                case REQUEST_CREATE_APPWIDGET:
738                    int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
739                    completeAddAppWidget(appWidgetId, mAddScreen);
740                    break;
741                case REQUEST_PICK_WALLPAPER:
742                    // We just wanted the activity result here so we can clear mWaitingForResult
743                    break;
744            }
745        } else if ((requestCode == REQUEST_PICK_APPWIDGET ||
746                requestCode == REQUEST_CREATE_APPWIDGET) && resultCode == RESULT_CANCELED &&
747                data != null) {
748            // Clean up the appWidgetId if we canceled
749            int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
750            if (appWidgetId != -1) {
751                mAppWidgetHost.deleteAppWidgetId(appWidgetId);
752            }
753        }
754    }
755
756    @Override
757    protected void onResume() {
758        super.onResume();
759        mPaused = false;
760        if (mRestoring || mOnResumeNeedsLoad) {
761            mWorkspaceLoading = true;
762            mModel.startLoader(this, true);
763            mRestoring = false;
764            mOnResumeNeedsLoad = false;
765        }
766        // When we resume Launcher, a different Activity might be responsible for the app
767        // market intent, so refresh the icon
768        updateAppMarketIcon();
769    }
770
771    @Override
772    protected void onPause() {
773        super.onPause();
774        // Some launcher layouts don't have a previous and next view
775        if (mPreviousView != null) {
776            dismissPreview(mPreviousView);
777        }
778        if (mNextView != null) {
779            dismissPreview(mNextView);
780        }
781        mPaused = true;
782        mDragController.cancelDrag();
783    }
784
785    @Override
786    public Object onRetainNonConfigurationInstance() {
787        // Flag the loader to stop early before switching
788        mModel.stopLoader();
789        mAllAppsGrid.surrender();
790        return Boolean.TRUE;
791    }
792
793    // We can't hide the IME if it was forced open.  So don't bother
794    /*
795    @Override
796    public void onWindowFocusChanged(boolean hasFocus) {
797        super.onWindowFocusChanged(hasFocus);
798
799        if (hasFocus) {
800            final InputMethodManager inputManager = (InputMethodManager)
801                    getSystemService(Context.INPUT_METHOD_SERVICE);
802            WindowManager.LayoutParams lp = getWindow().getAttributes();
803            inputManager.hideSoftInputFromWindow(lp.token, 0, new android.os.ResultReceiver(new
804                        android.os.Handler()) {
805                        protected void onReceiveResult(int resultCode, Bundle resultData) {
806                            Log.d(TAG, "ResultReceiver got resultCode=" + resultCode);
807                        }
808                    });
809            Log.d(TAG, "called hideSoftInputFromWindow from onWindowFocusChanged");
810        }
811    }
812    */
813
814    private boolean acceptFilter() {
815        final InputMethodManager inputManager = (InputMethodManager)
816                getSystemService(Context.INPUT_METHOD_SERVICE);
817        return !inputManager.isFullscreenMode();
818    }
819
820    @Override
821    public boolean onKeyDown(int keyCode, KeyEvent event) {
822        boolean handled = super.onKeyDown(keyCode, event);
823        if (!handled && acceptFilter() && keyCode != KeyEvent.KEYCODE_ENTER) {
824            boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
825                    keyCode, event);
826            if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
827                // something usable has been typed - start a search
828                // the typed text will be retrieved and cleared by
829                // showSearchDialog()
830                // If there are multiple keystrokes before the search dialog takes focus,
831                // onSearchRequested() will be called for every keystroke,
832                // but it is idempotent, so it's fine.
833                return onSearchRequested();
834            }
835        }
836
837        // Eat the long press event so the keyboard doesn't come up.
838        if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
839            return true;
840        }
841
842        return handled;
843    }
844
845    private String getTypedText() {
846        return mDefaultKeySsb.toString();
847    }
848
849    private void clearTypedText() {
850        mDefaultKeySsb.clear();
851        mDefaultKeySsb.clearSpans();
852        Selection.setSelection(mDefaultKeySsb, 0);
853    }
854
855    /**
856     * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
857     * State
858     */
859    private static State intToState(int stateOrdinal) {
860        State state = State.WORKSPACE;
861        final State[] stateValues = State.values();
862        for (int i = 0; i < stateValues.length; i++) {
863            if (stateValues[i].ordinal() == stateOrdinal) {
864                state = stateValues[i];
865                break;
866            }
867        }
868        return state;
869    }
870
871    /**
872     * Restores the previous state, if it exists.
873     *
874     * @param savedState The previous state.
875     */
876    private void restoreState(Bundle savedState) {
877        if (savedState == null) {
878            return;
879        }
880
881        State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
882
883        if (state == State.ALL_APPS) {
884            showAllApps(false);
885        } else if (state == State.CUSTOMIZE) {
886            showCustomizationDrawer(false);
887        }
888
889        final int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
890        if (currentScreen > -1) {
891            mWorkspace.setCurrentPage(currentScreen);
892        }
893
894        final int addScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
895
896        if (addScreen > -1) {
897            mAddScreen = addScreen;
898            mAddIntersectCellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
899            mAddIntersectCellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
900            mRestoring = true;
901        }
902
903        boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
904        if (renameFolder) {
905            long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
906            mFolderInfo = mModel.getFolderById(this, sFolders, id);
907            mRestoring = true;
908        }
909
910        // Restore the current AllApps drawer tab
911        if (mAllAppsGrid != null && mAllAppsGrid instanceof AllAppsTabbed) {
912            String curTab = savedState.getString("allapps_currentTab");
913            if (curTab != null) {
914                AllAppsTabbed tabhost = (AllAppsTabbed) mAllAppsGrid;
915                tabhost.setCurrentTabByTag(curTab);
916            }
917            int curPage = savedState.getInt("allapps_currentPage", -1);
918            if (curPage > -1) {
919                mAllAppsPagedView.setRestorePage(curPage);
920            }
921        }
922
923        // Restore the current customization drawer tab
924        if (mHomeCustomizationDrawer != null) {
925            String curTab = savedState.getString("customize_currentTab");
926            if (curTab != null) {
927                // We set this directly so that there is no delay before the tab is set
928                mCustomizePagedView.setCustomizationFilter(getCustomizeFilterForTabTag(curTab));
929                mHomeCustomizationDrawer.setCurrentTabByTag(curTab);
930            }
931
932            // Note: currently we do not restore the page for the customization tray because unlike
933            // AllApps, the page content can change drastically
934        }
935    }
936
937    /**
938     * Finds all the views we need and configure them properly.
939     */
940    private void setupViews() {
941        final DragController dragController = mDragController;
942
943        DragLayer dragLayer = (DragLayer) findViewById(R.id.drag_layer);
944        dragLayer.setDragController(dragController);
945
946        mAllAppsGrid = (AllAppsView)dragLayer.findViewById(R.id.all_apps_view);
947        mAllAppsGrid.setLauncher(this);
948        mAllAppsGrid.setDragController(dragController);
949        ((View) mAllAppsGrid).setWillNotDraw(false); // We don't want a hole punched in our window.
950        // Manage focusability manually since this thing is always visible (in non-xlarge)
951        ((View) mAllAppsGrid).setFocusable(false);
952
953        if (LauncherApplication.isScreenXLarge()) {
954            // They need to be INVISIBLE initially so that they will be measured in the layout.
955            // Otherwise the animations are messed up when we show them for the first time.
956            ((View) mAllAppsGrid).setVisibility(View.INVISIBLE);
957            mHomeCustomizationDrawer.setVisibility(View.INVISIBLE);
958        }
959
960        mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace);
961
962        final Workspace workspace = mWorkspace;
963        workspace.setHapticFeedbackEnabled(false);
964
965        DeleteZone deleteZone = (DeleteZone) dragLayer.findViewById(R.id.delete_zone);
966        mDeleteZone = deleteZone;
967
968        View handleView = findViewById(R.id.all_apps_button);
969        if (handleView != null && handleView instanceof HandleView) {
970            // we don't use handle view in xlarge mode
971            mHandleView = (HandleView)handleView;
972            mHandleView.setLauncher(this);
973            mHandleView.setOnClickListener(this);
974            mHandleView.setOnLongClickListener(this);
975        }
976
977        if (mCustomizePagedView != null) {
978            mCustomizePagedView.setLauncher(this);
979            mCustomizePagedView.setDragController(dragController);
980        } else {
981             ImageView hotseatLeft = (ImageView) findViewById(R.id.hotseat_left);
982             hotseatLeft.setContentDescription(mHotseatLabels[0]);
983             hotseatLeft.setImageDrawable(mHotseatIcons[0]);
984             ImageView hotseatRight = (ImageView) findViewById(R.id.hotseat_right);
985             hotseatRight.setContentDescription(mHotseatLabels[1]);
986             hotseatRight.setImageDrawable(mHotseatIcons[1]);
987
988             mPreviousView = (ImageView) dragLayer.findViewById(R.id.previous_screen);
989             mNextView = (ImageView) dragLayer.findViewById(R.id.next_screen);
990
991             Drawable previous = mPreviousView.getDrawable();
992             Drawable next = mNextView.getDrawable();
993             mWorkspace.setIndicators(previous, next);
994
995             mPreviousView.setHapticFeedbackEnabled(false);
996             mPreviousView.setOnLongClickListener(this);
997             mNextView.setHapticFeedbackEnabled(false);
998             mNextView.setOnLongClickListener(this);
999        }
1000
1001        workspace.setOnLongClickListener(this);
1002        workspace.setDragController(dragController);
1003        workspace.setLauncher(this);
1004
1005        deleteZone.setLauncher(this);
1006        deleteZone.setDragController(dragController);
1007        int deleteZoneHandleId;
1008
1009        final View allAppsButton = findViewById(R.id.all_apps_button);
1010        final View divider = findViewById(R.id.divider);
1011        final View configureButton = findViewById(R.id.configure_button);
1012
1013        if (LauncherApplication.isScreenXLarge()) {
1014            deleteZone.setOverlappingViews(new View[] { allAppsButton, divider, configureButton });
1015        } else {
1016            deleteZone.setOverlappingView(findViewById(R.id.all_apps_button_cluster));
1017        }
1018        dragController.addDragListener(deleteZone);
1019
1020        DeleteZone allAppsDeleteZone = (DeleteZone) findViewById(R.id.all_apps_delete_zone);
1021        if (allAppsDeleteZone != null) {
1022            allAppsDeleteZone.setLauncher(this);
1023            allAppsDeleteZone.setDragController(dragController);
1024            allAppsDeleteZone.setDragAndDropEnabled(false);
1025            dragController.addDragListener(allAppsDeleteZone);
1026            dragController.addDropTarget(allAppsDeleteZone);
1027        }
1028
1029        ApplicationInfoDropTarget allAppsInfoTarget = (ApplicationInfoDropTarget)
1030                findViewById(R.id.all_apps_info_target);
1031        if (allAppsInfoTarget != null) {
1032            allAppsInfoTarget.setLauncher(this);
1033            dragController.addDragListener(allAppsInfoTarget);
1034            allAppsInfoTarget.setDragAndDropEnabled(false);
1035            View marketButton = findViewById(R.id.market_button);
1036            if (marketButton != null) {
1037                allAppsInfoTarget.setOverlappingView(marketButton);
1038            }
1039        }
1040
1041        dragController.setDragScoller(workspace);
1042        dragController.setScrollView(dragLayer);
1043        dragController.setMoveTarget(workspace);
1044
1045        // The order here is bottom to top.
1046        dragController.addDropTarget(workspace);
1047        dragController.addDropTarget(deleteZone);
1048        if (allAppsInfoTarget != null) {
1049            dragController.addDropTarget(allAppsInfoTarget);
1050        }
1051        if (allAppsDeleteZone != null) {
1052            dragController.addDropTarget(allAppsDeleteZone);
1053        }
1054    }
1055
1056    @SuppressWarnings({"UnusedDeclaration"})
1057    public void previousScreen(View v) {
1058        if (mState != State.ALL_APPS) {
1059            mWorkspace.scrollLeft();
1060        }
1061    }
1062
1063    @SuppressWarnings({"UnusedDeclaration"})
1064    public void nextScreen(View v) {
1065        if (mState != State.ALL_APPS) {
1066            mWorkspace.scrollRight();
1067        }
1068    }
1069
1070    @SuppressWarnings({"UnusedDeclaration"})
1071    public void launchHotSeat(View v) {
1072        if (mState == State.ALL_APPS) return;
1073
1074        int index = -1;
1075        if (v.getId() == R.id.hotseat_left) {
1076            index = 0;
1077        } else if (v.getId() == R.id.hotseat_right) {
1078            index = 1;
1079        }
1080
1081        // reload these every tap; you never know when they might change
1082        loadHotseats();
1083        if (index >= 0 && index < mHotseats.length && mHotseats[index] != null) {
1084            Intent intent = mHotseats[index];
1085            startActivitySafely(
1086                mHotseats[index],
1087                "hotseat"
1088            );
1089        }
1090    }
1091
1092    /**
1093     * Creates a view representing a shortcut.
1094     *
1095     * @param info The data structure describing the shortcut.
1096     *
1097     * @return A View inflated from R.layout.application.
1098     */
1099    View createShortcut(ShortcutInfo info) {
1100        return createShortcut(R.layout.application,
1101                (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1102    }
1103
1104    /**
1105     * Creates a view representing a shortcut inflated from the specified resource.
1106     *
1107     * @param layoutResId The id of the XML layout used to create the shortcut.
1108     * @param parent The group the shortcut belongs to.
1109     * @param info The data structure describing the shortcut.
1110     *
1111     * @return A View inflated from layoutResId.
1112     */
1113    View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
1114        BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
1115        favorite.applyFromShortcutInfo(info, mIconCache);
1116        favorite.setOnClickListener(this);
1117        return favorite;
1118    }
1119
1120    /**
1121     * Add an application shortcut to the workspace.
1122     *
1123     * @param data The intent describing the application.
1124     * @param cellInfo The position on screen where to create the shortcut.
1125     */
1126    void completeAddApplication(Context context, Intent data, int screen,
1127            int intersectCellX, int intersectCellY) {
1128        final int[] cellXY = mTmpAddItemCellCoordinates;
1129        final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen);
1130
1131        if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) {
1132            showOutOfSpaceMessage();
1133            return;
1134        }
1135
1136        final ShortcutInfo info = mModel.getShortcutInfo(context.getPackageManager(),
1137                data, context);
1138
1139        if (info != null) {
1140            info.setActivity(data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK |
1141                    Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1142            info.container = ItemInfo.NO_ID;
1143            mWorkspace.addApplicationShortcut(info, screen, cellXY[0], cellXY[1],
1144                    isWorkspaceLocked(), mAddIntersectCellX, mAddIntersectCellY);
1145        } else {
1146            Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data);
1147        }
1148    }
1149
1150    /**
1151     * Add a shortcut to the workspace.
1152     *
1153     * @param data The intent describing the shortcut.
1154     * @param cellInfo The position on screen where to create the shortcut.
1155     */
1156    private void completeAddShortcut(Intent data, int screen,
1157            int intersectCellX, int intersectCellY) {
1158        final int[] cellXY = mTmpAddItemCellCoordinates;
1159        final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen);
1160
1161        int[] touchXY = null;
1162        if (mAddDropPosition != null && mAddDropPosition[0] > -1 && mAddDropPosition[1] > -1) {
1163            touchXY = mAddDropPosition;
1164        }
1165        boolean foundCellSpan = false;
1166        if (touchXY != null) {
1167            // when dragging and dropping, just find the closest free spot
1168            CellLayout screenLayout = (CellLayout) mWorkspace.getChildAt(screen);
1169            int[] result = screenLayout.findNearestVacantArea(
1170                    touchXY[0], touchXY[1], 1, 1, cellXY);
1171            foundCellSpan = (result != null);
1172        } else {
1173            foundCellSpan = layout.findCellForSpanThatIntersects(
1174                    cellXY, 1, 1, intersectCellX, intersectCellY);
1175        }
1176
1177        if (!foundCellSpan) {
1178            showOutOfSpaceMessage();
1179            return;
1180        }
1181
1182        final ShortcutInfo info = mModel.addShortcut(
1183                this, data, screen, cellXY[0], cellXY[1], false);
1184
1185        if (!mRestoring) {
1186            final View view = createShortcut(info);
1187            mWorkspace.addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, isWorkspaceLocked());
1188        }
1189    }
1190
1191    /**
1192     * Add a widget to the workspace.
1193     *
1194     * @param appWidgetId The app widget id
1195     * @param cellInfo The position on screen where to create the widget.
1196     */
1197    private void completeAddAppWidget(int appWidgetId, int screen) {
1198        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
1199
1200        // Calculate the grid spans needed to fit this widget
1201        CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen);
1202        int[] spanXY = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight, null);
1203
1204        // Try finding open space on Launcher screen
1205        // We have saved the position to which the widget was dragged-- this really only matters
1206        // if we are placing widgets on a "spring-loaded" screen
1207        final int[] cellXY = mTmpAddItemCellCoordinates;
1208
1209        // For now, we don't save the coordinate where we dropped the icon because we're not
1210        // supporting spring-loaded mini-screens; however, leaving the ability to directly place
1211        // a widget on the home screen in case we want to add it in the future
1212        int[] touchXY = null;
1213        if (mAddDropPosition != null && mAddDropPosition[0] > -1 && mAddDropPosition[1] > -1) {
1214            touchXY = mAddDropPosition;
1215        }
1216        boolean foundCellSpan = false;
1217        if (touchXY != null) {
1218            // when dragging and dropping, just find the closest free spot
1219            CellLayout screenLayout = (CellLayout) mWorkspace.getChildAt(screen);
1220            int[] result = screenLayout.findNearestVacantArea(
1221                    touchXY[0], touchXY[1], spanXY[0], spanXY[1], cellXY);
1222            foundCellSpan = (result != null);
1223        } else {
1224            // if we long pressed on an empty cell to bring up a menu,
1225            // make sure we intersect the empty cell
1226            // if mAddIntersectCellX/Y are -1 (e.g. we used menu -> add) then
1227            // findCellForSpanThatIntersects will just ignore them
1228            foundCellSpan = layout.findCellForSpanThatIntersects(cellXY, spanXY[0], spanXY[1],
1229                    mAddIntersectCellX, mAddIntersectCellY);
1230        }
1231
1232        if (!foundCellSpan) {
1233            if (appWidgetId != -1) mAppWidgetHost.deleteAppWidgetId(appWidgetId);
1234            showOutOfSpaceMessage();
1235            return;
1236        }
1237
1238        // Build Launcher-specific widget info and save to database
1239        LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId);
1240        launcherInfo.spanX = spanXY[0];
1241        launcherInfo.spanY = spanXY[1];
1242
1243        LauncherModel.addItemToDatabase(this, launcherInfo,
1244                LauncherSettings.Favorites.CONTAINER_DESKTOP,
1245                screen, cellXY[0], cellXY[1], false);
1246
1247        if (!mRestoring) {
1248            mDesktopItems.add(launcherInfo);
1249
1250            // Perform actual inflation because we're live
1251            launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
1252
1253            launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
1254            launcherInfo.hostView.setTag(launcherInfo);
1255
1256            mWorkspace.addInScreen(launcherInfo.hostView, screen, cellXY[0], cellXY[1],
1257                    launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
1258
1259            addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1260        }
1261    }
1262
1263    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1264        @Override
1265        public void onReceive(Context context, Intent intent) {
1266            final String action = intent.getAction();
1267            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1268                mUserPresent = false;
1269                updateRunning();
1270            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1271                mUserPresent = true;
1272                updateRunning();
1273            }
1274        }
1275    };
1276
1277    @Override
1278    public void onAttachedToWindow() {
1279        super.onAttachedToWindow();
1280
1281        // Listen for broadcasts related to user-presence
1282        final IntentFilter filter = new IntentFilter();
1283        filter.addAction(Intent.ACTION_SCREEN_OFF);
1284        filter.addAction(Intent.ACTION_USER_PRESENT);
1285        registerReceiver(mReceiver, filter);
1286
1287        mAttached = true;
1288        mVisible = true;
1289    }
1290
1291    @Override
1292    public void onDetachedFromWindow() {
1293        super.onDetachedFromWindow();
1294        mVisible = false;
1295
1296        if (mAttached) {
1297            unregisterReceiver(mReceiver);
1298            mAttached = false;
1299        }
1300        updateRunning();
1301    }
1302
1303    public void onWindowVisibilityChanged(int visibility) {
1304        mVisible = visibility == View.VISIBLE;
1305        updateRunning();
1306    }
1307
1308    private void sendAdvanceMessage(long delay) {
1309        mHandler.removeMessages(ADVANCE_MSG);
1310        Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1311        mHandler.sendMessageDelayed(msg, delay);
1312        mAutoAdvanceSentTime = System.currentTimeMillis();
1313    }
1314
1315    private void updateRunning() {
1316        boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
1317        if (autoAdvanceRunning != mAutoAdvanceRunning) {
1318            mAutoAdvanceRunning = autoAdvanceRunning;
1319            if (autoAdvanceRunning) {
1320                long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1321                sendAdvanceMessage(delay);
1322            } else {
1323                if (!mWidgetsToAdvance.isEmpty()) {
1324                    mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
1325                            (System.currentTimeMillis() - mAutoAdvanceSentTime));
1326                }
1327                mHandler.removeMessages(ADVANCE_MSG);
1328            }
1329        }
1330    }
1331
1332    private final Handler mHandler = new Handler() {
1333        @Override
1334        public void handleMessage(Message msg) {
1335            if (msg.what == ADVANCE_MSG) {
1336                int i = 0;
1337                for (View key: mWidgetsToAdvance.keySet()) {
1338                    final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1339                    final int delay = mAdvanceStagger * i;
1340                    if (v instanceof Advanceable) {
1341                       postDelayed(new Runnable() {
1342                           public void run() {
1343                               ((Advanceable) v).advance();
1344                           }
1345                       }, delay);
1346                    }
1347                    i++;
1348                }
1349                sendAdvanceMessage(mAdvanceInterval);
1350            }
1351        }
1352    };
1353
1354    void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1355        if (appWidgetInfo.autoAdvanceViewId == -1) return;
1356        View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1357        if (v instanceof Advanceable) {
1358            mWidgetsToAdvance.put(hostView, appWidgetInfo);
1359            ((Advanceable) v).willBeAdvancedByHost();
1360            updateRunning();
1361        }
1362    }
1363
1364    void removeWidgetToAutoAdvance(View hostView) {
1365        if (mWidgetsToAdvance.containsKey(hostView)) {
1366            mWidgetsToAdvance.remove(hostView);
1367            updateRunning();
1368        }
1369    }
1370
1371    public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1372        mDesktopItems.remove(launcherInfo);
1373        removeWidgetToAutoAdvance(launcherInfo.hostView);
1374        launcherInfo.hostView = null;
1375    }
1376
1377    void showOutOfSpaceMessage() {
1378        Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
1379    }
1380
1381    public LauncherAppWidgetHost getAppWidgetHost() {
1382        return mAppWidgetHost;
1383    }
1384
1385    public LauncherModel getModel() {
1386        return mModel;
1387    }
1388
1389    void closeSystemDialogs() {
1390        getWindow().closeAllPanels();
1391
1392        try {
1393            dismissDialog(DIALOG_CREATE_SHORTCUT);
1394            // Unlock the workspace if the dialog was showing
1395        } catch (Exception e) {
1396            // An exception is thrown if the dialog is not visible, which is fine
1397        }
1398
1399        try {
1400            dismissDialog(DIALOG_RENAME_FOLDER);
1401            // Unlock the workspace if the dialog was showing
1402        } catch (Exception e) {
1403            // An exception is thrown if the dialog is not visible, which is fine
1404        }
1405
1406        // Whatever we were doing is hereby canceled.
1407        mWaitingForResult = false;
1408    }
1409
1410    @Override
1411    protected void onNewIntent(Intent intent) {
1412        super.onNewIntent(intent);
1413
1414        // Close the menu
1415        if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1416            // also will cancel mWaitingForResult.
1417            closeSystemDialogs();
1418
1419            boolean alreadyOnHome = ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
1420                        != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
1421
1422            // in all these cases, only animate if we're already on home
1423            if (LauncherApplication.isScreenXLarge()) {
1424                mWorkspace.unshrink(alreadyOnHome);
1425            }
1426            if (!mWorkspace.isDefaultPageShowing()) {
1427                // on the phone, we don't animate the change to the workspace if all apps is visible
1428                mWorkspace.moveToDefaultScreen(alreadyOnHome &&
1429                        (LauncherApplication.isScreenXLarge() || mState != State.ALL_APPS));
1430            }
1431            showWorkspace(alreadyOnHome);
1432
1433            final View v = getWindow().peekDecorView();
1434            if (v != null && v.getWindowToken() != null) {
1435                InputMethodManager imm = (InputMethodManager)getSystemService(
1436                        INPUT_METHOD_SERVICE);
1437                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1438            }
1439        }
1440    }
1441
1442    @Override
1443    protected void onRestoreInstanceState(Bundle savedInstanceState) {
1444        // Do not call super here
1445        mSavedInstanceState = savedInstanceState;
1446    }
1447
1448    @Override
1449    protected void onSaveInstanceState(Bundle outState) {
1450        outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPage());
1451
1452        final ArrayList<Folder> folders = mWorkspace.getOpenFolders();
1453        if (folders.size() > 0) {
1454            final int count = folders.size();
1455            long[] ids = new long[count];
1456            for (int i = 0; i < count; i++) {
1457                final FolderInfo info = folders.get(i).getInfo();
1458                ids[i] = info.id;
1459            }
1460            outState.putLongArray(RUNTIME_STATE_USER_FOLDERS, ids);
1461        } else {
1462            super.onSaveInstanceState(outState);
1463        }
1464
1465        outState.putInt(RUNTIME_STATE, mState.ordinal());
1466
1467        if (mAddScreen > -1 && mWaitingForResult) {
1468            outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, mAddScreen);
1469            outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mAddIntersectCellX);
1470            outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mAddIntersectCellY);
1471        }
1472
1473        if (mFolderInfo != null && mWaitingForResult) {
1474            outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
1475            outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
1476        }
1477
1478        // Save the current AllApps drawer tab
1479        if (mAllAppsGrid != null && mAllAppsGrid instanceof AllAppsTabbed) {
1480            AllAppsTabbed tabhost = (AllAppsTabbed) mAllAppsGrid;
1481            String currentTabTag = tabhost.getCurrentTabTag();
1482            if (currentTabTag != null) {
1483                outState.putString("allapps_currentTab", currentTabTag);
1484                outState.putInt("allapps_currentPage", mAllAppsPagedView.getCurrentPage());
1485            }
1486        }
1487
1488        // Save the current customization drawer tab
1489        if (mHomeCustomizationDrawer != null) {
1490            String currentTabTag = mHomeCustomizationDrawer.getCurrentTabTag();
1491            if (currentTabTag != null) {
1492                outState.putString("customize_currentTab", currentTabTag);
1493            }
1494        }
1495    }
1496
1497    @Override
1498    public void onDestroy() {
1499        super.onDestroy();
1500
1501        try {
1502            mAppWidgetHost.stopListening();
1503        } catch (NullPointerException ex) {
1504            Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
1505        }
1506
1507        TextKeyListener.getInstance().release();
1508
1509        mModel.stopLoader();
1510
1511        unbindDesktopItems();
1512
1513        getContentResolver().unregisterContentObserver(mWidgetObserver);
1514
1515        // Some launcher layouts don't have a previous and next view
1516        if (mPreviousView != null) {
1517            dismissPreview(mPreviousView);
1518        }
1519        if (mNextView != null) {
1520            dismissPreview(mNextView);
1521        }
1522
1523        unregisterReceiver(mCloseSystemDialogsReceiver);
1524    }
1525
1526    @Override
1527    public void startActivityForResult(Intent intent, int requestCode) {
1528        if (requestCode >= 0) mWaitingForResult = true;
1529        super.startActivityForResult(intent, requestCode);
1530    }
1531
1532    @Override
1533    public void startSearch(String initialQuery, boolean selectInitialQuery,
1534            Bundle appSearchData, boolean globalSearch) {
1535
1536        showWorkspace(true);
1537
1538        if (initialQuery == null) {
1539            // Use any text typed in the launcher as the initial query
1540            initialQuery = getTypedText();
1541            clearTypedText();
1542        }
1543        if (appSearchData == null) {
1544            appSearchData = new Bundle();
1545            appSearchData.putString(Search.SOURCE, "launcher-search");
1546        }
1547
1548        final SearchManager searchManager =
1549                (SearchManager) getSystemService(Context.SEARCH_SERVICE);
1550        searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
1551            appSearchData, globalSearch);
1552    }
1553
1554    @Override
1555    public boolean onCreateOptionsMenu(Menu menu) {
1556        if (isWorkspaceLocked()) {
1557            return false;
1558        }
1559
1560        super.onCreateOptionsMenu(menu);
1561
1562        menu.add(MENU_GROUP_ADD, MENU_ADD, 0, R.string.menu_add)
1563                .setIcon(android.R.drawable.ic_menu_add)
1564                .setAlphabeticShortcut('A');
1565        menu.add(0, MENU_MANAGE_APPS, 0, R.string.menu_manage_apps)
1566                .setIcon(android.R.drawable.ic_menu_manage)
1567                .setAlphabeticShortcut('M');
1568        menu.add(MENU_GROUP_WALLPAPER, MENU_WALLPAPER_SETTINGS, 0, R.string.menu_wallpaper)
1569                 .setIcon(android.R.drawable.ic_menu_gallery)
1570                 .setAlphabeticShortcut('W');
1571        menu.add(0, MENU_SEARCH, 0, R.string.menu_search)
1572                .setIcon(android.R.drawable.ic_search_category_default)
1573                .setAlphabeticShortcut(SearchManager.MENU_KEY);
1574        menu.add(0, MENU_NOTIFICATIONS, 0, R.string.menu_notifications)
1575                .setIcon(com.android.internal.R.drawable.ic_menu_notifications)
1576                .setAlphabeticShortcut('N');
1577
1578        final Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS);
1579        settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1580                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1581
1582        menu.add(0, MENU_SETTINGS, 0, R.string.menu_settings)
1583                .setIcon(android.R.drawable.ic_menu_preferences).setAlphabeticShortcut('P')
1584                .setIntent(settings);
1585
1586        return true;
1587    }
1588
1589    @Override
1590    public boolean onPrepareOptionsMenu(Menu menu) {
1591        super.onPrepareOptionsMenu(menu);
1592
1593        // If all apps is animating, don't show the menu, because we don't know
1594        // which one to show.
1595        if (mAllAppsGrid.isAnimating()) {
1596            return false;
1597        }
1598
1599        // Only show the add and wallpaper options when we're not in all apps.
1600        boolean visible = !mAllAppsGrid.isVisible();
1601        menu.setGroupVisible(MENU_GROUP_ADD, visible);
1602        menu.setGroupVisible(MENU_GROUP_WALLPAPER, visible);
1603
1604        // Disable add if the workspace is full.
1605        if (visible) {
1606            CellLayout layout = (CellLayout) mWorkspace.getChildAt(mWorkspace.getCurrentPage());
1607            menu.setGroupEnabled(MENU_GROUP_ADD, layout.existsEmptyCell());
1608        }
1609
1610        return true;
1611    }
1612
1613    @Override
1614    public boolean onOptionsItemSelected(MenuItem item) {
1615        switch (item.getItemId()) {
1616            case MENU_ADD:
1617                addItems();
1618                return true;
1619            case MENU_MANAGE_APPS:
1620                manageApps();
1621                return true;
1622            case MENU_WALLPAPER_SETTINGS:
1623                startWallpaper();
1624                return true;
1625            case MENU_SEARCH:
1626                onSearchRequested();
1627                return true;
1628            case MENU_NOTIFICATIONS:
1629                showNotifications();
1630                return true;
1631        }
1632
1633        return super.onOptionsItemSelected(item);
1634    }
1635
1636    /**
1637     * Indicates that we want global search for this activity by setting the globalSearch
1638     * argument for {@link #startSearch} to true.
1639     */
1640
1641    @Override
1642    public boolean onSearchRequested() {
1643        startSearch(null, false, null, true);
1644        return true;
1645    }
1646
1647    public boolean isWorkspaceLocked() {
1648        return mWorkspaceLoading || mWaitingForResult;
1649    }
1650
1651    private void addItems() {
1652        if (LauncherApplication.isScreenXLarge()) {
1653            // Animate the widget chooser up from the bottom of the screen
1654            if (mState != State.CUSTOMIZE) {
1655                showCustomizationDrawer(true);
1656            }
1657        } else {
1658            showWorkspace(true);
1659            showAddDialog(-1, -1);
1660        }
1661    }
1662
1663    private void resetAddInfo() {
1664        mAddScreen = -1;
1665        mAddIntersectCellX = -1;
1666        mAddIntersectCellY = -1;
1667        mAddDropPosition = null;
1668    }
1669
1670    void addAppWidgetFromDrop(PendingAddWidgetInfo info, int screen, int[] position) {
1671        resetAddInfo();
1672        mAddScreen = screen;
1673        mAddDropPosition = position;
1674
1675        int appWidgetId = getAppWidgetHost().allocateAppWidgetId();
1676        AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, info.componentName);
1677        addAppWidgetImpl(appWidgetId, info);
1678    }
1679
1680    private void manageApps() {
1681        startActivity(new Intent(android.provider.Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS));
1682    }
1683
1684    void addAppWidgetFromPick(Intent data) {
1685        // TODO: catch bad widget exception when sent
1686        int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
1687        // TODO: Is this log message meaningful?
1688        if (LOGD) Log.d(TAG, "dumping extras content=" + data.getExtras());
1689        addAppWidgetImpl(appWidgetId, null);
1690    }
1691
1692    void addAppWidgetImpl(int appWidgetId, PendingAddWidgetInfo info) {
1693        AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
1694
1695        if (appWidget.configure != null) {
1696            // Launch over to configure widget, if needed
1697            Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
1698            intent.setComponent(appWidget.configure);
1699            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1700            if (info != null) {
1701                if (info.mimeType != null && !info.mimeType.isEmpty()) {
1702                    intent.putExtra(
1703                            InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE,
1704                            info.mimeType);
1705
1706                    final String mimeType = info.mimeType;
1707                    final ClipData clipData = (ClipData) info.configurationData;
1708                    final ClipDescription clipDesc = clipData.getDescription();
1709                    for (int i = 0; i < clipDesc.getMimeTypeCount(); ++i) {
1710                        if (clipDesc.getMimeType(i).equals(mimeType)) {
1711                            final ClipData.Item item = clipData.getItem(i);
1712                            final CharSequence stringData = item.getText();
1713                            final Uri uriData = item.getUri();
1714                            final Intent intentData = item.getIntent();
1715                            final String key =
1716                                InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA;
1717                            if (uriData != null) {
1718                                intent.putExtra(key, uriData);
1719                            } else if (intentData != null) {
1720                                intent.putExtra(key, intentData);
1721                            } else if (stringData != null) {
1722                                intent.putExtra(key, stringData);
1723                            }
1724                            break;
1725                        }
1726                    }
1727                }
1728            }
1729
1730            startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
1731        } else {
1732            // Otherwise just add it
1733            completeAddAppWidget(appWidgetId, mAddScreen);
1734        }
1735    }
1736
1737    void processShortcutFromDrop(ComponentName componentName, int screen, int[] position) {
1738        resetAddInfo();
1739        mAddScreen = screen;
1740        mAddDropPosition = position;
1741
1742        Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
1743        createShortcutIntent.setComponent(componentName);
1744        processShortcut(createShortcutIntent);
1745    }
1746
1747    void processShortcut(Intent intent) {
1748        // Handle case where user selected "Applications"
1749        String applicationName = getResources().getString(R.string.group_applications);
1750        String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
1751
1752        if (applicationName != null && applicationName.equals(shortcutName)) {
1753            Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
1754            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
1755
1756            Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
1757            pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
1758            pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application));
1759            startActivityForResultSafely(pickIntent, REQUEST_PICK_APPLICATION);
1760        } else {
1761            startActivityForResultSafely(intent, REQUEST_CREATE_SHORTCUT);
1762        }
1763    }
1764
1765    void processWallpaper(Intent intent) {
1766        startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
1767    }
1768
1769    void addLiveFolderFromDrop(ComponentName componentName, int screen, int[] position) {
1770        resetAddInfo();
1771        mAddScreen = screen;
1772        mAddDropPosition = position;
1773
1774        Intent createFolderIntent = new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER);
1775        createFolderIntent.setComponent(componentName);
1776
1777        addLiveFolder(createFolderIntent);
1778    }
1779
1780    void addLiveFolder(Intent intent) { // YYY add screen intersect etc. parameters here
1781        // Handle case where user selected "Folder"
1782        String folderName = getResources().getString(R.string.group_folder);
1783        String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
1784
1785        if (folderName != null && folderName.equals(shortcutName)) {
1786            addFolder(mAddScreen, mAddIntersectCellX, mAddIntersectCellY);
1787        } else {
1788            startActivityForResultSafely(intent, REQUEST_CREATE_LIVE_FOLDER);
1789        }
1790    }
1791
1792    void addFolder(int screen, int intersectCellX, int intersectCellY) {
1793        UserFolderInfo folderInfo = new UserFolderInfo();
1794        folderInfo.title = getText(R.string.folder_name);
1795
1796        final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen);
1797        final int[] cellXY = mTmpAddItemCellCoordinates;
1798        if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) {
1799            showOutOfSpaceMessage();
1800            return;
1801        }
1802
1803        // Update the model
1804        LauncherModel.addItemToDatabase(this, folderInfo,
1805                LauncherSettings.Favorites.CONTAINER_DESKTOP,
1806                screen, cellXY[0], cellXY[1], false);
1807        sFolders.put(folderInfo.id, folderInfo);
1808
1809        // Create the view
1810        FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
1811                (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()),
1812                folderInfo, mIconCache);
1813        mWorkspace.addInScreen(newFolder, screen, cellXY[0], cellXY[1], 1, 1, isWorkspaceLocked());
1814    }
1815
1816    void removeFolder(FolderInfo folder) {
1817        sFolders.remove(folder.id);
1818    }
1819
1820    private void completeAddLiveFolder(
1821            Intent data, int screen, int intersectCellX, int intersectCellY) {
1822        final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen);
1823        final int[] cellXY = mTmpAddItemCellCoordinates;
1824        if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) {
1825            showOutOfSpaceMessage();
1826            return;
1827        }
1828
1829        final LiveFolderInfo info = addLiveFolder(this, data, screen, cellXY[0], cellXY[1], false);
1830
1831        if (!mRestoring) {
1832            final View view = LiveFolderIcon.fromXml(R.layout.live_folder_icon, this,
1833                (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1834            mWorkspace.addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, isWorkspaceLocked());
1835        }
1836    }
1837
1838    static LiveFolderInfo addLiveFolder(Context context, Intent data,
1839            int screen, int cellX, int cellY, boolean notify) {
1840
1841        Intent baseIntent = data.getParcelableExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT);
1842        String name = data.getStringExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME);
1843
1844        Drawable icon = null;
1845        Intent.ShortcutIconResource iconResource = null;
1846
1847        Parcelable extra = data.getParcelableExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON);
1848        if (extra != null && extra instanceof Intent.ShortcutIconResource) {
1849            try {
1850                iconResource = (Intent.ShortcutIconResource) extra;
1851                final PackageManager packageManager = context.getPackageManager();
1852                Resources resources = packageManager.getResourcesForApplication(
1853                        iconResource.packageName);
1854                final int id = resources.getIdentifier(iconResource.resourceName, null, null);
1855                icon = resources.getDrawable(id);
1856            } catch (Exception e) {
1857                Log.w(TAG, "Could not load live folder icon: " + extra);
1858            }
1859        }
1860
1861        if (icon == null) {
1862            icon = context.getResources().getDrawable(R.drawable.ic_launcher_folder);
1863        }
1864
1865        final LiveFolderInfo info = new LiveFolderInfo();
1866        info.icon = Utilities.createIconBitmap(icon, context);
1867        info.title = name;
1868        info.iconResource = iconResource;
1869        info.uri = data.getData();
1870        info.baseIntent = baseIntent;
1871        info.displayMode = data.getIntExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE,
1872                LiveFolders.DISPLAY_MODE_GRID);
1873
1874        LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
1875                screen, cellX, cellY, notify);
1876        sFolders.put(info.id, info);
1877
1878        return info;
1879    }
1880
1881    private void showNotifications() {
1882        final StatusBarManager statusBar = (StatusBarManager) getSystemService(STATUS_BAR_SERVICE);
1883        if (statusBar != null) {
1884            statusBar.expand();
1885        }
1886    }
1887
1888    private void startWallpaper() {
1889        showWorkspace(true);
1890        final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
1891        Intent chooser = Intent.createChooser(pickWallpaper,
1892                getText(R.string.chooser_wallpaper));
1893        // NOTE: Adds a configure option to the chooser if the wallpaper supports it
1894        //       Removed in Eclair MR1
1895//        WallpaperManager wm = (WallpaperManager)
1896//                getSystemService(Context.WALLPAPER_SERVICE);
1897//        WallpaperInfo wi = wm.getWallpaperInfo();
1898//        if (wi != null && wi.getSettingsActivity() != null) {
1899//            LabeledIntent li = new LabeledIntent(getPackageName(),
1900//                    R.string.configure_wallpaper, 0);
1901//            li.setClassName(wi.getPackageName(), wi.getSettingsActivity());
1902//            chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { li });
1903//        }
1904        startActivityForResult(chooser, REQUEST_PICK_WALLPAPER);
1905    }
1906
1907    /**
1908     * Registers various content observers. The current implementation registers
1909     * only a favorites observer to keep track of the favorites applications.
1910     */
1911    private void registerContentObservers() {
1912        ContentResolver resolver = getContentResolver();
1913        resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
1914                true, mWidgetObserver);
1915    }
1916
1917    @Override
1918    public boolean dispatchKeyEvent(KeyEvent event) {
1919        if (event.getAction() == KeyEvent.ACTION_DOWN) {
1920            switch (event.getKeyCode()) {
1921                case KeyEvent.KEYCODE_HOME:
1922                    return true;
1923                case KeyEvent.KEYCODE_VOLUME_DOWN:
1924                    if (SystemProperties.getInt("debug.launcher2.dumpstate", 0) != 0) {
1925                        dumpState();
1926                        return true;
1927                    }
1928                    break;
1929            }
1930        } else if (event.getAction() == KeyEvent.ACTION_UP) {
1931            switch (event.getKeyCode()) {
1932                case KeyEvent.KEYCODE_HOME:
1933                    return true;
1934            }
1935        }
1936
1937        return super.dispatchKeyEvent(event);
1938    }
1939
1940    @Override
1941    public void onBackPressed() {
1942        if (mState == State.ALL_APPS || mState == State.CUSTOMIZE) {
1943            showWorkspace(true);
1944        } else {
1945            closeFolder();
1946        }
1947        // Some launcher layouts don't have a previous and next view
1948        if (mPreviousView != null) {
1949            dismissPreview(mPreviousView);
1950            dismissPreview(mNextView);
1951        }
1952    }
1953
1954    private void closeFolder() {
1955        Folder folder = mWorkspace.getOpenFolder();
1956        if (folder != null) {
1957            closeFolder(folder);
1958        }
1959    }
1960
1961    void closeFolder(Folder folder) {
1962        folder.getInfo().opened = false;
1963        ViewGroup parent = (ViewGroup) folder.getParent();
1964        if (parent != null) {
1965            CellLayout cl = (CellLayout) parent;
1966            cl.removeViewWithoutMarkingCells(folder);
1967            if (folder instanceof DropTarget) {
1968                // Live folders aren't DropTargets.
1969                mDragController.removeDropTarget((DropTarget)folder);
1970            }
1971        }
1972        folder.onClose();
1973    }
1974
1975    /**
1976     * Re-listen when widgets are reset.
1977     */
1978    private void onAppWidgetReset() {
1979        mAppWidgetHost.startListening();
1980    }
1981
1982    /**
1983     * Go through the and disconnect any of the callbacks in the drawables and the views or we
1984     * leak the previous Home screen on orientation change.
1985     */
1986    private void unbindDesktopItems() {
1987        for (ItemInfo item: mDesktopItems) {
1988            item.unbind();
1989        }
1990    }
1991
1992    /**
1993     * Launches the intent referred by the clicked shortcut.
1994     *
1995     * @param v The view representing the clicked shortcut.
1996     */
1997    public void onClick(View v) {
1998        Object tag = v.getTag();
1999        if (tag instanceof ShortcutInfo) {
2000            // Open shortcut
2001            final Intent intent = ((ShortcutInfo) tag).intent;
2002            int[] pos = new int[2];
2003            v.getLocationOnScreen(pos);
2004            intent.setSourceBounds(new Rect(pos[0], pos[1],
2005                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
2006            startActivitySafely(intent, tag);
2007        } else if (tag instanceof FolderInfo) {
2008            handleFolderClick((FolderInfo) tag);
2009        } else if (v == mHandleView) {
2010            if (mState == State.ALL_APPS) {
2011                showWorkspace(true);
2012            } else {
2013                showAllApps(true);
2014            }
2015        }
2016    }
2017
2018    public boolean onTouch(View v, MotionEvent event) {
2019        // this is an intercepted event being forwarded from mWorkspace;
2020        // clicking anywhere on the workspace causes the customization drawer to slide down
2021        showWorkspace(true);
2022        return false;
2023    }
2024
2025    /**
2026     * Event handler for the search button
2027     *
2028     * @param v The view that was clicked.
2029     */
2030    public void onClickSearchButton(View v) {
2031        startSearch(null, false, null, true);
2032        // Use a custom animation for launching search
2033        overridePendingTransition(R.anim.fade_in_fast, R.anim.fade_out_fast);
2034    }
2035
2036    /**
2037     * Event handler for the voice button
2038     *
2039     * @param v The view that was clicked.
2040     */
2041    public void onClickVoiceButton(View v) {
2042        startVoiceSearch();
2043    }
2044
2045    private void startVoiceSearch() {
2046        Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
2047        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2048        startActivity(intent);
2049    }
2050
2051    /**
2052     * Event handler for the "gear" button that appears on the home screen, which
2053     * enters home screen customization mode.
2054     *
2055     * @param v The view that was clicked.
2056     */
2057    public void onClickConfigureButton(View v) {
2058        addItems();
2059    }
2060
2061    /**
2062     * Event handler for the "grid" button that appears on the home screen, which
2063     * enters all apps mode.
2064     *
2065     * @param v The view that was clicked.
2066     */
2067    public void onClickAllAppsButton(View v) {
2068        showAllApps(true);
2069    }
2070
2071    public void onClickAppMarketButton(View v) {
2072        if (mAppMarketIntent != null) {
2073            startActivitySafely(mAppMarketIntent, "app market");
2074        }
2075    }
2076
2077    void startApplicationDetailsActivity(ComponentName componentName) {
2078        String packageName = componentName.getPackageName();
2079        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
2080                Uri.fromParts("package", packageName, null));
2081        startActivity(intent);
2082    }
2083
2084    void startApplicationUninstallActivity(ApplicationInfo appInfo) {
2085        if ((appInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) == 0) {
2086            // System applications cannot be installed. For now, show a toast explaining that.
2087            // We may give them the option of disabling apps this way.
2088            int messageId = R.string.uninstall_system_app_text;
2089            Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
2090        } else {
2091            String packageName = appInfo.componentName.getPackageName();
2092            String className = appInfo.componentName.getClassName();
2093            Intent intent = new Intent(
2094                    Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
2095            startActivity(intent);
2096        }
2097    }
2098
2099    void startActivitySafely(Intent intent, Object tag) {
2100        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2101        try {
2102            startActivity(intent);
2103        } catch (ActivityNotFoundException e) {
2104            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2105            Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
2106        } catch (SecurityException e) {
2107            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2108            Log.e(TAG, "Launcher does not have the permission to launch " + intent +
2109                    ". Make sure to create a MAIN intent-filter for the corresponding activity " +
2110                    "or use the exported attribute for this activity. "
2111                    + "tag="+ tag + " intent=" + intent, e);
2112        }
2113    }
2114
2115    void startActivityForResultSafely(Intent intent, int requestCode) {
2116        try {
2117            startActivityForResult(intent, requestCode);
2118        } catch (ActivityNotFoundException e) {
2119            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2120        } catch (SecurityException e) {
2121            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2122            Log.e(TAG, "Launcher does not have the permission to launch " + intent +
2123                    ". Make sure to create a MAIN intent-filter for the corresponding activity " +
2124                    "or use the exported attribute for this activity.", e);
2125        }
2126    }
2127
2128    private void handleFolderClick(FolderInfo folderInfo) {
2129        if (!folderInfo.opened) {
2130            // Close any open folder
2131            closeFolder();
2132            // Open the requested folder
2133            openFolder(folderInfo);
2134        } else {
2135            // Find the open folder...
2136            Folder openFolder = mWorkspace.getFolderForTag(folderInfo);
2137            int folderScreen;
2138            if (openFolder != null) {
2139                folderScreen = mWorkspace.getPageForView(openFolder);
2140                // .. and close it
2141                closeFolder(openFolder);
2142                if (folderScreen != mWorkspace.getCurrentPage()) {
2143                    // Close any folder open on the current screen
2144                    closeFolder();
2145                    // Pull the folder onto this screen
2146                    openFolder(folderInfo);
2147                }
2148            }
2149        }
2150    }
2151
2152    /**
2153     * Opens the user folder described by the specified tag. The opening of the folder
2154     * is animated relative to the specified View. If the View is null, no animation
2155     * is played.
2156     *
2157     * @param folderInfo The FolderInfo describing the folder to open.
2158     */
2159    public void openFolder(FolderInfo folderInfo) {
2160        Folder openFolder;
2161
2162        if (folderInfo instanceof UserFolderInfo) {
2163            openFolder = UserFolder.fromXml(this);
2164        } else if (folderInfo instanceof LiveFolderInfo) {
2165            openFolder = com.android.launcher2.LiveFolder.fromXml(this, folderInfo);
2166        } else {
2167            return;
2168        }
2169
2170        openFolder.setDragController(mDragController);
2171        openFolder.setLauncher(this);
2172
2173        openFolder.bind(folderInfo);
2174        folderInfo.opened = true;
2175
2176        mWorkspace.addInFullScreen(openFolder, folderInfo.screen);
2177
2178        openFolder.onOpen();
2179    }
2180
2181    public boolean onLongClick(View v) {
2182        switch (v.getId()) {
2183            case R.id.previous_screen:
2184                if (mState != State.ALL_APPS) {
2185                    mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
2186                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
2187                    showPreviews(v);
2188                }
2189                return true;
2190            case R.id.next_screen:
2191                if (mState != State.ALL_APPS) {
2192                    mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
2193                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
2194                    showPreviews(v);
2195                }
2196                return true;
2197            case R.id.all_apps_button:
2198                if (mState != State.ALL_APPS) {
2199                    mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
2200                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
2201                    showPreviews(v);
2202                }
2203                return true;
2204        }
2205
2206        if (isWorkspaceLocked()) {
2207            return false;
2208        }
2209
2210        if (!(v instanceof CellLayout)) {
2211            v = (View) v.getParent();
2212        }
2213
2214
2215        resetAddInfo();
2216        CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
2217        // This happens when long clicking an item with the dpad/trackball
2218        if (longClickCellInfo == null || !longClickCellInfo.valid) {
2219            return true;
2220        }
2221
2222        final View itemUnderLongClick = longClickCellInfo.cell;
2223
2224        if (mWorkspace.allowLongPress()) {
2225            if (itemUnderLongClick == null) {
2226                // User long pressed on empty space
2227                mWorkspace.setAllowLongPress(false);
2228                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
2229                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
2230                if (!LauncherApplication.isScreenXLarge()) {
2231                    showAddDialog(longClickCellInfo.cellX, longClickCellInfo.cellY);
2232                }
2233            } else {
2234                if (!(itemUnderLongClick instanceof Folder)) {
2235                    // User long pressed on an item
2236                    mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
2237                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
2238                    mAddIntersectCellX = longClickCellInfo.cellX;
2239                    mAddIntersectCellY = longClickCellInfo.cellY;
2240                    mWorkspace.startDrag(longClickCellInfo);
2241                }
2242            }
2243        }
2244        return true;
2245    }
2246
2247    @SuppressWarnings({"unchecked"})
2248    private void dismissPreview(final View v) {
2249        final PopupWindow window = (PopupWindow) v.getTag();
2250        if (window != null) {
2251            window.setOnDismissListener(new PopupWindow.OnDismissListener() {
2252                public void onDismiss() {
2253                    ViewGroup group = (ViewGroup) v.getTag(R.id.workspace);
2254                    int count = group.getChildCount();
2255                    for (int i = 0; i < count; i++) {
2256                        ((ImageView) group.getChildAt(i)).setImageDrawable(null);
2257                    }
2258                    ArrayList<Bitmap> bitmaps = (ArrayList<Bitmap>) v.getTag(R.id.icon);
2259                    for (Bitmap bitmap : bitmaps) bitmap.recycle();
2260
2261                    v.setTag(R.id.workspace, null);
2262                    v.setTag(R.id.icon, null);
2263                    window.setOnDismissListener(null);
2264                }
2265            });
2266            window.dismiss();
2267        }
2268        v.setTag(null);
2269    }
2270
2271    private void showPreviews(View anchor) {
2272        showPreviews(anchor, 0, mWorkspace.getChildCount());
2273    }
2274
2275    private void showPreviews(final View anchor, int start, int end) {
2276        final Resources resources = getResources();
2277        final Workspace workspace = mWorkspace;
2278
2279        CellLayout cell = ((CellLayout) workspace.getChildAt(start));
2280
2281        float max = workspace.getChildCount();
2282
2283        final Rect r = new Rect();
2284        resources.getDrawable(R.drawable.preview_background).getPadding(r);
2285        int extraW = (int) ((r.left + r.right) * max);
2286        int extraH = r.top + r.bottom;
2287
2288        int aW = cell.getWidth() - extraW;
2289        float w = aW / max;
2290
2291        int width = cell.getWidth();
2292        int height = cell.getHeight();
2293        int x = cell.getLeftPadding();
2294        int y = cell.getTopPadding();
2295        width -= (x + cell.getRightPadding());
2296        height -= (y + cell.getBottomPadding());
2297
2298        float scale = w / width;
2299
2300        int count = end - start;
2301
2302        final float sWidth = width * scale;
2303        float sHeight = height * scale;
2304
2305        LinearLayout preview = new LinearLayout(this);
2306
2307        PreviewTouchHandler handler = new PreviewTouchHandler(anchor);
2308        ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>(count);
2309
2310        for (int i = start; i < end; i++) {
2311            ImageView image = new ImageView(this);
2312            cell = (CellLayout) workspace.getChildAt(i);
2313
2314            final Bitmap bitmap = Bitmap.createBitmap((int) sWidth, (int) sHeight,
2315                    Bitmap.Config.ARGB_8888);
2316
2317            final Canvas c = new Canvas(bitmap);
2318            c.scale(scale, scale);
2319            c.translate(-cell.getLeftPadding(), -cell.getTopPadding());
2320            cell.drawChildren(c);
2321
2322            image.setBackgroundDrawable(resources.getDrawable(R.drawable.preview_background));
2323            image.setImageBitmap(bitmap);
2324            image.setTag(i);
2325            image.setOnClickListener(handler);
2326            image.setOnFocusChangeListener(handler);
2327            image.setFocusable(true);
2328            if (i == mWorkspace.getCurrentPage()) image.requestFocus();
2329
2330            preview.addView(image,
2331                    LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
2332
2333            bitmaps.add(bitmap);
2334        }
2335
2336        final PopupWindow p = new PopupWindow(this);
2337        p.setContentView(preview);
2338        p.setWidth((int) (sWidth * count + extraW));
2339        p.setHeight((int) (sHeight + extraH));
2340        p.setAnimationStyle(R.style.AnimationPreview);
2341        p.setOutsideTouchable(true);
2342        p.setFocusable(true);
2343        p.setBackgroundDrawable(new ColorDrawable(0));
2344        p.showAsDropDown(anchor, 0, 0);
2345
2346        p.setOnDismissListener(new PopupWindow.OnDismissListener() {
2347            public void onDismiss() {
2348                dismissPreview(anchor);
2349            }
2350        });
2351
2352        anchor.setTag(p);
2353        anchor.setTag(R.id.workspace, preview);
2354        anchor.setTag(R.id.icon, bitmaps);
2355    }
2356
2357    class PreviewTouchHandler implements View.OnClickListener, Runnable, View.OnFocusChangeListener {
2358        private final View mAnchor;
2359
2360        public PreviewTouchHandler(View anchor) {
2361            mAnchor = anchor;
2362        }
2363
2364        public void onClick(View v) {
2365            mWorkspace.snapToPage((Integer) v.getTag());
2366            v.post(this);
2367        }
2368
2369        public void run() {
2370            dismissPreview(mAnchor);
2371        }
2372
2373        public void onFocusChange(View v, boolean hasFocus) {
2374            if (hasFocus) {
2375                mWorkspace.snapToPage((Integer) v.getTag());
2376            }
2377        }
2378    }
2379
2380    Workspace getWorkspace() {
2381        return mWorkspace;
2382    }
2383
2384    @Override
2385    protected Dialog onCreateDialog(int id) {
2386        switch (id) {
2387            case DIALOG_CREATE_SHORTCUT:
2388                return new CreateShortcut().createDialog();
2389            case DIALOG_RENAME_FOLDER:
2390                return new RenameFolder().createDialog();
2391        }
2392
2393        return super.onCreateDialog(id);
2394    }
2395
2396    @Override
2397    protected void onPrepareDialog(int id, Dialog dialog) {
2398        switch (id) {
2399            case DIALOG_CREATE_SHORTCUT:
2400                break;
2401            case DIALOG_RENAME_FOLDER:
2402                if (mFolderInfo != null) {
2403                    EditText input = (EditText) dialog.findViewById(R.id.folder_name);
2404                    final CharSequence text = mFolderInfo.title;
2405                    input.setText(text);
2406                    input.setSelection(0, text.length());
2407                }
2408                break;
2409        }
2410    }
2411
2412    void showRenameDialog(FolderInfo info) {
2413        mFolderInfo = info;
2414        mWaitingForResult = true;
2415        showDialog(DIALOG_RENAME_FOLDER);
2416    }
2417
2418    private void showAddDialog(int intersectX, int intersectY) {
2419        resetAddInfo();
2420        mAddIntersectCellX = intersectX;
2421        mAddIntersectCellY = intersectY;
2422        mAddScreen = mWorkspace.getCurrentPage();
2423        mWaitingForResult = true;
2424        showDialog(DIALOG_CREATE_SHORTCUT);
2425    }
2426
2427    private void pickShortcut() {
2428        // Insert extra item to handle picking application
2429        Bundle bundle = new Bundle();
2430
2431        ArrayList<String> shortcutNames = new ArrayList<String>();
2432        shortcutNames.add(getString(R.string.group_applications));
2433        bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
2434
2435        ArrayList<ShortcutIconResource> shortcutIcons = new ArrayList<ShortcutIconResource>();
2436        shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
2437                        R.drawable.ic_launcher_application));
2438        bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons);
2439
2440        Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
2441        pickIntent.putExtra(Intent.EXTRA_INTENT, new Intent(Intent.ACTION_CREATE_SHORTCUT));
2442        pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_shortcut));
2443        pickIntent.putExtras(bundle);
2444
2445        startActivityForResult(pickIntent, REQUEST_PICK_SHORTCUT);
2446    }
2447
2448    private class RenameFolder {
2449        private EditText mInput;
2450
2451        Dialog createDialog() {
2452            final View layout = View.inflate(Launcher.this, R.layout.rename_folder, null);
2453            mInput = (EditText) layout.findViewById(R.id.folder_name);
2454
2455            AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
2456            builder.setIcon(0);
2457            builder.setTitle(getString(R.string.rename_folder_title));
2458            builder.setCancelable(true);
2459            builder.setOnCancelListener(new Dialog.OnCancelListener() {
2460                public void onCancel(DialogInterface dialog) {
2461                    cleanup();
2462                }
2463            });
2464            builder.setNegativeButton(getString(R.string.cancel_action),
2465                new Dialog.OnClickListener() {
2466                    public void onClick(DialogInterface dialog, int which) {
2467                        cleanup();
2468                    }
2469                }
2470            );
2471            builder.setPositiveButton(getString(R.string.rename_action),
2472                new Dialog.OnClickListener() {
2473                    public void onClick(DialogInterface dialog, int which) {
2474                        changeFolderName();
2475                    }
2476                }
2477            );
2478            builder.setView(layout);
2479
2480            final AlertDialog dialog = builder.create();
2481            dialog.setOnShowListener(new DialogInterface.OnShowListener() {
2482                public void onShow(DialogInterface dialog) {
2483                    mWaitingForResult = true;
2484                    mInput.requestFocus();
2485                    InputMethodManager inputManager = (InputMethodManager)
2486                            getSystemService(Context.INPUT_METHOD_SERVICE);
2487                    inputManager.showSoftInput(mInput, 0);
2488                }
2489            });
2490
2491            return dialog;
2492        }
2493
2494        private void changeFolderName() {
2495            final String name = mInput.getText().toString();
2496            if (!TextUtils.isEmpty(name)) {
2497                // Make sure we have the right folder info
2498                mFolderInfo = sFolders.get(mFolderInfo.id);
2499                mFolderInfo.title = name;
2500                LauncherModel.updateItemInDatabase(Launcher.this, mFolderInfo);
2501
2502                if (mWorkspaceLoading) {
2503                    lockAllApps();
2504                    mModel.startLoader(Launcher.this, false);
2505                } else {
2506                    final FolderIcon folderIcon = (FolderIcon)
2507                            mWorkspace.getViewForTag(mFolderInfo);
2508                    if (folderIcon != null) {
2509                        folderIcon.setText(name);
2510                        getWorkspace().requestLayout();
2511                    } else {
2512                        lockAllApps();
2513                        mWorkspaceLoading = true;
2514                        mModel.startLoader(Launcher.this, false);
2515                    }
2516                }
2517            }
2518            cleanup();
2519        }
2520
2521        private void cleanup() {
2522            dismissDialog(DIALOG_RENAME_FOLDER);
2523            mWaitingForResult = false;
2524            mFolderInfo = null;
2525        }
2526    }
2527
2528    // Now a part of LauncherModel.Callbacks. Used to reorder loading steps.
2529    public boolean isAllAppsVisible() {
2530        return mState == State.ALL_APPS;
2531    }
2532
2533    // AllAppsView.Watcher
2534    public void zoomed(float zoom) {
2535        // In XLarge view, we zoom down the workspace below all apps so it's still visible
2536        if (zoom == 1.0f && !LauncherApplication.isScreenXLarge()) {
2537            mWorkspace.setVisibility(View.GONE);
2538        }
2539    }
2540
2541    private void showToolbarButton(View button) {
2542        button.setAlpha(1.0f);
2543        button.setVisibility(View.VISIBLE);
2544        button.setFocusable(true);
2545        button.setClickable(true);
2546    }
2547
2548    private void hideToolbarButton(View button) {
2549        button.setAlpha(0.0f);
2550        // We can't set it to GONE, otherwise the RelativeLayout gets screwed up
2551        button.setVisibility(View.INVISIBLE);
2552        button.setFocusable(false);
2553        button.setClickable(false);
2554    }
2555
2556    /**
2557     * Helper function for showing or hiding a toolbar button, possibly animated.
2558     *
2559     * @param show If true, create an animation to the show the item. Otherwise, hide it.
2560     * @param view The toolbar button to be animated
2561     * @param seq A AnimatorSet that will be used to animate the transition. If null, the
2562     * transition will not be animated.
2563     */
2564    private void hideOrShowToolbarButton(boolean show, final View view, AnimatorSet seq) {
2565        final boolean showing = show;
2566        final boolean hiding = !show;
2567
2568        final int duration = show ?
2569                getResources().getInteger(R.integer.config_toolbarButtonFadeInTime) :
2570                getResources().getInteger(R.integer.config_toolbarButtonFadeOutTime);
2571
2572        if (seq != null) {
2573            Animator anim = ObjectAnimator.ofFloat(view, "alpha", show ? 1.0f : 0.0f);
2574            anim.setDuration(duration);
2575            anim.addListener(new AnimatorListenerAdapter() {
2576                @Override
2577                public void onAnimationStart(Animator animation) {
2578                    if (showing) showToolbarButton(view);
2579                }
2580                @Override
2581                public void onAnimationEnd(Animator animation) {
2582                    if (hiding) hideToolbarButton(view);
2583                }
2584            });
2585            seq.play(anim);
2586        } else {
2587            if (showing) {
2588                showToolbarButton(view);
2589            } else {
2590                hideToolbarButton(view);
2591            }
2592        }
2593    }
2594
2595    /**
2596     * Show/hide the appropriate toolbar buttons for newState.
2597     * If showSeq or hideSeq is null, the transition will be done immediately (not animated).
2598     *
2599     * @param newState The state that is being switched to
2600     * @param showSeq AnimatorSet in which to put "show" animations, or null.
2601     * @param hideSeq AnimatorSet in which to put "hide" animations, or null.
2602     */
2603    private void hideAndShowToolbarButtons(State newState, AnimatorSet showSeq, AnimatorSet hideSeq) {
2604        final View searchButton = findViewById(R.id.search_button_cluster);
2605        final View allAppsButton = findViewById(R.id.all_apps_button);
2606        final View divider = findViewById(R.id.divider);
2607        final View configureButton = findViewById(R.id.configure_button);
2608
2609        switch (newState) {
2610        case WORKSPACE:
2611            hideOrShowToolbarButton(true, searchButton, showSeq);
2612            hideOrShowToolbarButton(true, allAppsButton, showSeq);
2613            hideOrShowToolbarButton(true, divider, showSeq);
2614            hideOrShowToolbarButton(true, configureButton, showSeq);
2615            mDeleteZone.setOverlappingViews(new View[] { allAppsButton, divider, configureButton });
2616            mDeleteZone.setDragAndDropEnabled(true);
2617            mDeleteZone.setText(getResources().getString(R.string.delete_zone_label_workspace));
2618            break;
2619        case ALL_APPS:
2620            hideOrShowToolbarButton(false, configureButton, hideSeq);
2621            hideOrShowToolbarButton(false, searchButton, hideSeq);
2622            hideOrShowToolbarButton(false, divider, hideSeq);
2623            hideOrShowToolbarButton(false, allAppsButton, hideSeq);
2624            mDeleteZone.setDragAndDropEnabled(false);
2625            mDeleteZone.setText(getResources().getString(R.string.delete_zone_label_all_apps));
2626            break;
2627        case CUSTOMIZE:
2628            hideOrShowToolbarButton(false, allAppsButton, hideSeq);
2629            hideOrShowToolbarButton(false, searchButton, hideSeq);
2630            hideOrShowToolbarButton(false, divider, hideSeq);
2631            hideOrShowToolbarButton(false, configureButton, hideSeq);
2632            mDeleteZone.setDragAndDropEnabled(false);
2633            break;
2634        }
2635    }
2636
2637    /**
2638     * Helper method for the cameraZoomIn/cameraZoomOut animations
2639     * @param view The view being animated
2640     * @param state The state that we are moving in or out of -- either ALL_APPS or CUSTOMIZE
2641     * @param scaleFactor The scale factor used for the zoom
2642     */
2643    private void setPivotsForZoom(View view, State state, float scaleFactor) {
2644        final int height = view.getHeight();
2645
2646        view.setPivotX(view.getWidth() / 2.0f);
2647        // Set pivotY so that at the starting zoom factor, the view is partially
2648        // visible. Modifying initialHeightFactor changes how much of the view is
2649        // initially showing, and hence the perceived angle from which the view enters.
2650        final float initialHeightFactor = 0.2f;
2651        if (state == State.ALL_APPS) {
2652            view.setPivotY((1 + initialHeightFactor) * height);
2653        } else {
2654            view.setPivotY(-initialHeightFactor * height);
2655        }
2656    }
2657
2658    /**
2659     * Zoom the camera out from the workspace to reveal 'toView'.
2660     * Assumes that the view to show is anchored at either the very top or very bottom
2661     * of the screen.
2662     * @param toState The state to zoom out to. Must be ALL_APPS or CUSTOMIZE.
2663     */
2664    private void cameraZoomOut(State toState, boolean animated) {
2665        final Resources res = getResources();
2666        final int duration = res.getInteger(R.integer.config_allAppsZoomInTime);
2667        final float scale = (float) res.getInteger(R.integer.config_allAppsZoomScaleFactor);
2668        final boolean toAllApps = (toState == State.ALL_APPS);
2669        final View toView = toAllApps ? (View) mAllAppsGrid : mHomeCustomizationDrawer;
2670
2671        setPivotsForZoom(toView, toState, scale);
2672
2673        if (toAllApps) {
2674            mWorkspace.shrink(ShrinkState.BOTTOM_HIDDEN, animated);
2675        } else {
2676            mWorkspace.shrink(ShrinkState.TOP, animated);
2677        }
2678
2679        if (animated) {
2680            ValueAnimator scaleAnim = ObjectAnimator.ofPropertyValuesHolder(toView,
2681                    PropertyValuesHolder.ofFloat("scaleX", scale, 1.0f),
2682                    PropertyValuesHolder.ofFloat("scaleY", scale, 1.0f));
2683            scaleAnim.setDuration(duration);
2684
2685            scaleAnim.setInterpolator(new Workspace.ZoomOutInterpolator());
2686            scaleAnim.addListener(new AnimatorListenerAdapter() {
2687                @Override
2688                public void onAnimationStart(Animator animation) {
2689                    // Prepare the position
2690                    toView.setTranslationX(0.0f);
2691                    toView.setTranslationY(0.0f);
2692                    toView.setVisibility(View.VISIBLE);
2693                    toView.setAlpha(1.0f);
2694                }
2695                @Override
2696                public void onAnimationEnd(Animator animation) {
2697                    // If we don't set the final scale values here, if this animation is cancelled
2698                    // it will have the wrong scale value and subsequent cameraPan animations will
2699                    // not fix that
2700                    toView.setScaleX(1.0f);
2701                    toView.setScaleY(1.0f);
2702                }
2703            });
2704
2705            AnimatorSet toolbarHideAnim = new AnimatorSet();
2706            AnimatorSet toolbarShowAnim = new AnimatorSet();
2707            hideAndShowToolbarButtons(toState, toolbarShowAnim, toolbarHideAnim);
2708
2709            // toView should appear right at the end of the workspace shrink animation
2710            final int startDelay = res.getInteger(R.integer.config_workspaceShrinkTime) - duration;
2711
2712            if (mStateAnimation != null) mStateAnimation.cancel();
2713            mStateAnimation = new AnimatorSet();
2714            mStateAnimation.playTogether(scaleAnim, toolbarHideAnim);
2715            mStateAnimation.play(scaleAnim).after(startDelay);
2716
2717            // Show the new toolbar buttons just as the main animation is ending
2718            final int fadeInTime = res.getInteger(R.integer.config_toolbarButtonFadeInTime);
2719            mStateAnimation.play(toolbarShowAnim).after(duration + startDelay - fadeInTime);
2720            mStateAnimation.start();
2721        } else {
2722            toView.setTranslationX(0.0f);
2723            toView.setTranslationY(0.0f);
2724            toView.setScaleX(1.0f);
2725            toView.setScaleY(1.0f);
2726            toView.setVisibility(View.VISIBLE);
2727            hideAndShowToolbarButtons(toState, null, null);
2728        }
2729    }
2730
2731    /**
2732     * Zoom the camera back into the workspace, hiding 'fromView'.
2733     * This is the opposite of cameraZoomOut.
2734     * @param fromState The current state (must be ALL_APPS or CUSTOMIZE).
2735     * @param animated If true, the transition will be animated.
2736     */
2737    private void cameraZoomIn(State fromState, boolean animated) {
2738        cameraZoomIn(fromState, animated, false);
2739    }
2740
2741    private void cameraZoomIn(State fromState, boolean animated, boolean springLoaded) {
2742        Resources res = getResources();
2743        int duration = res.getInteger(R.integer.config_allAppsZoomOutTime);
2744        float scaleFactor = (float) res.getInteger(R.integer.config_allAppsZoomScaleFactor);
2745        final View fromView =
2746            (fromState == State.ALL_APPS) ? (View) mAllAppsGrid : mHomeCustomizationDrawer;
2747
2748        mCustomizePagedView.endChoiceMode();
2749        mAllAppsPagedView.endChoiceMode();
2750
2751        setPivotsForZoom(fromView, fromState, scaleFactor);
2752
2753        if (!springLoaded) {
2754            mWorkspace.unshrink(animated);
2755        }
2756
2757        if (animated) {
2758            if (mStateAnimation != null) mStateAnimation.cancel();
2759            mStateAnimation = new AnimatorSet();
2760            ValueAnimator scaleAnim = ObjectAnimator.ofPropertyValuesHolder(fromView,
2761                    PropertyValuesHolder.ofFloat("scaleX", scaleFactor),
2762                    PropertyValuesHolder.ofFloat("scaleY", scaleFactor));
2763            scaleAnim.setDuration(duration);
2764            scaleAnim.setInterpolator(new Workspace.ZoomInInterpolator());
2765
2766            ValueAnimator alphaAnim = ObjectAnimator.ofPropertyValuesHolder(fromView,
2767                    PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.0f));
2768            alphaAnim.setDuration(res.getInteger(R.integer.config_allAppsFadeOutTime));
2769            alphaAnim.addListener(new AnimatorListenerAdapter() {
2770                @Override
2771                public void onAnimationEnd(Animator animation) {
2772                    fromView.setVisibility(View.GONE);
2773                }
2774            });
2775
2776            AnimatorSet toolbarHideAnim = new AnimatorSet();
2777            AnimatorSet toolbarShowAnim = new AnimatorSet();
2778            if (!springLoaded) {
2779                hideAndShowToolbarButtons(State.WORKSPACE, toolbarShowAnim, toolbarHideAnim);
2780            }
2781
2782            mStateAnimation.playTogether(scaleAnim, toolbarHideAnim, alphaAnim);
2783
2784            // Show the new toolbar buttons at the very end of the whole animation
2785            final int fadeInTime = res.getInteger(R.integer.config_toolbarButtonFadeInTime);
2786            final int unshrinkTime = res.getInteger(R.integer.config_workspaceUnshrinkTime);
2787            mStateAnimation.play(toolbarShowAnim).after(unshrinkTime - fadeInTime);
2788            mStateAnimation.start();
2789        } else {
2790            fromView.setVisibility(View.GONE);
2791            if (!springLoaded) {
2792                hideAndShowToolbarButtons(State.WORKSPACE, null, null);
2793            }
2794        }
2795    }
2796
2797    /**
2798     * Pan the camera in the vertical plane between 'fromView' and 'toView'.
2799     * This is the transition used on xlarge screens to go between all apps and
2800     * the home customization drawer.
2801     * @param fromState The view to pan away from. Must be ALL_APPS or CUSTOMIZE.
2802     * @param toState The view to pan into the frame. Must be ALL_APPS or CUSTOMIZE.
2803     * @param animated If true, the transition will be animated.
2804     */
2805    private void cameraPan(State fromState, State toState, boolean animated) {
2806        final Resources res = getResources();
2807        final int duration = res.getInteger(R.integer.config_allAppsCameraPanTime);
2808        final int workspaceHeight = mWorkspace.getHeight();
2809
2810        final boolean fromAllApps = (fromState == State.ALL_APPS);
2811        final View fromView = fromAllApps ? (View) mAllAppsGrid : mHomeCustomizationDrawer;
2812        final View toView = fromAllApps ? mHomeCustomizationDrawer : (View) mAllAppsGrid;
2813
2814        final float fromViewStartY = fromAllApps ? 0.0f : fromView.getY();
2815        final float fromViewEndY = fromAllApps ? -fromView.getHeight() * 2 : workspaceHeight * 2;
2816        final float toViewStartY = fromAllApps ? workspaceHeight * 2 : -toView.getHeight() * 2;
2817        final float toViewEndY = fromAllApps ? workspaceHeight - toView.getHeight() : 0.0f;
2818
2819        mCustomizePagedView.endChoiceMode();
2820        mAllAppsPagedView.endChoiceMode();
2821
2822        if (toState == State.ALL_APPS) {
2823            mWorkspace.shrink(Workspace.ShrinkState.BOTTOM_HIDDEN, animated);
2824        } else {
2825            mWorkspace.shrink(Workspace.ShrinkState.TOP, animated);
2826        }
2827
2828        if (animated) {
2829            if (mStateAnimation != null) mStateAnimation.cancel();
2830            mStateAnimation = new AnimatorSet();
2831            mStateAnimation.addListener(new AnimatorListenerAdapter() {
2832                @Override
2833                public void onAnimationStart(Animator animation) {
2834                    toView.setVisibility(View.VISIBLE);
2835                    toView.setY(toViewStartY);
2836                    toView.setAlpha(1.0f);
2837                }
2838                @Override
2839                public void onAnimationEnd(Animator animation) {
2840                    fromView.setVisibility(View.GONE);
2841                }
2842            });
2843
2844            AnimatorSet toolbarHideAnim = new AnimatorSet();
2845            AnimatorSet toolbarShowAnim = new AnimatorSet();
2846            hideAndShowToolbarButtons(toState, toolbarShowAnim, toolbarHideAnim);
2847
2848            ObjectAnimator fromAnim = ObjectAnimator.ofFloat(fromView, "y",
2849                    fromViewStartY, fromViewEndY);
2850            fromAnim.setDuration(duration);
2851            ObjectAnimator toAnim = ObjectAnimator.ofPropertyValuesHolder(toView,
2852                    PropertyValuesHolder.ofFloat("y", toViewStartY, toViewEndY),
2853                    PropertyValuesHolder.ofFloat("scaleX", toView.getScaleX(), 1.0f),
2854                    PropertyValuesHolder.ofFloat("scaleY", toView.getScaleY(), 1.0f)
2855                    );
2856            fromAnim.setDuration(duration);
2857            mStateAnimation.playTogether(toolbarHideAnim, fromAnim, toAnim);
2858
2859            // Show the new toolbar buttons just as the main animation is ending
2860            final int fadeInTime = res.getInteger(R.integer.config_toolbarButtonFadeInTime);
2861            mStateAnimation.play(toolbarShowAnim).after(duration - fadeInTime);
2862            mStateAnimation.start();
2863        } else {
2864            fromView.setY(fromViewEndY);
2865            fromView.setVisibility(View.GONE);
2866            toView.setY(toViewEndY);
2867            toView.setScaleX(1.0f);
2868            toView.setScaleY(1.0f);
2869            toView.setVisibility(View.VISIBLE);
2870            hideAndShowToolbarButtons(toState, null, null);
2871        }
2872    }
2873
2874    void showAllApps(boolean animated) {
2875        if (mState == State.ALL_APPS) {
2876            return;
2877        }
2878
2879        if (LauncherApplication.isScreenXLarge()) {
2880            if (mState == State.CUSTOMIZE) {
2881                cameraPan(State.CUSTOMIZE, State.ALL_APPS, animated);
2882            } else {
2883                cameraZoomOut(State.ALL_APPS, animated);
2884            }
2885        } else {
2886            mAllAppsGrid.zoom(1.0f, animated);
2887        }
2888
2889        ((View) mAllAppsGrid).setFocusable(true);
2890        ((View) mAllAppsGrid).requestFocus();
2891
2892        // TODO: fade these two too
2893        mDeleteZone.setVisibility(View.GONE);
2894
2895        // Change the state *after* we've called all the transition code
2896        mState = State.ALL_APPS;
2897
2898        // send an accessibility event to announce the context change
2899        getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
2900    }
2901
2902    void showWorkspace(boolean animated) {
2903        showWorkspace(animated, null);
2904    }
2905
2906    void showWorkspace(boolean animated, CellLayout layout) {
2907        if (layout != null) {
2908            // always animated, but that's ok since we never specify a layout and
2909            // want no animation
2910            mWorkspace.unshrink(layout);
2911        } else {
2912            mWorkspace.unshrink(animated);
2913        }
2914        if (mState == State.ALL_APPS) {
2915            closeAllApps(animated);
2916        } else if (mState == State.CUSTOMIZE) {
2917            hideCustomizationDrawer(animated);
2918        }
2919
2920        // Change the state *after* we've called all the transition code
2921        mState = State.WORKSPACE;
2922
2923        // send an accessibility event to announce the context change
2924        getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
2925    }
2926
2927    void enterSpringLoadedDragMode(CellLayout layout) {
2928        mWorkspace.enterSpringLoadedDragMode(layout);
2929        if (mState == State.ALL_APPS) {
2930            cameraZoomIn(State.ALL_APPS, true, true);
2931            mState = State.ALL_APPS_SPRING_LOADED;
2932        } else if (mState == State.CUSTOMIZE) {
2933            cameraZoomIn(State.CUSTOMIZE, true, true);
2934            mState = State.CUSTOMIZE_SPRING_LOADED;
2935        }/* else {
2936            // we're already in spring loaded mode; don't do anything
2937        }*/
2938    }
2939
2940    void exitSpringLoadedDragMode() {
2941        if (mState == State.ALL_APPS_SPRING_LOADED) {
2942            mWorkspace.exitSpringLoadedDragMode(Workspace.ShrinkState.BOTTOM_VISIBLE);
2943            cameraZoomOut(State.ALL_APPS, true);
2944            mState = State.ALL_APPS;
2945        } else if (mState == State.CUSTOMIZE_SPRING_LOADED) {
2946            mWorkspace.exitSpringLoadedDragMode(Workspace.ShrinkState.TOP);
2947            cameraZoomOut(State.CUSTOMIZE, true);
2948            mState = State.CUSTOMIZE;
2949        }/* else {
2950            // we're not in spring loaded mode; don't do anything
2951        }*/
2952    }
2953
2954    /**
2955     * Things to test when changing this code.
2956     *   - Home from workspace
2957     *          - from center screen
2958     *          - from other screens
2959     *   - Home from all apps
2960     *          - from center screen
2961     *          - from other screens
2962     *   - Back from all apps
2963     *          - from center screen
2964     *          - from other screens
2965     *   - Launch app from workspace and quit
2966     *          - with back
2967     *          - with home
2968     *   - Launch app from all apps and quit
2969     *          - with back
2970     *          - with home
2971     *   - Go to a screen that's not the default, then all
2972     *     apps, and launch and app, and go back
2973     *          - with back
2974     *          -with home
2975     *   - On workspace, long press power and go back
2976     *          - with back
2977     *          - with home
2978     *   - On all apps, long press power and go back
2979     *          - with back
2980     *          - with home
2981     *   - On workspace, power off
2982     *   - On all apps, power off
2983     *   - Launch an app and turn off the screen while in that app
2984     *          - Go back with home key
2985     *          - Go back with back key  TODO: make this not go to workspace
2986     *          - From all apps
2987     *          - From workspace
2988     *   - Enter and exit car mode (becuase it causes an extra configuration changed)
2989     *          - From all apps
2990     *          - From the center workspace
2991     *          - From another workspace
2992     */
2993    void closeAllApps(boolean animated) {
2994        if (mState == State.ALL_APPS || mState == State.ALL_APPS_SPRING_LOADED) {
2995            mWorkspace.setVisibility(View.VISIBLE);
2996            if (LauncherApplication.isScreenXLarge()) {
2997                cameraZoomIn(State.ALL_APPS, animated);
2998            } else {
2999                mAllAppsGrid.zoom(0.0f, animated);
3000            }
3001            ((View)mAllAppsGrid).setFocusable(false);
3002            mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
3003        }
3004    }
3005
3006    void lockAllApps() {
3007        // TODO
3008    }
3009
3010    void unlockAllApps() {
3011        // TODO
3012    }
3013
3014    // Show the customization drawer (only exists in x-large configuration)
3015    private void showCustomizationDrawer(boolean animated) {
3016        if (mState == State.ALL_APPS) {
3017            cameraPan(State.ALL_APPS, State.CUSTOMIZE, animated);
3018        } else {
3019            cameraZoomOut(State.CUSTOMIZE, animated);
3020        }
3021        // Change the state *after* we've called all the transition code
3022        mState = State.CUSTOMIZE;
3023    }
3024
3025    // Hide the customization drawer (only exists in x-large configuration)
3026    void hideCustomizationDrawer(boolean animated) {
3027        if (mState == State.CUSTOMIZE || mState == State.CUSTOMIZE_SPRING_LOADED) {
3028            cameraZoomIn(State.CUSTOMIZE, animated);
3029        }
3030    }
3031
3032    void addExternalItemToScreen(ItemInfo itemInfo, CellLayout layout) {
3033        if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) {
3034            showOutOfSpaceMessage();
3035        }
3036    }
3037
3038    void onWorkspaceClick(CellLayout layout) {
3039        showWorkspace(true, layout);
3040    }
3041
3042    // if successful in getting icon, return it; otherwise, set button to use default drawable
3043    private Drawable.ConstantState updateButtonWithIconFromExternalActivity(
3044            int buttonId, ComponentName activityName, int fallbackDrawableId) {
3045        ImageView button = (ImageView) findViewById(buttonId);
3046        Drawable toolbarIcon = null;
3047        try {
3048            PackageManager packageManager = getPackageManager();
3049            // Look for the toolbar icon specified in the activity meta-data
3050            Bundle metaData = packageManager.getActivityInfo(
3051                    activityName, PackageManager.GET_META_DATA).metaData;
3052            if (metaData != null) {
3053                int iconResId = metaData.getInt(TOOLBAR_ICON_METADATA_NAME);
3054                if (iconResId != 0) {
3055                    Resources res = packageManager.getResourcesForActivity(activityName);
3056                    toolbarIcon = res.getDrawable(iconResId);
3057                }
3058            }
3059        } catch (NameNotFoundException e) {
3060            // Do nothing
3061        }
3062        // If we were unable to find the icon via the meta-data, use a generic one
3063        if (toolbarIcon == null) {
3064            button.setImageResource(fallbackDrawableId);
3065            return null;
3066        } else {
3067            button.setImageDrawable(toolbarIcon);
3068            return toolbarIcon.getConstantState();
3069        }
3070    }
3071
3072    private void updateButtonWithDrawable(int buttonId, Drawable.ConstantState d) {
3073        ImageView button = (ImageView) findViewById(buttonId);
3074        button.setImageDrawable(d.newDrawable(getResources()));
3075    }
3076
3077    private void updateGlobalSearchIcon() {
3078        if (LauncherApplication.isScreenXLarge()) {
3079            final SearchManager searchManager =
3080                    (SearchManager) getSystemService(Context.SEARCH_SERVICE);
3081            ComponentName activityName = searchManager.getGlobalSearchActivity();
3082            if (activityName != null) {
3083                sGlobalSearchIcon = updateButtonWithIconFromExternalActivity(
3084                        R.id.search_button, activityName, R.drawable.search_button_generic);
3085            } else {
3086                findViewById(R.id.search_button).setVisibility(View.GONE);
3087            }
3088        }
3089    }
3090
3091    private void updateGlobalSearchIcon(Drawable.ConstantState d) {
3092        updateButtonWithDrawable(R.id.search_button, d);
3093    }
3094
3095    private void updateVoiceSearchIcon() {
3096        if (LauncherApplication.isScreenXLarge()) {
3097            Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
3098            ComponentName activityName = intent.resolveActivity(getPackageManager());
3099            if (activityName != null) {
3100                sVoiceSearchIcon = updateButtonWithIconFromExternalActivity(
3101                        R.id.voice_button, activityName, R.drawable.ic_voice_search);
3102            } else {
3103                findViewById(R.id.voice_button).setVisibility(View.GONE);
3104            }
3105        }
3106    }
3107
3108    private void updateVoiceSearchIcon(Drawable.ConstantState d) {
3109        updateButtonWithDrawable(R.id.voice_button, d);
3110    }
3111
3112    /**
3113     * Sets the app market icon (shown when all apps is visible on x-large screens)
3114     */
3115    private void updateAppMarketIcon() {
3116        if (LauncherApplication.isScreenXLarge()) {
3117            Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET);
3118            // Find the app market activity by resolving an intent.
3119            // (If multiple app markets are installed, it will return the ResolverActivity.)
3120            ComponentName activityName = intent.resolveActivity(getPackageManager());
3121            if (activityName != null) {
3122                mAppMarketIntent = intent;
3123                sAppMarketIcon = updateButtonWithIconFromExternalActivity(
3124                        R.id.market_button, activityName, R.drawable.app_market_generic);
3125            }
3126        }
3127    }
3128
3129    private void updateAppMarketIcon(Drawable.ConstantState d) {
3130        updateButtonWithDrawable(R.id.market_button, d);
3131    }
3132
3133    /**
3134     * Displays the shortcut creation dialog and launches, if necessary, the
3135     * appropriate activity.
3136     */
3137    private class CreateShortcut implements DialogInterface.OnClickListener,
3138            DialogInterface.OnCancelListener, DialogInterface.OnDismissListener,
3139            DialogInterface.OnShowListener {
3140
3141        private AddAdapter mAdapter;
3142
3143        Dialog createDialog() {
3144            mAdapter = new AddAdapter(Launcher.this);
3145
3146            final AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
3147            builder.setTitle(getString(R.string.menu_item_add_item));
3148            builder.setAdapter(mAdapter, this);
3149
3150            builder.setInverseBackgroundForced(true);
3151
3152            AlertDialog dialog = builder.create();
3153            dialog.setOnCancelListener(this);
3154            dialog.setOnDismissListener(this);
3155            dialog.setOnShowListener(this);
3156
3157            return dialog;
3158        }
3159
3160        public void onCancel(DialogInterface dialog) {
3161            mWaitingForResult = false;
3162            cleanup();
3163        }
3164
3165        public void onDismiss(DialogInterface dialog) {
3166        }
3167
3168        private void cleanup() {
3169            try {
3170                dismissDialog(DIALOG_CREATE_SHORTCUT);
3171            } catch (Exception e) {
3172                // An exception is thrown if the dialog is not visible, which is fine
3173            }
3174        }
3175
3176        /**
3177         * Handle the action clicked in the "Add to home" dialog.
3178         */
3179        public void onClick(DialogInterface dialog, int which) {
3180            Resources res = getResources();
3181            cleanup();
3182
3183            switch (which) {
3184                case AddAdapter.ITEM_SHORTCUT: {
3185                    pickShortcut();
3186                    break;
3187                }
3188
3189                case AddAdapter.ITEM_APPWIDGET: {
3190                    int appWidgetId = Launcher.this.mAppWidgetHost.allocateAppWidgetId();
3191
3192                    Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
3193                    pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
3194                    // start the pick activity
3195                    startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
3196                    break;
3197                }
3198
3199                case AddAdapter.ITEM_LIVE_FOLDER: {
3200                    // Insert extra item to handle inserting folder
3201                    Bundle bundle = new Bundle();
3202
3203                    ArrayList<String> shortcutNames = new ArrayList<String>();
3204                    shortcutNames.add(res.getString(R.string.group_folder));
3205                    bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
3206
3207                    ArrayList<ShortcutIconResource> shortcutIcons =
3208                            new ArrayList<ShortcutIconResource>();
3209                    shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
3210                            R.drawable.ic_launcher_folder));
3211                    bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons);
3212
3213                    Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
3214                    pickIntent.putExtra(Intent.EXTRA_INTENT,
3215                            new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER));
3216                    pickIntent.putExtra(Intent.EXTRA_TITLE,
3217                            getText(R.string.title_select_live_folder));
3218                    pickIntent.putExtras(bundle);
3219
3220                    startActivityForResult(pickIntent, REQUEST_PICK_LIVE_FOLDER);
3221                    break;
3222                }
3223
3224                case AddAdapter.ITEM_WALLPAPER: {
3225                    startWallpaper();
3226                    break;
3227                }
3228            }
3229        }
3230
3231        public void onShow(DialogInterface dialog) {
3232            mWaitingForResult = true;
3233        }
3234    }
3235
3236    /**
3237     * Receives notifications when applications are added/removed.
3238     */
3239    private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
3240        @Override
3241        public void onReceive(Context context, Intent intent) {
3242            closeSystemDialogs();
3243            String reason = intent.getStringExtra("reason");
3244            if (!"homekey".equals(reason)) {
3245                boolean animate = true;
3246                if (mPaused || "lock".equals(reason)) {
3247                    animate = false;
3248                }
3249                showWorkspace(animate);
3250            }
3251        }
3252    }
3253
3254    /**
3255     * Receives notifications whenever the appwidgets are reset.
3256     */
3257    private class AppWidgetResetObserver extends ContentObserver {
3258        public AppWidgetResetObserver() {
3259            super(new Handler());
3260        }
3261
3262        @Override
3263        public void onChange(boolean selfChange) {
3264            onAppWidgetReset();
3265        }
3266    }
3267
3268    /**
3269     * If the activity is currently paused, signal that we need to re-run the loader
3270     * in onResume.
3271     *
3272     * This needs to be called from incoming places where resources might have been loaded
3273     * while we are paused.  That is becaues the Configuration might be wrong
3274     * when we're not running, and if it comes back to what it was when we
3275     * were paused, we are not restarted.
3276     *
3277     * Implementation of the method from LauncherModel.Callbacks.
3278     *
3279     * @return true if we are currently paused.  The caller might be able to
3280     * skip some work in that case since we will come back again.
3281     */
3282    public boolean setLoadOnResume() {
3283        if (mPaused) {
3284            Log.i(TAG, "setLoadOnResume");
3285            mOnResumeNeedsLoad = true;
3286            return true;
3287        } else {
3288            return false;
3289        }
3290    }
3291
3292    /**
3293     * Implementation of the method from LauncherModel.Callbacks.
3294     */
3295    public int getCurrentWorkspaceScreen() {
3296        if (mWorkspace != null) {
3297            return mWorkspace.getCurrentPage();
3298        } else {
3299            return SCREEN_COUNT / 2;
3300        }
3301    }
3302
3303    void setAllAppsPagedView(AllAppsPagedView view) {
3304        mAllAppsPagedView = view;
3305    }
3306
3307    /**
3308     * Refreshes the shortcuts shown on the workspace.
3309     *
3310     * Implementation of the method from LauncherModel.Callbacks.
3311     */
3312    public void startBinding() {
3313        final Workspace workspace = mWorkspace;
3314        int count = workspace.getChildCount();
3315        for (int i = 0; i < count; i++) {
3316            // Use removeAllViewsInLayout() to avoid an extra requestLayout() and invalidate().
3317            ((ViewGroup) workspace.getChildAt(i)).removeAllViewsInLayout();
3318        }
3319
3320        if (DEBUG_USER_INTERFACE) {
3321            android.widget.Button finishButton = new android.widget.Button(this);
3322            finishButton.setText("Finish");
3323            workspace.addInScreen(finishButton, 1, 0, 0, 1, 1);
3324
3325            finishButton.setOnClickListener(new android.widget.Button.OnClickListener() {
3326                public void onClick(View v) {
3327                    finish();
3328                }
3329            });
3330        }
3331    }
3332
3333    /**
3334     * Bind the items start-end from the list.
3335     *
3336     * Implementation of the method from LauncherModel.Callbacks.
3337     */
3338    public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end) {
3339
3340        setLoadOnResume();
3341
3342        final Workspace workspace = mWorkspace;
3343
3344        for (int i=start; i<end; i++) {
3345            final ItemInfo item = shortcuts.get(i);
3346            mDesktopItems.add(item);
3347            switch (item.itemType) {
3348                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3349                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3350                    final View shortcut = createShortcut((ShortcutInfo)item);
3351                    workspace.addInScreen(shortcut, item.screen, item.cellX, item.cellY, 1, 1,
3352                            false);
3353                    break;
3354                case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
3355                    final FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
3356                            (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
3357                            (UserFolderInfo) item, mIconCache);
3358                    workspace.addInScreen(newFolder, item.screen, item.cellX, item.cellY, 1, 1,
3359                            false);
3360                    break;
3361                case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
3362                    final FolderIcon newLiveFolder = LiveFolderIcon.fromXml(
3363                            R.layout.live_folder_icon, this,
3364                            (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
3365                            (LiveFolderInfo) item);
3366                    workspace.addInScreen(newLiveFolder, item.screen, item.cellX, item.cellY, 1, 1,
3367                            false);
3368                    break;
3369            }
3370        }
3371
3372        workspace.requestLayout();
3373    }
3374
3375    /**
3376     * Implementation of the method from LauncherModel.Callbacks.
3377     */
3378    public void bindFolders(HashMap<Long, FolderInfo> folders) {
3379        setLoadOnResume();
3380        sFolders.clear();
3381        sFolders.putAll(folders);
3382    }
3383
3384    /**
3385     * Add the views for a widget to the workspace.
3386     *
3387     * Implementation of the method from LauncherModel.Callbacks.
3388     */
3389    public void bindAppWidget(LauncherAppWidgetInfo item) {
3390        setLoadOnResume();
3391
3392        final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
3393        if (DEBUG_WIDGETS) {
3394            Log.d(TAG, "bindAppWidget: " + item);
3395        }
3396        final Workspace workspace = mWorkspace;
3397
3398        final int appWidgetId = item.appWidgetId;
3399        final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
3400        if (DEBUG_WIDGETS) {
3401            Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider);
3402        }
3403
3404        item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
3405
3406        item.hostView.setAppWidget(appWidgetId, appWidgetInfo);
3407        item.hostView.setTag(item);
3408
3409        workspace.addInScreen(item.hostView, item.screen, item.cellX,
3410                item.cellY, item.spanX, item.spanY, false);
3411
3412        addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
3413
3414        workspace.requestLayout();
3415
3416        mDesktopItems.add(item);
3417
3418        if (DEBUG_WIDGETS) {
3419            Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
3420                    + (SystemClock.uptimeMillis()-start) + "ms");
3421        }
3422    }
3423
3424    /**
3425     * Callback saying that there aren't any more items to bind.
3426     *
3427     * Implementation of the method from LauncherModel.Callbacks.
3428     */
3429    public void finishBindingItems() {
3430        setLoadOnResume();
3431
3432        if (mSavedState != null) {
3433            if (!mWorkspace.hasFocus()) {
3434                mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
3435            }
3436
3437            final long[] userFolders = mSavedState.getLongArray(RUNTIME_STATE_USER_FOLDERS);
3438            if (userFolders != null) {
3439                for (long folderId : userFolders) {
3440                    final FolderInfo info = sFolders.get(folderId);
3441                    if (info != null) {
3442                        openFolder(info);
3443                    }
3444                }
3445                final Folder openFolder = mWorkspace.getOpenFolder();
3446                if (openFolder != null) {
3447                    openFolder.requestFocus();
3448                }
3449            }
3450
3451            mSavedState = null;
3452        }
3453
3454        if (mSavedInstanceState != null) {
3455            super.onRestoreInstanceState(mSavedInstanceState);
3456            mSavedInstanceState = null;
3457        }
3458
3459        mWorkspaceLoading = false;
3460    }
3461
3462    /**
3463     * Updates the icons on the launcher that are affected by changes to the package list
3464     * on the device.
3465     */
3466    private void updateIconsAffectedByPackageManagerChanges() {
3467        updateAppMarketIcon();
3468        updateGlobalSearchIcon();
3469        updateVoiceSearchIcon();
3470    }
3471
3472    /**
3473     * Add the icons for all apps.
3474     *
3475     * Implementation of the method from LauncherModel.Callbacks.
3476     */
3477    public void bindAllApplications(ArrayList<ApplicationInfo> apps) {
3478        mAllAppsGrid.setApps(apps);
3479        if (mCustomizePagedView != null) {
3480            mCustomizePagedView.setApps(apps);
3481        }
3482        updateIconsAffectedByPackageManagerChanges();
3483    }
3484
3485    /**
3486     * A package was installed.
3487     *
3488     * Implementation of the method from LauncherModel.Callbacks.
3489     */
3490    public void bindAppsAdded(ArrayList<ApplicationInfo> apps) {
3491        setLoadOnResume();
3492        removeDialog(DIALOG_CREATE_SHORTCUT);
3493        mAllAppsGrid.addApps(apps);
3494        if (mCustomizePagedView != null) {
3495            mCustomizePagedView.addApps(apps);
3496        }
3497        updateIconsAffectedByPackageManagerChanges();
3498    }
3499
3500    /**
3501     * A package was updated.
3502     *
3503     * Implementation of the method from LauncherModel.Callbacks.
3504     */
3505    public void bindAppsUpdated(ArrayList<ApplicationInfo> apps) {
3506        setLoadOnResume();
3507        removeDialog(DIALOG_CREATE_SHORTCUT);
3508        mWorkspace.updateShortcuts(apps);
3509        mAllAppsGrid.updateApps(apps);
3510        if (mCustomizePagedView != null) {
3511            mCustomizePagedView.updateApps(apps);
3512        }
3513        updateIconsAffectedByPackageManagerChanges();
3514    }
3515
3516    /**
3517     * A package was uninstalled.
3518     *
3519     * Implementation of the method from LauncherModel.Callbacks.
3520     */
3521    public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent) {
3522        removeDialog(DIALOG_CREATE_SHORTCUT);
3523        if (permanent) {
3524            mWorkspace.removeItems(apps);
3525        }
3526        mAllAppsGrid.removeApps(apps);
3527        if (mCustomizePagedView != null) {
3528            mCustomizePagedView.removeApps(apps);
3529        }
3530        updateIconsAffectedByPackageManagerChanges();
3531    }
3532
3533    /**
3534     * A number of packages were updated.
3535     */
3536    public void bindPackagesUpdated() {
3537        // update the customization drawer contents
3538        if (mCustomizePagedView != null) {
3539            mCustomizePagedView.update();
3540        }
3541    }
3542
3543    /**
3544     * Prints out out state for debugging.
3545     */
3546    public void dumpState() {
3547        Log.d(TAG, "BEGIN launcher2 dump state for launcher " + this);
3548        Log.d(TAG, "mSavedState=" + mSavedState);
3549        Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
3550        Log.d(TAG, "mRestoring=" + mRestoring);
3551        Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
3552        Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
3553        Log.d(TAG, "mDesktopItems.size=" + mDesktopItems.size());
3554        Log.d(TAG, "sFolders.size=" + sFolders.size());
3555        mModel.dumpState();
3556        mAllAppsGrid.dumpState();
3557        Log.d(TAG, "END launcher2 dump state");
3558    }
3559}
3560