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