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