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