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