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