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