Bug 865060 - Clean up threads in AboutHome views. r=lucasr
authorBrian Nicholson <bnicholson@mozilla.com>
Wed, 24 Apr 2013 11:11:21 -0700
changeset 140728 7190f15f3f686d17d7c7c88115e227da4c51d0c0
parent 140727 d73d6d6be727a9921322e41b369da20d045ecfd4
child 140729 d38a000208e936dd14da653b6a2bfc28dc3320a9
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslucasr
bugs865060
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 865060 - Clean up threads in AboutHome views. r=lucasr
mobile/android/base/widget/AddonsSection.java
mobile/android/base/widget/LastTabsSection.java
mobile/android/base/widget/RemoteTabsSection.java
mobile/android/base/widget/TopSitesView.java
--- a/mobile/android/base/widget/AddonsSection.java
+++ b/mobile/android/base/widget/AddonsSection.java
@@ -5,32 +5,34 @@
 
 package org.mozilla.gecko.widget;
 
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.Favicons;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.UiAsyncTask;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.text.SpannableString;
 import android.text.style.TextAppearanceSpan;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.TextView;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.zip.ZipEntry;
@@ -133,19 +135,19 @@ public class AddonsSection extends About
         } catch (MalformedURLException e) {
             // Defaults to pageUrl = iconUrl in case of error
         }
 
         return pageUrl;
     }
 
     public void readRecommendedAddons() {
-        ThreadUtils.postToBackgroundThread(new Runnable() {
+        new UiAsyncTask<Void, Void, JSONArray>(ThreadUtils.getBackgroundHandler()) {
             @Override
-            public void run() {
+            public JSONArray doInBackground(Void... params) {
                 final String addonsFilename = "recommended-addons.json";
                 String jsonString;
                 try {
                     jsonString = mActivity.getProfile().readFile(addonsFilename);
                 } catch (IOException ioe) {
                     Log.i(LOGTAG, "filestream is null");
                     jsonString = readFromZipFile(addonsFilename);
                 }
@@ -154,73 +156,77 @@ public class AddonsSection extends About
                 if (jsonString != null) {
                     try {
                         addonsArray = new JSONObject(jsonString).getJSONArray("addons");
                     } catch (JSONException e) {
                         Log.i(LOGTAG, "error reading json file", e);
                     }
                 }
 
-                final JSONArray array = addonsArray;
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            if (array == null || array.length() == 0) {
-                                hide();
-                                return;
-                            }
+                return addonsArray;
+            }
+
+            @Override
+            public void onPostExecute(JSONArray addons) {
+                if (addons == null || addons.length() == 0) {
+                    hide();
+                    return;
+                }
 
-                            for (int i = 0; i < array.length(); i++) {
-                                JSONObject jsonobj = array.getJSONObject(i);
-                                String name = jsonobj.getString("name");
-                                String version = jsonobj.getString("version");
-                                String text = name + " " + version;
-
-                                SpannableString spannable = new SpannableString(text);
-                                spannable.setSpan(sSubTitleSpan, name.length() + 1, text.length(), 0);
+                try {
+                    for (int i = 0; i < addons.length(); i++) {
+                        View addonView = createAddonView(addons.getJSONObject(i), getItemsContainer());
+                        addItem(addonView);
+                    }
+                } catch (JSONException e) {
+                    Log.e(LOGTAG, "Error reading JSON", e);
+                    return;
+                }
 
-                                final TextView row = (TextView) LayoutInflater.from(mContext).inflate(R.layout.abouthome_addon_row, getItemsContainer(), false);
-                                row.setText(spannable, TextView.BufferType.SPANNABLE);
-
-                                Drawable drawable = mContext.getResources().getDrawable(R.drawable.ic_addons_empty);
-                                drawable.setBounds(sIconBounds);
-                                row.setCompoundDrawables(drawable, null, null, null);
-
-                                String iconUrl = jsonobj.getString("iconURL");
-                                String pageUrl = getPageUrlFromIconUrl(iconUrl);
+                show();
+            }
+        }.execute();
+    }
 
-                                final String homepageUrl = jsonobj.getString("homepageURL");
-                                row.setOnClickListener(new View.OnClickListener() {
-                                    @Override
-                                    public void onClick(View v) {
-                                        if (mUriLoadListener != null)
-                                            mUriLoadListener.onAboutHomeUriLoad(homepageUrl);
-                                    }
-                                });
-                                row.setOnKeyListener(GamepadUtils.getClickDispatcher());
+    View createAddonView(JSONObject addonJSON, ViewGroup parent) throws JSONException {
+        String name = addonJSON.getString("name");
+        String version = addonJSON.getString("version");
+        String text = name + " " + version;
+
+        SpannableString spannable = new SpannableString(text);
+        spannable.setSpan(sSubTitleSpan, name.length() + 1, text.length(), 0);
+
+        final TextView row = (TextView) LayoutInflater.from(mContext).inflate(R.layout.abouthome_addon_row, getItemsContainer(), false);
+        row.setText(spannable, TextView.BufferType.SPANNABLE);
 
-                                Favicons favicons = Favicons.getInstance();
-                                favicons.loadFavicon(pageUrl, iconUrl, true,
-                                            new Favicons.OnFaviconLoadedListener() {
-                                    @Override
-                                    public void onFaviconLoaded(String url, Bitmap favicon) {
-                                        if (favicon != null) {
-                                            Drawable drawable = new BitmapDrawable(favicon);
-                                            drawable.setBounds(sIconBounds);
-                                            row.setCompoundDrawables(drawable, null, null, null);
-                                        }
-                                    }
-                                });
+        Drawable drawable = mContext.getResources().getDrawable(R.drawable.ic_addons_empty);
+        drawable.setBounds(sIconBounds);
+        row.setCompoundDrawables(drawable, null, null, null);
+
+        String iconUrl = addonJSON.getString("iconURL");
+        String pageUrl = getPageUrlFromIconUrl(iconUrl);
 
-                                addItem(row);
-                            }
-
-                            show();
-                        } catch (JSONException e) {
-                            Log.i(LOGTAG, "error reading json file", e);
-                        }
-                    }
-                });
+        final String homepageUrl = addonJSON.getString("homepageURL");
+        row.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mUriLoadListener != null)
+                    mUriLoadListener.onAboutHomeUriLoad(homepageUrl);
             }
         });
