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