Bug 824994 - Hide the marketplace promo if the user has visited the marketplace before r=mfinkle
authorWes Johnston <wjohnston@mozilla.com>
Wed, 30 Jan 2013 12:26:07 -0500
changeset 120385 e93f506bc784b4f07071dad621dd905264fc9117
parent 120384 f13ebcc74a0dcc82988d4b29f4eef853c78afe8c
child 120386 7cd7a9bc640666ff805f71fb0de12021576861df
push id24251
push userryanvm@gmail.com
push dateThu, 31 Jan 2013 20:56:22 +0000
treeherdermozilla-central@683b08dc1afd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs824994
milestone21.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 824994 - Hide the marketplace promo if the user has visited the marketplace before r=mfinkle
mobile/android/base/AboutHomeContent.java
mobile/android/base/AboutHomePromoBox.java
mobile/android/base/db/BrowserDB.java
mobile/android/base/db/LocalBrowserDB.java
--- a/mobile/android/base/AboutHomeContent.java
+++ b/mobile/android/base/AboutHomeContent.java
@@ -14,19 +14,16 @@ import org.mozilla.gecko.db.BrowserDB.To
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.GeckoAsyncTask;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.OnAccountsUpdateListener;
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -94,26 +91,22 @@ public class AboutHomeContent extends Sc
     }
 
     private Context mContext;
     private BrowserApp mActivity;
     UriLoadCallback mUriLoadCallback = null;
     VoidCallback mLoadCompleteCallback = null;
     private LayoutInflater mInflater;
 
-    private AccountManager mAccountManager;
-    private OnAccountsUpdateListener mAccountListener = null;
-
     private ContentObserver mTabsContentObserver = null;
 
     protected TopSitesCursorAdapter mTopSitesAdapter;
     protected TopSitesGridView mTopSitesGrid;
 
     private AboutHomePromoBox mPromoBox;
-    private AboutHomePromoBox.Type mPrelimPromoBoxType;
     protected AboutHomeSection mAddons;
     protected AboutHomeSection mLastTabs;
     protected AboutHomeSection mRemoteTabs;
 
     private View.OnClickListener mRemoteTabClickListener;
 
     private static Rect sIconBounds;
     private static TextAppearanceSpan sSubTitleSpan;
@@ -141,25 +134,16 @@ public class AboutHomeContent extends Sc
 
     public void init() {
         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);
 
         inflate();
 
-        mAccountManager = AccountManager.get(mContext);
-
-        // The listener will run on the background thread (see 2nd argument)
-        mAccountManager.addOnAccountsUpdatedListener(mAccountListener = new OnAccountsUpdateListener() {
-            public void onAccountsUpdated(Account[] accounts) {
-                updateLayoutForSync();
-            }
-        }, GeckoAppShell.getHandler(), false);
-        
         // 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(GeckoAppShell.getHandler()) {
             public void onChange(boolean selfChange) {
                 update(EnumSet.of(AboutHomeContent.UpdateFlags.REMOTE_TABS));
             }
@@ -171,19 +155,16 @@ public class AboutHomeContent extends Sc
             @Override
             public void onClick(View v) {
                 int flags = Tabs.LOADURL_NEW_TAB;
                 if (Tabs.getInstance().getSelectedTab().isPrivate())
                     flags |= Tabs.LOADURL_PRIVATE;
                 Tabs.getInstance().loadUrl((String) v.getTag(), flags);
             }
         };
