Add facility to switch to new fragments from preferences.

Change-Id: I009315b59cf81b4962e9c5a4490f0f82743ed64a
diff --git a/api/current.xml b/api/current.xml
index 859175e..76d8eed 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -133430,6 +133430,17 @@
  visibility="public"
 >
 </method>
+<method name="getFragment"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getIntent"
  return="android.content.Intent"
  abstract="false"
@@ -133992,6 +134003,19 @@
 <parameter name="enabled" type="boolean">
 </parameter>
 </method>
+<method name="setFragment"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="java.lang.String">
+</parameter>
+</method>
 <method name="setIntent"
  return="void"
  abstract="false"
@@ -134310,6 +134334,8 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<implements name="android.preference.PreferenceFragment.OnPreferenceStartFragmentCallback">
+</implements>
 <constructor name="PreferenceActivity"
  type="android.preference.PreferenceActivity"
  static="false"
@@ -134455,6 +134481,21 @@
  visibility="public"
 >
 </method>
+<method name="onPreferenceStartFragment"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="caller" type="android.preference.PreferenceFragment">
+</parameter>
+<parameter name="pref" type="android.preference.Preference">
+</parameter>
+</method>
 <method name="onPreferenceTreeClick"
  return="boolean"
  abstract="false"
@@ -134678,6 +134719,29 @@
 </parameter>
 </method>
 </class>
+<interface name="PreferenceFragment.OnPreferenceStartFragmentCallback"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onPreferenceStartFragment"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="caller" type="android.preference.PreferenceFragment">
+</parameter>
+<parameter name="pref" type="android.preference.Preference">
+</parameter>
+</method>
+</interface>
 <class name="PreferenceGroup"
  extends="android.preference.Preference"
  abstract="true"
@@ -266144,7 +266208,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="fileName" type="java.lang.String">
+<parameter name="filename" type="java.lang.String">
 </parameter>
 </method>
 </interface>
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 9beaec0..e3351b0 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2055,9 +2055,10 @@
     /**
      * Flag for {@link #popBackStack(String, int)}
      * and {@link #popBackStack(int, int)}: If set, and the name or ID of
-     * a back stack entry has been supplied, then that entry will also be
-     * removed.  Otherwise, all entries up to but not including that entry
-     * will be removed
+     * a back stack entry has been supplied, then all matching entries will
+     * be consumed until one that doesn't match is found or the bottom of
+     * the stack is reached.  Otherwise, all entries up to but not including that entry
+     * will be removed.
      */
     public static final int POP_BACK_STACK_INCLUSIVE = 1<<0;
 
