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