Bookmark page changes

 Bug: 6372933
 Fix a race condition between update & destroy
 Fix update path
 Add some extra debug support

Change-Id: I66a450e175b22d992de97d5bc24200961144412a
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 90ec428..8adc741 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -591,6 +591,8 @@
     <string name="pref_development_error_console" translatable="false">Show JavaScript Console</string>
     <!-- Do not translate. Development option to reset the prologin time [CHAR LIMIT=20] -->
     <string name="pref_development_reset_prelogin" translatable="false">Reset prelogin</string>
+    <!-- Do not translate. Development option to add more bookmarks [CHAR LIMIT=20] -->
+    <string name="pref_development_add_bookmarks" tranlsatable="false">Moar bookmarks!</string>
     <!-- Settings screen, setting option name -->
     <string name="pref_default_text_encoding">Text encoding</string>
     <!-- Options in the Default encoding dialog box -->
diff --git a/res/xml/debug_preferences.xml b/res/xml/debug_preferences.xml
index 029bc4f..1653549 100644
--- a/res/xml/debug_preferences.xml
+++ b/res/xml/debug_preferences.xml
@@ -91,4 +91,8 @@
         android:key="reset_prelogin"
         android:title="@string/pref_development_reset_prelogin" />
 
+    <Preference
+        android:key="add_bookmarks"
+        android:title="@string/pref_development_add_bookmarks" />
+
 </PreferenceScreen>
diff --git a/src/com/android/browser/BrowserBookmarksAdapterItem.java b/src/com/android/browser/BrowserBookmarksAdapterItem.java
index 913b0fd..ee2ed70 100644
--- a/src/com/android/browser/BrowserBookmarksAdapterItem.java
+++ b/src/com/android/browser/BrowserBookmarksAdapterItem.java
@@ -15,7 +15,6 @@
  */
 package com.android.browser;
 
-import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 
 public class BrowserBookmarksAdapterItem {
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index 5a609b1..a038a2d 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -161,6 +161,10 @@
 
     @Override
     public void onLoaderReset(Loader<Cursor> loader) {
+        if (loader.getId() >= LOADER_BOOKMARKS) {
+            BrowserBookmarksAdapter adapter = mBookmarkAdapters.get(loader.getId());
+            adapter.changeCursor(null);
+        }
     }
 
     @Override
diff --git a/src/com/android/browser/PreferenceKeys.java b/src/com/android/browser/PreferenceKeys.java
index ff42aaf..f7dc0e0 100644
--- a/src/com/android/browser/PreferenceKeys.java
+++ b/src/com/android/browser/PreferenceKeys.java
@@ -76,6 +76,7 @@
     static final String PREF_SMALL_SCREEN = "small_screen";
     static final String PREF_WIDE_VIEWPORT = "wide_viewport";
     static final String PREF_RESET_PRELOGIN = "reset_prelogin";
+    static final String PREF_ADD_BOOKMARKS = "add_bookmarks";
 
     // ----------------------
     // Keys for lab_preferences.xml
diff --git a/src/com/android/browser/preferences/DebugPreferencesFragment.java b/src/com/android/browser/preferences/DebugPreferencesFragment.java
index 24821d1..80347bd 100644
--- a/src/com/android/browser/preferences/DebugPreferencesFragment.java
+++ b/src/com/android/browser/preferences/DebugPreferencesFragment.java
@@ -25,6 +25,7 @@
 import com.android.browser.GoogleAccountLogin;
 import com.android.browser.PreferenceKeys;
 import com.android.browser.R;
+import com.android.browser.provider.BrowserProvider2;
 
 public class DebugPreferencesFragment extends PreferenceFragment
         implements OnPreferenceClickListener {
@@ -37,6 +38,8 @@
 
         Preference e = findPreference(PreferenceKeys.PREF_RESET_PRELOGIN);
         e.setOnPreferenceClickListener(this);
+        e = findPreference(PreferenceKeys.PREF_ADD_BOOKMARKS);
+        e.setOnPreferenceClickListener(this);
     }
 
     @Override
@@ -47,6 +50,11 @@
                     .apply();
             return true;
         }
+        if (PreferenceKeys.PREF_ADD_BOOKMARKS.equals(preference.getKey())) {
+            getActivity().getContentResolver().insert(
+                    BrowserProvider2.DEBUG_ADD_BOOKMARKS_URI, null);
+            return true;
+        }
         return false;
     }
 }
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index a66c333..34a153e 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -75,6 +75,9 @@
     static final Uri LEGACY_AUTHORITY_URI = new Uri.Builder()
             .authority(LEGACY_AUTHORITY).scheme("content").build();
 
+    public static final Uri DEBUG_ADD_BOOKMARKS_URI = Uri.withAppendedPath(
+            BrowserContract.AUTHORITY_URI, "debug-add-bookmarks");
+
     public static interface Thumbnails {
         public static final Uri CONTENT_URI = Uri.withAppendedPath(
                 BrowserContract.AUTHORITY_URI, "thumbnails");
@@ -149,6 +152,7 @@
     static final int THUMBNAILS = 10;
     static final int THUMBNAILS_ID = 11;
     static final int OMNIBOX_SUGGESTIONS = 20;
+    static final int DEBUG_ADD_BOOKMARKS = 30;
 
     static final int BOOKMARKS = 1000;
     static final int BOOKMARKS_ID = 1001;
@@ -230,6 +234,9 @@
         matcher.addURI(authority, "thumbnails/#", THUMBNAILS_ID);
         matcher.addURI(authority, "omnibox_suggestions", OMNIBOX_SUGGESTIONS);
 
+        // Debug
+        matcher.addURI(authority, "debug-add-bookmarks", DEBUG_ADD_BOOKMARKS);
+
         // Legacy
         matcher.addURI(LEGACY_AUTHORITY, "searches", SEARCHES);
         matcher.addURI(LEGACY_AUTHORITY, "searches/#", SEARCHES_ID);
@@ -1482,6 +1489,11 @@
                 break;
             }
 
