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