+        row.setOnKeyListener(GamepadUtils.getClickDispatcher());
+
+        Favicons favicons = Favicons.getInstance();
+        favicons.loadFavicon(pageUrl, iconUrl, true,
+                new Favicons.OnFaviconLoadedListener() {
+            @Override
+            public void onFaviconLoaded(String url, Bitmap favicon) {
+                if (favicon != null) {
+                    Drawable drawable = new BitmapDrawable(favicon);
+                    drawable.setBounds(sIconBounds);
+                    row.setCompoundDrawables(drawable, null, null, null);
+                }
+            }
+        });
+
+        return row;
     }
 }
--- a/mobile/android/base/widget/LastTabsSection.java
+++ b/mobile/android/base/widget/LastTabsSection.java
@@ -7,108 +7,135 @@ package org.mozilla.gecko.widget;
 
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SessionParser;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.UiAsyncTask;
 
 import android.content.ContentResolver;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 import java.util.ArrayList;
 
 public class LastTabsSection extends AboutHomeSection {
     private Context mContext;
 
+    private class TabInfo {
+        final String url;
+        final String title;
+        final Bitmap favicon;
+
+        TabInfo(String url, String title, Bitmap favicon) {
+            this.url = url;
+            this.title = title;
+            this.favicon = favicon;
+        }
+    }
+
     public LastTabsSection(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
     }
 
     public void readLastTabs() {
-        ThreadUtils.postToBackgroundThread(new Runnable() {
+        new UiAsyncTask<Void, Void, ArrayList<TabInfo>>(ThreadUtils.getBackgroundHandler()) {
             @Override
-            public void run() {
+            protected ArrayList<TabInfo> doInBackground(Void... params) {
                 String jsonString = GeckoProfile.get(mContext).readSessionFile(true);
                 if (jsonString == null) {
                     // no previous session data
-                    return;
+                    return null;
                 }
 
-                final ArrayList<String> lastTabUrlsList = new ArrayList<String>();
+                final ArrayList<TabInfo> lastTabs = new ArrayList<TabInfo>();
                 new SessionParser() {
                     @Override
                     public void onTabRead(final SessionTab tab) {
                         final String url = tab.getSelectedUrl();
                         // don't show last tabs for about:home
                         if (url.equals("about:home")) {
                             return;
                         }
 
                         ContentResolver resolver = mContext.getContentResolver();
                         final Bitmap favicon = BrowserDB.getFaviconForUrl(resolver, url);
-                        lastTabUrlsList.add(url);
-
-                        LastTabsSection.this.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                View container = LayoutInflater.from(mContext).inflate(R.layout.abouthome_last_tabs_row, getItemsContainer(), false);
-                                ((TextView) container.findViewById(R.id.last_tab_title)).setText(tab.getSelectedTitle());
-                                ((TextView) container.findViewById(R.id.last_tab_url)).setText(tab.getSelectedUrl());
-                                if (favicon != null) {
-                                    ((ImageView) container.findViewById(R.id.last_tab_favicon)).setImageBitmap(favicon);
-                                }
-
-                                container.setOnClickListener(new View.OnClickListener() {
-                                    @Override
-                                    public void onClick(View v) {
-                                        int flags = Tabs.LOADURL_NEW_TAB;
-                                        if (Tabs.getInstance().getSelectedTab().isPrivate())
-                                            flags |= Tabs.LOADURL_PRIVATE;
-                                        Tabs.getInstance().loadUrl(url, flags);
-                                    }
-                                });
-                                container.setOnKeyListener(GamepadUtils.getClickDispatcher());
-
-                                addItem(container);
-                            }
-                        });
+                        final String title = tab.getSelectedTitle();
+                        lastTabs.add(new TabInfo(url, title, favicon));
                     }
                 }.parse(jsonString);
 
-                final int nu = lastTabUrlsList.size();
-                if (nu >= 1) {
-                    post(new Runnable() {
+                return lastTabs;
+            }
+
+            @Override
+            public void onPostExecute(final ArrayList<TabInfo> lastTabs) {
+                if (lastTabs == null) {
+                    return;
+                }
+
+                for (TabInfo tab : lastTabs) {
+                    final View tabView = createTabView(tab, getItemsContainer());
+                    addItem(tabView);
+                }
+
+                // If we have at least one tab from last time, show the
+                // container view.
+                final int numTabs = lastTabs.size();
+                if (numTabs > 1) {
+                    // If we have more than one tab from last time, show the
+                    // "Open all tabs" button
+                    showMoreText();
+                    setOnMoreTextClickListener(new View.OnClickListener() {
                         @Override
-                        public void run() {
-                            if (nu > 1) {
-                                showMoreText();
-                                setOnMoreTextClickListener(new View.OnClickListener() {
-                                    @Override
-                                    public void onClick(View v) {
-                                        int flags = Tabs.LOADURL_NEW_TAB;
-                                        if (Tabs.getInstance().getSelectedTab().isPrivate())
-                                            flags |= Tabs.LOADURL_PRIVATE;
-                                        for (String url : lastTabUrlsList) {
-                                            Tabs.getInstance().loadUrl(url, flags);
-                                        }
-                                    }
-                                });
-                            } else if (nu == 1) {
-                                hideMoreText();
+                        public void onClick(View v) {
+                            int flags = Tabs.LOADURL_NEW_TAB;
+                            if (Tabs.getInstance().getSelectedTab().isPrivate())
+                                flags |= Tabs.LOADURL_PRIVATE;
+                            for (TabInfo tab : lastTabs) {
+                                Tabs.getInstance().loadUrl(tab.url, flags);
                             }
-                            show();
                         }
                     });
+                    show();
+                } else if (numTabs == 1) {
+                    hideMoreText();
+                    show();
                 }
             }
+        }.execute();
+    }
+
+    View createTabView(final TabInfo tab, ViewGroup parent) {
+        final String url = tab.url;
+        final Bitmap favicon = tab.favicon;
+
+        View tabView = LayoutInflater.from(mContext).inflate(R.layout.abouthome_last_tabs_row, parent, false);
+        ((TextView) tabView.findViewById(R.id.last_tab_title)).setText(tab.title);
+        ((TextView) tabView.findViewById(R.id.last_tab_url)).setText(url);
+        if (favicon != null) {
+            ((ImageView) tabView.findViewById(R.id.last_tab_favicon)).setImageBitmap(favicon);
+        }
+
+        tabView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                int flags = Tabs.LOADURL_NEW_TAB;
+                if (Tabs.getInstance().getSelectedTab().isPrivate())
+                    flags |= Tabs.LOADURL_PRIVATE;
+                Tabs.getInstance().loadUrl(url, flags);
+            }
         });
+        tabView.setOnKeyListener(GamepadUtils.getClickDispatcher());
+
+        return tabView;
     }
 }
--- a/mobile/android/base/widget/RemoteTabsSection.java
+++ b/mobile/android/base/widget/RemoteTabsSection.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko.widget;
 
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.TabsAccessor;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.UiAsyncTask;
 
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.TextView;
 
@@ -51,32 +52,32 @@ public class RemoteTabsSection extends A
                 if (Tabs.getInstance().getSelectedTab().isPrivate())
                     flags |= Tabs.LOADURL_PRIVATE;
                 Tabs.getInstance().loadUrl((String) v.getTag(), flags);
             }
         };
     }
 
     public void loadRemoteTabs() {
-        ThreadUtils.postToBackgroundThread(new Runnable() {
+        new UiAsyncTask<Void, Void, Boolean>(ThreadUtils.getBackgroundHandler()) {
             @Override
-            public void run() {
+            public Boolean doInBackground(Void... params) {
                 if (!SyncAccounts.syncAccountsExist(mActivity)) {
-                    post(new Runnable() {
-                        @Override
-                        public void run() {
-                            hide();
-                        }
-                    });
-                    return;
+                    return false;
                 }
+                TabsAccessor.getTabs(getContext(), NUMBER_OF_REMOTE_TABS, RemoteTabsSection.this);
+                return true;
+            }
 
-                TabsAccessor.getTabs(getContext(), NUMBER_OF_REMOTE_TABS, RemoteTabsSection.this);
+            public void onPostExecute(Boolean syncAccountsExist) {
+                if (!syncAccountsExist) {
+                    hide();
+                }
             }
-        });
+        }.execute();
     }
 
     @Override
     public void onQueryTabsComplete(List<TabsAccessor.RemoteTab> tabsList) {
         ArrayList<TabsAccessor.RemoteTab> tabs = new ArrayList<TabsAccessor.RemoteTab> (tabsList);
         if (tabs == null || tabs.size() == 0) {
             hide();
             return;
--- a/mobile/android/base/widget/TopSitesView.java
+++ b/mobile/android/base/widget/TopSitesView.java
@@ -189,56 +189,53 @@ public class TopSitesView extends GridVi
         int w = getColumnWidth(measuredWidth);
         ThumbnailHelper.getInstance().setThumbnailWidth(w);
         heightMeasureSpec = MeasureSpec.makeMeasureSpec((int)(w*ThumbnailHelper.THUMBNAIL_ASPECT_RATIO*numRows) + getPaddingTop() + getPaddingBottom(),
                                                              MeasureSpec.EXACTLY);
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
     public void loadTopSites() {
-        ThreadUtils.postToBackgroundThread(new Runnable() {
+        final ContentResolver resolver = mContext.getContentResolver();
+        final Cursor oldCursor = (mTopSitesAdapter != null) ? mTopSitesAdapter.getCursor() : null;
+
+        new UiAsyncTask<Void, Void, Cursor>(ThreadUtils.getBackgroundHandler()) {
             @Override
-            public void run() {
-                final ContentResolver resolver = mContext.getContentResolver();
-
-                // Swap in the new cursor.
-                final Cursor oldCursor = (mTopSitesAdapter != null) ? mTopSitesAdapter.getCursor() : null;
-                final Cursor newCursor = BrowserDB.getTopSites(resolver, mNumberOfTopSites);
+            protected Cursor doInBackground(Void... params) {
+                return BrowserDB.getTopSites(resolver, mNumberOfTopSites);
+            }
 
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (mTopSitesAdapter == null) {
-                            mTopSitesAdapter = new TopSitesCursorAdapter(mContext,
-                                                                         R.layout.abouthome_topsite_item,
-                                                                         newCursor,
-                                                                         new String[] { URLColumns.TITLE },
-                                                                         new int[] { R.id.title });
+            @Override
+            protected void onPostExecute(Cursor newCursor) {
+                if (mTopSitesAdapter == null) {
+                    mTopSitesAdapter = new TopSitesCursorAdapter(mContext,
+                                                                 R.layout.abouthome_topsite_item,
+                                                                 newCursor,
+                                                                 new String[] { URLColumns.TITLE },
+                                                                 new int[] { R.id.title });
 
-                            setAdapter(mTopSitesAdapter);
-                        } else {
-                            mTopSitesAdapter.changeCursor(newCursor);
-                        }
+                    setAdapter(mTopSitesAdapter);
+                } else {
+                    mTopSitesAdapter.changeCursor(newCursor);
+                }
 
-                        if (mTopSitesAdapter.getCount() > 0)
-                            loadTopSitesThumbnails(resolver);
+                if (mTopSitesAdapter.getCount() > 0)
+                    loadTopSitesThumbnails(resolver);
 
-                        // Free the old Cursor in the right thread now.
-                        if (oldCursor != null && !oldCursor.isClosed())
-                            oldCursor.close();
+                // 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 (mLoadCompleteListener != null)
-                            mLoadCompleteListener.onAboutHomeLoadComplete();
-                    }
-                });
+                // 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 (mLoadCompleteListener != null)
+                    mLoadCompleteListener.onAboutHomeLoadComplete();
             }
-        });
+        }.execute();
     }
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
         if (mPendingThumbnails != null) {
             updateTopSitesThumbnails(mPendingThumbnails);
@@ -326,28 +323,33 @@ public class TopSitesView extends GridVi
             if (c != null)
                 c.close();
         }
 
         return thumbnails;
     }
 
     private void loadTopSitesThumbnails(final ContentResolver cr) {
-        final List<String> urls = getTopSitesUrls();
-        if (urls.size() == 0)
-            return;
-
         (new UiAsyncTask<Void, Void, Map<String, Bitmap> >(ThreadUtils.getBackgroundHandler()) {
             @Override
             public Map<String, Bitmap> doInBackground(Void... params) {
+                final List<String> urls = getTopSitesUrls();
+                if (urls.size() == 0) {
+                    return null;
+                }
+
                 return getThumbnailsFromCursor(BrowserDB.getThumbnailsForUrls(cr, urls));
             }
 
             @Override
             public void onPostExecute(Map<String, Bitmap> thumbnails) {
+                if (thumbnails == null) {
+                    return;
+                }
+
                 // If we're waiting for a layout to happen, the GridView may be
                 // stale, so store the pending thumbnails here. They will be
                 // shown on the next layout pass.
                 if (isLayoutRequested()) {
                     mPendingThumbnails = thumbnails;
                 } else {
                     updateTopSitesThumbnails(thumbnails);
                 }