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