diff options
author | Dianne Hackborn <hackbod@google.com> | 2010-09-12 19:27:46 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2010-09-13 22:49:54 -0700 |
commit | a21e3da55940e239addd80bf379091a1d85d006f (patch) | |
tree | e3a34c539ef03235f8e5db63e28b1c35a3b872e0 /core/java/android/preference | |
parent | ade9916d5ca5795ced859a7d7405ef32140ccee8 (diff) | |
download | frameworks_base-a21e3da55940e239addd80bf379091a1d85d006f.zip frameworks_base-a21e3da55940e239addd80bf379091a1d85d006f.tar.gz frameworks_base-a21e3da55940e239addd80bf379091a1d85d006f.tar.bz2 |
Fix issue #2967969: Crash rotating screen on "delete account" dialog
- Have PreferenceActivity save and restore its header state.
- Keep track of the current header selection.
- When headers are updated, try to retain the current header selection.
Also fix issue #2995541: Cannot add new contact. We were not allowing
fragment transactions in some cases.
Change-Id: I4aa4c703ed5f4ecf9f425cd7eeea4740c6360ce9
Diffstat (limited to 'core/java/android/preference')
-rw-r--r-- | core/java/android/preference/PreferenceActivity.java | 288 |
1 files changed, 238 insertions, 50 deletions
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java index 2efb09d..60d810e 100644 --- a/core/java/android/preference/PreferenceActivity.java +++ b/core/java/android/preference/PreferenceActivity.java @@ -29,10 +29,11 @@ import android.content.Intent; import android.content.res.Configuration; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.os.Parcel; +import android.os.Parcelable; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Xml; @@ -40,6 +41,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; +import android.widget.AbsListView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.FrameLayout; @@ -112,7 +114,11 @@ public abstract class PreferenceActivity extends ListActivity implements PreferenceFragment.OnPreferenceStartFragmentCallback { private static final String TAG = "PreferenceActivity"; - private static final String PREFERENCES_TAG = "android:preferences"; + // Constants for state save/restore + private static final String HEADERS_TAG = ":android:headers"; + private static final String CUR_HEADER_TAG = ":android:cur_header"; + private static final String SINGLE_PANE_TAG = ":android:single_pane"; + private static final String PREFERENCES_TAG = ":android:preferences"; /** * When starting this activity, the invoking Intent can contain this extra @@ -165,6 +171,8 @@ public abstract class PreferenceActivity extends ListActivity implements private boolean mSinglePane; + private Header mCurHeader; + // --- State for old mode when showing a single preference list private PreferenceManager mPreferenceManager; @@ -186,17 +194,29 @@ public abstract class PreferenceActivity extends ListActivity implements @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_BIND_PREFERENCES: + case MSG_BIND_PREFERENCES: { bindPreferences(); - break; - case MSG_BUILD_HEADERS: + } break; + case MSG_BUILD_HEADERS: { + ArrayList<Header> oldHeaders = new ArrayList<Header>(mHeaders); + mHeaders.clear(); onBuildHeaders(mHeaders); mAdapter.notifyDataSetChanged(); Header header = onGetNewHeader(); if (header != null && header.fragment != null) { - switchToHeader(header.fragment, header.fragmentArguments); + Header mappedHeader = findBestMatchingHeader(header, oldHeaders); + if (mappedHeader == null || mCurHeader != mappedHeader) { + switchToHeader(header); + } + } else if (mCurHeader != null) { + Header mappedHeader = findBestMatchingHeader(mCurHeader, mHeaders); + if (mappedHeader != null) { + setSelectedHeader(mappedHeader); + } else { + switchToHeader(null); + } } - break; + } break; } } }; @@ -235,13 +255,7 @@ public abstract class PreferenceActivity extends ListActivity implements // All view fields must be updated every time, because the view may be recycled Header header = getItem(position); - if (header.icon == null) { - holder.icon.setImageDrawable(null); - holder.icon.setImageResource(header.iconRes); - } else { - holder.icon.setImageResource(0); - holder.icon.setImageDrawable(header.icon); - } + holder.icon.setImageResource(header.iconRes); holder.title.setText(header.title); if (TextUtils.isEmpty(header.summary)) { holder.summary.setVisibility(View.GONE); @@ -255,9 +269,24 @@ public abstract class PreferenceActivity extends ListActivity implements } /** + * Default value for {@link Header#id Header.id} indicating that no + * identifier value is set. All other values (including those below -1) + * are valid. + */ + public static final long HEADER_ID_UNDEFINED = -1; + + /** * Description of a single Header item that the user can select. */ - public static class Header { + public static final class Header implements Parcelable { + /** + * Identifier for this header, to correlate with a new list when + * it is updated. The default value is + * {@link PreferenceActivity#HEADER_ID_UNDEFINED}, meaning no id. + * @attr ref android.R.styleable#PreferenceHeader_id + */ + public long id = HEADER_ID_UNDEFINED; + /** * Title of the header that is shown to the user. * @attr ref android.R.styleable#PreferenceHeader_title @@ -277,12 +306,6 @@ public abstract class PreferenceActivity extends ListActivity implements public int iconRes; /** - * Optional icon drawable to show for this header. (If this is non-null, - * the iconRes will be ignored.) - */ - public Drawable icon; - - /** * Full class name of the fragment to display when this header is * selected. * @attr ref android.R.styleable#PreferenceHeader_fragment @@ -299,6 +322,62 @@ public abstract class PreferenceActivity extends ListActivity implements * Intent to launch when the preference is selected. */ public Intent intent; + + /** + * Optional additional data for use by subclasses of PreferenceActivity. + */ + public Bundle extras; + + public Header() { + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(id); + TextUtils.writeToParcel(title, dest, flags); + TextUtils.writeToParcel(summary, dest, flags); + dest.writeInt(iconRes); + dest.writeString(fragment); + dest.writeBundle(fragmentArguments); + if (intent != null) { + dest.writeInt(1); + intent.writeToParcel(dest, flags); + } else { + dest.writeInt(0); + } + dest.writeBundle(extras); + } + + public void readFromParcel(Parcel in) { + id = in.readLong(); + title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + iconRes = in.readInt(); + fragment = in.readString(); + fragmentArguments = in.readBundle(); + if (in.readInt() != 0) { + intent = Intent.CREATOR.createFromParcel(in); + } + extras = in.readBundle(); + } + + Header(Parcel in) { + readFromParcel(in); + } + + public static final Creator<Header> CREATOR = new Creator<Header>() { + public Header createFromParcel(Parcel source) { + return new Header(source); + } + public Header[] newArray(int size) { + return new Header[size]; + } + }; } @Override @@ -314,40 +393,66 @@ public abstract class PreferenceActivity extends ListActivity implements String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT); Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); - if (initialFragment != null && mSinglePane) { - // If we are just showing a fragment, we want to run in - // new fragment mode, but don't need to compute and show - // the headers. - getListView().setVisibility(View.GONE); - mPrefsContainer.setVisibility(View.VISIBLE); - switchToHeader(initialFragment, initialArguments); + if (savedInstanceState != null) { + // We are restarting from a previous saved state; used that to + // initialize, instead of starting fresh. + ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG); + if (headers != null) { + mHeaders.addAll(headers); + int curHeader = savedInstanceState.getInt(CUR_HEADER_TAG, + (int)HEADER_ID_UNDEFINED); + if (curHeader >= 0 && curHeader < mHeaders.size()) { + setSelectedHeader(mHeaders.get(curHeader)); + } + } + mSinglePane = savedInstanceState.getBoolean(SINGLE_PANE_TAG); } else { - // We need to try to build the headers. - onBuildHeaders(mHeaders); - - // If there are headers, then at this point we need to show - // them and, depending on the screen, we may also show in-line - // the currently selected preference fragment. - if (mHeaders.size() > 0) { - mAdapter = new HeaderAdapter(this, mHeaders); - setListAdapter(mAdapter); - if (!mSinglePane) { - mPrefsContainer.setVisibility(View.VISIBLE); - if (initialFragment == null) { - Header h = onGetInitialHeader(); - initialFragment = h.fragment; - initialArguments = h.fragmentArguments; + if (initialFragment != null && mSinglePane) { + // If we are just showing a fragment, we want to run in + // new fragment mode, but don't need to compute and show + // the headers. + switchToHeader(initialFragment, initialArguments); + + } else { + // We need to try to build the headers. + onBuildHeaders(mHeaders); + + // If there are headers, then at this point we need to show + // them and, depending on the screen, we may also show in-line + // the currently selected preference fragment. + if (mHeaders.size() > 0) { + if (!mSinglePane) { + if (initialFragment == null) { + Header h = onGetInitialHeader(); + switchToHeader(h); + } else { + switchToHeader(initialFragment, initialArguments); + } } - switchToHeader(initialFragment, initialArguments); } + } + } + // The default configuration is to only show the list view. Adjust + // visibility for other configurations. + if (initialFragment != null && mSinglePane) { + // Single pane, showing just a prefs fragment. + getListView().setVisibility(View.GONE); + mPrefsContainer.setVisibility(View.VISIBLE); + } else if (mHeaders.size() > 0) { + mAdapter = new HeaderAdapter(this, mHeaders); + setListAdapter(mAdapter); + if (!mSinglePane) { + // Multi-pane. + getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); + mPrefsContainer.setVisibility(View.VISIBLE); + } + } else { // If there are no headers, we are in the old "just show a screen // of preferences" mode. - } else { - mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE); - mPreferenceManager.setOnPreferenceTreeClickListener(this); - } + mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE); + mPreferenceManager.setOnPreferenceTreeClickListener(this); } getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); @@ -534,6 +639,9 @@ public abstract class PreferenceActivity extends ListActivity implements TypedArray sa = getResources().obtainAttributes(attrs, com.android.internal.R.styleable.PreferenceHeader); + header.id = sa.getInt( + com.android.internal.R.styleable.PreferenceHeader_id, + (int)HEADER_ID_UNDEFINED); header.title = sa.getText( com.android.internal.R.styleable.PreferenceHeader_title); header.summary = sa.getText( @@ -621,6 +729,17 @@ public abstract class PreferenceActivity extends ListActivity implements protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); + if (mHeaders.size() > 0) { + outState.putParcelableArrayList(HEADERS_TAG, mHeaders); + if (mCurHeader != null) { + int index = mHeaders.indexOf(mCurHeader); + if (index >= 0) { + outState.putInt(CUR_HEADER_TAG, index); + } + } + } + outState.putBoolean(SINGLE_PANE_TAG, mSinglePane); + if (mPreferenceManager != null) { final PreferenceScreen preferenceScreen = getPreferenceScreen(); if (preferenceScreen != null) { @@ -680,7 +799,7 @@ public abstract class PreferenceActivity extends ListActivity implements /** * Called when the user selects an item in the header list. The default * implementation will call either {@link #startWithFragment(String, Bundle)} - * or {@link #switchToHeader(String, Bundle)} as appropriate. + * or {@link #switchToHeader(Header)} as appropriate. * * @param header The header that was selected. * @param position The header's position in the list. @@ -690,7 +809,7 @@ public abstract class PreferenceActivity extends ListActivity implements if (mSinglePane) { startWithFragment(header.fragment, header.fragmentArguments); } else { - switchToHeader(header.fragment, header.fragmentArguments); + switchToHeader(header); } } else if (header.intent != null) { startActivity(header.intent); @@ -715,6 +834,16 @@ public abstract class PreferenceActivity extends ListActivity implements startActivity(intent); } + void setSelectedHeader(Header header) { + mCurHeader = header; + int index = mHeaders.indexOf(header); + if (index >= 0) { + getListView().setItemChecked(index, true); + } else { + getListView().clearChoices(); + } + } + /** * When in two-pane mode, switch the fragment pane to show the given * preference fragment. @@ -723,6 +852,8 @@ public abstract class PreferenceActivity extends ListActivity implements * @param args Optional arguments to supply to the fragment. */ public void switchToHeader(String fragmentName, Bundle args) { + setSelectedHeader(null); + getFragmentManager().popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE); Fragment f = Fragment.instantiate(this, fragmentName, args); @@ -731,6 +862,63 @@ public abstract class PreferenceActivity extends ListActivity implements } /** + * When in two-pane mode, switch to the fragment pane to show the given + * preference fragment. + * + * @param header The new header to display. + */ + public void switchToHeader(Header header) { + switchToHeader(header.fragment, header.fragmentArguments); + mCurHeader = header; + setSelectedHeader(header); + } + + Header findBestMatchingHeader(Header cur, ArrayList<Header> from) { + ArrayList<Header> matches = new ArrayList<Header>(); + for (int j=0; j<from.size(); j++) { + Header oh = from.get(j); + if (cur == oh || (cur.id != HEADER_ID_UNDEFINED && cur.id == oh.id)) { + // Must be this one. + matches.clear(); + matches.add(oh); + break; + } + if (cur.fragment != null) { + if (cur.fragment.equals(oh.fragment)) { + matches.add(oh); + } + } else if (cur.intent != null) { + if (cur.intent.equals(oh.intent)) { + matches.add(oh); + } + } else if (cur.title != null) { + if (cur.title.equals(oh.title)) { + matches.add(oh); + } + } + } + final int NM = matches.size(); + if (NM == 1) { + return matches.get(0); + } else if (NM > 1) { + for (int j=0; j<NM; j++) { + Header oh = matches.get(j); + if (cur.fragmentArguments != null && + cur.fragmentArguments.equals(oh.fragmentArguments)) { + return oh; + } + if (cur.extras != null && cur.extras.equals(oh.extras)) { + return oh; + } + if (cur.title != null && cur.title.equals(oh.title)) { + return oh; + } + } + } + return null; + } + + /** * Start a new fragment. * * @param fragment The fragment to start |