@@ -2066,7 +2067,7 @@
      * to pop, else false.
      */
     public boolean popBackStack() {
-        return popBackStack(null, 0);
+        return popBackStack(null, -1);
     }
 
     /**
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index c0e757d..2054e2a 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -629,7 +629,7 @@
         if (mBackStack == null) {
             return false;
         }
-        if (name == null && id < 0) {
+        if (name == null && id < 0 && (flags&Activity.POP_BACK_STACK_INCLUSIVE) == 0) {
             int last = mBackStack.size()-1;
             if (last < 0) {
                 return false;
@@ -644,22 +644,37 @@
                 }
             });
         } else {
-            int index = mBackStack.size()-1;
-            while (index >= 0) {
-                BackStackEntry bss = mBackStack.get(index);
-                if (name != null && name.equals(bss.getName())) {
-                    break;
+            int index = -1;
+            if (name != null || id >= 0) {
+                // If a name or ID is specified, look for that place in
+                // the stack.
+                index = mBackStack.size()-1;
+                while (index >= 0) {
+                    BackStackEntry bss = mBackStack.get(index);
+                    if (name != null && name.equals(bss.getName())) {
+                        break;
+                    }
+                    if (id >= 0 && id == bss.mIndex) {
+                        break;
+                    }
+                    index--;
                 }
-                if (id >= 0 && id == bss.mIndex) {
-                    break;
+                if (index < 0) {
+                    return false;
                 }
-                index--;
-            }
-            if (index < 0) {
-                return false;
-            }
-            if ((flags&Activity.POP_BACK_STACK_INCLUSIVE) != 0) {
-                index--;
+                if ((flags&Activity.POP_BACK_STACK_INCLUSIVE) != 0) {
+                    index--;
+                    // Consume all following entries that match.
+                    while (index >= 0) {
+                        BackStackEntry bss = mBackStack.get(index);
+                        if ((name != null && name.equals(bss.getName()))
+                                || (id >= 0 && id == bss.mIndex)) {
+                            index--;
+                            continue;
+                        }
+                        break;
+                    }
+                }
             }
             if (index == mBackStack.size()-1) {
                 return false;
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 381f794..117e507 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -56,6 +56,7 @@
  * @attr ref android.R.styleable#Preference_title
  * @attr ref android.R.styleable#Preference_summary
  * @attr ref android.R.styleable#Preference_order
+ * @attr ref android.R.styleable#Preference_fragment
  * @attr ref android.R.styleable#Preference_layout
  * @attr ref android.R.styleable#Preference_widgetLayout
  * @attr ref android.R.styleable#Preference_enabled
@@ -88,6 +89,7 @@
     private CharSequence mSummary;
     private String mKey;
     private Intent mIntent;
+    private String mFragment;
     private boolean mEnabled = true;
     private boolean mSelectable = true;
     private boolean mRequiresKey;
@@ -210,6 +212,10 @@
                     mOrder = a.getInt(attr, mOrder);
                     break;
 
+                case com.android.internal.R.styleable.Preference_fragment:
+                    mFragment = a.getString(attr);
+                    break;
+
                 case com.android.internal.R.styleable.Preference_layout:
                     mLayoutResId = a.getResourceId(attr, mLayoutResId);
                     break;
@@ -315,6 +321,24 @@
     }
 
     /**
+     * Sets the class name of a fragment to be shown when this Preference is clicked.
+     *
+     * @param fragment The class name of the fragment associated with this Preference.
+     */
+    public void setFragment(String fragment) {
+        mFragment = fragment;
+    }
+
+    /**
+     * Return the fragment class name associated with this Preference.
+     *
+     * @return The fragment class name last set via {@link #setFragment} or XML.
+     */
+    public String getFragment() {
+        return mFragment;
+    }
+
+    /**
      * Sets the layout resource that is inflated as the {@link View} to be shown
      * for this Preference. In most cases, the default layout is sufficient for
      * custom Preference objects and only the widget layout needs to be changed.
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index fdc874b..e13c3e8 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -93,11 +93,22 @@
  *
  * {@sample development/samples/ApiDemos/res/xml/preference_headers.xml headers}
  *
- * See {@link PreferenceFragment} for information on implementing the
+ * <p>The first header is shown by Prefs1Fragment, which populates itself
+ * from the following XML resource:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/xml/fragmented_preferences.xml preferences}
+ *
+ * <p>Note that this XML resource contains a preference screen holding another
+ * fragment, the Prefs1FragmentInner implemented here.  This allows the user
+ * to traverse down a hierarchy of preferences; pressing back will pop each
+ * fragment off the stack to return to the previous preferences.
+ *
+ * <p>See {@link PreferenceFragment} for information on implementing the
  * fragments themselves.
  */
 public abstract class PreferenceActivity extends ListActivity implements