+            case DEBUG_ADD_BOOKMARKS: {
+                mOpenHelper.addDefaultBookmarks(db, FIXED_ID_ROOT);
+                break;
+            }
+
             default: {
                 throw new UnsupportedOperationException("Unknown insert URI " + uri);
             }
diff --git a/src/com/android/browser/util/ThreadedCursorAdapter.java b/src/com/android/browser/util/ThreadedCursorAdapter.java
index fe59ad1..d0bf715 100644
--- a/src/com/android/browser/util/ThreadedCursorAdapter.java
+++ b/src/com/android/browser/util/ThreadedCursorAdapter.java
@@ -21,8 +21,8 @@
 import android.os.HandlerThread;
 import android.os.Message;
 import android.os.Process;
+import android.os.SystemProperties;
 import android.util.Log;
-import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Adapter;
@@ -32,12 +32,12 @@
 import com.android.browser.R;
 
 import java.lang.ref.WeakReference;
-import java.util.HashMap;
 
 public abstract class ThreadedCursorAdapter<T> extends BaseAdapter {
 
-    private static final String LOGTAG = "tca";
+    private static final String LOGTAG = "BookmarksThreadedAdapter";
     private static final boolean DEBUG = false;
+    private static boolean sEnableBitmapRecycling = true;
 
     private Context mContext;
     private Object mCursorLock = new Object();
@@ -46,6 +46,15 @@
     private Handler mLoadHandler;
     private Handler mHandler;
     private int mSize;
+    private boolean mHasCursor;
+    private long mGeneration;
+
+    static {
+        // TODO: Remove this once recycling is either stabilized or scrapped
+        sEnableBitmapRecycling = SystemProperties
+                .getBoolean("com.android.browser.recycling", sEnableBitmapRecycling);
+        Log.d(LOGTAG, "Bitmap recycling enabled: " + sEnableBitmapRecycling);
+    }
 
     private class LoadContainer {
         WeakReference<View> view;
@@ -53,10 +62,12 @@
         T bind_object;
         Adapter owner;
         boolean loaded;
+        long generation;
     }
 
     public ThreadedCursorAdapter(Context context, Cursor c) {
         mContext = context;
+        mHasCursor = (c != null);
         mCursorAdapter = new CursorAdapter(context, c, 0) {
 
             @Override
@@ -73,6 +84,7 @@
             public void notifyDataSetChanged() {
                 super.notifyDataSetChanged();
                 mSize = getCount();
+                mGeneration++;
                 ThreadedCursorAdapter.this.notifyDataSetChanged();
             }
 
@@ -80,6 +92,7 @@
             public void notifyDataSetInvalidated() {
                 super.notifyDataSetInvalidated();
                 mSize = getCount();
+                mGeneration++;
                 ThreadedCursorAdapter.this.notifyDataSetInvalidated();
             }
 
@@ -109,7 +122,9 @@
                 View view = container.view.get();
                 if (view == null
                         || container.owner != ThreadedCursorAdapter.this
-                        || container.position != msg.what) {
+                        || container.position != msg.what
+                        || view.getWindowToken() == null
+                        || container.generation != mGeneration) {
                     return;
                 }
                 container.loaded = true;
@@ -142,7 +157,12 @@
         }
         synchronized (mCursorLock) {
             Cursor c = (Cursor) mCursorAdapter.getItem(position);
-            container.bind_object = getRowObject(c, container.bind_object);
+            if (c == null || c.isClosed()) {
+                return;
+            }
+            final T recycleObject = sEnableBitmapRecycling
+                    ? container.bind_object : null;
+            container.bind_object = getRowObject(c, recycleObject);
         }
         mHandler.obtainMessage(position, container).sendToTarget();
     }
@@ -161,14 +181,18 @@
         }
         if (container.position == position
                 && container.owner == this
-                && container.loaded) {
+                && container.loaded
+                && container.generation == mGeneration) {
             bindView(convertView, container.bind_object);
         } else {
             bindView(convertView, cachedLoadObject());
-            container.position = position;
-            container.loaded = false;
-            container.owner = this;
-            mLoadHandler.obtainMessage(position, container).sendToTarget();
+            if (mHasCursor) {
+                container.position = position;
+                container.loaded = false;
+                container.owner = this;
+                container.generation = mGeneration;
+                mLoadHandler.obtainMessage(position, container).sendToTarget();
+            }
         }
         return convertView;
     }
@@ -181,7 +205,10 @@
     }
 
     public void changeCursor(Cursor cursor) {
+        mLoadHandler.removeCallbacksAndMessages(null);
+        mHandler.removeCallbacksAndMessages(null);
         synchronized (mCursorLock) {
+            mHasCursor = (cursor != null);
             mCursorAdapter.changeCursor(cursor);
         }
     }