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