Bug 838793 - Part 1: Convert AboutHomeContent to a Fragment. r=lucasr
authorBrian Nicholson <bnicholson@mozilla.com>
Tue, 16 Apr 2013 14:34:46 -0700
changeset 128998 cacd9189f531691a41beddaac22ca87f28eb91b2
parent 128997 ee3e4ea1ea746fc944104485ef698d323813b227
child 128999 843223330b5ebf1a52e53c930244185edb761ae4
push id24553
push userryanvm@gmail.com
push dateWed, 17 Apr 2013 16:44:13 +0000
treeherdermozilla-central@3607139bd503 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslucasr
bugs838793
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 838793 - Part 1: Convert AboutHomeContent to a Fragment. r=lucasr
mobile/android/base/AwesomeBar.java
mobile/android/base/BrowserApp.java
mobile/android/base/GeckoActivity.java
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoViewsFactory.java
mobile/android/base/Makefile.in
mobile/android/base/PrivateTab.java
mobile/android/base/Tab.java
mobile/android/base/Tabs.java
mobile/android/base/gfx/LayerView.java
mobile/android/base/resources/layout-xlarge-land-v11/abouthome_content.xml
mobile/android/base/resources/layout/abouthome_content.xml
mobile/android/base/resources/layout/gecko_app.xml
mobile/android/base/widget/AboutHome.java
mobile/android/base/widget/AboutHomeContent.java
mobile/android/base/widget/AboutHomeView.java
mobile/android/base/widget/AddonsSection.java
mobile/android/base/widget/LastTabsSection.java
mobile/android/base/widget/TopSitesView.java
--- a/mobile/android/base/AwesomeBar.java
+++ b/mobile/android/base/AwesomeBar.java
@@ -23,29 +23,29 @@ import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.Spanned;
 import android.text.TextUtils;
 import android.text.TextWatcher;
+import android.util.AttributeSet;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.ImageButton;
-import android.widget.ListView;
 import android.widget.TabWidget;
 import android.widget.Toast;
 
 import java.net.URLEncoder;
 
 public class AwesomeBar extends GeckoActivity {
     private static final String LOGTAG = "GeckoAwesomeBar";
 
@@ -63,21 +63,22 @@ public class AwesomeBar extends GeckoAct
     private CustomEditText mText;
     private ImageButton mGoButton;
     private ContextMenuSubject mContextMenuSubject;
     private boolean mIsUsingGestureKeyboard;
     private boolean mDelayRestartInput;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
+        LayoutInflater.from(this).setFactory(this);
+
         super.onCreate(savedInstanceState);
 
         Log.d(LOGTAG, "creating awesomebar");
 
-        LayoutInflater.from(this).setFactory(GeckoViewsFactory.getInstance());
         setContentView(R.layout.awesomebar);
 
         mGoButton = (ImageButton) findViewById(R.id.awesomebar_button);
         mText = (CustomEditText) findViewById(R.id.awesomebar_text);
 
         TabWidget tabWidget = (TabWidget) findViewById(android.R.id.tabs);
         tabWidget.setDividerDrawable(null);
 
@@ -260,16 +261,31 @@ public class AwesomeBar extends GeckoAct
         boolean showReadingList = intent.getBooleanExtra(READING_LIST_KEY, false);
         if (showReadingList) {
             BookmarksTab bookmarksTab = mAwesomeTabs.getBookmarksTab();
             bookmarksTab.setShowReadingList(true);
             mAwesomeTabs.setCurrentItemByTag(bookmarksTab.getTag());
         }
     }
 
+    /*
+     * Only one factory can be set on the inflater; however, we want to use two
+     * factories (GeckoViewsFactory and the FragmentActivity factory).
+     * Overriding onCreateView() here allows us to dispatch view creation to
+     * both factories.
+     */
+    @Override
+    public View onCreateView(String name, Context context, AttributeSet attrs) {
+        View view = GeckoViewsFactory.getInstance().onCreateView(name, context, attrs);
+        if (view == null) {
+            view = super.onCreateView(name, context, attrs);
+        }
+        return view;
+    }
+
     private boolean handleBackKey() {
         // Let mAwesomeTabs try to handle the back press, since we may be in a
         // bookmarks sub-folder.
         if (mAwesomeTabs.onBackPressed())
             return true;
 
         // If mAwesomeTabs.onBackPressed() returned false, we didn't move up
         // a folder level, so just exit the activity.
@@ -522,32 +538,30 @@ public class AwesomeBar extends GeckoAct
             this.keyword = keyword;
             this.display = display;
         }
     };
 
     @Override
     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
         super.onCreateContextMenu(menu, view, menuInfo);
