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