-        PreferenceManager.OnPreferenceTreeClickListener {
+        PreferenceManager.OnPreferenceTreeClickListener,
+        PreferenceFragment.OnPreferenceStartFragmentCallback {
     private static final String TAG = "PreferenceActivity";
 
     private static final String PREFERENCES_TAG = "android:preferences";
@@ -106,6 +117,8 @@
 
     private static final String EXTRA_PREFS_NO_HEADERS = ":android:no_headers";
 
+    private static final String BACK_STACK_PREFS = ":android:prefs";
+
     // extras that allow any preference activity to be launched as part of a wizard
 
     // show Back and Next buttons? takes boolean parameter
@@ -206,16 +219,19 @@
     public static class Header {
         /**
          * Title of the header that is shown to the user.
+         * @attr ref android.R.styleable#PreferenceHeader_title
          */
         CharSequence title;
 
         /**
          * Optional summary describing what this header controls.
+         * @attr ref android.R.styleable#PreferenceHeader_summary
          */
         CharSequence summary;
 
         /**
          * Optional icon resource to show for this header.
+         * @attr ref android.R.styleable#PreferenceHeader_icon
          */
         int iconRes;
 
@@ -228,6 +244,7 @@
         /**
          * Full class name of the fragment to display when this header is
          * selected.
+         * @attr ref android.R.styleable#PreferenceHeader_fragment
          */
         String fragment;
     }
@@ -551,6 +568,8 @@
      * @param fragmentName The name of the fragment to display.
      */
     public void switchToHeader(String fragmentName) {
+        popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE);
+
         Fragment f;
         try {
             f = Fragment.instantiate(this, fragmentName);
@@ -561,6 +580,20 @@
         openFragmentTransaction().replace(com.android.internal.R.id.prefs, f).commit();
     }
 
+    @Override
+    public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
+        Fragment f;
+        try {
+            f = Fragment.instantiate(this, pref.getFragment());
+        } catch (Exception e) {
+            Log.w(TAG, "Failure instantiating fragment " + pref.getFragment(), e);
+            return false;
+        }
+        openFragmentTransaction().replace(com.android.internal.R.id.prefs, f)
+                .addToBackStack(BACK_STACK_PREFS).commit();
+        return true;
+    }
+
     /**
      * Posts a message to bind the preferences to the list view.
      * <p>
diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index ac61574..a5395e2 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -125,6 +125,21 @@
         }
     };
 
+    /**
+     * Interface that PreferenceFragment's containing activity should
+     * implement to be able to process preference items that wish to
+     * switch to a new fragment.
+     */
+    public interface OnPreferenceStartFragmentCallback {
+        /**
+         * Called when the user has clicked on a Preference that has
+         * a fragment class name associated with it.  The implementation
+         * to should instantiate and switch to an instance of the given
+         * fragment.
+         */
+        boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref);
+    }
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -168,6 +183,13 @@
     }
 
     @Override
+    public void onDestroyView() {
+        mList = null;
+        mHandler.removeCallbacks(mRequestFocus);
+        super.onDestroyView();
+    }
+
+    @Override
     public void onDestroy() {
         super.onDestroy();
         mPreferenceManager.dispatchActivityDestroy();
@@ -251,7 +273,13 @@
     /**
      * {@inheritDoc}
      */
-    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+            Preference preference) {
+        if (preference.getFragment() != null &&
+                getActivity() instanceof OnPreferenceStartFragmentCallback) {
+            return ((OnPreferenceStartFragmentCallback)getActivity()).onPreferenceStartFragment(
+                    this, preference);
+        }
         return false;
     }
 
diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java
index 95e54324..f34f4a3 100644
--- a/core/java/android/preference/PreferenceScreen.java
+++ b/core/java/android/preference/PreferenceScreen.java
@@ -136,7 +136,7 @@
     
     @Override
     protected void onClick() {
-        if (getIntent() != null || getPreferenceCount() == 0) {
+        if (getIntent() != null || getFragment() != null || getPreferenceCount() == 0) {
             return;
         }
         
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 728f999..97c5822 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3457,6 +3457,9 @@
         <!-- The order for the Preference (lower values are to be ordered first). If this is not
              specified, the default orderin will be alphabetic. -->
         <attr name="order" format="integer" />
+        <!-- When used inside of a modern PreferenceActivity, this declares
+             a new PreferenceFragment to be shown when the user selects this item. -->
+        <attr name="fragment" />
         <!-- The layout for the Preference in a PreferenceActivity screen. This should
              rarely need to be changed, look at widgetLayout instead. -->
         <attr name="layout" />