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