-
-        mPrelimPromoBoxType = (new Random()).nextFloat() < 0.5 ? AboutHomePromoBox.Type.SYNC :
-                AboutHomePromoBox.Type.APPS;
     }
 
     private void inflate() {
         mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         mInflater.inflate(R.layout.abouthome_content, this);
 
         mTopSitesGrid = (TopSitesGridView)findViewById(R.id.top_sites_grid);
         mTopSitesGrid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@@ -256,21 +237,16 @@ public class AboutHomeContent extends Sc
 
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mActivity.getLightweightTheme().removeListener(this);
     }
 
     public void onDestroy() {
-        if (mAccountListener != null) {
-            mAccountManager.removeOnAccountsUpdatedListener(mAccountListener);
-            mAccountListener = null;
-        }
-
         if (mTopSitesAdapter != null) {
             Cursor cursor = mTopSitesAdapter.getCursor();
             if (cursor != null && !cursor.isClosed())
                 cursor.close();
         }
 
         if (mTabsContentObserver != null) {
             mActivity.getContentResolver().unregisterContentObserver(mTabsContentObserver);
@@ -287,47 +263,37 @@ public class AboutHomeContent extends Sc
 
     private void setTopSitesVisibility(boolean hasTopSites) {
         int visibility = hasTopSites ? View.VISIBLE : View.GONE;
 
         findViewById(R.id.top_sites_title).setVisibility(visibility);
         findViewById(R.id.top_sites_grid).setVisibility(visibility);
     }
 
-    private void updateLayout(boolean syncIsSetup) {
+    private void updateLayout() {
         boolean hasTopSites = mTopSitesAdapter.getCount() > 0;
         setTopSitesVisibility(hasTopSites);
-
-        AboutHomePromoBox.Type type = mPrelimPromoBoxType;
-        if (syncIsSetup && type == AboutHomePromoBox.Type.SYNC)
-            type = AboutHomePromoBox.Type.APPS;
-
-        mPromoBox.show(type);
+        mPromoBox.showRandomPromo();
     }
 
     private void updateLayoutForSync() {
         final GeckoApp.StartupMode startupMode = mActivity.getStartupMode();
-        final boolean syncIsSetup = SyncAccounts.syncAccountsExist(mContext);
 
         post(new Runnable() {
             public void run() {
                 // The listener might run before the UI is initially updated.
                 // In this case, we should simply wait for the initial setup
                 // to happen.
                 if (mTopSitesAdapter != null)
-                    updateLayout(syncIsSetup);
+                    updateLayout();
             }
         });
     }
 
     private void loadTopSites() {
-        // The SyncAccounts.syncAccountsExist method should not be called on
-        // UI thread as it touches disk to access a sqlite DB.
-        final boolean syncIsSetup = SyncAccounts.syncAccountsExist(mActivity);
-
         final ContentResolver resolver = mActivity.getContentResolver();
         Cursor old = null;
         if (mTopSitesAdapter != null) {
             old = mTopSitesAdapter.getCursor();
         }
         // Swap in the new cursor.
         final Cursor oldCursor = old;
         final Cursor newCursor = BrowserDB.getTopSites(resolver, mNumberOfTopSites);
@@ -344,17 +310,17 @@ public class AboutHomeContent extends Sc
                     mTopSitesGrid.setAdapter(mTopSitesAdapter);
                 } else {
                     mTopSitesAdapter.changeCursor(newCursor);
                 }
 
                 if (mTopSitesAdapter.getCount() > 0)
                     loadTopSitesThumbnails(resolver);
 
-                updateLayout(syncIsSetup);
+                updateLayout();
 
                 // 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.
--- a/mobile/android/base/AboutHomePromoBox.java
+++ b/mobile/android/base/AboutHomePromoBox.java
@@ -1,129 +1,199 @@
 /* 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.db.BrowserDB;
 import org.mozilla.gecko.sync.setup.activities.SetupSyncActivity;
+import org.mozilla.gecko.sync.setup.SyncAccounts;
+import org.mozilla.gecko.util.GeckoAsyncTask;
 
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.OnAccountsUpdateListener;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.text.SpannableString;
 import android.text.style.StyleSpan;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.widget.TextView;
 
+import java.util.ArrayList;
+import java.util.Random;
+
 /**
  * A promotional box for the about:home page. The layout contains an ImageView to the left of a
  * TextView whose resources may be overidden to display custom values for a new type of promo box.
  * To do this, add a new Type value and update show() to call setResources() for your values -
  * including a set[Box Type]Resources() helper method is recommended.
  */
 public class AboutHomePromoBox extends TextView implements View.OnClickListener {
-    private static final String LOGTAG = "AboutHomePromoBox";
+    private static final String LOGTAG = "GeckoAboutHomePromoBox";
+
+    /* Small class for implementing a new promo box type. Implementors should override canShow and onClick
+     * to handle their own needs. By default the box is always showable and does nothing when clicked.
+     */
+    public static class Type {
+        public int text;
+        public int boldText;
+        public int image;
+        public Type(int aText, int aBoldText, int aImage) {
+            text = aText;
+            boldText = aBoldText;
+            image = aImage;
+        }
+        public boolean canShow() {
+            return true;
+        }
+        public void onClick(View v) { }
+    }
 
-    public enum Type { NONE, SYNC, APPS };
+    private class SyncType extends Type {
+        private OnAccountsUpdateListener mAccountListener;
+        public SyncType(int aText, int aBoldText, int aImage) {
+            super(aText, aBoldText, aImage);
+            // The listener will run on the background thread (see 2nd argument)
+            mAccountListener = new OnAccountsUpdateListener() {
+                public void onAccountsUpdated(Account[] accounts) {
+                    showRandomPromo();
+                }
+            };
+            AccountManager.get(mContext).addOnAccountsUpdatedListener(mAccountListener, GeckoAppShell.getHandler(), false);
+        }
+        @Override
+        public boolean canShow() {
+             return !SyncAccounts.syncAccountsExist(mContext);
+        }
+        @Override
+        public void onClick(View v) {
+            final Context context = v.getContext();
+            final Intent intent = new Intent(context, SetupSyncActivity.class);
+            context.startActivity(intent);
+        }
 
+        public void onDestroy() {
+            if (mAccountListener != null) {
+                AccountManager.get(mContext).removeOnAccountsUpdatedListener(mAccountListener);
+                mAccountListener = null;
+            }
+        }
+    }
+
+    private ArrayList<Type> mTypes;
     private Type mType;
 
     private final Context mContext;
 
-    // Use setResources() to set these variables for each PromoBox type.
-    private int mTextResource;
-    private int mBoldTextResource;
-    private int mImageResource;
-
     public AboutHomePromoBox(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         mContext = context;
         setOnClickListener(this);
+
+        mTypes = new ArrayList<Type>();
+        mTypes.add(new SyncType(R.string.abouthome_about_sync,
+                            R.string.abouthome_sync_bold_name,
+                            R.drawable.abouthome_promo_logo_sync));
+
+        mTypes.add(new Type(R.string.abouthome_about_apps,
+                            R.string.abouthome_apps_bold_name,
+                            R.drawable.abouthome_promo_logo_apps) {
+            @Override
+            public boolean canShow() {
+                final ContentResolver resolver = mContext.getContentResolver();
+                return !BrowserDB.isVisited(resolver, "https://marketplace.firefox.com/");
+            }
+            @Override
+            public void onClick(View v) {
+                Tabs.getInstance().loadUrl("https://marketplace.firefox.com/", Tabs.LOADURL_NEW_TAB);
+
+                // this isn't as good as being notified whenever this url is added to the database,
+                // if the user visits the marketplace through some other means, we'll have to wait
+                // until we are refreshed or restarted to get the correct value
+                v.postDelayed(new Runnable() {
+                    public void run() {
+                        showRandomPromo();
+                    }
+                }, 5000);
+            }
+        });
     }
 
     @Override
     public void onClick(View v) {
-        switch (mType) {
-            case SYNC:
-                final Context context = v.getContext();
-                final Intent intent = new Intent(context, SetupSyncActivity.class);
-                context.startActivity(intent);
-                break;
+        if (mType != null)
+            mType.onClick(v);
+    }
 
-            case APPS:
-                Tabs.getInstance().loadUrl("https://marketplace.mozilla.org", Tabs.LOADURL_NEW_TAB);
-                break;
-
-            default:
-                Log.e(LOGTAG, "Invalid type was set when promo box was clicked.");
-                break;
-        }
+    private interface GetTypesCallback {
+        void onGotTypes(ArrayList<Type> types);
     }
 
     /**
      * Shows the specified promo box. If a promo box is already active, it will be overidden with a
      * promo box of the specified type.
      */
-    public void show(Type type) {
-        mType = type;
-        switch (type) {
-            case SYNC:
-                setSyncResources();
-                break;
-
-            case APPS:
-                setAppsResources();
-                break;
-
-            default:
-                Log.e(LOGTAG, "show() - Invalid AboutHomePromoBox.Type specified.");
-                hide();
-                return;
-        }
-        updateViewResources();
-        setVisibility(View.VISIBLE);
+    public void showRandomPromo() {
+        getAvailableTypes(new GetTypesCallback() {
+            public void onGotTypes(ArrayList<Type> types) {
+                if (types.size() == 0) {
+                    hide();
+                    return;
+                }
+                int idx = new Random().nextInt(types.size());
+                mType = types.get(idx);
+                updateViewResources();
+                setVisibility(View.VISIBLE);
+            }
+        });
     }
 
     public void hide() {
         setVisibility(View.GONE);
         mType = null;
     }
 
-    private void setResources(int textResource, int boldTextResource, int imageResource) {
-        mTextResource = textResource;
-        mBoldTextResource = boldTextResource;
-        mImageResource = imageResource;
-    }
-
     private void updateViewResources() {
         updateTextViewResources();
-        setCompoundDrawablesWithIntrinsicBounds(mImageResource, 0, 0, 0);
+        setCompoundDrawablesWithIntrinsicBounds(mType.image, 0, 0, 0);
     }
 
     private void updateTextViewResources() {
-        final String promoText = mContext.getResources().getString(mTextResource);
-        final String boldName = mContext.getResources().getString(mBoldTextResource);
-        final int styleIndex = promoText.indexOf(boldName);
+        final String text = mContext.getResources().getString(mType.text);
+        final String boldText = mContext.getResources().getString(mType.boldText);
+        final int styleIndex = text.indexOf(boldText);
         if (styleIndex < 0)
-            setText(promoText);
+            setText(text);
         else {
-            final SpannableString spannableText = new SpannableString(promoText);
-            spannableText.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), styleIndex,
-                    styleIndex + boldName.length(), 0);
+            final SpannableString spannableText = new SpannableString(text);
+            spannableText.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), styleIndex, styleIndex + boldText.length(), 0);
             setText(spannableText, TextView.BufferType.SPANNABLE);
         }
     }
 
-    // Type.SYNC: Setup Firefox sync.
-    private void setSyncResources() {
-        setResources(R.string.abouthome_about_sync, R.string.abouthome_sync_bold_name,
-                R.drawable.abouthome_promo_logo_sync);
-    }
+    private void getAvailableTypes(final GetTypesCallback callback) {
+        (new GeckoAsyncTask<Void, Void, ArrayList<Type>>(GeckoApp.mAppContext, GeckoAppShell.getHandler()) {
+            @Override
+            public ArrayList<Type> doInBackground(Void... params) {
+                // Run all of this on a background thread
+                ArrayList<Type> availTypes = new ArrayList<Type>();
+                for (int i = 0; i < mTypes.size(); i++) {
+                    Type t = mTypes.get(i);
+                    if (t.canShow()) {
+                        availTypes.add(t);
+                    }
+                }
+                return availTypes;
+            }
 
-    // Types.APPS: Visit the Marketplace.
-    private void setAppsResources() {
-        setResources(R.string.abouthome_about_apps, R.string.abouthome_apps_bold_name,
-                R.drawable.abouthome_promo_logo_apps);
+            @Override
+            public void onPostExecute(ArrayList<Type> types) {
+                callback.onGotTypes(types);
+            }
+        }).execute();
     }
 }
--- a/mobile/android/base/db/BrowserDB.java
+++ b/mobile/android/base/db/BrowserDB.java
@@ -58,16 +58,18 @@ public class BrowserDB {
         public void removeHistoryEntry(ContentResolver cr, int id);
 
         public void removeHistoryEntry(ContentResolver cr, String url);
 
         public void clearHistory(ContentResolver cr);
 
         public Cursor getBookmarksInFolder(ContentResolver cr, long folderId);
 
+        public boolean isVisited(ContentResolver cr, String uri);
+
         public boolean isBookmark(ContentResolver cr, String uri);
 
         public boolean isReadingListItem(ContentResolver cr, String uri);
 
         public String getUrlForKeyword(ContentResolver cr, String keyword);
 
         public void addBookmark(ContentResolver cr, String title, String uri);
 
@@ -181,17 +183,21 @@ public class BrowserDB {
 
     public static Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
         return sDb.getBookmarksInFolder(cr, folderId);
     }
 
     public static String getUrlForKeyword(ContentResolver cr, String keyword) {
         return sDb.getUrlForKeyword(cr, keyword);
     }
-    
+
+    public static boolean isVisited(ContentResolver cr, String uri) {
+        return sDb.isVisited(cr, uri);
+    }
+
     public static boolean isBookmark(ContentResolver cr, String uri) {
         return sDb.isBookmark(cr, uri);
     }
 
     public static boolean isReadingListItem(ContentResolver cr, String uri) {
         return sDb.isReadingListItem(cr, uri);
     }
 
--- a/mobile/android/base/db/LocalBrowserDB.java
+++ b/mobile/android/base/db/LocalBrowserDB.java
@@ -1157,9 +1157,26 @@ public class LocalBrowserDB implements B
 
     public void unpinAllSites(ContentResolver cr) {
         cr.delete(mBookmarksUriWithProfile,
                   Bookmarks.PARENT + " == ?",
                   new String[] {
                       String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID)
                   });
     }
+
+    public boolean isVisited(ContentResolver cr, String uri) {
+        int count = 0;
+        try {
+            Cursor c = cr.query(historyUriWithLimit(1),
+                                new String[] { History._ID },
+                                History.URL + " = ?",
+                                new String[] { uri },
+                                History.URL);
+            count = c.getCount();
+            c.close();
+        } catch (NullPointerException e) {
+            Log.e(LOGTAG, "NullPointerException in isVisited");
+        }
+
+        return (count > 0);
+    }
 }