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