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