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