Fix preference caching to increase recycling of preference views.

Add extra safety measures - no recycling of derived preferences as
they may override onCreateView or getView
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 08a2a9f..197d976 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -188,17 +188,7 @@
         mContext = context;
 
         TypedArray a = context.obtainStyledAttributes(attrs,
-                com.android.internal.R.styleable.Preference);
-        if (a.hasValue(com.android.internal.R.styleable.Preference_layout) ||
-                a.hasValue(com.android.internal.R.styleable.Preference_widgetLayout)) {
-            // This preference has a custom layout defined (not one taken from
-            // the default style)
-            mHasSpecifiedLayout = true;
-        }
-        a.recycle();
-        
-        a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Preference,
-                defStyle, 0);
+                com.android.internal.R.styleable.Preference, defStyle, 0);
         for (int i = a.getIndexCount(); i >= 0; i--) {
             int attr = a.getIndex(i); 
             switch (attr) {
@@ -252,6 +242,11 @@
             }
         }
         a.recycle();
+
+        if (!getClass().getName().startsWith("android.preference")) {
+            // For subclasses not in this package, assume the worst and don't cache views
+            mHasSpecifiedLayout = true;
+        }
     }
     
     /**
@@ -332,11 +327,11 @@
      * @see #setWidgetLayoutResource(int)
      */
     public void setLayoutResource(int layoutResId) {
-        
-        if (!mHasSpecifiedLayout) {
+        if (layoutResId != mLayoutResId) {
+            // Layout changed
             mHasSpecifiedLayout = true;
         }
-        
+
         mLayoutResId = layoutResId;
     }
     
@@ -360,6 +355,10 @@
      * @see #setLayoutResource(int)
      */
     public void setWidgetLayoutResource(int widgetLayoutResId) {
+        if (widgetLayoutResId != mWidgetLayoutResId) {
+            // Layout changed
+            mHasSpecifiedLayout = true;
+        }
         mWidgetLayoutResId = widgetLayoutResId;
     }
 
diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java
index 14c0054..a908ecd 100644
--- a/core/java/android/preference/PreferenceGroupAdapter.java
+++ b/core/java/android/preference/PreferenceGroupAdapter.java
@@ -69,7 +69,9 @@
      * count once--when the adapter is being set). We will not recycle views for
      * Preference subclasses seen after the count has been returned.
      */
-    private List<String> mPreferenceClassNames;
+    private ArrayList<PreferenceLayout> mPreferenceLayouts;
+
+    private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();
 
     /**
      * Blocks the mPreferenceClassNames from being changed anymore.
@@ -86,14 +88,37 @@
         }
     };
 
+    private static class PreferenceLayout implements Comparable<PreferenceLayout> {
+        private int resId;
+        private int widgetResId;
+        private String name;
+
+        public int compareTo(PreferenceLayout other) {
+            int compareNames = name.compareTo(other.name);
+            if (compareNames == 0) {
+                if (resId == other.resId) {
+                    if (widgetResId == other.widgetResId) {
+                        return 0;
+                    } else {
+                        return widgetResId - other.widgetResId;
+                    }
+                } else {
+                    return resId - other.resId;
+                }
+            } else {
+                return compareNames;
+            }
+        }
+    }
+
     public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
         mPreferenceGroup = preferenceGroup;
         // If this group gets or loses any children, let us know
         mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
-        
+
         mPreferenceList = new ArrayList<Preference>();
-        mPreferenceClassNames = new ArrayList<String>();
-        
+        mPreferenceLayouts = new ArrayList<PreferenceLayout>();
+
         syncMyPreferences();
     }
 
@@ -102,7 +127,7 @@
             if (mIsSyncing) {
                 return;
             }
-        
+
             mIsSyncing = true;
         }
 
@@ -128,7 +153,7 @@
             
             preferences.add(preference);
             
-            if (!mHasReturnedViewTypeCount) {
+            if (!mHasReturnedViewTypeCount && !preference.hasSpecifiedLayout()) {
                 addPreferenceClassName(preference);
             }
             
@@ -143,15 +168,28 @@
         }
     }
 
+    /**
+     * Creates a string that includes the preference name, layout id and widget layout id.
+     * If a particular preference type uses 2 different resources, they will be treated as
+     * different view types.
+     */
+    private PreferenceLayout createPreferenceLayout(Preference preference, PreferenceLayout in) {
+        PreferenceLayout pl = in != null? in : new PreferenceLayout();
+        pl.name = preference.getClass().getName();
+        pl.resId = preference.getLayoutResource();
+        pl.widgetResId = preference.getWidgetLayoutResource();
+        return pl;
+    }
+
     private void addPreferenceClassName(Preference preference) {
-        final String name = preference.getClass().getName();
-        int insertPos = Collections.binarySearch(mPreferenceClassNames, name);
-        
+        final PreferenceLayout pl = createPreferenceLayout(preference, null);
+        int insertPos = Collections.binarySearch(mPreferenceLayouts, pl);
+
         // Only insert if it doesn't exist (when it is negative).
         if (insertPos < 0) {
             // Convert to insert index
             insertPos = insertPos * -1 - 1;
-            mPreferenceClassNames.add(insertPos, name);
+            mPreferenceLayouts.add(insertPos, pl);
         }
     }
     
@@ -171,19 +209,15 @@
 
     public View getView(int position, View convertView, ViewGroup parent) {
         final Preference preference = this.getItem(position);
-        
-        if (preference.hasSpecifiedLayout()) {
-            // If the preference had specified a layout (as opposed to the
-            // default), don't use convert views.
+        // Build a PreferenceLayout to compare with known ones that are cacheable.
+        mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
+
+        // If it's not one of the cached ones, set the convertView to null so that 
+        // the layout gets re-created by the Preference.
+        if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) {
             convertView = null;
-        } else {
-            // TODO: better way of doing this
-            final String name = preference.getClass().getName();
-            if (Collections.binarySearch(mPreferenceClassNames, name) < 0) {
-                convertView = null;
-            }
         }
-        
+
         return preference.getView(convertView, parent);
     }
 
@@ -225,8 +259,9 @@
             return IGNORE_ITEM_VIEW_TYPE;
         }
 
-        final String name = preference.getClass().getName();
-        int viewType = Collections.binarySearch(mPreferenceClassNames, name);
+        mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
+
+        int viewType = Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout);
         if (viewType < 0) {
             // This is a class that was seen after we returned the count, so
             // don't recycle it.
@@ -242,7 +277,7 @@
             mHasReturnedViewTypeCount = true;
         }
         
-        return Math.max(1, mPreferenceClassNames.size());
+        return Math.max(1, mPreferenceLayouts.size());
     }
 
 }