1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.preference; 18 19 import android.app.Dialog; 20 import android.content.Context; 21 import android.content.DialogInterface; 22 import android.os.Bundle; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.text.TextUtils; 26 import android.util.AttributeSet; 27 import android.view.View; 28 import android.view.Window; 29 import android.widget.AbsListView; 30 import android.widget.Adapter; 31 import android.widget.AdapterView; 32 import android.widget.ListAdapter; 33 import android.widget.ListView; 34 35 /** 36 * Represents a top-level {@link Preference} that 37 * is the root of a Preference hierarchy. A {@link PreferenceActivity} 38 * points to an instance of this class to show the preferences. To instantiate 39 * this class, use {@link PreferenceManager#createPreferenceScreen(Context)}. 40 * <ul> 41 * This class can appear in two places: 42 * <li> When a {@link PreferenceActivity} points to this, it is used as the root 43 * and is not shown (only the contained preferences are shown). 44 * <li> When it appears inside another preference hierarchy, it is shown and 45 * serves as the gateway to another screen of preferences (either by showing 46 * another screen of preferences as a {@link Dialog} or via a 47 * {@link Context#startActivity(android.content.Intent)} from the 48 * {@link Preference#getIntent()}). The children of this {@link PreferenceScreen} 49 * are NOT shown in the screen that this {@link PreferenceScreen} is shown in. 50 * Instead, a separate screen will be shown when this preference is clicked. 51 * </ul> 52 * <p>Here's an example XML layout of a PreferenceScreen:</p> 53 * <pre> 54 <PreferenceScreen 55 xmlns:android="http://schemas.android.com/apk/res/android" 56 android:key="first_preferencescreen"> 57 <CheckBoxPreference 58 android:key="wifi enabled" 59 android:title="WiFi" /> 60 <PreferenceScreen 61 android:key="second_preferencescreen" 62 android:title="WiFi settings"> 63 <CheckBoxPreference 64 android:key="prefer wifi" 65 android:title="Prefer WiFi" /> 66 ... other preferences here ... 67 </PreferenceScreen> 68 </PreferenceScreen> </pre> 69 * <p> 70 * In this example, the "first_preferencescreen" will be used as the root of the 71 * hierarchy and given to a {@link PreferenceActivity}. The first screen will 72 * show preferences "WiFi" (which can be used to quickly enable/disable WiFi) 73 * and "WiFi settings". The "WiFi settings" is the "second_preferencescreen" and when 74 * clicked will show another screen of preferences such as "Prefer WiFi" (and 75 * the other preferences that are children of the "second_preferencescreen" tag). 76 * 77 * @see PreferenceCategory 78 */ 79 public final class PreferenceScreen extends PreferenceGroup implements AdapterView.OnItemClickListener, 80 DialogInterface.OnDismissListener { 81 82 private ListAdapter mRootAdapter; 83 84 private Dialog mDialog; 85 86 private ListView mListView; 87 88 /** 89 * Do NOT use this constructor, use {@link PreferenceManager#createPreferenceScreen(Context)}. 90 * @hide- 91 */ 92 public PreferenceScreen(Context context, AttributeSet attrs) { 93 super(context, attrs, com.android.internal.R.attr.preferenceScreenStyle); 94 } 95 96 /** 97 * Returns an adapter that can be attached to a {@link PreferenceActivity} 98 * or {@link PreferenceFragment} to show the preferences contained in this 99 * {@link PreferenceScreen}. 100 * <p> 101 * This {@link PreferenceScreen} will NOT appear in the returned adapter, instead 102 * it appears in the hierarchy above this {@link PreferenceScreen}. 103 * <p> 104 * This adapter's {@link Adapter#getItem(int)} should always return a 105 * subclass of {@link Preference}. 106 * 107 * @return An adapter that provides the {@link Preference} contained in this 108 * {@link PreferenceScreen}. 109 */ 110 public ListAdapter getRootAdapter() { 111 if (mRootAdapter == null) { 112 mRootAdapter = onCreateRootAdapter(); 113 } 114 115 return mRootAdapter; 116 } 117 118 /** 119 * Creates the root adapter. 120 * 121 * @return An adapter that contains the preferences contained in this {@link PreferenceScreen}. 122 * @see #getRootAdapter() 123 */ 124 protected ListAdapter onCreateRootAdapter() { 125 return new PreferenceGroupAdapter(this); 126 } 127 128 /** 129 * Binds a {@link ListView} to the preferences contained in this {@link PreferenceScreen} via 130 * {@link #getRootAdapter()}. It also handles passing list item clicks to the corresponding 131 * {@link Preference} contained by this {@link PreferenceScreen}. 132 * 133 * @param listView The list view to attach to. 134 */ 135 public void bind(ListView listView) { 136 listView.setOnItemClickListener(this); 137 listView.setAdapter(getRootAdapter()); 138 139 onAttachedToActivity(); 140 } 141 142 @Override 143 protected void onClick() { 144 if (getIntent() != null || getFragment() != null || getPreferenceCount() == 0) { 145 return; 146 } 147 148 showDialog(null); 149 } 150 151 private void showDialog(Bundle state) { 152 Context context = getContext(); 153 if (mListView != null) { 154 mListView.setAdapter(null); 155 } 156 mListView = new ListView(context); 157 bind(mListView); 158 159 // Set the title bar if title is available, else no title bar 160 final CharSequence title = getTitle(); 161 Dialog dialog = mDialog = new Dialog(context, context.getThemeResId()); 162 if (TextUtils.isEmpty(title)) { 163 dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); 164 } else { 165 dialog.setTitle(title); 166 } 167 dialog.setContentView(mListView); 168 dialog.setOnDismissListener(this); 169 if (state != null) { 170 dialog.onRestoreInstanceState(state); 171 } 172 173 // Add the screen to the list of preferences screens opened as dialogs 174 getPreferenceManager().addPreferencesScreen(dialog); 175 176 dialog.show(); 177 } 178 179 public void onDismiss(DialogInterface dialog) { 180 mDialog = null; 181 getPreferenceManager().removePreferencesScreen(dialog); 182 } 183 184 /** 185 * Used to get a handle to the dialog. 186 * This is useful for cases where we want to manipulate the dialog 187 * as we would with any other activity or view. 188 */ 189 public Dialog getDialog() { 190 return mDialog; 191 } 192 193 public void onItemClick(AdapterView parent, View view, int position, long id) { 194 // If the list has headers, subtract them from the index. 195 if (parent instanceof ListView) { 196 position -= ((ListView) parent).getHeaderViewsCount(); 197 } 198 Object item = getRootAdapter().getItem(position); 199 if (!(item instanceof Preference)) return; 200 201 final Preference preference = (Preference) item; 202 preference.performClick(this); 203 } 204 205 @Override 206 protected boolean isOnSameScreenAsChildren() { 207 return false; 208 } 209 210 @Override 211 protected Parcelable onSaveInstanceState() { 212 final Parcelable superState = super.onSaveInstanceState(); 213 final Dialog dialog = mDialog; 214 if (dialog == null || !dialog.isShowing()) { 215 return superState; 216 } 217 218 final SavedState myState = new SavedState(superState); 219 myState.isDialogShowing = true; 220 myState.dialogBundle = dialog.onSaveInstanceState(); 221 return myState; 222 } 223 224 @Override 225 protected void onRestoreInstanceState(Parcelable state) { 226 if (state == null || !state.getClass().equals(SavedState.class)) { 227 // Didn't save state for us in onSaveInstanceState 228 super.onRestoreInstanceState(state); 229 return; 230 } 231 232 SavedState myState = (SavedState) state; 233 super.onRestoreInstanceState(myState.getSuperState()); 234 if (myState.isDialogShowing) { 235 showDialog(myState.dialogBundle); 236 } 237 } 238 239 private static class SavedState extends BaseSavedState { 240 boolean isDialogShowing; 241 Bundle dialogBundle; 242 243 public SavedState(Parcel source) { 244 super(source); 245 isDialogShowing = source.readInt() == 1; 246 dialogBundle = source.readBundle(); 247 } 248 249 @Override 250 public void writeToParcel(Parcel dest, int flags) { 251 super.writeToParcel(dest, flags); 252 dest.writeInt(isDialogShowing ? 1 : 0); 253 dest.writeBundle(dialogBundle); 254 } 255 256 public SavedState(Parcelable superState) { 257 super(superState); 258 } 259 260 public static final Parcelable.Creator<SavedState> CREATOR = 261 new Parcelable.Creator<SavedState>() { 262 public SavedState createFromParcel(Parcel in) { 263 return new SavedState(in); 264 } 265 266 public SavedState[] newArray(int size) { 267 return new SavedState[size]; 268 } 269 }; 270 } 271 272 } 273