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