• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
20 import android.annotation.CallSuper;
21 import android.annotation.DrawableRes;
22 import android.annotation.Nullable;
23 import android.annotation.StringRes;
24 import android.app.AlertDialog;
25 import android.app.Dialog;
26 import android.compat.annotation.UnsupportedAppUsage;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.SharedPreferences;
30 import android.content.res.TypedArray;
31 import android.graphics.drawable.Drawable;
32 import android.os.Bundle;
33 import android.os.Parcel;
34 import android.os.Parcelable;
35 import android.text.TextUtils;
36 import android.util.AttributeSet;
37 import android.view.LayoutInflater;
38 import android.view.View;
39 import android.widget.TextView;
40 
41 /**
42  * A base class for {@link Preference} objects that are
43  * dialog-based. These preferences will, when clicked, open a dialog showing the
44  * actual preference controls.
45  *
46  * @attr ref android.R.styleable#DialogPreference_dialogTitle
47  * @attr ref android.R.styleable#DialogPreference_dialogMessage
48  * @attr ref android.R.styleable#DialogPreference_dialogIcon
49  * @attr ref android.R.styleable#DialogPreference_dialogLayout
50  * @attr ref android.R.styleable#DialogPreference_positiveButtonText
51  * @attr ref android.R.styleable#DialogPreference_negativeButtonText
52  *
53  * @deprecated Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
54  *      <a href="{@docRoot}reference/androidx/preference/package-summary.html">
55  *      Preference Library</a> for consistent behavior across all devices. For more information on
56  *      using the AndroidX Preference Library see
57  *      <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>.
58  */
59 @Deprecated
60 public abstract class DialogPreference extends Preference implements
61         DialogInterface.OnClickListener, DialogInterface.OnDismissListener,
62         PreferenceManager.OnActivityDestroyListener {
63     @UnsupportedAppUsage
64     private AlertDialog.Builder mBuilder;
65 
66     @UnsupportedAppUsage
67     private CharSequence mDialogTitle;
68     @UnsupportedAppUsage
69     private CharSequence mDialogMessage;
70     @UnsupportedAppUsage
71     private Drawable mDialogIcon;
72     @UnsupportedAppUsage
73     private CharSequence mPositiveButtonText;
74     @UnsupportedAppUsage
75     private CharSequence mNegativeButtonText;
76     private int mDialogLayoutResId;
77 
78     /** The dialog, if it is showing. */
79     @UnsupportedAppUsage
80     private Dialog mDialog;
81 
82     /** Which button was clicked. */
83     @UnsupportedAppUsage
84     private int mWhichButtonClicked;
85 
86     /** Dismiss the dialog on the UI thread, but not inline with handlers */
87     private final Runnable mDismissRunnable = new Runnable() {
88         @Override
89         public void run() {
90             mDialog.dismiss();
91         }
92     };
93 
DialogPreference( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)94     public DialogPreference(
95             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
96         super(context, attrs, defStyleAttr, defStyleRes);
97 
98         final TypedArray a = context.obtainStyledAttributes(attrs,
99                 com.android.internal.R.styleable.DialogPreference, defStyleAttr, defStyleRes);
100         mDialogTitle = a.getString(com.android.internal.R.styleable.DialogPreference_dialogTitle);
101         if (mDialogTitle == null) {
102             // Fallback on the regular title of the preference
103             // (the one that is seen in the list)
104             mDialogTitle = getTitle();
105         }
106         mDialogMessage = a.getString(com.android.internal.R.styleable.DialogPreference_dialogMessage);
107         mDialogIcon = a.getDrawable(com.android.internal.R.styleable.DialogPreference_dialogIcon);
108         mPositiveButtonText = a.getString(com.android.internal.R.styleable.DialogPreference_positiveButtonText);
109         mNegativeButtonText = a.getString(com.android.internal.R.styleable.DialogPreference_negativeButtonText);
110         mDialogLayoutResId = a.getResourceId(com.android.internal.R.styleable.DialogPreference_dialogLayout,
111                 mDialogLayoutResId);
112         a.recycle();
113     }
114 
DialogPreference(Context context, AttributeSet attrs, int defStyleAttr)115     public DialogPreference(Context context, AttributeSet attrs, int defStyleAttr) {
116         this(context, attrs, defStyleAttr, 0);
117     }
118 
DialogPreference(Context context, AttributeSet attrs)119     public DialogPreference(Context context, AttributeSet attrs) {
120         this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle);
121     }
122 
DialogPreference(Context context)123     public DialogPreference(Context context) {
124         this(context, null);
125     }
126 
127     /**
128      * Sets the title of the dialog. This will be shown on subsequent dialogs.
129      *
130      * @param dialogTitle The title.
131      */
setDialogTitle(CharSequence dialogTitle)132     public void setDialogTitle(CharSequence dialogTitle) {
133         mDialogTitle = dialogTitle;
134     }
135 
136     /**
137      * @see #setDialogTitle(CharSequence)
138      * @param dialogTitleResId The dialog title as a resource.
139      */
setDialogTitle(int dialogTitleResId)140     public void setDialogTitle(int dialogTitleResId) {
141         setDialogTitle(getContext().getString(dialogTitleResId));
142     }
143 
144     /**
145      * Returns the title to be shown on subsequent dialogs.
146      * @return The title.
147      */
getDialogTitle()148     public CharSequence getDialogTitle() {
149         return mDialogTitle;
150     }
151 
152     /**
153      * Sets the message of the dialog. This will be shown on subsequent dialogs.
154      * <p>
155      * This message forms the content View of the dialog and conflicts with
156      * list-based dialogs, for example. If setting a custom View on a dialog via
157      * {@link #setDialogLayoutResource(int)}, include a text View with ID
158      * {@link android.R.id#message} and it will be populated with this message.
159      *
160      * @param dialogMessage The message.
161      */
setDialogMessage(CharSequence dialogMessage)162     public void setDialogMessage(CharSequence dialogMessage) {
163         mDialogMessage = dialogMessage;
164     }
165 
166     /**
167      * @see #setDialogMessage(CharSequence)
168      * @param dialogMessageResId The dialog message as a resource.
169      */
setDialogMessage(int dialogMessageResId)170     public void setDialogMessage(int dialogMessageResId) {
171         setDialogMessage(getContext().getString(dialogMessageResId));
172     }
173 
174     /**
175      * Returns the message to be shown on subsequent dialogs.
176      * @return The message.
177      */
getDialogMessage()178     public CharSequence getDialogMessage() {
179         return mDialogMessage;
180     }
181 
182     /**
183      * Sets the icon of the dialog. This will be shown on subsequent dialogs.
184      *
185      * @param dialogIcon The icon, as a {@link Drawable}.
186      */
setDialogIcon(Drawable dialogIcon)187     public void setDialogIcon(Drawable dialogIcon) {
188         mDialogIcon = dialogIcon;
189     }
190 
191     /**
192      * Sets the icon (resource ID) of the dialog. This will be shown on
193      * subsequent dialogs.
194      *
195      * @param dialogIconRes The icon, as a resource ID.
196      */
setDialogIcon(@rawableRes int dialogIconRes)197     public void setDialogIcon(@DrawableRes int dialogIconRes) {
198         mDialogIcon = getContext().getDrawable(dialogIconRes);
199     }
200 
201     /**
202      * Returns the icon to be shown on subsequent dialogs.
203      * @return The icon, as a {@link Drawable}.
204      */
getDialogIcon()205     public Drawable getDialogIcon() {
206         return mDialogIcon;
207     }
208 
209     /**
210      * Sets the text of the positive button of the dialog. This will be shown on
211      * subsequent dialogs.
212      *
213      * @param positiveButtonText The text of the positive button.
214      */
setPositiveButtonText(CharSequence positiveButtonText)215     public void setPositiveButtonText(CharSequence positiveButtonText) {
216         mPositiveButtonText = positiveButtonText;
217     }
218 
219     /**
220      * @see #setPositiveButtonText(CharSequence)
221      * @param positiveButtonTextResId The positive button text as a resource.
222      */
setPositiveButtonText(@tringRes int positiveButtonTextResId)223     public void setPositiveButtonText(@StringRes int positiveButtonTextResId) {
224         setPositiveButtonText(getContext().getString(positiveButtonTextResId));
225     }
226 
227     /**
228      * Returns the text of the positive button to be shown on subsequent
229      * dialogs.
230      *
231      * @return The text of the positive button.
232      */
getPositiveButtonText()233     public CharSequence getPositiveButtonText() {
234         return mPositiveButtonText;
235     }
236 
237     /**
238      * Sets the text of the negative button of the dialog. This will be shown on
239      * subsequent dialogs.
240      *
241      * @param negativeButtonText The text of the negative button.
242      */
setNegativeButtonText(CharSequence negativeButtonText)243     public void setNegativeButtonText(CharSequence negativeButtonText) {
244         mNegativeButtonText = negativeButtonText;
245     }
246 
247     /**
248      * @see #setNegativeButtonText(CharSequence)
249      * @param negativeButtonTextResId The negative button text as a resource.
250      */
setNegativeButtonText(@tringRes int negativeButtonTextResId)251     public void setNegativeButtonText(@StringRes int negativeButtonTextResId) {
252         setNegativeButtonText(getContext().getString(negativeButtonTextResId));
253     }
254 
255     /**
256      * Returns the text of the negative button to be shown on subsequent
257      * dialogs.
258      *
259      * @return The text of the negative button.
260      */
getNegativeButtonText()261     public CharSequence getNegativeButtonText() {
262         return mNegativeButtonText;
263     }
264 
265     /**
266      * Sets the layout resource that is inflated as the {@link View} to be shown
267      * as the content View of subsequent dialogs.
268      *
269      * @param dialogLayoutResId The layout resource ID to be inflated.
270      * @see #setDialogMessage(CharSequence)
271      */
setDialogLayoutResource(int dialogLayoutResId)272     public void setDialogLayoutResource(int dialogLayoutResId) {
273         mDialogLayoutResId = dialogLayoutResId;
274     }
275 
276     /**
277      * Returns the layout resource that is used as the content View for
278      * subsequent dialogs.
279      *
280      * @return The layout resource.
281      */
getDialogLayoutResource()282     public int getDialogLayoutResource() {
283         return mDialogLayoutResId;
284     }
285 
286     /**
287      * Prepares the dialog builder to be shown when the preference is clicked.
288      * Use this to set custom properties on the dialog.
289      * <p>
290      * Do not {@link AlertDialog.Builder#create()} or
291      * {@link AlertDialog.Builder#show()}.
292      */
onPrepareDialogBuilder(AlertDialog.Builder builder)293     protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
294     }
295 
296     @Override
onClick()297     protected void onClick() {
298         if (mDialog != null && mDialog.isShowing()) return;
299 
300         showDialog(null);
301     }
302 
303     /**
304      * Shows the dialog associated with this Preference. This is normally initiated
305      * automatically on clicking on the preference. Call this method if you need to
306      * show the dialog on some other event.
307      *
308      * @param state Optional instance state to restore on the dialog
309      */
showDialog(Bundle state)310     protected void showDialog(Bundle state) {
311         Context context = getContext();
312 
313         mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
314 
315         mBuilder = new AlertDialog.Builder(context)
316             .setTitle(mDialogTitle)
317             .setIcon(mDialogIcon)
318             .setPositiveButton(mPositiveButtonText, this)
319             .setNegativeButton(mNegativeButtonText, this);
320 
321         View contentView = onCreateDialogView();
322         if (contentView != null) {
323             onBindDialogView(contentView);
324             mBuilder.setView(contentView);
325         } else {
326             mBuilder.setMessage(mDialogMessage);
327         }
328 
329         onPrepareDialogBuilder(mBuilder);
330 
331         getPreferenceManager().registerOnActivityDestroyListener(this);
332 
333         // Create the dialog
334         final Dialog dialog = mDialog = mBuilder.create();
335         if (state != null) {
336             dialog.onRestoreInstanceState(state);
337         }
338         dialog.setOnShowListener(new DialogInterface.OnShowListener() {
339             @Override
340             public void onShow(DialogInterface dialog) {
341                 removeDismissCallbacks();
342             }
343         });
344         dialog.setOnDismissListener(this);
345         dialog.show();
346     }
347 
348     /**
349      * Get the DecorView.
350      * @return the DecorView for the current dialog window, if it exists.
351      * If the window does not exist, null is returned.
352      */
353     @Nullable
getDecorView()354     private View getDecorView() {
355         if (mDialog != null && mDialog.getWindow() != null) {
356             return mDialog.getWindow().getDecorView();
357         }
358         return null;
359     }
360 
postDismiss()361     void postDismiss() {
362         removeDismissCallbacks();
363         View decorView = getDecorView();
364         if (decorView != null) {
365             // If decorView is null, dialog was already dismissed
366             decorView.post(mDismissRunnable);
367         }
368     }
369 
removeDismissCallbacks()370     private void removeDismissCallbacks() {
371         View decorView = getDecorView();
372         if (decorView != null) {
373             decorView.removeCallbacks(mDismissRunnable);
374         }
375     }
376 
377     /**
378      * Creates the content view for the dialog (if a custom content view is
379      * required). By default, it inflates the dialog layout resource if it is
380      * set.
381      *
382      * @return The content View for the dialog.
383      * @see #setLayoutResource(int)
384      */
onCreateDialogView()385     protected View onCreateDialogView() {
386         if (mDialogLayoutResId == 0) {
387             return null;
388         }
389 
390         LayoutInflater inflater = LayoutInflater.from(mBuilder.getContext());
391         return inflater.inflate(mDialogLayoutResId, null);
392     }
393 
394     /**
395      * Binds views in the content View of the dialog to data.
396      * <p>
397      * Make sure to call through to the superclass implementation.
398      *
399      * @param view The content View of the dialog, if it is custom.
400      */
401     @CallSuper
onBindDialogView(View view)402     protected void onBindDialogView(View view) {
403         View dialogMessageView = view.findViewById(com.android.internal.R.id.message);
404 
405         if (dialogMessageView != null) {
406             final CharSequence message = getDialogMessage();
407             int newVisibility = View.GONE;
408 
409             if (!TextUtils.isEmpty(message)) {
410                 if (dialogMessageView instanceof TextView) {
411                     ((TextView) dialogMessageView).setText(message);
412                 }
413 
414                 newVisibility = View.VISIBLE;
415             }
416 
417             if (dialogMessageView.getVisibility() != newVisibility) {
418                 dialogMessageView.setVisibility(newVisibility);
419             }
420         }
421     }
422 
onClick(DialogInterface dialog, int which)423     public void onClick(DialogInterface dialog, int which) {
424         mWhichButtonClicked = which;
425     }
426 
427     @Override
onDismiss(DialogInterface dialog)428     public void onDismiss(DialogInterface dialog) {
429         removeDismissCallbacks();
430         getPreferenceManager().unregisterOnActivityDestroyListener(this);
431 
432         mDialog = null;
433         onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
434     }
435 
436     /**
437      * Called when the dialog is dismissed and should be used to save data to
438      * the {@link SharedPreferences}.
439      *
440      * @param positiveResult Whether the positive button was clicked (true), or
441      *            the negative button was clicked or the dialog was canceled (false).
442      */
onDialogClosed(boolean positiveResult)443     protected void onDialogClosed(boolean positiveResult) {
444     }
445 
446     /**
447      * Gets the dialog that is shown by this preference.
448      *
449      * @return The dialog, or null if a dialog is not being shown.
450      */
getDialog()451     public Dialog getDialog() {
452         return mDialog;
453     }
454 
455     /**
456      * {@inheritDoc}
457      */
onActivityDestroy()458     public void onActivityDestroy() {
459 
460         if (mDialog == null || !mDialog.isShowing()) {
461             return;
462         }
463 
464         mDialog.dismiss();
465     }
466 
467     @Override
onSaveInstanceState()468     protected Parcelable onSaveInstanceState() {
469         final Parcelable superState = super.onSaveInstanceState();
470         if (mDialog == null || !mDialog.isShowing()) {
471             return superState;
472         }
473 
474         final SavedState myState = new SavedState(superState);
475         myState.isDialogShowing = true;
476         myState.dialogBundle = mDialog.onSaveInstanceState();
477         return myState;
478     }
479 
480     @Override
onRestoreInstanceState(Parcelable state)481     protected void onRestoreInstanceState(Parcelable state) {
482         if (state == null || !state.getClass().equals(SavedState.class)) {
483             // Didn't save state for us in onSaveInstanceState
484             super.onRestoreInstanceState(state);
485             return;
486         }
487 
488         SavedState myState = (SavedState) state;
489         super.onRestoreInstanceState(myState.getSuperState());
490         if (myState.isDialogShowing) {
491             showDialog(myState.dialogBundle);
492         }
493     }
494 
495     private static class SavedState extends BaseSavedState {
496         boolean isDialogShowing;
497         Bundle dialogBundle;
498 
SavedState(Parcel source)499         public SavedState(Parcel source) {
500             super(source);
501             isDialogShowing = source.readInt() == 1;
502             dialogBundle = source.readBundle();
503         }
504 
505         @Override
writeToParcel(Parcel dest, int flags)506         public void writeToParcel(Parcel dest, int flags) {
507             super.writeToParcel(dest, flags);
508             dest.writeInt(isDialogShowing ? 1 : 0);
509             dest.writeBundle(dialogBundle);
510         }
511 
SavedState(Parcelable superState)512         public SavedState(Parcelable superState) {
513             super(superState);
514         }
515 
516         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
517                 new Parcelable.Creator<SavedState>() {
518             public SavedState createFromParcel(Parcel in) {
519                 return new SavedState(in);
520             }
521 
522             public SavedState[] newArray(int size) {
523                 return new SavedState[size];
524             }
525         };
526     }
527 
528 }
529