-        ListView list = (ListView) view;
         AwesomeBarTab tab = mAwesomeTabs.getAwesomeBarTabForView(view);
         mContextMenuSubject = tab.getSubject(menu, view, menuInfo);
     }
 
     @Override
     public boolean onContextItemSelected(MenuItem item) {
         if (mContextMenuSubject == null)
             return false;
 
         final int id = mContextMenuSubject.id;
         final String url = mContextMenuSubject.url;
         final byte[] b = mContextMenuSubject.favicon;
         final String title = mContextMenuSubject.title;
         final String keyword = mContextMenuSubject.keyword;
-        final int display = mContextMenuSubject.display;
 
         switch (item.getItemId()) {
             case R.id.open_in_reader: {
                 if (url == null) {
                     Log.e(LOGTAG, "Can't open in reader mode because URL is null");
                     break;
                 }
 
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -10,18 +10,17 @@ import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
-import org.mozilla.gecko.widget.AboutHomeContent;
-import org.mozilla.gecko.widget.TopSitesView;
+import org.mozilla.gecko.widget.AboutHome;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.DialogInterface;
@@ -37,17 +36,16 @@ import android.graphics.drawable.Drawabl
 import android.net.Uri;
 import android.nfc.NdefMessage;
 import android.nfc.NdefRecord;
 import android.nfc.NfcAdapter;
 import android.nfc.NfcEvent;
 import android.os.Build;
 import android.os.Bundle;
 import android.util.Log;
-import android.view.ContextMenu.ContextMenuInfo;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.SubMenu;
@@ -61,26 +59,27 @@ import android.widget.Toast;
 import java.io.InputStream;
 import java.net.URL;
 import java.util.EnumSet;
 import java.util.Vector;
 
 abstract public class BrowserApp extends GeckoApp
                                  implements TabsPanel.TabsLayoutChangeListener,
                                             PropertyAnimator.PropertyAnimationListener,
-                                            View.OnKeyListener {
+                                            View.OnKeyListener,
+                                            AboutHome.UriLoadListener,
+                                            AboutHome.LoadCompleteListener {
     private static final String LOGTAG = "GeckoBrowserApp";
 
     private static final String PREF_CHROME_DYNAMICTOOLBAR = "browser.chrome.dynamictoolbar";
 
     private static final int TABS_ANIMATION_DURATION = 450;
 
     public static BrowserToolbar mBrowserToolbar;
-    private AboutHomeContent mAboutHomeContent;
-    private Boolean mAboutHomeShowing = null;
+    private AboutHome mAboutHome;
     protected Telemetry.Timer mAboutHomeStartupTimer = null;
 
     private static final int ADDON_MENU_OFFSET = 1000;
     private class MenuItemInfo {
         public int id;
         public String label;
         public String icon;
         public boolean checkable;
@@ -357,18 +356,17 @@ abstract public class BrowserApp extends
 
         // Gamepad support only exists in API-level >= 9
         if (Build.VERSION.SDK_INT >= 9 &&
             (event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
             switch (keyCode) {
                 case KeyEvent.KEYCODE_BUTTON_Y:
                     // Toggle/focus the address bar on gamepad-y button.
                     if (mBrowserToolbar.isVisible()) {
-                        if (isDynamicToolbarEnabled() &&
-                            Boolean.FALSE.equals(mAboutHomeShowing)) {
+                        if (isDynamicToolbarEnabled() && !mAboutHome.getUserVisibleHint()) {
                             mBrowserToolbar.animateVisibility(false);
                             mLayerView.requestFocus();
                         } else {
                             // Just focus the address bar when about:home is visible
                             // or when the dynamic toolbar isn't enabled.
                             mBrowserToolbar.requestFocusFromTouch();
                         }
                     } else {
@@ -458,18 +456,18 @@ abstract public class BrowserApp extends
         });
     }
 
     @Override
     void onStatePurged() {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
-                if (mAboutHomeContent != null)
-                    mAboutHomeContent.setLastTabsVisibility(false);
+                if (mAboutHome != null)
+                    mAboutHome.setLastTabsVisibility(false);
             }
         });
 
         super.onStatePurged();
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -483,26 +481,33 @@ abstract public class BrowserApp extends
         ((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideTabsTouchListener());
         ((GeckoApp.MainLayout) mMainLayout).setMotionEventInterceptor(new MotionEventInterceptor() {
             @Override
             public boolean onInterceptMotionEvent(View view, MotionEvent event) {
                 // If we get a gamepad panning MotionEvent while the focus is not on the layerview,
                 // put the focus on the layerview and carry on
                 LayerView layerView = mLayerView;
                 if (layerView != null && !layerView.hasFocus() && GamepadUtils.isPanningControl(event)) {
-                    if (Boolean.FALSE.equals(mAboutHomeShowing)) {
+                    if (mAboutHome.getUserVisibleHint()) {
                         layerView.requestFocus();
                     } else {
-                        mAboutHomeContent.requestFocus();
+                        mAboutHome.requestFocus();
                     }
                 }
                 return false;
             }
         });
 
+        // AboutHome will be dynamically attached and detached as
+        // about:home is shown. Adding/removing the fragment is not synchronous,
+        // so we can't use Fragment#isVisible() to determine whether the
+        // about:home is shown. Instead, we use Fragment#getUserVisibleHint()
+        // with the hint we set ourselves.
+        mAboutHome = AboutHome.newInstance();
+        mAboutHome.setUserVisibleHint(false);
 
         mBrowserToolbar = new BrowserToolbar(this);
         mBrowserToolbar.from(actionBar);
 
         // Intercept key events for gamepad shortcuts
         actionBar.setOnKeyListener(this);
 
         if (mTabsPanel != null) {
@@ -569,19 +574,17 @@ abstract public class BrowserApp extends
     }
 
     private void setDynamicToolbarEnabled(boolean enabled) {
         if (enabled) {
             setToolbarMargin(0);
         } else {
             // Immediately show the toolbar when disabling the dynamic
             // toolbar.
-            if (mAboutHomeContent != null) {
-                mAboutHomeContent.setPadding(0, 0, 0, 0);
-            }
+            mAboutHome.setPadding(0, 0, 0, 0);
             mBrowserToolbar.cancelVisibilityAnimation();
             mBrowserToolbar.getLayout().scrollTo(0, 0);
         }
 
         // Refresh the margins to reset the padding on the spacer and
         // make sure that Gecko is in sync.
         ((BrowserToolbarLayout)mBrowserToolbar.getLayout()).refreshMargins();
     }
@@ -605,18 +608,16 @@ abstract public class BrowserApp extends
     }
 
     @Override
     public void onDestroy() {
         if (mPrefObserverId != null) {
             PrefsHelper.removeObserver(mPrefObserverId);
             mPrefObserverId = null;
         }
-        if (mAboutHomeContent != null)
-            mAboutHomeContent.onDestroy();
         if (mBrowserToolbar != null)
             mBrowserToolbar.onDestroy();
 
         unregisterEventListener("CharEncoding:Data");
         unregisterEventListener("CharEncoding:State");
         unregisterEventListener("Feedback:LastUrl");
         unregisterEventListener("Feedback:OpenPlayStore");
         unregisterEventListener("Feedback:MaybeLater");
@@ -631,23 +632,16 @@ abstract public class BrowserApp extends
                 nfc.setNdefPushMessageCallback(null, this);
             }
         }
 
         super.onDestroy();
     }
 
     @Override
-    public void onContentChanged() {
-        super.onContentChanged();
-        if (mAboutHomeContent != null)
-            mAboutHomeContent.onActivityContentChanged();
-    }
-
-    @Override
     protected void finishProfileMigration() {
         // Update about:home with the new information.
         updateAboutHomeTopSites();
 
         super.finishProfileMigration();
     }
 
     @Override
@@ -693,25 +687,25 @@ abstract public class BrowserApp extends
     }
 
     private void setToolbarMargin(int margin) {
         ((RelativeLayout.LayoutParams) mGeckoLayout.getLayoutParams()).topMargin = margin;
         mGeckoLayout.requestLayout();
     }
 
     public void setToolbarHeight(int aHeight, int aVisibleHeight) {
-        if (!isDynamicToolbarEnabled() || Boolean.TRUE.equals(mAboutHomeShowing)) {
+        if (!isDynamicToolbarEnabled() || mAboutHome.getUserVisibleHint()) {
             // Use aVisibleHeight here so that when the dynamic toolbar is
             // enabled, the padding will animate with the toolbar becoming
             // visible.
             if (isDynamicToolbarEnabled()) {
                 // When the dynamic toolbar is enabled, set the padding on the
                 // about:home widget directly - this is to avoid resizing the
                 // LayerView, which can cause visible artifacts.
-                mAboutHomeContent.setPadding(0, aVisibleHeight, 0, 0);
+                mAboutHome.setPadding(0, aVisibleHeight, 0, 0);
             } else {
                 setToolbarMargin(aVisibleHeight);
             }
             aHeight = aVisibleHeight = 0;
         } else {
             setToolbarMargin(0);
         }
 
@@ -788,19 +782,16 @@ abstract public class BrowserApp extends
             // The favicon view is different now, so we need to update the DoorHangerPopup anchor view.
             if (mDoorHangerPopup != null)
                 mDoorHangerPopup.setAnchor(mBrowserToolbar.mFavicon);
         }
 
         invalidateOptionsMenu();
         updateSideBarState();
         mTabsPanel.refresh();
-
-        if (mAboutHomeContent != null)
-            mAboutHomeContent.refresh();
     }
 
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
         String url = resultCode == Activity.RESULT_OK ? data.getStringExtra(AwesomeBar.URL_KEY) : null;
         mBrowserToolbar.fromAwesomeBarSearch(url);
     }
@@ -1189,85 +1180,56 @@ abstract public class BrowserApp extends
 
         // Reset favicon load state
         tab.setFaviconLoadId(Favicons.NOT_LOADING);
     }
 
 
     /* About:home UI */
     void updateAboutHomeTopSites() {
-        if (mAboutHomeContent == null)
-            return;
-
-        mAboutHomeContent.update(EnumSet.of(AboutHomeContent.UpdateFlags.TOP_SITES));
+        mAboutHome.update(EnumSet.of(AboutHome.UpdateFlags.TOP_SITES));
     }
 
     private void showAboutHome() {
-        // Don't create an additional AboutHomeRunnable if about:home
-        // is already visible.
-        if (mAboutHomeShowing != null && mAboutHomeShowing)
+        if (mAboutHome.getUserVisibleHint()) {
             return;
+        }
 
-        mAboutHomeShowing = true;
-        Runnable r = new AboutHomeRunnable(true);
-        ThreadUtils.getUiHandler().postAtFrontOfQueue(r);
+        // We use commitAllowingStateLoss() instead of commit() here to avoid an
+        // IllegalStateException. showAboutHome() and hideAboutHome() are
+        // executed inside of tab's onChange() callback. Since that callback can
+        // be triggered asynchronously from Gecko, it's possible that this
+        // method can be called while Fennec is in the background. If that
+        // happens, using commit() would throw an IllegalStateException since
+        // it can't be used between the Activity's onSaveInstanceState() and
+        // onResume().
+        getSupportFragmentManager().beginTransaction()
+                .add(R.id.gecko_layout, mAboutHome).commitAllowingStateLoss();
+        mAboutHome.setUserVisibleHint(true);
+
+        mBrowserToolbar.setNextFocusDownId(R.id.abouthome_content);
+
+        // Refresh margins to possibly restore the toolbar padding
+        ((BrowserToolbarLayout)mBrowserToolbar.getLayout()).refreshMargins();
     }
 
     private void hideAboutHome() {
-        // If hideAboutHome gets called before showAboutHome, we still need
-        // to create an AboutHomeRunnable to hide the about:home content.
-        if (mAboutHomeShowing != null && !mAboutHomeShowing)
+        if (!mAboutHome.getUserVisibleHint()) {
             return;
-
-        mBrowserToolbar.setShadowVisibility(true);
-        mAboutHomeShowing = false;
-        Runnable r = new AboutHomeRunnable(false);
-        ThreadUtils.getUiHandler().postAtFrontOfQueue(r);
-    }
-
-    private class AboutHomeRunnable implements Runnable {
-        boolean mShow;
-        AboutHomeRunnable(boolean show) {
-            mShow = show;
         }
 
-        @Override
-        public void run() {
-            if (mShow) {
-                if (mAboutHomeContent == null) {
-                    mAboutHomeContent = (AboutHomeContent) findViewById(R.id.abouthome_content);
-                    mAboutHomeContent.init();
-                    mAboutHomeContent.update(AboutHomeContent.UpdateFlags.ALL);
-                    mAboutHomeContent.setUriLoadCallback(new AboutHomeContent.UriLoadCallback() {
-                        @Override
-                        public void callback(String url) {
-                            mBrowserToolbar.setProgressVisibility(true);
-                            Tabs.getInstance().loadUrl(url);
-                        }
-                    });
-                    mAboutHomeContent.setLoadCompleteCallback(new AboutHomeContent.VoidCallback() {
-                        @Override
-                        public void callback() {
-                            mAboutHomeStartupTimer.stop();
-                        }
-                    });
-                } else {
-                    mAboutHomeContent.update(EnumSet.of(AboutHomeContent.UpdateFlags.TOP_SITES,
-                                                        AboutHomeContent.UpdateFlags.REMOTE_TABS));
-                }
-                mAboutHomeContent.setVisibility(View.VISIBLE);
-                mBrowserToolbar.setNextFocusDownId(R.id.abouthome_content);
-            } else {
-                findViewById(R.id.abouthome_content).setVisibility(View.GONE);
-                mBrowserToolbar.setNextFocusDownId(R.id.layer_view);
-            }
+        getSupportFragmentManager().beginTransaction()
+                .remove(mAboutHome).commitAllowingStateLoss();
+        mAboutHome.setUserVisibleHint(false);
 
-            // Refresh margins to possibly restore the toolbar padding
-            ((BrowserToolbarLayout)mBrowserToolbar.getLayout()).refreshMargins();
-        }
+        mBrowserToolbar.setShadowVisibility(true);
+        mBrowserToolbar.setNextFocusDownId(R.id.layer_view);
+
+        // Refresh margins to possibly restore the toolbar padding
+        ((BrowserToolbarLayout)mBrowserToolbar.getLayout()).refreshMargins();
     }
 
     private class HideTabsTouchListener implements TouchEventInterceptor {
         private boolean mIsHidingTabs = false;
 
         @Override
         public boolean onInterceptTouchEvent(View view, MotionEvent event) {
             // We need to account for scroll state for the touched view otherwise
@@ -1572,49 +1534,16 @@ abstract public class BrowserApp extends
         findInPage.setEnabled(!tab.getURL().equals("about:home"));
 
         charEncoding.setVisible(GeckoPreferences.getCharEncodingState());
 
         return true;
     }
 
     @Override
-    public boolean onContextItemSelected(MenuItem item) {
-        ContextMenuInfo info = item.getMenuInfo();
-
-        switch (item.getItemId()) {
-            case R.id.abouthome_open_new_tab:
-                mAboutHomeContent.openNewTab(info);
-                return true;
-
-            case R.id.abouthome_open_private_tab:
-                mAboutHomeContent.openNewPrivateTab(info);
-                return true;
-
-            case R.id.abouthome_topsites_edit:
-                mAboutHomeContent.editSite(info);
-                return true;
-
-            case R.id.abouthome_topsites_unpin:
-                mAboutHomeContent.unpinSite(info, TopSitesView.UnpinFlags.REMOVE_PIN);
-                return true;
-
-            case R.id.abouthome_topsites_pin:
-                mAboutHomeContent.pinSite(info);
-                return true;
-
-            case R.id.abouthome_topsites_remove:
-                mAboutHomeContent.unpinSite(info, TopSitesView.UnpinFlags.REMOVE_HISTORY);
-                return true;
-
-        }
-        return super.onContextItemSelected(item);
-    }
-
-    @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         if (item.getItemId() == R.id.toggle_profiling) {
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("ToggleProfiling", null));
             return true;
         }
 
         Tab tab = null;
         Intent intent = null;
@@ -1787,23 +1716,24 @@ abstract public class BrowserApp extends
                 // Don't bother sending a message if there is no URL.
                 if (url.length() > 0)
                     GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Feedback:LastUrl", url));
             }
         }).execute();
     }
 
     @Override
