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.compat.annotation.UnsupportedAppUsage; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.res.TypedArray; 24 import android.graphics.drawable.Drawable; 25 import android.os.Build; 26 import android.os.Bundle; 27 import android.os.Parcel; 28 import android.os.Parcelable; 29 import android.text.TextUtils; 30 import android.util.AttributeSet; 31 import android.view.LayoutInflater; 32 import android.view.View; 33 import android.view.Window; 34 import android.widget.Adapter; 35 import android.widget.AdapterView; 36 import android.widget.ListAdapter; 37 import android.widget.ListView; 38 import android.widget.TextView; 39 40 /** 41 * Represents a top-level {@link Preference} that 42 * is the root of a Preference hierarchy. A {@link PreferenceActivity} 43 * points to an instance of this class to show the preferences. To instantiate 44 * this class, use {@link PreferenceManager#createPreferenceScreen(Context)}. 45 * <ul> 46 * This class can appear in two places: 47 * <li> When a {@link PreferenceActivity} points to this, it is used as the root 48 * and is not shown (only the contained preferences are shown). 49 * <li> When it appears inside another preference hierarchy, it is shown and 50 * serves as the gateway to another screen of preferences (either by showing 51 * another screen of preferences as a {@link Dialog} or via a 52 * {@link Context#startActivity(android.content.Intent)} from the 53 * {@link Preference#getIntent()}). The children of this {@link PreferenceScreen} 54 * are NOT shown in the screen that this {@link PreferenceScreen} is shown in. 55 * Instead, a separate screen will be shown when this preference is clicked. 56 * </ul> 57 * <p>Here's an example XML layout of a PreferenceScreen:</p> 58 * <pre> 59 <PreferenceScreen 60 xmlns:android="http://schemas.android.com/apk/res/android" 61 android:key="first_preferencescreen"> 62 <CheckBoxPreference 63 android:key="wifi enabled" 64 android:title="WiFi" /> 65 <PreferenceScreen 66 android:key="second_preferencescreen" 67 android:title="WiFi settings"> 68 <CheckBoxPreference 69 android:key="prefer wifi" 70 android:title="Prefer WiFi" /> 71 ... other preferences here ... 72 </PreferenceScreen> 73 </PreferenceScreen> </pre> 74 * <p> 75 * In this example, the "first_preferencescreen" will be used as the root of the 76 * hierarchy and given to a {@link PreferenceActivity}. The first screen will 77 * show preferences "WiFi" (which can be used to quickly enable/disable WiFi) 78 * and "WiFi settings". The "WiFi settings" is the "second_preferencescreen" and when 79 * clicked will show another screen of preferences such as "Prefer WiFi" (and 80 * the other preferences that are children of the "second_preferencescreen" tag). 81 * 82 * <div class="special reference"> 83 * <h3>Developer Guides</h3> 84 * <p>For information about building a settings UI with Preferences, 85 * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a> 86 * guide.</p> 87 * </div> 88 * 89 * @see PreferenceCategory 90 * 91 * @deprecated Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 92 * <a href="{@docRoot}reference/androidx/preference/package-summary.html"> 93 * Preference Library</a> for consistent behavior across all devices. For more information on 94 * using the AndroidX Preference Library see 95 * <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>. 96 */ 97 @Deprecated 98 public final class PreferenceScreen extends PreferenceGroup implements AdapterView.OnItemClickListener, 99 DialogInterface.OnDismissListener { 100 101 @UnsupportedAppUsage 102 private ListAdapter mRootAdapter; 103 104 private Dialog mDialog; 105 106 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 107 private ListView mListView; 108 109 private int mLayoutResId = com.android.internal.R.layout.preference_list_fragment; 110 private Drawable mDividerDrawable; 111 private boolean mDividerSpecified; 112 113 /** 114 * Do NOT use this constructor, use {@link PreferenceManager#createPreferenceScreen(Context)}. 115 * @hide- 116 */ 117 @UnsupportedAppUsage PreferenceScreen(Context context, AttributeSet attrs)118 public PreferenceScreen(Context context, AttributeSet attrs) { 119 super(context, attrs, com.android.internal.R.attr.preferenceScreenStyle); 120 121 TypedArray a = context.obtainStyledAttributes(null, 122 com.android.internal.R.styleable.PreferenceScreen, 123 com.android.internal.R.attr.preferenceScreenStyle, 124 0); 125 126 mLayoutResId = a.getResourceId( 127 com.android.internal.R.styleable.PreferenceScreen_screenLayout, 128 mLayoutResId); 129 if (a.hasValueOrEmpty(com.android.internal.R.styleable.PreferenceScreen_divider)) { 130 mDividerDrawable = 131 a.getDrawable(com.android.internal.R.styleable.PreferenceScreen_divider); 132 mDividerSpecified = true; 133 } 134 135 a.recycle(); 136 } 137 138 /** 139 * Returns an adapter that can be attached to a {@link PreferenceActivity} 140 * or {@link PreferenceFragment} to show the preferences contained in this 141 * {@link PreferenceScreen}. 142 * <p> 143 * This {@link PreferenceScreen} will NOT appear in the returned adapter, instead 144 * it appears in the hierarchy above this {@link PreferenceScreen}. 145 * <p> 146 * This adapter's {@link Adapter#getItem(int)} should always return a 147 * subclass of {@link Preference}. 148 * 149 * @return An adapter that provides the {@link Preference} contained in this 150 * {@link PreferenceScreen}. 151 */ getRootAdapter()152 public ListAdapter getRootAdapter() { 153 if (mRootAdapter == null) { 154 mRootAdapter = onCreateRootAdapter(); 155 } 156 157 return mRootAdapter; 158 } 159 160 /** 161 * Creates the root adapter. 162 * 163 * @return An adapter that contains the preferences contained in this {@link PreferenceScreen}. 164 * @see #getRootAdapter() 165 */ onCreateRootAdapter()166 protected ListAdapter onCreateRootAdapter() { 167 return new PreferenceGroupAdapter(this); 168 } 169 170 /** 171 * Binds a {@link ListView} to the preferences contained in this {@link PreferenceScreen} via 172 * {@link #getRootAdapter()}. It also handles passing list item clicks to the corresponding 173 * {@link Preference} contained by this {@link PreferenceScreen}. 174 * 175 * @param listView The list view to attach to. 176 */ bind(ListView listView)177 public void bind(ListView listView) { 178 listView.setOnItemClickListener(this); 179 listView.setAdapter(getRootAdapter()); 180 181 onAttachedToActivity(); 182 } 183 184 @Override onClick()185 protected void onClick() { 186 if (getIntent() != null || getFragment() != null || getPreferenceCount() == 0) { 187 return; 188 } 189 190 showDialog(null); 191 } 192 showDialog(Bundle state)193 private void showDialog(Bundle state) { 194 Context context = getContext(); 195 if (mListView != null) { 196 mListView.setAdapter(null); 197 } 198 199 LayoutInflater inflater = (LayoutInflater) 200 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 201 View childPrefScreen = inflater.inflate(mLayoutResId, null); 202 View titleView = childPrefScreen.findViewById(android.R.id.title); 203 mListView = (ListView) childPrefScreen.findViewById(android.R.id.list); 204 if (mDividerSpecified) { 205 mListView.setDivider(mDividerDrawable); 206 } 207 208 bind(mListView); 209 210 // Set the title bar if title is available, else no title bar 211 final CharSequence title = getTitle(); 212 Dialog dialog = mDialog = new Dialog(context, context.getThemeResId()); 213 if (TextUtils.isEmpty(title)) { 214 if (titleView != null) { 215 titleView.setVisibility(View.GONE); 216 } 217 dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); 218 } else { 219 if (titleView instanceof TextView) { 220 ((TextView) titleView).setText(title); 221 titleView.setVisibility(View.VISIBLE); 222 } else { 223 dialog.setTitle(title); 224 } 225 } 226 dialog.setContentView(childPrefScreen); 227 dialog.setOnDismissListener(this); 228 if (state != null) { 229 dialog.onRestoreInstanceState(state); 230 } 231 232 // Add the screen to the list of preferences screens opened as dialogs 233 getPreferenceManager().addPreferencesScreen(dialog); 234 235 dialog.show(); 236 } 237 onDismiss(DialogInterface dialog)238 public void onDismiss(DialogInterface dialog) { 239 mDialog = null; 240 getPreferenceManager().removePreferencesScreen(dialog); 241 } 242 243 /** 244 * Used to get a handle to the dialog. 245 * This is useful for cases where we want to manipulate the dialog 246 * as we would with any other activity or view. 247 */ getDialog()248 public Dialog getDialog() { 249 return mDialog; 250 } 251 onItemClick(AdapterView parent, View view, int position, long id)252 public void onItemClick(AdapterView parent, View view, int position, long id) { 253 // If the list has headers, subtract them from the index. 254 if (parent instanceof ListView) { 255 position -= ((ListView) parent).getHeaderViewsCount(); 256 } 257 Object item = getRootAdapter().getItem(position); 258 if (!(item instanceof Preference)) return; 259 260 final Preference preference = (Preference) item; 261 preference.performClick(this); 262 } 263 264 @Override isOnSameScreenAsChildren()265 protected boolean isOnSameScreenAsChildren() { 266 return false; 267 } 268 269 @Override onSaveInstanceState()270 protected Parcelable onSaveInstanceState() { 271 final Parcelable superState = super.onSaveInstanceState(); 272 final Dialog dialog = mDialog; 273 if (dialog == null || !dialog.isShowing()) { 274 return superState; 275 } 276 277 final SavedState myState = new SavedState(superState); 278 myState.isDialogShowing = true; 279 myState.dialogBundle = dialog.onSaveInstanceState(); 280 return myState; 281 } 282 283 @Override onRestoreInstanceState(Parcelable state)284 protected void onRestoreInstanceState(Parcelable state) { 285 if (state == null || !state.getClass().equals(SavedState.class)) { 286 // Didn't save state for us in onSaveInstanceState 287 super.onRestoreInstanceState(state); 288 return; 289 } 290 291 SavedState myState = (SavedState) state; 292 super.onRestoreInstanceState(myState.getSuperState()); 293 if (myState.isDialogShowing) { 294 showDialog(myState.dialogBundle); 295 } 296 } 297 298 private static class SavedState extends BaseSavedState { 299 boolean isDialogShowing; 300 Bundle dialogBundle; 301 SavedState(Parcel source)302 public SavedState(Parcel source) { 303 super(source); 304 isDialogShowing = source.readInt() == 1; 305 dialogBundle = source.readBundle(); 306 } 307 308 @Override writeToParcel(Parcel dest, int flags)309 public void writeToParcel(Parcel dest, int flags) { 310 super.writeToParcel(dest, flags); 311 dest.writeInt(isDialogShowing ? 1 : 0); 312 dest.writeBundle(dialogBundle); 313 } 314 SavedState(Parcelable superState)315 public SavedState(Parcelable superState) { 316 super(superState); 317 } 318 319 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR = 320 new Parcelable.Creator<SavedState>() { 321 public SavedState createFromParcel(Parcel in) { 322 return new SavedState(in); 323 } 324 325 public SavedState[] newArray(int size) { 326 return new SavedState[size]; 327 } 328 }; 329 } 330 331 } 332