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