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