-    public void onResume()
-    {
-        super.onResume();
-        if (mAboutHomeContent != null) {
-            mAboutHomeContent.refresh();
-        }
+    public void onAboutHomeUriLoad(String url) {
+        mBrowserToolbar.setProgressVisibility(true);
+        Tabs.getInstance().loadUrl(url);
+    }
 
+    @Override
+    public void onAboutHomeLoadComplete() {
+        mAboutHomeStartupTimer.stop();
     }
 
     @Override
     public int getLayout() { return R.layout.gecko_app; }
 
     @Override
     protected String getDefaultProfileName() {
         String profile = GeckoProfile.findDefaultProfile(this);
--- a/mobile/android/base/GeckoActivity.java
+++ b/mobile/android/base/GeckoActivity.java
@@ -1,24 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
-import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.support.v4.app.FragmentActivity;
 
 interface GeckoActivityStatus {
     public boolean isGeckoActivityOpened();
     public boolean isFinishing();  // typically from android.app.Activity
 };
 
-public class GeckoActivity extends Activity implements GeckoActivityStatus {
+public class GeckoActivity extends FragmentActivity implements GeckoActivityStatus {
     // has this activity recently started another Gecko activity?
     private boolean mGeckoActivityOpened = false;
 
     @Override
     public void onPause() {
         super.onPause();
 
         if (getApplication() instanceof GeckoApplication) {
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1313,26 +1313,57 @@ abstract public class GeckoApp
         if ("default".equals(AppConstants.MOZ_UPDATE_CHANNEL)) {
             enableStrictMode();
         }
 
         // The clock starts...now. Better hurry!
         mJavaUiStartupTimer = new Telemetry.Timer("FENNEC_STARTUP_TIME_JAVAUI");
         mGeckoReadyStartupTimer = new Telemetry.Timer("FENNEC_STARTUP_TIME_GECKOREADY");
 
+        String args = getIntent().getStringExtra("args");
+
+        String profileName = null;
+        String profilePath = null;
+        if (args != null) {
+            if (args.contains("-P")) {
+                Pattern p = Pattern.compile("(?:-P\\s*)(\\w*)(\\s*)");
+                Matcher m = p.matcher(args);
+                if (m.find()) {
+                    profileName = m.group(1);
+                }
+            }
+            if (args.contains("-profile")) {
+                Pattern p = Pattern.compile("(?:-profile\\s*)(\\S*)(\\s*)");
+                Matcher m = p.matcher(args);
+                if (m.find()) {
+                    profilePath =  m.group(1);
+                }
+                if (profileName == null) {
+                    profileName = getDefaultProfileName();
+                    if (profileName == null)
+                        profileName = "default";
+                }
+                GeckoApp.sIsUsingCustomProfile = true;
+            }
+            if (profileName != null || profilePath != null) {
+                mProfile = GeckoProfile.get(this, profileName, profilePath);
+            }
+        }
+
+        BrowserDB.initialize(getProfile().getName());
         ((GeckoApplication)getApplication()).initialize();
 
         mAppContext = this;
         ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
 
         Tabs.getInstance().attachToActivity(this);
         Favicons.getInstance().attachToContext(this);
 
         // Check to see if the activity is restarted after configuration change.
-        if (getLastNonConfigurationInstance() != null) {
+        if (getLastCustomNonConfigurationInstance() != null) {
             // Restart the application as a safe way to handle the configuration change.
             doRestart();
             System.exit(0);
             return;
         }
 
         if (sGeckoThread != null) {
             // this happens when the GeckoApp activity is destroyed by android
@@ -1351,17 +1382,17 @@ abstract public class GeckoApp
                 java.lang.reflect.Field creatorField =
                     inputBindResultClass.getField("CREATOR");
                 Log.i(LOGTAG, "froyo startup fix: " + String.valueOf(creatorField.get(null)));
             } catch (Exception e) {
                 Log.w(LOGTAG, "froyo startup fix failed", e);
             }
         }
 
-        LayoutInflater.from(this).setFactory(GeckoViewsFactory.getInstance());
+        LayoutInflater.from(this).setFactory(this);
 
         super.onCreate(savedInstanceState);
 
         mOrientation = getResources().getConfiguration().orientation;
 
         setContentView(getLayout());
 
         // setup gecko layout
@@ -1434,47 +1465,16 @@ abstract public class GeckoApp
 
     private void initialize() {
         mInitialized = true;
 
         invalidateOptionsMenu();
 
         Intent intent = getIntent();
         String action = intent.getAction();
-        String args = intent.getStringExtra("args");
-
-        String profileName = null;
-        String profilePath = null;
-        if (args != null) {
-            if (args.contains("-P")) {
-                Pattern p = Pattern.compile("(?:-P\\s*)(\\w*)(\\s*)");
-                Matcher m = p.matcher(args);
-                if (m.find()) {
-                    profileName = m.group(1);
-                }
-            }
-            if (args.contains("-profile")) {
-                Pattern p = Pattern.compile("(?:-profile\\s*)(\\S*)(\\s*)");
-                Matcher m = p.matcher(args);
-                if (m.find()) {
-                    profilePath =  m.group(1);
-                }
-                if (profileName == null) {
-                    profileName = getDefaultProfileName();
-                    if (profileName == null)
-                        profileName = "default";
-                }
-                GeckoApp.sIsUsingCustomProfile = true;
-            }
-            if (profileName != null || profilePath != null) {
-                mProfile = GeckoProfile.get(this, profileName, profilePath);
-            }
-        }
-
-        BrowserDB.initialize(getProfile().getName());
 
         String passedUri = null;
         String uri = getURIFromIntent(intent);
         if (uri != null && uri.length() > 0) {
             passedUri = uri;
         }
 
         if (mRestoreMode == RESTORE_NONE && shouldRestoreSession()) {
@@ -2057,45 +2057,54 @@ abstract public class GeckoApp
         if (files == null)
             return;
         for (File file : files) {
             file.delete();
         }
     }
 
     @Override
-    public void onContentChanged() {
-        super.onContentChanged();
-    }
-
-
-    @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
 
         if (mOrientation != newConfig.orientation) {
             mOrientation = newConfig.orientation;
             if (mFormAssistPopup != null)
                 mFormAssistPopup.hide();
             SiteIdentityPopup.getInstance().dismiss();
             refreshChrome();
         }
     }
 
     @Override
-    public Object onRetainNonConfigurationInstance() {
+    public Object onRetainCustomNonConfigurationInstance() {
         // Send a non-null value so that we can restart the application, 
         // when activity restarts due to configuration change.
         return Boolean.TRUE;
     } 
 
     public String getContentProcessName() {
         return AppConstants.MOZ_CHILD_PROCESS_NAME;
     }
 
+    /*
+     * Only one factory can be set on the inflater; however, we want to use two
+     * factories (GeckoViewsFactory and the FragmentActivity factory).
+     * Overriding onCreateView() here allows us to dispatch view creation to
+     * both factories.
+     */
+    @Override
+    public View onCreateView(String name, Context context, AttributeSet attrs) {
+        View view = GeckoViewsFactory.getInstance().onCreateView(name, context, attrs);
+        if (view == null) {
+            view = super.onCreateView(name, context, attrs);
+        }
+        return view;
+    }
+
     public void addEnvToIntent(Intent intent) {
         Map<String,String> envMap = System.getenv();
         Set<Map.Entry<String,String>> envSet = envMap.entrySet();
         Iterator<Map.Entry<String,String>> envIter = envSet.iterator();
         int c = 0;
         while (envIter.hasNext()) {
             Map.Entry<String,String> entry = envIter.next();
             intent.putExtra("env" + c, entry.getKey() + "="
--- a/mobile/android/base/GeckoViewsFactory.java
+++ b/mobile/android/base/GeckoViewsFactory.java
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.gfx.LayerView;
-import org.mozilla.gecko.widget.AboutHomeContent;
+import org.mozilla.gecko.widget.AboutHomeView;
 import org.mozilla.gecko.widget.AddonsSection;
 import org.mozilla.gecko.widget.IconTabWidget;
 import org.mozilla.gecko.widget.LastTabsSection;
 import org.mozilla.gecko.widget.LinkTextView;
 import org.mozilla.gecko.widget.PromoBox;
 import org.mozilla.gecko.widget.RemoteTabsSection;
 import org.mozilla.gecko.widget.TabRow;
 import org.mozilla.gecko.widget.ThumbnailView;
@@ -41,17 +41,17 @@ public final class GeckoViewsFactory imp
     private GeckoViewsFactory() {
         // initialize the hashmap to a capacity that is a prime number greater than
         // (size * 4/3). The size is the number of items we expect to put in it, and
         // 4/3 is the inverse of the default load factor.
         mFactoryMap = new HashMap<String, Constructor<? extends View>>(53);
         Class<Context> arg1Class = Context.class;
         Class<AttributeSet> arg2Class = AttributeSet.class;
         try {
-            mFactoryMap.put("AboutHomeContent", AboutHomeContent.class.getConstructor(arg1Class, arg2Class));
+            mFactoryMap.put("AboutHomeView", AboutHomeView.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("AddonsSection", AddonsSection.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("LastTabsSection", LastTabsSection.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("PromoBox", PromoBox.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("RemoteTabsSection", RemoteTabsSection.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("TopSitesView", TopSitesView.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("AwesomeBarTabs", AwesomeBarTabs.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("AwesomeBarTabs$BackgroundLayout", AwesomeBarTabs.BackgroundLayout.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("BackButton", BackButton.class.getConstructor(arg1Class, arg2Class));
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -204,17 +204,18 @@ FENNEC_JAVA_FILES = \
   gfx/SubdocumentScrollHelper.java \
   gfx/TextLayer.java \
   gfx/TextureGenerator.java \
   gfx/TextureReaper.java \
   gfx/TileLayer.java \
   gfx/TouchEventHandler.java \
   gfx/ViewTransform.java \
   gfx/VirtualLayer.java \
-  widget/AboutHomeContent.java \
+  widget/AboutHome.java \
+  widget/AboutHomeView.java \
   widget/AboutHomeSection.java \
   widget/AddonsSection.java \
   widget/DateTimePicker.java \
   widget/IconTabWidget.java \
   widget/LastTabsSection.java \
   widget/LinkTextView.java \
   widget/PromoBox.java \
   widget/RemoteTabsSection.java \
--- a/mobile/android/base/PrivateTab.java
+++ b/mobile/android/base/PrivateTab.java
@@ -1,18 +1,20 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import android.content.Context;
+
 public class PrivateTab extends Tab {
-    public PrivateTab(int id, String url, boolean external, int parentId, String title) {
-        super(id, url, external, parentId, title);
+    public PrivateTab(Context context, int id, String url, boolean external, int parentId, String title) {
+        super(context, id, url, external, parentId, title);
     }
 
     @Override
     protected void saveThumbnailToDB() {}
 
     @Override
     public boolean isPrivate() {
         return true;
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -8,16 +8,17 @@ package org.mozilla.gecko;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.gfx.Layer;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.ContentResolver;
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
@@ -52,29 +53,31 @@ public class Tab {
     private boolean mReadingListItem;
     private long mFaviconLoadId;
     private String mDocumentURI;
     private String mContentType;
     private boolean mHasTouchListeners;
     private ZoomConstraints mZoomConstraints;
     private ArrayList<View> mPluginViews;
     private HashMap<Object, Layer> mPluginLayers;
-    private int mBackgroundColor = Color.WHITE;
+    private int mBackgroundColor;
     private int mState;
     private Bitmap mThumbnailBitmap;
     private boolean mDesktopMode;
     private boolean mEnteringReaderMode;
+    private Context mContext;
     private static final int MAX_HISTORY_LIST_SIZE = 50;
 
     public static final int STATE_DELAYED = 0;
     public static final int STATE_LOADING = 1;
     public static final int STATE_SUCCESS = 2;
     public static final int STATE_ERROR = 3;
 
-    public Tab(int id, String url, boolean external, int parentId, String title) {
+    public Tab(Context context, int id, String url, boolean external, int parentId, String title) {
+        mContext = context;
         mId = id;
         mLastUsed = 0;
         mUrl = url;
         mUserSearch = "";
         mExternal = external;
         mParentId = parentId;
         mTitle = title == null ? "" : title;
         mFavicon = null;
@@ -91,16 +94,21 @@ public class Tab {
         mReadingListItem = false;
         mFaviconLoadId = 0;
         mDocumentURI = "";
         mContentType = "";
         mZoomConstraints = new ZoomConstraints(false);
         mPluginViews = new ArrayList<View>();
         mPluginLayers = new HashMap<Object, Layer>();
         mState = shouldShowProgress(url) ? STATE_SUCCESS : STATE_LOADING;
+
+        // At startup, the background is set to a color specified by LayerView
+        // when the LayerView is created. Shortly after, this background color
+        // will be used before the tab's content is shown.
+        mBackgroundColor = getBackgroundColorForUrl(url);
     }
 
     private ContentResolver getContentResolver() {
         return Tabs.getInstance().getContentResolver();
     }
 
     public void onDestroy() {
         Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.CLOSED);
@@ -549,25 +557,32 @@ public class Tab {
         setContentType(message.getString("contentType"));
         clearFavicon();
         setFeedsEnabled(false);
         updateTitle(null);
         updateIdentityData(null);
         setReaderEnabled(false);
         setZoomConstraints(new ZoomConstraints(true));
         setHasTouchListeners(false);
-        setBackgroundColor(Color.WHITE);
+        setBackgroundColor(getBackgroundColorForUrl(uri));
 
         Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, uri);
     }
 
     private boolean shouldShowProgress(String url) {
         return "about:home".equals(url) || ReaderModeUtils.isAboutReader(url);
     }
 
+    private int getBackgroundColorForUrl(String url) {
+        if ("about:home".equals(url)) {
+            return mContext.getResources().getColor(R.color.background_normal);
+        }
+        return Color.WHITE;
+    }
+
     void handleDocumentStart(boolean showProgress, String url) {
         setState(shouldShowProgress(url) ? STATE_SUCCESS : STATE_LOADING);
         updateIdentityData(null);
         setReaderEnabled(false);
     }
 
     void handleDocumentStop(boolean success) {
         setState(success ? STATE_SUCCESS : STATE_ERROR);
--- a/mobile/android/base/Tabs.java
+++ b/mobile/android/base/Tabs.java
@@ -166,18 +166,18 @@ public class Tabs implements GeckoEventL
                     }
                 }
             };
             BrowserDB.registerBookmarkObserver(getContentResolver(), mContentObserver);
         }
     }
 
     private Tab addTab(int id, String url, boolean external, int parentId, String title, boolean isPrivate) {
-        final Tab tab = isPrivate ? new PrivateTab(id, url, external, parentId, title) :
-                                    new Tab(id, url, external, parentId, title);
+        final Tab tab = isPrivate ? new PrivateTab(mActivity, id, url, external, parentId, title) :
+                                    new Tab(mActivity, id, url, external, parentId, title);
         synchronized (this) {
             lazyRegisterBookmarkObserver();
             mTabs.put(id, tab);
             mOrder.add(tab);
         }
 
         // Suppress the ADDED event to prevent animation of tabs created via session restore.
         if (mInitialTabsAdded) {
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -178,25 +178,30 @@ public class LayerView extends FrameLayo
     protected void onAttachedToWindow() {
         // This check should not be done before the view is attached to a window
         // as hardware acceleration will not be enabled at that point.
         // We must create and add the SurfaceView instance before the view tree
         // is fully created to avoid flickering (see bug 801477).
         if (shouldUseTextureView()) {
             mTextureView = new TextureView(getContext());
             mTextureView.setSurfaceTextureListener(new SurfaceTextureListener());
-            mTextureView.setBackgroundColor(Color.WHITE);
+
+            // The background is set to this color when the LayerView is
+            // created, and it will be shown immediately at startup. Shortly
+            // after, the tab's background color will be used before any content
+            // is shown.
+            mTextureView.setBackgroundResource(R.color.background_normal);
             addView(mTextureView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
         } else {
             // This will stop PropertyAnimator from creating a drawing cache (i.e. a bitmap)
             // from a SurfaceView, which is just not possible (the bitmap will be transparent).
             setWillNotCacheDrawing(false);
 
             mSurfaceView = new LayerSurfaceView(getContext(), this);
-            mSurfaceView.setBackgroundColor(Color.WHITE);
+            mSurfaceView.setBackgroundResource(R.color.background_normal);
             addView(mSurfaceView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
 
             SurfaceHolder holder = mSurfaceView.getHolder();
             holder.addCallback(new SurfaceListener());
             holder.setFormat(PixelFormat.RGB_565);
         }
     }
 
--- a/mobile/android/base/resources/layout-xlarge-land-v11/abouthome_content.xml
+++ b/mobile/android/base/resources/layout-xlarge-land-v11/abouthome_content.xml
@@ -1,15 +1,20 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:gecko="http://schemas.android.com/apk/res-auto">
+<org.mozilla.gecko.widget.AboutHomeView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:gecko="http://schemas.android.com/apk/res-auto"
+		android:id="@+id/abouthome_content"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:background="@color/background_normal">
 
     <LinearLayout android:orientation="horizontal"
                   android:layout_width="fill_parent"
                   android:layout_height="fill_parent">
 
         <LinearLayout android:orientation="vertical"
                       android:layout_width="0dp"
                       android:layout_height="fill_parent"
@@ -100,9 +105,9 @@
                                          gecko:more_text="@string/remote_tabs_show_all"/>
 
             </LinearLayout>
 
         </RelativeLayout>
 
     </LinearLayout>
 
-</merge>
+</org.mozilla.gecko.widget.AboutHomeView>
--- a/mobile/android/base/resources/layout/abouthome_content.xml
+++ b/mobile/android/base/resources/layout/abouthome_content.xml
@@ -1,15 +1,20 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:gecko="http://schemas.android.com/apk/res-auto">
+<org.mozilla.gecko.widget.AboutHomeView
+		xmlns:android="http://schemas.android.com/apk/res/android"
+		xmlns:gecko="http://schemas.android.com/apk/res-auto"
+		android:id="@+id/abouthome_content"
+		android:layout_width="fill_parent"
+		android:layout_height="fill_parent"
+		android:background="@color/background_normal">
 
     <RelativeLayout android:layout_width="fill_parent"
                     android:layout_height="fill_parent"
                     android:paddingLeft="@dimen/abouthome_gutter_large"
                     android:paddingRight="@dimen/abouthome_gutter_large">
 
         <Gecko.ImageView android:id="@+id/abouthome_logo"
                          android:src="@drawable/abouthome_logo"
@@ -85,9 +90,9 @@
                                      android:visibility="gone"
                                      gecko:title="@string/remote_tabs"
                                      gecko:more_text="@string/remote_tabs_show_all"/>
 
         </LinearLayout>
 
     </RelativeLayout>
 
-</merge>
+</org.mozilla.gecko.widget.AboutHomeView>
--- a/mobile/android/base/resources/layout/gecko_app.xml
+++ b/mobile/android/base/resources/layout/gecko_app.xml
@@ -23,21 +23,16 @@
 
         <RelativeLayout android:id="@+id/gecko_layout"
                         android:layout_width="fill_parent"
                         android:layout_height="fill_parent"
                         android:layout_above="@+id/find_in_page">
 
             <include layout="@layout/shared_ui_components"/>
 
-            <Gecko.AboutHomeContent android:id="@+id/abouthome_content"
-                                    android:layout_width="fill_parent"
-                                    android:layout_height="fill_parent"
-                                    android:background="@color/background_normal"/>
-
         </RelativeLayout>
 
         <org.mozilla.gecko.FindInPageBar android:id="@+id/find_in_page"
                                          android:layout_width="fill_parent"
                                          android:layout_height="wrap_content"
                                          android:layout_alignParentBottom="true"
                                          style="@style/FindBar"
                                          android:visibility="gone"/>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/AboutHome.java
@@ -0,0 +1,279 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.widget;
+
+import java.util.EnumSet;
+
+import org.mozilla.gecko.GeckoApplication;
+import org.mozilla.gecko.LightweightTheme;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class AboutHome extends Fragment {
+    public static enum UpdateFlags {
+        TOP_SITES,
+        PREVIOUS_TABS,
+        RECOMMENDED_ADDONS,
+        REMOTE_TABS;
+
+        public static final EnumSet<UpdateFlags> ALL = EnumSet.allOf(UpdateFlags.class);
+    }
+
+    private UriLoadListener mUriLoadListener;
+    private LoadCompleteListener mLoadCompleteListener;
+    private LightweightTheme mLightweightTheme;
+    private ContentObserver mTabsContentObserver;
+    private int mPaddingLeft;
+    private int mPaddingRight;
+    private int mPaddingTop;
+    private int mPaddingBottom;
+    private AboutHomeView mAboutHomeView;
+    private AddonsSection mAddonsSection;
+    private LastTabsSection mLastTabsSection;
+    private RemoteTabsSection mRemoteTabsSection;
+    private TopSitesView mTopSitesView;
+
+    public interface UriLoadListener {
+        public void onAboutHomeUriLoad(String uriSpec);
+    }
+
+    public interface LoadCompleteListener {
+        public void onAboutHomeLoadComplete();
+    }
+
+    public static AboutHome newInstance() {
+        return new AboutHome();
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mLightweightTheme = ((GeckoApplication) getActivity().getApplication()).getLightweightTheme();
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        try {
+            mUriLoadListener = (UriLoadListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString()
+                    + " must implement UriLoadListener");
+        }
+
+        try {
+            mLoadCompleteListener = (LoadCompleteListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString()
+                    + " must implement LoadCompleteListener");
+        }
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+
+        mUriLoadListener = null;
+        mLoadCompleteListener = null;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        super.onCreateView(inflater, container, savedInstanceState);
+
+        // Synchronized with onUpdate(), which runs in a background thread.
+        synchronized (this) {
+            mAboutHomeView = (AboutHomeView) inflater.inflate(R.layout.abouthome_content, container, false);
+            mAddonsSection = (AddonsSection) mAboutHomeView.findViewById(R.id.recommended_addons);
+            mLastTabsSection = (LastTabsSection) mAboutHomeView.findViewById(R.id.last_tabs);
+            mRemoteTabsSection = (RemoteTabsSection) mAboutHomeView.findViewById(R.id.remote_tabs);
+            mTopSitesView = (TopSitesView) mAboutHomeView.findViewById(R.id.top_sites_grid);
+        }
+
+        mAboutHomeView.setLightweightTheme(mLightweightTheme);
+        mLightweightTheme.addListener(mAboutHomeView);
+
+        return mAboutHomeView;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        view.setPadding(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom);
+        ((PromoBox) view.findViewById(R.id.promo_box)).showRandomPromo();
+        update(AboutHome.UpdateFlags.ALL);
+
+        mTopSitesView.setLoadCompleteListener(mLoadCompleteListener);
+        mTopSitesView.setUriLoadListener(mUriLoadListener);
+        mAddonsSection.setUriLoadListener(mUriLoadListener);
+
+        // Reload the mobile homepage on inbound tab syncs
+        // Because the tabs URI is coarse grained, this updates the
+        // remote tabs component on *every* tab change
+        // The observer will run on the background thread (see constructor argument)
+        mTabsContentObserver = new ContentObserver(ThreadUtils.getBackgroundHandler()) {
+            @Override
+            public void onChange(boolean selfChange) {
+                update(EnumSet.of(AboutHome.UpdateFlags.REMOTE_TABS));
+            }
+        };
+        getActivity().getContentResolver().registerContentObserver(BrowserContract.Tabs.CONTENT_URI,
+                false, mTabsContentObserver);
+    }
+
+    @Override
+    public void onDestroyView() {
+        mLightweightTheme.removeListener(mAboutHomeView);
+        getActivity().getContentResolver().unregisterContentObserver(mTabsContentObserver);
+
+        synchronized (this) {
+            mTopSitesView.onDestroy();
+            mAboutHomeView = null;
+            mAddonsSection = null;
+            mLastTabsSection = null;
+            mRemoteTabsSection = null;
+            mTopSitesView = null;
+        }
+
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+
+        // Reattach the fragment, forcing a reinflation of its view.
+        // We use commitAllowingStateLoss() instead of commit() here to avoid
+        // an IllegalStateException. If the phone is rotated while Fennec
+        // is in the background, onConfigurationChanged() is fired.
+        // onConfigurationChanged() is called before onResume(), so
+        // using commit() would throw an IllegalStateException since it can't
+        // be used between the Activity's onSaveInstanceState() and
+        // onResume().
+        if (isVisible()) {
+            getFragmentManager().beginTransaction()
+                                .detach(this)
+                                .attach(this)
+                                .commitAllowingStateLoss();
+        }
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        ContextMenuInfo info = item.getMenuInfo();
+
+        if (getView() == null) {
+            return true;
+        }
+
+        switch (item.getItemId()) {
+            case R.id.abouthome_open_new_tab:
+                mTopSitesView.openNewTab(info);
+                return true;
+
+            case R.id.abouthome_open_private_tab:
+                mTopSitesView.openNewPrivateTab(info);
+                return true;
+
+            case R.id.abouthome_topsites_edit:
+                mTopSitesView.editSite(info);
+                return true;
+
+            case R.id.abouthome_topsites_unpin:
+                mTopSitesView.unpinSite(info, TopSitesView.UnpinFlags.REMOVE_PIN);
+                return true;
+
+            case R.id.abouthome_topsites_pin:
+                mTopSitesView.pinSite(info);
+                return true;
+
+            case R.id.abouthome_topsites_remove:
+                mTopSitesView.unpinSite(info, TopSitesView.UnpinFlags.REMOVE_HISTORY);
+                return true;
+
+        }
+        return super.onContextItemSelected(item);
+    }
+
+    public void update(final EnumSet<UpdateFlags> flags) {
+        ThreadUtils.postToBackgroundThread(new Runnable() {
+            @Override
+            public void run() {
+                synchronized (AboutHome.this) {
+                    if (getView() == null) {
+                        return;
+                    }
+
+                    if (flags.contains(UpdateFlags.TOP_SITES)) {
+                        mTopSitesView.loadTopSites();
+                    }
+
+                    if (flags.contains(UpdateFlags.PREVIOUS_TABS)) {
+                        mLastTabsSection.readLastTabs();
+                    }
+
+                    if (flags.contains(UpdateFlags.RECOMMENDED_ADDONS)) {
+                        mAddonsSection.readRecommendedAddons();
+                    }
+
+                    if (flags.contains(UpdateFlags.REMOTE_TABS)) {
+                        mRemoteTabsSection.loadRemoteTabs();
+                    }
+                }
+            }
+        });
+    }
+
+    // Must be run on the UI thread
+    public void setLastTabsVisibility(boolean visible) {
+        if (mAboutHomeView == null) {
+            return;
+        }
+
+        if (visible)
+            mLastTabsSection.show();
+        else
+            mLastTabsSection.hide();
+    }
+
+    public void requestFocus() {
+        View view = getView();
+        if (view != null) {
+            view.requestFocus();
+        }
+    }
+
+    // Must be run on the UI thread
+    public void setPadding(int left, int top, int right, int bottom) {
+        View view = getView();
+        if (view != null) {
+            view.setPadding(left, top, right, bottom);
+        }
+
+        // If the padding has changed but the view hasn't been created yet,
+        // store the padding values here; they will be used later in
+        // onViewCreated().
+        mPaddingLeft = left;
+        mPaddingRight = right;
+        mPaddingTop = top;
+        mPaddingBottom = bottom;
+    }
+}
deleted file mode 100644
--- a/mobile/android/base/widget/AboutHomeContent.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.LightweightTheme;
-import org.mozilla.gecko.LightweightThemeDrawable;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.Context;
-import android.database.ContentObserver;
-import android.util.AttributeSet;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.LayoutInflater;
-import android.widget.ScrollView;
-
-import java.util.EnumSet;
-
-public class AboutHomeContent extends ScrollView
-                              implements LightweightTheme.OnChangeListener {
-    public static enum UpdateFlags {
-        TOP_SITES,
-        PREVIOUS_TABS,
-        RECOMMENDED_ADDONS,
-        REMOTE_TABS;
-
-        public static final EnumSet<UpdateFlags> ALL = EnumSet.allOf(UpdateFlags.class);
-    }
-
-    private Context mContext;
-    private BrowserApp mActivity;
-    private UriLoadCallback mUriLoadCallback = null;
-
-    private ContentObserver mTabsContentObserver = null;
-
-    protected TopSitesView mTopSites;
-    protected AddonsSection mAddons;
-    protected LastTabsSection mLastTabs;
-    protected RemoteTabsSection mRemoteTabs;
-    private PromoBox mPromoBox;
-
-    public interface UriLoadCallback {
-        public void callback(String uriSpec);
-    }
-
-    public interface VoidCallback {
-        public void callback();
-    }
-
-    public AboutHomeContent(Context context) {
-        super(context);
-        mContext = context;
-        mActivity = (BrowserApp) context;
-    }
-
-    public AboutHomeContent(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mContext = context;
-        mActivity = (BrowserApp) context;
-    }
-
-    public void init() {
-        inflate();
-
-        // Reload the mobile homepage on inbound tab syncs
-        // Because the tabs URI is coarse grained, this updates the
-        // remote tabs component on *every* tab change
-        // The observer will run on the background thread (see constructor argument)
-        mTabsContentObserver = new ContentObserver(ThreadUtils.getBackgroundHandler()) {
-            @Override
-            public void onChange(boolean selfChange) {
-                update(EnumSet.of(AboutHomeContent.UpdateFlags.REMOTE_TABS));
-            }
-        };
-        mActivity.getContentResolver().registerContentObserver(BrowserContract.Tabs.CONTENT_URI,
-                false, mTabsContentObserver);
-    }
-
-    private void inflate() {
-        LayoutInflater.from(mContext).inflate(R.layout.abouthome_content, this);
-
-        mTopSites = (TopSitesView) findViewById(R.id.top_sites_grid);
-        mPromoBox = (PromoBox) findViewById(R.id.promo_box);
-        mAddons = (AddonsSection) findViewById(R.id.recommended_addons);
-        mLastTabs = (LastTabsSection) findViewById(R.id.last_tabs);
-        mRemoteTabs = (RemoteTabsSection) findViewById(R.id.remote_tabs);
-
-        // Inform the new views about the UriLoadCallback
-        if (mUriLoadCallback != null)
-            setUriLoadCallback(mUriLoadCallback);
-
-        mPromoBox.showRandomPromo();
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mActivity.getLightweightTheme().addListener(this);
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mActivity.getLightweightTheme().removeListener(this);
-    }
-
-    public void onDestroy() {
-        if (mTopSites != null) {
-            mTopSites.onDestroy();
-        }
-
-        if (mTabsContentObserver != null) {
-            mActivity.getContentResolver().unregisterContentObserver(mTabsContentObserver);
-            mTabsContentObserver = null;
-        }
-    }
-
-    private void loadTopSites() {
-        mTopSites.loadTopSites();
-    }
-
-    public void openNewTab(ContextMenuInfo info) {
-        mTopSites.openNewTab(info);
-    }
-
-    public void openNewPrivateTab(ContextMenuInfo info) {
-        mTopSites.openNewPrivateTab(info);
-    }
-
-    public void pinSite(ContextMenuInfo info) {
-        mTopSites.pinSite(info);
-    }
-
-    public void unpinSite(ContextMenuInfo info, final TopSitesView.UnpinFlags flags) {
-        mTopSites.unpinSite(info, flags);
-    }
-
-    public void editSite(ContextMenuInfo info) {
-        mTopSites.editSite(info);
-    }
-
-    public void update(final EnumSet<UpdateFlags> flags) {
-        ThreadUtils.postToBackgroundThread(new Runnable() {
-            @Override
-            public void run() {
-                if (flags.contains(UpdateFlags.TOP_SITES))
-                    loadTopSites();
-
-                if (flags.contains(UpdateFlags.PREVIOUS_TABS))
-                    readLastTabs();
-
-                if (flags.contains(UpdateFlags.RECOMMENDED_ADDONS))
-                    readRecommendedAddons();
-
-                if (flags.contains(UpdateFlags.REMOTE_TABS))
-                    loadRemoteTabs();
-            }
-        });
-    }
-
-    public void setUriLoadCallback(UriLoadCallback uriLoadCallback) {
-        mUriLoadCallback = uriLoadCallback;
-        mTopSites.setUriLoadCallback(uriLoadCallback);
-        mAddons.setUriLoadCallback(uriLoadCallback);
-    }
-
-    public void setLoadCompleteCallback(VoidCallback callback) {
-        if (mTopSites != null)
-            mTopSites.setLoadCompleteCallback(callback);
-    }
-
-    public void onActivityContentChanged() {
-        update(EnumSet.of(UpdateFlags.TOP_SITES));
-    }
-
-    /**
-     * Reinflates and updates all components of this view.
-     */
-    public void refresh() {
-        mTopSites.onDestroy();
-
-        // We must remove the currently inflated view to allow for reinflation.
-        removeAllViews();
-
-        inflate();
-
-        // Refresh all elements.
-        update(AboutHomeContent.UpdateFlags.ALL);
-    }
-
-    public void setLastTabsVisibility(boolean visible) {
-        if (visible)
-            mLastTabs.show();
-        else
-            mLastTabs.hide();
-    }
-
-    private void readLastTabs() {
-        mLastTabs.readLastTabs(mActivity.getProfile());
-    }
-
-    private void loadRemoteTabs() {
-        mRemoteTabs.loadRemoteTabs();
-    }
-
-    private void readRecommendedAddons() {
-        mAddons.readRecommendedAddons();
-    }
-
-    @Override
-    public void onLightweightThemeChanged() {
-        LightweightThemeDrawable drawable = mActivity.getLightweightTheme().getColorDrawable(this);
-        if (drawable == null)
-            return;
-
-         drawable.setAlpha(255, 0);
-         setBackgroundDrawable(drawable);
-    }
-
-    @Override
-    public void onLightweightThemeReset() {
-        setBackgroundColor(getContext().getResources().getColor(R.color.background_normal));
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        onLightweightThemeChanged();
-    }
-}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/AboutHomeView.java
@@ -0,0 +1,55 @@
+package org.mozilla.gecko.widget;
+
+import org.mozilla.gecko.LightweightTheme;
+import org.mozilla.gecko.LightweightThemeDrawable;
+import org.mozilla.gecko.R;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ScrollView;
+
+public class AboutHomeView extends ScrollView implements LightweightTheme.OnChangeListener {
+    private LightweightTheme mLightweightTheme;
+
+    public AboutHomeView(Context context) {
+        super(context);
+    }
+
+    public AboutHomeView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AboutHomeView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setLightweightTheme(LightweightTheme theme) {
+        mLightweightTheme = theme;
+    }
+
+    @Override
+    public void onLightweightThemeChanged() {
+        if (mLightweightTheme == null) {
+            throw new IllegalStateException("setLightweightTheme() must be called before this view can listen to theme changes.");
+        }
+
+        LightweightThemeDrawable drawable = mLightweightTheme.getColorDrawable(this);
+        if (drawable == null) {
+            return;
+        }
+
+        drawable.setAlpha(255, 0);
+        setBackgroundDrawable(drawable);
+    }
+
+    @Override
+    public void onLightweightThemeReset() {
+        setBackgroundColor(getResources().getColor(R.color.background_normal));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        onLightweightThemeChanged();
+    }
+}
--- a/mobile/android/base/widget/AddonsSection.java
+++ b/mobile/android/base/widget/AddonsSection.java
@@ -35,41 +35,41 @@ import java.net.URL;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 public class AddonsSection extends AboutHomeSection {
     private static final String LOGTAG = "GeckoAboutHomeAddons";
 
     private Context mContext;
     private BrowserApp mActivity;
-    private AboutHomeContent.UriLoadCallback mUriLoadCallback = null;
+    private AboutHome.UriLoadListener mUriLoadListener;
 
     private static Rect sIconBounds;
     private static TextAppearanceSpan sSubTitleSpan;
 
     public AddonsSection(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
         mActivity = (BrowserApp) context;
 
         int iconSize = mContext.getResources().getDimensionPixelSize(R.dimen.abouthome_addon_icon_size);
         sIconBounds = new Rect(0, 0, iconSize, iconSize);
         sSubTitleSpan = new TextAppearanceSpan(mContext, R.style.AboutHome_TextAppearance_SubTitle);
 
         setOnMoreTextClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                if (mUriLoadCallback != null)
-                    mUriLoadCallback.callback("https://addons.mozilla.org/android");
+                if (mUriLoadListener != null)
+                    mUriLoadListener.onAboutHomeUriLoad(mContext.getString(R.string.bookmarkdefaults_url_addons));
             }
         });
     }
 
-    public void setUriLoadCallback(AboutHomeContent.UriLoadCallback uriLoadCallback) {
-        mUriLoadCallback = uriLoadCallback;
+    public void setUriLoadListener(AboutHome.UriLoadListener uriLoadListener) {
+        mUriLoadListener = uriLoadListener;
     }
 
     private String readFromZipFile(String filename) {
         ZipFile zip = null;
         String str = null;
         try {
             InputStream fileStream = null;
             File applicationPackage = new File(mActivity.getApplication().getPackageResourcePath());
@@ -185,18 +185,18 @@ public class AddonsSection extends About
 
                         String iconUrl = jsonobj.getString("iconURL");
                         String pageUrl = getPageUrlFromIconUrl(iconUrl);
 
                         final String homepageUrl = jsonobj.getString("homepageURL");
                         row.setOnClickListener(new View.OnClickListener() {
                             @Override
                             public void onClick(View v) {
-                                if (mUriLoadCallback != null)
-                                    mUriLoadCallback.callback(homepageUrl);
+                                if (mUriLoadListener != null)
+                                    mUriLoadListener.onAboutHomeUriLoad(homepageUrl);
                             }
                         });
                         row.setOnKeyListener(GamepadUtils.getClickDispatcher());
 
                         Favicons favicons = Favicons.getInstance();
                         favicons.loadFavicon(pageUrl, iconUrl, true,
                                     new Favicons.OnFaviconLoadedListener() {
                             @Override
--- a/mobile/android/base/widget/LastTabsSection.java
+++ b/mobile/android/base/widget/LastTabsSection.java
@@ -26,18 +26,18 @@ import java.util.ArrayList;
 public class LastTabsSection extends AboutHomeSection {
     private Context mContext;
 
     public LastTabsSection(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
     }
 
-    public void readLastTabs(GeckoProfile profile) {
-        String jsonString = profile.readSessionFile(true);
+    public void readLastTabs() {
+        String jsonString = GeckoProfile.get(mContext).readSessionFile(true);
         if (jsonString == null) {
             // no previous session data
             return;
         }
 
         final ArrayList<String> lastTabUrlsList = new ArrayList<String>();
         new SessionParser() {
             @Override
--- a/mobile/android/base/widget/TopSitesView.java
+++ b/mobile/android/base/widget/TopSitesView.java
@@ -61,18 +61,18 @@ public class TopSitesView extends GridVi
 
     public static enum UnpinFlags {
         REMOVE_PIN,
         REMOVE_HISTORY
     }
 
     private Context mContext;
     private BrowserApp mActivity;
-    private AboutHomeContent.UriLoadCallback mUriLoadCallback = null;
-    private AboutHomeContent.VoidCallback mLoadCompleteCallback = null;
+    private AboutHome.UriLoadListener mUriLoadListener;
+    private AboutHome.LoadCompleteListener mLoadCompleteListener;
 
     protected TopSitesCursorAdapter mTopSitesAdapter;
 
     private static Drawable sPinDrawable = null;
     private int mThumbnailBackground;
     private Map<String, Bitmap> mPendingThumbnails;
 
     public TopSitesView(Context context) {
@@ -100,18 +100,18 @@ public class TopSitesView extends GridVi
                 String spec = holder.getUrl();
 
                 // If we don't have a url, this must be an empty row. Show the edit dialog box
                 if (TextUtils.isEmpty(spec)) {
                     editSite(spec, position);
                     return;
                 }
 
-                if (mUriLoadCallback != null)
-                    mUriLoadCallback.callback(spec);
+                if (mUriLoadListener != null)
+                    mUriLoadListener.onAboutHomeUriLoad(spec);
             }
         });
 
         setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
             @Override
             public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
                 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
 
@@ -219,18 +219,18 @@ public class TopSitesView extends GridVi
 
                 // Free the old Cursor in the right thread now.
                 if (oldCursor != null && !oldCursor.isClosed())
                     oldCursor.close();
 
                 // Even if AboutHome isn't necessarily entirely loaded if we
                 // get here, for phones this is the part the user initially sees,
                 // so it's the one we will care about for now.
-                if (mLoadCompleteCallback != null)
-                    mLoadCompleteCallback.callback();
+                if (mLoadCompleteListener != null)
+                    mLoadCompleteListener.onAboutHomeLoadComplete();
             }
         });
     }
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
@@ -349,22 +349,22 @@ public class TopSitesView extends GridVi
         }).execute();
     }
 
     private void setTopSitesConstants() {
         mNumberOfTopSites = getResources().getInteger(R.integer.number_of_top_sites);
         mNumberOfCols = getResources().getInteger(R.integer.number_of_top_sites_cols);
     }
 
-    public void setUriLoadCallback(AboutHomeContent.UriLoadCallback uriLoadCallback) {
-        mUriLoadCallback = uriLoadCallback;
+    public void setUriLoadListener(AboutHome.UriLoadListener uriLoadListener) {
+        mUriLoadListener = uriLoadListener;
     }
 
-    public void setLoadCompleteCallback(AboutHomeContent.VoidCallback callback) {
-        mLoadCompleteCallback = callback;
+    public void setLoadCompleteListener(AboutHome.LoadCompleteListener listener) {
+        mLoadCompleteListener = listener;
     }
 
     private class TopSitesViewHolder {
         public TextView titleView = null;
         public ImageView thumbnailView = null;
         public ImageView pinnedView = null;
         private String mTitle = null;
         private String mUrl = null;