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