Launcher.java revision ab1ebd7e6f10a352867d4e38ce6421a38b0f50d2
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher2;
18
19import com.android.common.Search;
20
21import android.app.Activity;
22import android.app.AlertDialog;
23import android.app.Dialog;
24import android.app.SearchManager;
25import android.app.StatusBarManager;
26import android.app.WallpaperManager;
27import android.content.ActivityNotFoundException;
28import android.content.BroadcastReceiver;
29import android.content.ComponentName;
30import android.content.ContentResolver;
31import android.content.Context;
32import android.content.DialogInterface;
33import android.content.Intent;
34import android.content.Intent.ShortcutIconResource;
35import android.content.IntentFilter;
36import android.content.pm.ActivityInfo;
37import android.content.pm.PackageManager;
38import android.content.pm.ResolveInfo;
39import android.content.res.Configuration;
40import android.content.res.Resources;
41import android.content.res.TypedArray;
42import android.database.ContentObserver;
43import android.graphics.Bitmap;
44import android.graphics.Rect;
45import android.graphics.Canvas;
46import android.graphics.drawable.Drawable;
47import android.graphics.drawable.ColorDrawable;
48import android.net.Uri;
49import android.os.Bundle;
50import android.os.Handler;
51import android.os.Parcelable;
52import android.os.SystemProperties;
53import android.provider.LiveFolders;
54import android.text.Selection;
55import android.text.SpannableStringBuilder;
56import android.text.TextUtils;
57import android.text.method.TextKeyListener;
58import android.util.Log;
59import android.view.Display;
60import android.view.HapticFeedbackConstants;
61import android.view.KeyEvent;
62import android.view.LayoutInflater;
63import android.view.Menu;
64import android.view.MenuItem;
65import android.view.View;
66import android.view.ViewGroup;
67import android.view.View.OnLongClickListener;
68import android.view.inputmethod.InputMethodManager;
69import android.widget.EditText;
70import android.widget.TextView;
71import android.widget.Toast;
72import android.widget.ImageView;
73import android.widget.PopupWindow;
74import android.widget.LinearLayout;
75import android.appwidget.AppWidgetManager;
76import android.appwidget.AppWidgetProviderInfo;
77
78import java.util.ArrayList;
79import java.util.List;
80import java.util.HashMap;
81import java.io.DataOutputStream;
82import java.io.FileNotFoundException;
83import java.io.IOException;
84import java.io.DataInputStream;
85
86import com.android.launcher.R;
87
88/**
89 * Default launcher application.
90 */
91public final class Launcher extends Activity
92        implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, AllAppsView.Watcher {
93    static final String TAG = "Launcher";
94    static final boolean LOGD = false;
95
96    static final boolean PROFILE_STARTUP = false;
97    static final boolean PROFILE_ROTATE = false;
98    static final boolean DEBUG_USER_INTERFACE = false;
99
100    private static final int WALLPAPER_SCREENS_SPAN = 2;
101
102    private static final int MENU_GROUP_ADD = 1;
103    private static final int MENU_GROUP_WALLPAPER = MENU_GROUP_ADD + 1;
104
105    private static final int MENU_ADD = Menu.FIRST + 1;
106    private static final int MENU_WALLPAPER_SETTINGS = MENU_ADD + 1;
107    private static final int MENU_SEARCH = MENU_WALLPAPER_SETTINGS + 1;
108    private static final int MENU_NOTIFICATIONS = MENU_SEARCH + 1;
109    private static final int MENU_SETTINGS = MENU_NOTIFICATIONS + 1;
110
111    private static final int REQUEST_CREATE_SHORTCUT = 1;
112    private static final int REQUEST_CREATE_LIVE_FOLDER = 4;
113    private static final int REQUEST_CREATE_APPWIDGET = 5;
114    private static final int REQUEST_PICK_APPLICATION = 6;
115    private static final int REQUEST_PICK_SHORTCUT = 7;
116    private static final int REQUEST_PICK_LIVE_FOLDER = 8;
117    private static final int REQUEST_PICK_APPWIDGET = 9;
118    private static final int REQUEST_PICK_WALLPAPER = 10;
119
120    static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
121
122    static final int SCREEN_COUNT = 5;
123    static final int DEFAULT_SCREEN = 2;
124    static final int NUMBER_CELLS_X = 4;
125    static final int NUMBER_CELLS_Y = 4;
126
127    static final int DIALOG_CREATE_SHORTCUT = 1;
128    static final int DIALOG_RENAME_FOLDER = 2;
129
130    private static final String PREFERENCES = "launcher.preferences";
131
132    // Type: int
133    private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
134    // Type: boolean
135    private static final String RUNTIME_STATE_ALL_APPS_FOLDER = "launcher.all_apps_folder";
136    // Type: long
137    private static final String RUNTIME_STATE_USER_FOLDERS = "launcher.user_folder";
138    // Type: int
139    private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
140    // Type: int
141    private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cellX";
142    // Type: int
143    private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cellY";
144    // Type: int
145    private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_spanX";
146    // Type: int
147    private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_spanY";
148    // Type: int
149    private static final String RUNTIME_STATE_PENDING_ADD_COUNT_X = "launcher.add_countX";
150    // Type: int
151    private static final String RUNTIME_STATE_PENDING_ADD_COUNT_Y = "launcher.add_countY";
152    // Type: int[]
153    private static final String RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS = "launcher.add_occupied_cells";
154    // Type: boolean
155    private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
156    // Type: long
157    private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
158
159    static final int APPWIDGET_HOST_ID = 1024;
160
161    private static final Object sLock = new Object();
162    private static int sScreen = DEFAULT_SCREEN;
163
164    private final BroadcastReceiver mCloseSystemDialogsReceiver
165            = new CloseSystemDialogsIntentReceiver();
166    private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
167
168    private LayoutInflater mInflater;
169
170    private DragController mDragController;
171    private Workspace mWorkspace;
172
173    private AppWidgetManager mAppWidgetManager;
174    private LauncherAppWidgetHost mAppWidgetHost;
175
176    private CellLayout.CellInfo mAddItemCellInfo;
177    private CellLayout.CellInfo mMenuAddInfo;
178    private final int[] mCellCoordinates = new int[2];
179    private FolderInfo mFolderInfo;
180
181    private DeleteZone mDeleteZone;
182    private HandleView mHandleView;
183    private AllAppsView mAllAppsGrid;
184
185    private Bundle mSavedState;
186
187    private SpannableStringBuilder mDefaultKeySsb = null;
188
189    private boolean mWorkspaceLoading = true;
190
191    private boolean mPaused = true;
192    private boolean mRestoring;
193    private boolean mWaitingForResult;
194
195    private Bundle mSavedInstanceState;
196
197    private LauncherModel mModel;
198    private IconCache mIconCache;
199
200    private ArrayList<ItemInfo> mDesktopItems = new ArrayList<ItemInfo>();
201    private static HashMap<Long, FolderInfo> mFolders = new HashMap<Long, FolderInfo>();
202
203    private ImageView mPreviousView;
204    private ImageView mNextView;
205
206    // Hotseats (quick-launch icons next to AllApps)
207    // TODO: move these intial intents out to Uris in an XML resource
208    private static final int NUM_HOTSEATS = 2;
209    private String[] mHotseatConfig = null;
210    private Intent[] mHotseats = null;
211    private Drawable[] mHotseatIcons = null;
212    private CharSequence[] mHotseatLabels = null;
213
214    @Override
215    protected void onCreate(Bundle savedInstanceState) {
216        super.onCreate(savedInstanceState);
217
218        LauncherApplication app = ((LauncherApplication)getApplication());
219        mModel = app.setLauncher(this);
220        mIconCache = app.getIconCache();
221        mDragController = new DragController(this);
222        mInflater = getLayoutInflater();
223
224        mAppWidgetManager = AppWidgetManager.getInstance(this);
225        mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
226        mAppWidgetHost.startListening();
227
228        if (PROFILE_STARTUP) {
229            android.os.Debug.startMethodTracing("/sdcard/launcher");
230        }
231
232        loadHotseats();
233        checkForLocaleChange();
234        setWallpaperDimension();
235
236        setContentView(R.layout.launcher);
237        setupViews();
238
239        registerContentObservers();
240
241        lockAllApps();
242
243        mSavedState = savedInstanceState;
244        restoreState(mSavedState);
245
246        if (PROFILE_STARTUP) {
247            android.os.Debug.stopMethodTracing();
248        }
249
250        // We have a new AllAppsView, we need to re-bind everything, and it could have
251        // changed in our absence.
252        mModel.setAllAppsDirty();
253        mModel.setWorkspaceDirty();
254
255        if (!mRestoring) {
256            mModel.startLoader(this, true);
257        }
258
259        // For handling default keys
260        mDefaultKeySsb = new SpannableStringBuilder();
261        Selection.setSelection(mDefaultKeySsb, 0);
262
263        IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
264        registerReceiver(mCloseSystemDialogsReceiver, filter);
265    }
266
267    private void checkForLocaleChange() {
268        final LocaleConfiguration localeConfiguration = new LocaleConfiguration();
269        readConfiguration(this, localeConfiguration);
270
271        final Configuration configuration = getResources().getConfiguration();
272
273        final String previousLocale = localeConfiguration.locale;
274        final String locale = configuration.locale.toString();
275
276        final int previousMcc = localeConfiguration.mcc;
277        final int mcc = configuration.mcc;
278
279        final int previousMnc = localeConfiguration.mnc;
280        final int mnc = configuration.mnc;
281
282        boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;
283
284        if (localeChanged) {
285            localeConfiguration.locale = locale;
286            localeConfiguration.mcc = mcc;
287            localeConfiguration.mnc = mnc;
288
289            writeConfiguration(this, localeConfiguration);
290            mIconCache.flush();
291
292            loadHotseats();
293        }
294    }
295
296    private static class LocaleConfiguration {
297        public String locale;
298        public int mcc = -1;
299        public int mnc = -1;
300    }
301
302    private static void readConfiguration(Context context, LocaleConfiguration configuration) {
303        DataInputStream in = null;
304        try {
305            in = new DataInputStream(context.openFileInput(PREFERENCES));
306            configuration.locale = in.readUTF();
307            configuration.mcc = in.readInt();
308            configuration.mnc = in.readInt();
309        } catch (FileNotFoundException e) {
310            // Ignore
311        } catch (IOException e) {
312            // Ignore
313        } finally {
314            if (in != null) {
315                try {
316                    in.close();
317                } catch (IOException e) {
318                    // Ignore
319                }
320            }
321        }
322    }
323
324    private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
325        DataOutputStream out = null;
326        try {
327            out = new DataOutputStream(context.openFileOutput(PREFERENCES, MODE_PRIVATE));
328            out.writeUTF(configuration.locale);
329            out.writeInt(configuration.mcc);
330            out.writeInt(configuration.mnc);
331            out.flush();
332        } catch (FileNotFoundException e) {
333            // Ignore
334        } catch (IOException e) {
335            //noinspection ResultOfMethodCallIgnored
336            context.getFileStreamPath(PREFERENCES).delete();
337        } finally {
338            if (out != null) {
339                try {
340                    out.close();
341                } catch (IOException e) {
342                    // Ignore
343                }
344            }
345        }
346    }
347
348    static int getScreen() {
349        synchronized (sLock) {
350            return sScreen;
351        }
352    }
353
354    static void setScreen(int screen) {
355        synchronized (sLock) {
356            sScreen = screen;
357        }
358    }
359
360    private void setWallpaperDimension() {
361        WallpaperManager wpm = (WallpaperManager)getSystemService(WALLPAPER_SERVICE);
362
363        Display display = getWindowManager().getDefaultDisplay();
364        boolean isPortrait = display.getWidth() < display.getHeight();
365
366        final int width = isPortrait ? display.getWidth() : display.getHeight();
367        final int height = isPortrait ? display.getHeight() : display.getWidth();
368        wpm.suggestDesiredDimensions(width * WALLPAPER_SCREENS_SPAN, height);
369    }
370
371    // Note: This doesn't do all the client-id magic that BrowserProvider does
372    // in Browser. (http://b/2425179)
373    private Uri getDefaultBrowserUri() {
374        String url = getString(R.string.default_browser_url);
375        if (url.indexOf("{CID}") != -1) {
376            url = url.replace("{CID}", "android-google");
377        }
378        return Uri.parse(url);
379    }
380
381    // Load the Intent templates from arrays.xml to populate the hotseats. For
382    // each Intent, if it resolves to a single app, use that as the launch
383    // intent & use that app's label as the contentDescription. Otherwise,
384    // retain the ResolveActivity so the user can pick an app.
385    private void loadHotseats() {
386        if (mHotseatConfig == null) {
387            mHotseatConfig = getResources().getStringArray(R.array.hotseats);
388            if (mHotseatConfig.length > 0) {
389                mHotseats = new Intent[mHotseatConfig.length];
390                mHotseatLabels = new CharSequence[mHotseatConfig.length];
391                mHotseatIcons = new Drawable[mHotseatConfig.length];
392            } else {
393                mHotseats = null;
394                mHotseatIcons = null;
395                mHotseatLabels = null;
396            }
397
398            TypedArray hotseatIconDrawables = getResources().obtainTypedArray(R.array.hotseat_icons);
399            for (int i=0; i<mHotseatConfig.length; i++) {
400                // load icon for this slot; currently unrelated to the actual activity
401                try {
402                    mHotseatIcons[i] = hotseatIconDrawables.getDrawable(i);
403                } catch (ArrayIndexOutOfBoundsException ex) {
404                    Log.w(TAG, "Missing hotseat_icons array item #" + i);
405                    mHotseatIcons[i] = null;
406                }
407            }
408            hotseatIconDrawables.recycle();
409        }
410
411        PackageManager pm = getPackageManager();
412        for (int i=0; i<mHotseatConfig.length; i++) {
413            Intent intent = null;
414            if (mHotseatConfig[i].equals("*BROWSER*")) {
415                // magic value meaning "launch user's default web browser"
416                // replace it with a generic web request so we can see if there is indeed a default
417                String defaultUri = getString(R.string.default_browser_url);
418                intent = new Intent(
419                        Intent.ACTION_VIEW,
420                        ((defaultUri != null)
421                            ? Uri.parse(defaultUri)
422                            : getDefaultBrowserUri())
423                    ).addCategory(Intent.CATEGORY_BROWSABLE);
424                // note: if the user launches this without a default set, she
425                // will always be taken to the default URL above; this is
426                // unavoidable as we must specify a valid URL in order for the
427                // chooser to appear, and once the user selects something, that
428                // URL is unavoidably sent to the chosen app.
429            } else {
430                try {
431                    intent = Intent.parseUri(mHotseatConfig[i], 0);
432                } catch (java.net.URISyntaxException ex) {
433                    Log.w(TAG, "Invalid hotseat intent: " + mHotseatConfig[i]);
434                    // bogus; leave intent=null
435                }
436            }
437
438            if (intent == null) {
439                mHotseats[i] = null;
440                mHotseatLabels[i] = getText(R.string.activity_not_found);
441                continue;
442            }
443
444            if (LOGD) {
445                Log.d(TAG, "loadHotseats: hotseat " + i
446                    + " initial intent=["
447                    + intent.toUri(Intent.URI_INTENT_SCHEME)
448                    + "]");
449            }
450
451            ResolveInfo bestMatch = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
452            List<ResolveInfo> allMatches = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
453            if (LOGD) {
454                Log.d(TAG, "Best match for intent: " + bestMatch);
455                Log.d(TAG, "All matches: ");
456                for (ResolveInfo ri : allMatches) {
457                    Log.d(TAG, "  --> " + ri);
458                }
459            }
460            // did this resolve to a single app, or the resolver?
461            if (allMatches.size() == 0 || bestMatch == null) {
462                // can't find any activity to handle this. let's leave the
463                // intent as-is and let Launcher show a toast when it fails
464                // to launch.
465                mHotseats[i] = intent;
466
467                // set accessibility text to "Not installed"
468                mHotseatLabels[i] = getText(R.string.activity_not_found);
469            } else {
470                boolean found = false;
471                for (ResolveInfo ri : allMatches) {
472                    if (bestMatch.activityInfo.name.equals(ri.activityInfo.name)
473                        && bestMatch.activityInfo.applicationInfo.packageName
474                            .equals(ri.activityInfo.applicationInfo.packageName)) {
475                        found = true;
476                        break;
477                    }
478                }
479
480                if (!found) {
481                    if (LOGD) Log.d(TAG, "Multiple options, no default yet");
482                    // the bestMatch is probably the ResolveActivity, meaning the
483                    // user has not yet selected a default
484                    // so: we'll keep the original intent for now
485                    mHotseats[i] = intent;
486
487                    // set the accessibility text to "Select shortcut"
488                    mHotseatLabels[i] = getText(R.string.title_select_shortcut);
489                } else {
490                    // we have an app!
491                    // now reconstruct the intent to launch it through the front
492                    // door
493                    ComponentName com = new ComponentName(
494                        bestMatch.activityInfo.applicationInfo.packageName,
495                        bestMatch.activityInfo.name);
496                    mHotseats[i] = new Intent(Intent.ACTION_MAIN).setComponent(com);
497
498                    // load the app label for accessibility
499                    mHotseatLabels[i] = bestMatch.activityInfo.loadLabel(pm);
500                }
501            }
502
503            if (LOGD) {
504                Log.d(TAG, "loadHotseats: hotseat " + i
505                    + " final intent=["
506                    + ((mHotseats[i] == null)
507                        ? "null"
508                        : mHotseats[i].toUri(Intent.URI_INTENT_SCHEME))
509                    + "] label=[" + mHotseatLabels[i]
510                    + "]"
511                    );
512            }
513        }
514    }
515
516    @Override
517    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
518        mWaitingForResult = false;
519
520        // The pattern used here is that a user PICKs a specific application,
521        // which, depending on the target, might need to CREATE the actual target.
522
523        // For example, the user would PICK_SHORTCUT for "Music playlist", and we
524        // launch over to the Music app to actually CREATE_SHORTCUT.
525
526        if (resultCode == RESULT_OK && mAddItemCellInfo != null) {
527            switch (requestCode) {
528                case REQUEST_PICK_APPLICATION:
529                    completeAddApplication(this, data, mAddItemCellInfo);
530                    break;
531                case REQUEST_PICK_SHORTCUT:
532                    processShortcut(data);
533                    break;
534                case REQUEST_CREATE_SHORTCUT:
535                    completeAddShortcut(data, mAddItemCellInfo);
536                    break;
537                case REQUEST_PICK_LIVE_FOLDER:
538                    addLiveFolder(data);
539                    break;
540                case REQUEST_CREATE_LIVE_FOLDER:
541                    completeAddLiveFolder(data, mAddItemCellInfo);
542                    break;
543                case REQUEST_PICK_APPWIDGET:
544                    addAppWidget(data);
545                    break;
546                case REQUEST_CREATE_APPWIDGET:
547                    completeAddAppWidget(data, mAddItemCellInfo);
548                    break;
549                case REQUEST_PICK_WALLPAPER:
550                    // We just wanted the activity result here so we can clear mWaitingForResult
551                    break;
552            }
553        } else if ((requestCode == REQUEST_PICK_APPWIDGET ||
554                requestCode == REQUEST_CREATE_APPWIDGET) && resultCode == RESULT_CANCELED &&
555                data != null) {
556            // Clean up the appWidgetId if we canceled
557            int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
558            if (appWidgetId != -1) {
559                mAppWidgetHost.deleteAppWidgetId(appWidgetId);
560            }
561        }
562    }
563
564    @Override
565    protected void onResume() {
566        super.onResume();
567
568        mPaused = false;
569
570        if (mRestoring) {
571            mWorkspaceLoading = true;
572            mModel.startLoader(this, true);
573            mRestoring = false;
574        }
575    }
576
577    @Override
578    protected void onPause() {
579        super.onPause();
580        dismissPreview(mPreviousView);
581        dismissPreview(mNextView);
582        mDragController.cancelDrag();
583    }
584
585    @Override
586    public Object onRetainNonConfigurationInstance() {
587        // Flag the loader to stop early before switching
588        mModel.stopLoader();
589        mAllAppsGrid.surrender();
590
591        if (PROFILE_ROTATE) {
592            android.os.Debug.startMethodTracing("/sdcard/launcher-rotate");
593        }
594        return Boolean.TRUE;
595    }
596
597    // We can't hide the IME if it was forced open.  So don't bother
598    /*
599    @Override
600    public void onWindowFocusChanged(boolean hasFocus) {
601        super.onWindowFocusChanged(hasFocus);
602
603        if (hasFocus) {
604            final InputMethodManager inputManager = (InputMethodManager)
605                    getSystemService(Context.INPUT_METHOD_SERVICE);
606            WindowManager.LayoutParams lp = getWindow().getAttributes();
607            inputManager.hideSoftInputFromWindow(lp.token, 0, new android.os.ResultReceiver(new
608                        android.os.Handler()) {
609                        protected void onReceiveResult(int resultCode, Bundle resultData) {
610                            Log.d(TAG, "ResultReceiver got resultCode=" + resultCode);
611                        }
612                    });
613            Log.d(TAG, "called hideSoftInputFromWindow from onWindowFocusChanged");
614        }
615    }
616    */
617
618    private boolean acceptFilter() {
619        final InputMethodManager inputManager = (InputMethodManager)
620                getSystemService(Context.INPUT_METHOD_SERVICE);
621        return !inputManager.isFullscreenMode();
622    }
623
624    @Override
625    public boolean onKeyDown(int keyCode, KeyEvent event) {
626        boolean handled = super.onKeyDown(keyCode, event);
627        if (!handled && acceptFilter() && keyCode != KeyEvent.KEYCODE_ENTER) {
628            boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
629                    keyCode, event);
630            if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
631                // something usable has been typed - start a search
632                // the typed text will be retrieved and cleared by
633                // showSearchDialog()
634                // If there are multiple keystrokes before the search dialog takes focus,
635                // onSearchRequested() will be called for every keystroke,
636                // but it is idempotent, so it's fine.
637                return onSearchRequested();
638            }
639        }
640
641        // Eat the long press event so the keyboard doesn't come up.
642        if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
643            return true;
644        }
645
646        return handled;
647    }
648
649    private String getTypedText() {
650        return mDefaultKeySsb.toString();
651    }
652
653    private void clearTypedText() {
654        mDefaultKeySsb.clear();
655        mDefaultKeySsb.clearSpans();
656        Selection.setSelection(mDefaultKeySsb, 0);
657    }
658
659    /**
660     * Restores the previous state, if it exists.
661     *
662     * @param savedState The previous state.
663     */
664    private void restoreState(Bundle savedState) {
665        if (savedState == null) {
666            return;
667        }
668
669        final boolean allApps = savedState.getBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, false);
670        if (allApps) {
671            showAllApps(false);
672        }
673
674        final int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
675        if (currentScreen > -1) {
676            mWorkspace.setCurrentScreen(currentScreen);
677        }
678
679        final int addScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
680        if (addScreen > -1) {
681            mAddItemCellInfo = new CellLayout.CellInfo();
682            final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo;
683            addItemCellInfo.valid = true;
684            addItemCellInfo.screen = addScreen;
685            addItemCellInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
686            addItemCellInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
687            addItemCellInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
688            addItemCellInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
689            addItemCellInfo.findVacantCellsFromOccupied(
690                    savedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS),
691                    savedState.getInt(RUNTIME_STATE_PENDING_ADD_COUNT_X),
692                    savedState.getInt(RUNTIME_STATE_PENDING_ADD_COUNT_Y));
693            mRestoring = true;
694        }
695
696        boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
697        if (renameFolder) {
698            long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
699            mFolderInfo = mModel.getFolderById(this, mFolders, id);
700            mRestoring = true;
701        }
702    }
703
704    /**
705     * Finds all the views we need and configure them properly.
706     */
707    private void setupViews() {
708        DragController dragController = mDragController;
709
710        DragLayer dragLayer = (DragLayer) findViewById(R.id.drag_layer);
711        dragLayer.setDragController(dragController);
712
713        mAllAppsGrid = (AllAppsView)dragLayer.findViewById(R.id.all_apps_view);
714        mAllAppsGrid.setLauncher(this);
715        mAllAppsGrid.setDragController(dragController);
716        ((View) mAllAppsGrid).setWillNotDraw(false); // We don't want a hole punched in our window.
717        // Manage focusability manually since this thing is always visible
718        ((View) mAllAppsGrid).setFocusable(false);
719
720        mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace);
721        final Workspace workspace = mWorkspace;
722        workspace.setHapticFeedbackEnabled(false);
723
724        DeleteZone deleteZone = (DeleteZone) dragLayer.findViewById(R.id.delete_zone);
725        mDeleteZone = deleteZone;
726
727        mHandleView = (HandleView) findViewById(R.id.all_apps_button);
728        mHandleView.setLauncher(this);
729        mHandleView.setOnClickListener(this);
730        mHandleView.setOnLongClickListener(this);
731
732        ImageView hotseatLeft = (ImageView) findViewById(R.id.hotseat_left);
733        hotseatLeft.setContentDescription(mHotseatLabels[0]);
734        hotseatLeft.setImageDrawable(mHotseatIcons[0]);
735        ImageView hotseatRight = (ImageView) findViewById(R.id.hotseat_right);
736        hotseatRight.setContentDescription(mHotseatLabels[1]);
737        hotseatRight.setImageDrawable(mHotseatIcons[1]);
738
739        mPreviousView = (ImageView) dragLayer.findViewById(R.id.previous_screen);
740        mNextView = (ImageView) dragLayer.findViewById(R.id.next_screen);
741
742        Drawable previous = mPreviousView.getDrawable();
743        Drawable next = mNextView.getDrawable();
744        mWorkspace.setIndicators(previous, next);
745
746        mPreviousView.setHapticFeedbackEnabled(false);
747        mPreviousView.setOnLongClickListener(this);
748        mNextView.setHapticFeedbackEnabled(false);
749        mNextView.setOnLongClickListener(this);
750
751        workspace.setOnLongClickListener(this);
752        workspace.setDragController(dragController);
753        workspace.setLauncher(this);
754
755        deleteZone.setLauncher(this);
756        deleteZone.setDragController(dragController);
757        deleteZone.setHandle(findViewById(R.id.all_apps_button_cluster));
758
759        dragController.setDragScoller(workspace);
760        dragController.setDragListener(deleteZone);
761        dragController.setScrollView(dragLayer);
762        dragController.setMoveTarget(workspace);
763
764        // The order here is bottom to top.
765        dragController.addDropTarget(workspace);
766        dragController.addDropTarget(deleteZone);
767    }
768
769    @SuppressWarnings({"UnusedDeclaration"})
770    public void previousScreen(View v) {
771        if (!isAllAppsVisible()) {
772            mWorkspace.scrollLeft();
773        }
774    }
775
776    @SuppressWarnings({"UnusedDeclaration"})
777    public void nextScreen(View v) {
778        if (!isAllAppsVisible()) {
779            mWorkspace.scrollRight();
780        }
781    }
782
783    @SuppressWarnings({"UnusedDeclaration"})
784    public void launchHotSeat(View v) {
785        int index = -1;
786        if (v.getId() == R.id.hotseat_left) {
787            index = 0;
788        } else if (v.getId() == R.id.hotseat_right) {
789            index = 1;
790        }
791
792        // reload these every tap; you never know when they might change
793        loadHotseats();
794        if (index >= 0 && index < mHotseats.length && mHotseats[index] != null) {
795            Intent intent = mHotseats[index];
796            startActivitySafely(
797                mHotseats[index],
798                "hotseat"
799            );
800        }
801    }
802
803    /**
804     * Creates a view representing a shortcut.
805     *
806     * @param info The data structure describing the shortcut.
807     *
808     * @return A View inflated from R.layout.application.
809     */
810    View createShortcut(ShortcutInfo info) {
811        return createShortcut(R.layout.application,
812                (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info);
813    }
814
815    /**
816     * Creates a view representing a shortcut inflated from the specified resource.
817     *
818     * @param layoutResId The id of the XML layout used to create the shortcut.
819     * @param parent The group the shortcut belongs to.
820     * @param info The data structure describing the shortcut.
821     *
822     * @return A View inflated from layoutResId.
823     */
824    View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
825        TextView favorite = (TextView) mInflater.inflate(layoutResId, parent, false);
826
827        favorite.setCompoundDrawablesWithIntrinsicBounds(null,
828                new FastBitmapDrawable(info.getIcon(mIconCache)),
829                null, null);
830        favorite.setText(info.title);
831        favorite.setTag(info);
832        favorite.setOnClickListener(this);
833
834        return favorite;
835    }
836
837    /**
838     * Add an application shortcut to the workspace.
839     *
840     * @param data The intent describing the application.
841     * @param cellInfo The position on screen where to create the shortcut.
842     */
843    void completeAddApplication(Context context, Intent data, CellLayout.CellInfo cellInfo) {
844        cellInfo.screen = mWorkspace.getCurrentScreen();
845        if (!findSingleSlot(cellInfo)) return;
846
847        final ShortcutInfo info = mModel.getShortcutInfo(context.getPackageManager(),
848                data, context);
849
850        if (info != null) {
851            info.setActivity(data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK |
852                    Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
853            info.container = ItemInfo.NO_ID;
854            mWorkspace.addApplicationShortcut(info, cellInfo, isWorkspaceLocked());
855        } else {
856            Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data);
857        }
858    }
859
860    /**
861     * Add a shortcut to the workspace.
862     *
863     * @param data The intent describing the shortcut.
864     * @param cellInfo The position on screen where to create the shortcut.
865     */
866    private void completeAddShortcut(Intent data, CellLayout.CellInfo cellInfo) {
867        cellInfo.screen = mWorkspace.getCurrentScreen();
868        if (!findSingleSlot(cellInfo)) return;
869
870        final ShortcutInfo info = mModel.addShortcut(this, data, cellInfo, false);
871
872        if (!mRestoring) {
873            final View view = createShortcut(info);
874            mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1,
875                    isWorkspaceLocked());
876        }
877    }
878
879
880    /**
881     * Add a widget to the workspace.
882     *
883     * @param data The intent describing the appWidgetId.
884     * @param cellInfo The position on screen where to create the widget.
885     */
886    private void completeAddAppWidget(Intent data, CellLayout.CellInfo cellInfo) {
887        Bundle extras = data.getExtras();
888        int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
889
890        if (LOGD) Log.d(TAG, "dumping extras content=" + extras.toString());
891
892        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
893
894        // Calculate the grid spans needed to fit this widget
895        CellLayout layout = (CellLayout) mWorkspace.getChildAt(cellInfo.screen);
896        int[] spans = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight);
897
898        // Try finding open space on Launcher screen
899        final int[] xy = mCellCoordinates;
900        if (!findSlot(cellInfo, xy, spans[0], spans[1])) {
901            if (appWidgetId != -1) mAppWidgetHost.deleteAppWidgetId(appWidgetId);
902            return;
903        }
904
905        // Build Launcher-specific widget info and save to database
906        LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId);
907        launcherInfo.spanX = spans[0];
908        launcherInfo.spanY = spans[1];
909
910        LauncherModel.addItemToDatabase(this, launcherInfo,
911                LauncherSettings.Favorites.CONTAINER_DESKTOP,
912                mWorkspace.getCurrentScreen(), xy[0], xy[1], false);
913
914        if (!mRestoring) {
915            mDesktopItems.add(launcherInfo);
916
917            // Perform actual inflation because we're live
918            launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
919
920            launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
921            launcherInfo.hostView.setTag(launcherInfo);
922
923            mWorkspace.addInCurrentScreen(launcherInfo.hostView, xy[0], xy[1],
924                    launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
925        }
926    }
927
928    public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
929        mDesktopItems.remove(launcherInfo);
930        launcherInfo.hostView = null;
931    }
932
933    public LauncherAppWidgetHost getAppWidgetHost() {
934        return mAppWidgetHost;
935    }
936
937    void closeSystemDialogs() {
938        getWindow().closeAllPanels();
939
940        try {
941            dismissDialog(DIALOG_CREATE_SHORTCUT);
942            // Unlock the workspace if the dialog was showing
943        } catch (Exception e) {
944            // An exception is thrown if the dialog is not visible, which is fine
945        }
946
947        try {
948            dismissDialog(DIALOG_RENAME_FOLDER);
949            // Unlock the workspace if the dialog was showing
950        } catch (Exception e) {
951            // An exception is thrown if the dialog is not visible, which is fine
952        }
953
954        // Whatever we were doing is hereby canceled.
955        mWaitingForResult = false;
956    }
957
958    @Override
959    protected void onNewIntent(Intent intent) {
960        super.onNewIntent(intent);
961
962        // Close the menu
963        if (Intent.ACTION_MAIN.equals(intent.getAction())) {
964            // also will cancel mWaitingForResult.
965            closeSystemDialogs();
966
967            boolean alreadyOnHome = ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
968                        != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
969            boolean allAppsVisible = isAllAppsVisible();
970            if (!mWorkspace.isDefaultScreenShowing()) {
971                mWorkspace.moveToDefaultScreen(alreadyOnHome && !allAppsVisible);
972            }
973            closeAllApps(alreadyOnHome && allAppsVisible);
974
975            final View v = getWindow().peekDecorView();
976            if (v != null && v.getWindowToken() != null) {
977                InputMethodManager imm = (InputMethodManager)getSystemService(
978                        INPUT_METHOD_SERVICE);
979                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
980            }
981        }
982    }
983
984    @Override
985    protected void onRestoreInstanceState(Bundle savedInstanceState) {
986        // Do not call super here
987        mSavedInstanceState = savedInstanceState;
988    }
989
990    @Override
991    protected void onSaveInstanceState(Bundle outState) {
992        outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentScreen());
993
994        final ArrayList<Folder> folders = mWorkspace.getOpenFolders();
995        if (folders.size() > 0) {
996            final int count = folders.size();
997            long[] ids = new long[count];
998            for (int i = 0; i < count; i++) {
999                final FolderInfo info = folders.get(i).getInfo();
1000                ids[i] = info.id;
1001            }
1002            outState.putLongArray(RUNTIME_STATE_USER_FOLDERS, ids);
1003        } else {
1004            super.onSaveInstanceState(outState);
1005        }
1006
1007        // TODO should not do this if the drawer is currently closing.
1008        if (isAllAppsVisible()) {
1009            outState.putBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, true);
1010        }
1011
1012        if (mAddItemCellInfo != null && mAddItemCellInfo.valid && mWaitingForResult) {
1013            final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo;
1014            final CellLayout layout = (CellLayout) mWorkspace.getChildAt(addItemCellInfo.screen);
1015
1016            outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, addItemCellInfo.screen);
1017            outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, addItemCellInfo.cellX);
1018            outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, addItemCellInfo.cellY);
1019            outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, addItemCellInfo.spanX);
1020            outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, addItemCellInfo.spanY);
1021            outState.putInt(RUNTIME_STATE_PENDING_ADD_COUNT_X, layout.getCountX());
1022            outState.putInt(RUNTIME_STATE_PENDING_ADD_COUNT_Y, layout.getCountY());
1023            outState.putBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS,
1024                   layout.getOccupiedCells());
1025        }
1026
1027        if (mFolderInfo != null && mWaitingForResult) {
1028            outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
1029            outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
1030        }
1031    }
1032
1033    @Override
1034    public void onDestroy() {
1035        super.onDestroy();
1036
1037        try {
1038            mAppWidgetHost.stopListening();
1039        } catch (NullPointerException ex) {
1040            Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
1041        }
1042
1043        TextKeyListener.getInstance().release();
1044
1045        mModel.stopLoader();
1046
1047        unbindDesktopItems();
1048
1049        getContentResolver().unregisterContentObserver(mWidgetObserver);
1050
1051        dismissPreview(mPreviousView);
1052        dismissPreview(mNextView);
1053
1054        unregisterReceiver(mCloseSystemDialogsReceiver);
1055    }
1056
1057    @Override
1058    public void startActivityForResult(Intent intent, int requestCode) {
1059        if (requestCode >= 0) mWaitingForResult = true;
1060        super.startActivityForResult(intent, requestCode);
1061    }
1062
1063    @Override
1064    public void startSearch(String initialQuery, boolean selectInitialQuery,
1065            Bundle appSearchData, boolean globalSearch) {
1066
1067        closeAllApps(true);
1068
1069        if (initialQuery == null) {
1070            // Use any text typed in the launcher as the initial query
1071            initialQuery = getTypedText();
1072            clearTypedText();
1073        }
1074        if (appSearchData == null) {
1075            appSearchData = new Bundle();
1076            appSearchData.putString(Search.SOURCE, "launcher-search");
1077        }
1078
1079        final SearchManager searchManager =
1080                (SearchManager) getSystemService(Context.SEARCH_SERVICE);
1081        searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
1082            appSearchData, globalSearch);
1083    }
1084
1085    @Override
1086    public boolean onCreateOptionsMenu(Menu menu) {
1087        if (isWorkspaceLocked()) {
1088            return false;
1089        }
1090
1091        super.onCreateOptionsMenu(menu);
1092
1093        menu.add(MENU_GROUP_ADD, MENU_ADD, 0, R.string.menu_add)
1094                .setIcon(android.R.drawable.ic_menu_add)
1095                .setAlphabeticShortcut('A');
1096        menu.add(MENU_GROUP_WALLPAPER, MENU_WALLPAPER_SETTINGS, 0, R.string.menu_wallpaper)
1097                 .setIcon(android.R.drawable.ic_menu_gallery)
1098                 .setAlphabeticShortcut('W');
1099        menu.add(0, MENU_SEARCH, 0, R.string.menu_search)
1100                .setIcon(android.R.drawable.ic_search_category_default)
1101                .setAlphabeticShortcut(SearchManager.MENU_KEY);
1102        menu.add(0, MENU_NOTIFICATIONS, 0, R.string.menu_notifications)
1103                .setIcon(com.android.internal.R.drawable.ic_menu_notifications)
1104                .setAlphabeticShortcut('N');
1105
1106        final Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS);
1107        settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1108                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1109
1110        menu.add(0, MENU_SETTINGS, 0, R.string.menu_settings)
1111                .setIcon(android.R.drawable.ic_menu_preferences).setAlphabeticShortcut('P')
1112                .setIntent(settings);
1113
1114        return true;
1115    }
1116
1117    @Override
1118    public boolean onPrepareOptionsMenu(Menu menu) {
1119        super.onPrepareOptionsMenu(menu);
1120
1121        // If all apps is animating, don't show the menu, because we don't know
1122        // which one to show.
1123        if (mAllAppsGrid.isVisible() && !mAllAppsGrid.isOpaque()) {
1124            return false;
1125        }
1126
1127        // Only show the add and wallpaper options when we're not in all apps.
1128        boolean visible = !mAllAppsGrid.isOpaque();
1129        menu.setGroupVisible(MENU_GROUP_ADD, visible);
1130        menu.setGroupVisible(MENU_GROUP_WALLPAPER, visible);
1131
1132        // Disable add if the workspace is full.
1133        if (visible) {
1134            mMenuAddInfo = mWorkspace.findAllVacantCells(null);
1135            menu.setGroupEnabled(MENU_GROUP_ADD, mMenuAddInfo != null && mMenuAddInfo.valid);
1136        }
1137
1138        return true;
1139    }
1140
1141    @Override
1142    public boolean onOptionsItemSelected(MenuItem item) {
1143        switch (item.getItemId()) {
1144            case MENU_ADD:
1145                addItems();
1146                return true;
1147            case MENU_WALLPAPER_SETTINGS:
1148                startWallpaper();
1149                return true;
1150            case MENU_SEARCH:
1151                onSearchRequested();
1152                return true;
1153            case MENU_NOTIFICATIONS:
1154                showNotifications();
1155                return true;
1156        }
1157
1158        return super.onOptionsItemSelected(item);
1159    }
1160
1161    /**
1162     * Indicates that we want global search for this activity by setting the globalSearch
1163     * argument for {@link #startSearch} to true.
1164     */
1165
1166    @Override
1167    public boolean onSearchRequested() {
1168        startSearch(null, false, null, true);
1169        return true;
1170    }
1171
1172    public boolean isWorkspaceLocked() {
1173        return mWorkspaceLoading || mWaitingForResult;
1174    }
1175
1176    private void addItems() {
1177        closeAllApps(true);
1178        showAddDialog(mMenuAddInfo);
1179    }
1180
1181    void addAppWidget(Intent data) {
1182        // TODO: catch bad widget exception when sent
1183        int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
1184        AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
1185
1186        if (appWidget.configure != null) {
1187            // Launch over to configure widget, if needed
1188            Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
1189            intent.setComponent(appWidget.configure);
1190            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1191
1192            startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
1193        } else {
1194            // Otherwise just add it
1195            onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data);
1196        }
1197    }
1198
1199    void processShortcut(Intent intent) {
1200        // Handle case where user selected "Applications"
1201        String applicationName = getResources().getString(R.string.group_applications);
1202        String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
1203
1204        if (applicationName != null && applicationName.equals(shortcutName)) {
1205            Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
1206            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
1207
1208            Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
1209            pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
1210            startActivityForResult(pickIntent, REQUEST_PICK_APPLICATION);
1211        } else {
1212            startActivityForResult(intent, REQUEST_CREATE_SHORTCUT);
1213        }
1214    }
1215
1216    void addLiveFolder(Intent intent) {
1217        // Handle case where user selected "Folder"
1218        String folderName = getResources().getString(R.string.group_folder);
1219        String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
1220
1221        if (folderName != null && folderName.equals(shortcutName)) {
1222            addFolder();
1223        } else {
1224            startActivityForResult(intent, REQUEST_CREATE_LIVE_FOLDER);
1225        }
1226    }
1227
1228    void addFolder() {
1229        UserFolderInfo folderInfo = new UserFolderInfo();
1230        folderInfo.title = getText(R.string.folder_name);
1231
1232        CellLayout.CellInfo cellInfo = mAddItemCellInfo;
1233        cellInfo.screen = mWorkspace.getCurrentScreen();
1234        if (!findSingleSlot(cellInfo)) return;
1235
1236        // Update the model
1237        LauncherModel.addItemToDatabase(this, folderInfo,
1238                LauncherSettings.Favorites.CONTAINER_DESKTOP,
1239                mWorkspace.getCurrentScreen(), cellInfo.cellX, cellInfo.cellY, false);
1240        mFolders.put(folderInfo.id, folderInfo);
1241
1242        // Create the view
1243        FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
1244                (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), folderInfo);
1245        mWorkspace.addInCurrentScreen(newFolder,
1246                cellInfo.cellX, cellInfo.cellY, 1, 1, isWorkspaceLocked());
1247    }
1248
1249    void removeFolder(FolderInfo folder) {
1250        mFolders.remove(folder.id);
1251    }
1252
1253    private void completeAddLiveFolder(Intent data, CellLayout.CellInfo cellInfo) {
1254        cellInfo.screen = mWorkspace.getCurrentScreen();
1255        if (!findSingleSlot(cellInfo)) return;
1256
1257        final LiveFolderInfo info = addLiveFolder(this, data, cellInfo, false);
1258
1259        if (!mRestoring) {
1260            final View view = LiveFolderIcon.fromXml(R.layout.live_folder_icon, this,
1261                (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info);
1262            mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1,
1263                    isWorkspaceLocked());
1264        }
1265    }
1266
1267    static LiveFolderInfo addLiveFolder(Context context, Intent data,
1268            CellLayout.CellInfo cellInfo, boolean notify) {
1269
1270        Intent baseIntent = data.getParcelableExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT);
1271        String name = data.getStringExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME);
1272
1273        Drawable icon = null;
1274        Intent.ShortcutIconResource iconResource = null;
1275
1276        Parcelable extra = data.getParcelableExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON);
1277        if (extra != null && extra instanceof Intent.ShortcutIconResource) {
1278            try {
1279                iconResource = (Intent.ShortcutIconResource) extra;
1280                final PackageManager packageManager = context.getPackageManager();
1281                Resources resources = packageManager.getResourcesForApplication(
1282                        iconResource.packageName);
1283                final int id = resources.getIdentifier(iconResource.resourceName, null, null);
1284                icon = resources.getDrawable(id);
1285            } catch (Exception e) {
1286                Log.w(TAG, "Could not load live folder icon: " + extra);
1287            }
1288        }
1289
1290        if (icon == null) {
1291            icon = context.getResources().getDrawable(R.drawable.ic_launcher_folder);
1292        }
1293
1294        final LiveFolderInfo info = new LiveFolderInfo();
1295        info.icon = Utilities.createIconBitmap(icon, context);
1296        info.title = name;
1297        info.iconResource = iconResource;
1298        info.uri = data.getData();
1299        info.baseIntent = baseIntent;
1300        info.displayMode = data.getIntExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE,
1301                LiveFolders.DISPLAY_MODE_GRID);
1302
1303        LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
1304                cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
1305        mFolders.put(info.id, info);
1306
1307        return info;
1308    }
1309
1310    private boolean findSingleSlot(CellLayout.CellInfo cellInfo) {
1311        final int[] xy = new int[2];
1312        if (findSlot(cellInfo, xy, 1, 1)) {
1313            cellInfo.cellX = xy[0];
1314            cellInfo.cellY = xy[1];
1315            return true;
1316        }
1317        return false;
1318    }
1319
1320    private boolean findSlot(CellLayout.CellInfo cellInfo, int[] xy, int spanX, int spanY) {
1321        if (!cellInfo.findCellForSpan(xy, spanX, spanY)) {
1322            boolean[] occupied = mSavedState != null ?
1323                    mSavedState.getBooleanArray(RUNTIME_STATE_PENDING_ADD_OCCUPIED_CELLS) : null;
1324            cellInfo = mWorkspace.findAllVacantCells(occupied);
1325            if (!cellInfo.findCellForSpan(xy, spanX, spanY)) {
1326                Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
1327                return false;
1328            }
1329        }
1330        return true;
1331    }
1332
1333    private void showNotifications() {
1334        final StatusBarManager statusBar = (StatusBarManager) getSystemService(STATUS_BAR_SERVICE);
1335        if (statusBar != null) {
1336            statusBar.expand();
1337        }
1338    }
1339
1340    private void startWallpaper() {
1341        closeAllApps(true);
1342        final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
1343        Intent chooser = Intent.createChooser(pickWallpaper,
1344                getText(R.string.chooser_wallpaper));
1345        // NOTE: Adds a configure option to the chooser if the wallpaper supports it
1346        //       Removed in Eclair MR1
1347//        WallpaperManager wm = (WallpaperManager)
1348//                getSystemService(Context.WALLPAPER_SERVICE);
1349//        WallpaperInfo wi = wm.getWallpaperInfo();
1350//        if (wi != null && wi.getSettingsActivity() != null) {
1351//            LabeledIntent li = new LabeledIntent(getPackageName(),
1352//                    R.string.configure_wallpaper, 0);
1353//            li.setClassName(wi.getPackageName(), wi.getSettingsActivity());
1354//            chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { li });
1355//        }
1356        startActivityForResult(chooser, REQUEST_PICK_WALLPAPER);
1357    }
1358
1359    /**
1360     * Registers various content observers. The current implementation registers
1361     * only a favorites observer to keep track of the favorites applications.
1362     */
1363    private void registerContentObservers() {
1364        ContentResolver resolver = getContentResolver();
1365        resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
1366                true, mWidgetObserver);
1367    }
1368
1369    @Override
1370    public boolean dispatchKeyEvent(KeyEvent event) {
1371        if (event.getAction() == KeyEvent.ACTION_DOWN) {
1372            switch (event.getKeyCode()) {
1373                case KeyEvent.KEYCODE_HOME:
1374                    return true;
1375                case KeyEvent.KEYCODE_VOLUME_DOWN:
1376                    if (SystemProperties.getInt("debug.launcher2.dumpstate", 0) != 0) {
1377                        dumpState();
1378                        return true;
1379                    }
1380                    break;
1381            }
1382        } else if (event.getAction() == KeyEvent.ACTION_UP) {
1383            switch (event.getKeyCode()) {
1384                case KeyEvent.KEYCODE_HOME:
1385                    return true;
1386            }
1387        }
1388
1389        return super.dispatchKeyEvent(event);
1390    }
1391
1392    @Override
1393    public void onBackPressed() {
1394        if (isAllAppsVisible()) {
1395            closeAllApps(true);
1396        } else {
1397            closeFolder();
1398        }
1399        dismissPreview(mPreviousView);
1400        dismissPreview(mNextView);
1401    }
1402
1403    private void closeFolder() {
1404        Folder folder = mWorkspace.getOpenFolder();
1405        if (folder != null) {
1406            closeFolder(folder);
1407        }
1408    }
1409
1410    void closeFolder(Folder folder) {
1411        folder.getInfo().opened = false;
1412        ViewGroup parent = (ViewGroup) folder.getParent();
1413        if (parent != null) {
1414            parent.removeView(folder);
1415            if (folder instanceof DropTarget) {
1416                // Live folders aren't DropTargets.
1417                mDragController.removeDropTarget((DropTarget)folder);
1418            }
1419        }
1420        folder.onClose();
1421    }
1422
1423    /**
1424     * Re-listen when widgets are reset.
1425     */
1426    private void onAppWidgetReset() {
1427        mAppWidgetHost.startListening();
1428    }
1429
1430    /**
1431     * Go through the and disconnect any of the callbacks in the drawables and the views or we
1432     * leak the previous Home screen on orientation change.
1433     */
1434    private void unbindDesktopItems() {
1435        for (ItemInfo item: mDesktopItems) {
1436            item.unbind();
1437        }
1438    }
1439
1440    /**
1441     * Launches the intent referred by the clicked shortcut.
1442     *
1443     * @param v The view representing the clicked shortcut.
1444     */
1445    public void onClick(View v) {
1446        Object tag = v.getTag();
1447        if (tag instanceof ShortcutInfo) {
1448            // Open shortcut
1449            final Intent intent = ((ShortcutInfo) tag).intent;
1450            int[] pos = new int[2];
1451            v.getLocationOnScreen(pos);
1452            intent.setSourceBounds(new Rect(pos[0], pos[1],
1453                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
1454            startActivitySafely(intent, tag);
1455        } else if (tag instanceof FolderInfo) {
1456            handleFolderClick((FolderInfo) tag);
1457        } else if (v == mHandleView) {
1458            if (isAllAppsVisible()) {
1459                closeAllApps(true);
1460            } else {
1461                showAllApps(true);
1462            }
1463        }
1464    }
1465
1466    void startActivitySafely(Intent intent, Object tag) {
1467        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1468        try {
1469            startActivity(intent);
1470        } catch (ActivityNotFoundException e) {
1471            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
1472            Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
1473        } catch (SecurityException e) {
1474            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
1475            Log.e(TAG, "Launcher does not have the permission to launch " + intent +
1476                    ". Make sure to create a MAIN intent-filter for the corresponding activity " +
1477                    "or use the exported attribute for this activity. "
1478                    + "tag="+ tag + " intent=" + intent, e);
1479        }
1480    }
1481
1482    void startActivityForResultSafely(Intent intent, int requestCode) {
1483        try {
1484            startActivityForResult(intent, requestCode);
1485        } catch (ActivityNotFoundException e) {
1486            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
1487        } catch (SecurityException e) {
1488            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
1489            Log.e(TAG, "Launcher does not have the permission to launch " + intent +
1490                    ". Make sure to create a MAIN intent-filter for the corresponding activity " +
1491                    "or use the exported attribute for this activity.", e);
1492        }
1493    }
1494
1495    private void handleFolderClick(FolderInfo folderInfo) {
1496        if (!folderInfo.opened) {
1497            // Close any open folder
1498            closeFolder();
1499            // Open the requested folder
1500            openFolder(folderInfo);
1501        } else {
1502            // Find the open folder...
1503            Folder openFolder = mWorkspace.getFolderForTag(folderInfo);
1504            int folderScreen;
1505            if (openFolder != null) {
1506                folderScreen = mWorkspace.getScreenForView(openFolder);
1507                // .. and close it
1508                closeFolder(openFolder);
1509                if (folderScreen != mWorkspace.getCurrentScreen()) {
1510                    // Close any folder open on the current screen
1511                    closeFolder();
1512                    // Pull the folder onto this screen
1513                    openFolder(folderInfo);
1514                }
1515            }
1516        }
1517    }
1518
1519    /**
1520     * Opens the user fodler described by the specified tag. The opening of the folder
1521     * is animated relative to the specified View. If the View is null, no animation
1522     * is played.
1523     *
1524     * @param folderInfo The FolderInfo describing the folder to open.
1525     */
1526    private void openFolder(FolderInfo folderInfo) {
1527        Folder openFolder;
1528
1529        if (folderInfo instanceof UserFolderInfo) {
1530            openFolder = UserFolder.fromXml(this);
1531        } else if (folderInfo instanceof LiveFolderInfo) {
1532            openFolder = com.android.launcher2.LiveFolder.fromXml(this, folderInfo);
1533        } else {
1534            return;
1535        }
1536
1537        openFolder.setDragController(mDragController);
1538        openFolder.setLauncher(this);
1539
1540        openFolder.bind(folderInfo);
1541        folderInfo.opened = true;
1542
1543        mWorkspace.addInScreen(openFolder, folderInfo.screen, 0, 0, 4, 4);
1544        openFolder.onOpen();
1545    }
1546
1547    public boolean onLongClick(View v) {
1548        switch (v.getId()) {
1549            case R.id.previous_screen:
1550                if (!isAllAppsVisible()) {
1551                    mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
1552                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
1553                    showPreviews(v);
1554                }
1555                return true;
1556            case R.id.next_screen:
1557                if (!isAllAppsVisible()) {
1558                    mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
1559                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
1560                    showPreviews(v);
1561                }
1562                return true;
1563            case R.id.all_apps_button:
1564                if (!isAllAppsVisible()) {
1565                    mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
1566                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
1567                    showPreviews(v);
1568                }
1569                return true;
1570        }
1571
1572        if (isWorkspaceLocked()) {
1573            return false;
1574        }
1575
1576        if (!(v instanceof CellLayout)) {
1577            v = (View) v.getParent();
1578        }
1579
1580        CellLayout.CellInfo cellInfo = (CellLayout.CellInfo) v.getTag();
1581
1582        // This happens when long clicking an item with the dpad/trackball
1583        if (cellInfo == null) {
1584            return true;
1585        }
1586
1587        if (mWorkspace.allowLongPress()) {
1588            if (cellInfo.cell == null) {
1589                if (cellInfo.valid) {
1590                    // User long pressed on empty space
1591                    mWorkspace.setAllowLongPress(false);
1592                    mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
1593                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
1594                    showAddDialog(cellInfo);
1595                }
1596            } else {
1597                if (!(cellInfo.cell instanceof Folder)) {
1598                    // User long pressed on an item
1599                    mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
1600                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
1601                    mWorkspace.startDrag(cellInfo);
1602                }
1603            }
1604        }
1605        return true;
1606    }
1607
1608    @SuppressWarnings({"unchecked"})
1609    private void dismissPreview(final View v) {
1610        final PopupWindow window = (PopupWindow) v.getTag();
1611        if (window != null) {
1612            window.setOnDismissListener(new PopupWindow.OnDismissListener() {
1613                public void onDismiss() {
1614                    ViewGroup group = (ViewGroup) v.getTag(R.id.workspace);
1615                    int count = group.getChildCount();
1616                    for (int i = 0; i < count; i++) {
1617                        ((ImageView) group.getChildAt(i)).setImageDrawable(null);
1618                    }
1619                    ArrayList<Bitmap> bitmaps = (ArrayList<Bitmap>) v.getTag(R.id.icon);
1620                    for (Bitmap bitmap : bitmaps) bitmap.recycle();
1621
1622                    v.setTag(R.id.workspace, null);
1623                    v.setTag(R.id.icon, null);
1624                    window.setOnDismissListener(null);
1625                }
1626            });
1627            window.dismiss();
1628        }
1629        v.setTag(null);
1630    }
1631
1632    private void showPreviews(View anchor) {
1633        showPreviews(anchor, 0, mWorkspace.getChildCount());
1634    }
1635
1636    private void showPreviews(final View anchor, int start, int end) {
1637        final Resources resources = getResources();
1638        final Workspace workspace = mWorkspace;
1639
1640        CellLayout cell = ((CellLayout) workspace.getChildAt(start));
1641
1642        float max = workspace.getChildCount();
1643
1644        final Rect r = new Rect();
1645        resources.getDrawable(R.drawable.preview_background).getPadding(r);
1646        int extraW = (int) ((r.left + r.right) * max);
1647        int extraH = r.top + r.bottom;
1648
1649        int aW = cell.getWidth() - extraW;
1650        float w = aW / max;
1651
1652        int width = cell.getWidth();
1653        int height = cell.getHeight();
1654        int x = cell.getLeftPadding();
1655        int y = cell.getTopPadding();
1656        width -= (x + cell.getRightPadding());
1657        height -= (y + cell.getBottomPadding());
1658
1659        float scale = w / width;
1660
1661        int count = end - start;
1662
1663        final float sWidth = width * scale;
1664        float sHeight = height * scale;
1665
1666        LinearLayout preview = new LinearLayout(this);
1667
1668        PreviewTouchHandler handler = new PreviewTouchHandler(anchor);
1669        ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>(count);
1670
1671        for (int i = start; i < end; i++) {
1672            ImageView image = new ImageView(this);
1673            cell = (CellLayout) workspace.getChildAt(i);
1674
1675            final Bitmap bitmap = Bitmap.createBitmap((int) sWidth, (int) sHeight,
1676                    Bitmap.Config.ARGB_8888);
1677
1678            final Canvas c = new Canvas(bitmap);
1679            c.scale(scale, scale);
1680            c.translate(-cell.getLeftPadding(), -cell.getTopPadding());
1681            cell.dispatchDraw(c);
1682
1683            image.setBackgroundDrawable(resources.getDrawable(R.drawable.preview_background));
1684            image.setImageBitmap(bitmap);
1685            image.setTag(i);
1686            image.setOnClickListener(handler);
1687            image.setOnFocusChangeListener(handler);
1688            image.setFocusable(true);
1689            if (i == mWorkspace.getCurrentScreen()) image.requestFocus();
1690
1691            preview.addView(image,
1692                    LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
1693
1694            bitmaps.add(bitmap);
1695        }
1696
1697        final PopupWindow p = new PopupWindow(this);
1698        p.setContentView(preview);
1699        p.setWidth((int) (sWidth * count + extraW));
1700        p.setHeight((int) (sHeight + extraH));
1701        p.setAnimationStyle(R.style.AnimationPreview);
1702        p.setOutsideTouchable(true);
1703        p.setFocusable(true);
1704        p.setBackgroundDrawable(new ColorDrawable(0));
1705        p.showAsDropDown(anchor, 0, 0);
1706
1707        p.setOnDismissListener(new PopupWindow.OnDismissListener() {
1708            public void onDismiss() {
1709                dismissPreview(anchor);
1710            }
1711        });
1712
1713        anchor.setTag(p);
1714        anchor.setTag(R.id.workspace, preview);
1715        anchor.setTag(R.id.icon, bitmaps);
1716    }
1717
1718    class PreviewTouchHandler implements View.OnClickListener, Runnable, View.OnFocusChangeListener {
1719        private final View mAnchor;
1720
1721        public PreviewTouchHandler(View anchor) {
1722            mAnchor = anchor;
1723        }
1724
1725        public void onClick(View v) {
1726            mWorkspace.snapToScreen((Integer) v.getTag());
1727            v.post(this);
1728        }
1729
1730        public void run() {
1731            dismissPreview(mAnchor);
1732        }
1733
1734        public void onFocusChange(View v, boolean hasFocus) {
1735            if (hasFocus) {
1736                mWorkspace.snapToScreen((Integer) v.getTag());
1737            }
1738        }
1739    }
1740
1741    Workspace getWorkspace() {
1742        return mWorkspace;
1743    }
1744
1745    @Override
1746    protected Dialog onCreateDialog(int id) {
1747        switch (id) {
1748            case DIALOG_CREATE_SHORTCUT:
1749                return new CreateShortcut().createDialog();
1750            case DIALOG_RENAME_FOLDER:
1751                return new RenameFolder().createDialog();
1752        }
1753
1754        return super.onCreateDialog(id);
1755    }
1756
1757    @Override
1758    protected void onPrepareDialog(int id, Dialog dialog) {
1759        switch (id) {
1760            case DIALOG_CREATE_SHORTCUT:
1761                break;
1762            case DIALOG_RENAME_FOLDER:
1763                if (mFolderInfo != null) {
1764                    EditText input = (EditText) dialog.findViewById(R.id.folder_name);
1765                    final CharSequence text = mFolderInfo.title;
1766                    input.setText(text);
1767                    input.setSelection(0, text.length());
1768                }
1769                break;
1770        }
1771    }
1772
1773    void showRenameDialog(FolderInfo info) {
1774        mFolderInfo = info;
1775        mWaitingForResult = true;
1776        showDialog(DIALOG_RENAME_FOLDER);
1777    }
1778
1779    private void showAddDialog(CellLayout.CellInfo cellInfo) {
1780        mAddItemCellInfo = cellInfo;
1781        mWaitingForResult = true;
1782        showDialog(DIALOG_CREATE_SHORTCUT);
1783    }
1784
1785    private void pickShortcut() {
1786        Bundle bundle = new Bundle();
1787
1788        ArrayList<String> shortcutNames = new ArrayList<String>();
1789        shortcutNames.add(getString(R.string.group_applications));
1790        bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
1791
1792        ArrayList<ShortcutIconResource> shortcutIcons = new ArrayList<ShortcutIconResource>();
1793        shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
1794                        R.drawable.ic_launcher_application));
1795        bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons);
1796
1797        Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
1798        pickIntent.putExtra(Intent.EXTRA_INTENT, new Intent(Intent.ACTION_CREATE_SHORTCUT));
1799        pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_shortcut));
1800        pickIntent.putExtras(bundle);
1801
1802        startActivityForResult(pickIntent, REQUEST_PICK_SHORTCUT);
1803    }
1804
1805    private class RenameFolder {
1806        private EditText mInput;
1807
1808        Dialog createDialog() {
1809            final View layout = View.inflate(Launcher.this, R.layout.rename_folder, null);
1810            mInput = (EditText) layout.findViewById(R.id.folder_name);
1811
1812            AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
1813            builder.setIcon(0);
1814            builder.setTitle(getString(R.string.rename_folder_title));
1815            builder.setCancelable(true);
1816            builder.setOnCancelListener(new Dialog.OnCancelListener() {
1817                public void onCancel(DialogInterface dialog) {
1818                    cleanup();
1819                }
1820            });
1821            builder.setNegativeButton(getString(R.string.cancel_action),
1822                new Dialog.OnClickListener() {
1823                    public void onClick(DialogInterface dialog, int which) {
1824                        cleanup();
1825                    }
1826                }
1827            );
1828            builder.setPositiveButton(getString(R.string.rename_action),
1829                new Dialog.OnClickListener() {
1830                    public void onClick(DialogInterface dialog, int which) {
1831                        changeFolderName();
1832                    }
1833                }
1834            );
1835            builder.setView(layout);
1836
1837            final AlertDialog dialog = builder.create();
1838            dialog.setOnShowListener(new DialogInterface.OnShowListener() {
1839                public void onShow(DialogInterface dialog) {
1840                    mWaitingForResult = true;
1841                    mInput.requestFocus();
1842                    InputMethodManager inputManager = (InputMethodManager)
1843                            getSystemService(Context.INPUT_METHOD_SERVICE);
1844                    inputManager.showSoftInput(mInput, 0);
1845                }
1846            });
1847
1848            return dialog;
1849        }
1850
1851        private void changeFolderName() {
1852            final String name = mInput.getText().toString();
1853            if (!TextUtils.isEmpty(name)) {
1854                // Make sure we have the right folder info
1855                mFolderInfo = mFolders.get(mFolderInfo.id);
1856                mFolderInfo.title = name;
1857                LauncherModel.updateItemInDatabase(Launcher.this, mFolderInfo);
1858
1859                if (mWorkspaceLoading) {
1860                    lockAllApps();
1861                    mModel.setWorkspaceDirty();
1862                    mModel.startLoader(Launcher.this, false);
1863                } else {
1864                    final FolderIcon folderIcon = (FolderIcon)
1865                            mWorkspace.getViewForTag(mFolderInfo);
1866                    if (folderIcon != null) {
1867                        folderIcon.setText(name);
1868                        getWorkspace().requestLayout();
1869                    } else {
1870                        lockAllApps();
1871                        mModel.setWorkspaceDirty();
1872                        mWorkspaceLoading = true;
1873                        mModel.startLoader(Launcher.this, false);
1874                    }
1875                }
1876            }
1877            cleanup();
1878        }
1879
1880        private void cleanup() {
1881            dismissDialog(DIALOG_RENAME_FOLDER);
1882            mWaitingForResult = false;
1883            mFolderInfo = null;
1884        }
1885    }
1886
1887    boolean isAllAppsVisible() {
1888        return mAllAppsGrid.isVisible();
1889    }
1890
1891    // AllAppsView.Watcher
1892    public void zoomed(float zoom) {
1893        if (zoom == 1.0f) {
1894            mWorkspace.setVisibility(View.GONE);
1895        }
1896    }
1897
1898    void showAllApps(boolean animated) {
1899        mAllAppsGrid.zoom(1.0f, animated);
1900
1901        ((View) mAllAppsGrid).setFocusable(true);
1902        ((View) mAllAppsGrid).requestFocus();
1903
1904        // TODO: fade these two too
1905        mDeleteZone.setVisibility(View.GONE);
1906    }
1907
1908    /**
1909     * Things to test when changing this code.
1910     *   - Home from workspace
1911     *          - from center screen
1912     *          - from other screens
1913     *   - Home from all apps
1914     *          - from center screen
1915     *          - from other screens
1916     *   - Back from all apps
1917     *          - from center screen
1918     *          - from other screens
1919     *   - Launch app from workspace and quit
1920     *          - with back
1921     *          - with home
1922     *   - Launch app from all apps and quit
1923     *          - with back
1924     *          - with home
1925     *   - Go to a screen that's not the default, then all
1926     *     apps, and launch and app, and go back
1927     *          - with back
1928     *          -with home
1929     *   - On workspace, long press power and go back
1930     *          - with back
1931     *          - with home
1932     *   - On all apps, long press power and go back
1933     *          - with back
1934     *          - with home
1935     *   - On workspace, power off
1936     *   - On all apps, power off
1937     *   - Launch an app and turn off the screen while in that app
1938     *          - Go back with home key
1939     *          - Go back with back key  TODO: make this not go to workspace
1940     *          - From all apps
1941     *          - From workspace
1942     *   - Enter and exit car mode (becuase it causes an extra configuration changed)
1943     *          - From all apps
1944     *          - From the center workspace
1945     *          - From another workspace
1946     */
1947    void closeAllApps(boolean animated) {
1948        if (mAllAppsGrid.isVisible()) {
1949            mWorkspace.setVisibility(View.VISIBLE);
1950            mAllAppsGrid.zoom(0.0f, animated);
1951            ((View)mAllAppsGrid).setFocusable(false);
1952            mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus();
1953        }
1954    }
1955
1956    void lockAllApps() {
1957        // TODO
1958    }
1959
1960    void unlockAllApps() {
1961        // TODO
1962    }
1963
1964    /**
1965     * Displays the shortcut creation dialog and launches, if necessary, the
1966     * appropriate activity.
1967     */
1968    private class CreateShortcut implements DialogInterface.OnClickListener,
1969            DialogInterface.OnCancelListener, DialogInterface.OnDismissListener,
1970            DialogInterface.OnShowListener {
1971
1972        private AddAdapter mAdapter;
1973
1974        Dialog createDialog() {
1975            mAdapter = new AddAdapter(Launcher.this);
1976
1977            final AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
1978            builder.setTitle(getString(R.string.menu_item_add_item));
1979            builder.setAdapter(mAdapter, this);
1980
1981            builder.setInverseBackgroundForced(true);
1982
1983            AlertDialog dialog = builder.create();
1984            dialog.setOnCancelListener(this);
1985            dialog.setOnDismissListener(this);
1986            dialog.setOnShowListener(this);
1987
1988            return dialog;
1989        }
1990
1991        public void onCancel(DialogInterface dialog) {
1992            mWaitingForResult = false;
1993            cleanup();
1994        }
1995
1996        public void onDismiss(DialogInterface dialog) {
1997        }
1998
1999        private void cleanup() {
2000            try {
2001                dismissDialog(DIALOG_CREATE_SHORTCUT);
2002            } catch (Exception e) {
2003                // An exception is thrown if the dialog is not visible, which is fine
2004            }
2005        }
2006
2007        /**
2008         * Handle the action clicked in the "Add to home" dialog.
2009         */
2010        public void onClick(DialogInterface dialog, int which) {
2011            Resources res = getResources();
2012            cleanup();
2013
2014            switch (which) {
2015                case AddAdapter.ITEM_SHORTCUT: {
2016                    // Insert extra item to handle picking application
2017                    pickShortcut();
2018                    break;
2019                }
2020
2021                case AddAdapter.ITEM_APPWIDGET: {
2022                    int appWidgetId = Launcher.this.mAppWidgetHost.allocateAppWidgetId();
2023
2024                    Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
2025                    pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2026                    // start the pick activity
2027                    startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
2028                    break;
2029                }
2030
2031                case AddAdapter.ITEM_LIVE_FOLDER: {
2032                    // Insert extra item to handle inserting folder
2033                    Bundle bundle = new Bundle();
2034
2035                    ArrayList<String> shortcutNames = new ArrayList<String>();
2036                    shortcutNames.add(res.getString(R.string.group_folder));
2037                    bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
2038
2039                    ArrayList<ShortcutIconResource> shortcutIcons =
2040                            new ArrayList<ShortcutIconResource>();
2041                    shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
2042                            R.drawable.ic_launcher_folder));
2043                    bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons);
2044
2045                    Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
2046                    pickIntent.putExtra(Intent.EXTRA_INTENT,
2047                            new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER));
2048                    pickIntent.putExtra(Intent.EXTRA_TITLE,
2049                            getText(R.string.title_select_live_folder));
2050                    pickIntent.putExtras(bundle);
2051
2052                    startActivityForResult(pickIntent, REQUEST_PICK_LIVE_FOLDER);
2053                    break;
2054                }
2055
2056                case AddAdapter.ITEM_WALLPAPER: {
2057                    startWallpaper();
2058                    break;
2059                }
2060            }
2061        }
2062
2063        public void onShow(DialogInterface dialog) {
2064            mWaitingForResult = true;
2065        }
2066    }
2067
2068    /**
2069     * Receives notifications when applications are added/removed.
2070     */
2071    private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
2072        @Override
2073        public void onReceive(Context context, Intent intent) {
2074            closeSystemDialogs();
2075            String reason = intent.getStringExtra("reason");
2076            if (!"homekey".equals(reason)) {
2077                boolean animate = true;
2078                if (mPaused || "lock".equals(reason)) {
2079                    animate = false;
2080                }
2081                closeAllApps(animate);
2082            }
2083        }
2084    }
2085
2086    /**
2087     * Receives notifications whenever the appwidgets are reset.
2088     */
2089    private class AppWidgetResetObserver extends ContentObserver {
2090        public AppWidgetResetObserver() {
2091            super(new Handler());
2092        }
2093
2094        @Override
2095        public void onChange(boolean selfChange) {
2096            onAppWidgetReset();
2097        }
2098    }
2099
2100    /**
2101     * Implementation of the method from LauncherModel.Callbacks.
2102     */
2103    public int getCurrentWorkspaceScreen() {
2104        return mWorkspace.getCurrentScreen();
2105    }
2106
2107    /**
2108     * Refreshes the shortcuts shown on the workspace.
2109     *
2110     * Implementation of the method from LauncherModel.Callbacks.
2111     */
2112    public void startBinding() {
2113        final Workspace workspace = mWorkspace;
2114        int count = workspace.getChildCount();
2115        for (int i = 0; i < count; i++) {
2116            // Use removeAllViewsInLayout() to avoid an extra requestLayout() and invalidate().
2117            ((ViewGroup) workspace.getChildAt(i)).removeAllViewsInLayout();
2118        }
2119
2120        if (DEBUG_USER_INTERFACE) {
2121            android.widget.Button finishButton = new android.widget.Button(this);
2122            finishButton.setText("Finish");
2123            workspace.addInScreen(finishButton, 1, 0, 0, 1, 1);
2124
2125            finishButton.setOnClickListener(new android.widget.Button.OnClickListener() {
2126                public void onClick(View v) {
2127                    finish();
2128                }
2129            });
2130        }
2131    }
2132
2133    /**
2134     * Bind the items start-end from the list.
2135     *
2136     * Implementation of the method from LauncherModel.Callbacks.
2137     */
2138    public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end) {
2139
2140        final Workspace workspace = mWorkspace;
2141
2142        for (int i=start; i<end; i++) {
2143            final ItemInfo item = shortcuts.get(i);
2144            mDesktopItems.add(item);
2145            switch (item.itemType) {
2146                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
2147                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2148                    final View shortcut = createShortcut((ShortcutInfo)item);
2149                    workspace.addInScreen(shortcut, item.screen, item.cellX, item.cellY, 1, 1,
2150                            false);
2151                    break;
2152                case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
2153                    final FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
2154                            (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()),
2155                            (UserFolderInfo) item);
2156                    workspace.addInScreen(newFolder, item.screen, item.cellX, item.cellY, 1, 1,
2157                            false);
2158                    break;
2159                case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
2160                    final FolderIcon newLiveFolder = LiveFolderIcon.fromXml(
2161                            R.layout.live_folder_icon, this,
2162                            (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()),
2163                            (LiveFolderInfo) item);
2164                    workspace.addInScreen(newLiveFolder, item.screen, item.cellX, item.cellY, 1, 1,
2165                            false);
2166                    break;
2167            }
2168        }
2169
2170        workspace.requestLayout();
2171    }
2172
2173    /**
2174     * Implementation of the method from LauncherModel.Callbacks.
2175     */
2176    public void bindFolders(HashMap<Long, FolderInfo> folders) {
2177        mFolders.clear();
2178        mFolders.putAll(folders);
2179    }
2180
2181    /**
2182     * Add the views for a widget to the workspace.
2183     *
2184     * Implementation of the method from LauncherModel.Callbacks.
2185     */
2186    public void bindAppWidget(LauncherAppWidgetInfo item) {
2187        final Workspace workspace = mWorkspace;
2188
2189        final int appWidgetId = item.appWidgetId;
2190        final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
2191        item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
2192
2193        item.hostView.setAppWidget(appWidgetId, appWidgetInfo);
2194        item.hostView.setTag(item);
2195
2196        workspace.addInScreen(item.hostView, item.screen, item.cellX,
2197                item.cellY, item.spanX, item.spanY, false);
2198
2199        workspace.requestLayout();
2200
2201        mDesktopItems.add(item);
2202    }
2203
2204    /**
2205     * Callback saying that there aren't any more items to bind.
2206     *
2207     * Implementation of the method from LauncherModel.Callbacks.
2208     */
2209    public void finishBindingItems() {
2210        if (mSavedState != null) {
2211            if (!mWorkspace.hasFocus()) {
2212                mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus();
2213            }
2214
2215            final long[] userFolders = mSavedState.getLongArray(RUNTIME_STATE_USER_FOLDERS);
2216            if (userFolders != null) {
2217                for (long folderId : userFolders) {
2218                    final FolderInfo info = mFolders.get(folderId);
2219                    if (info != null) {
2220                        openFolder(info);
2221                    }
2222                }
2223                final Folder openFolder = mWorkspace.getOpenFolder();
2224                if (openFolder != null) {
2225                    openFolder.requestFocus();
2226                }
2227            }
2228
2229            mSavedState = null;
2230        }
2231
2232        if (mSavedInstanceState != null) {
2233            super.onRestoreInstanceState(mSavedInstanceState);
2234            mSavedInstanceState = null;
2235        }
2236
2237        mWorkspaceLoading = false;
2238    }
2239
2240    /**
2241     * Add the icons for all apps.
2242     *
2243     * Implementation of the method from LauncherModel.Callbacks.
2244     */
2245    public void bindAllApplications(ArrayList<ApplicationInfo> apps) {
2246        mAllAppsGrid.setApps(apps);
2247    }
2248
2249    /**
2250     * A package was installed.
2251     *
2252     * Implementation of the method from LauncherModel.Callbacks.
2253     */
2254    public void bindAppsAdded(ArrayList<ApplicationInfo> apps) {
2255        removeDialog(DIALOG_CREATE_SHORTCUT);
2256        mAllAppsGrid.addApps(apps);
2257    }
2258
2259    /**
2260     * A package was updated.
2261     *
2262     * Implementation of the method from LauncherModel.Callbacks.
2263     */
2264    public void bindAppsUpdated(ArrayList<ApplicationInfo> apps) {
2265        removeDialog(DIALOG_CREATE_SHORTCUT);
2266        mWorkspace.updateShortcuts(apps);
2267        mAllAppsGrid.updateApps(apps);
2268    }
2269
2270    /**
2271     * A package was uninstalled.
2272     *
2273     * Implementation of the method from LauncherModel.Callbacks.
2274     */
2275    public void bindAppsRemoved(ArrayList<ApplicationInfo> apps) {
2276        removeDialog(DIALOG_CREATE_SHORTCUT);
2277        mWorkspace.removeItems(apps);
2278        mAllAppsGrid.removeApps(apps);
2279    }
2280
2281    /**
2282     * Prints out out state for debugging.
2283     */
2284    public void dumpState() {
2285        Log.d(TAG, "BEGIN launcher2 dump state for launcher " + this);
2286        Log.d(TAG, "mSavedState=" + mSavedState);
2287        Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
2288        Log.d(TAG, "mRestoring=" + mRestoring);
2289        Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
2290        Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
2291        Log.d(TAG, "mDesktopItems.size=" + mDesktopItems.size());
2292        Log.d(TAG, "mFolders.size=" + mFolders.size());
2293        mModel.dumpState();
2294        mAllAppsGrid.dumpState();
2295        Log.d(TAG, "END launcher2 dump state");
2296    }
2297}
2298