/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.preference; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.TextView; /** * A base class for {@link Preference} objects that are * dialog-based. These preferences will, when clicked, open a dialog showing the * actual preference controls. * * @attr ref android.R.styleable#DialogPreference_dialogTitle * @attr ref android.R.styleable#DialogPreference_dialogMessage * @attr ref android.R.styleable#DialogPreference_dialogIcon * @attr ref android.R.styleable#DialogPreference_dialogLayout * @attr ref android.R.styleable#DialogPreference_positiveButtonText * @attr ref android.R.styleable#DialogPreference_negativeButtonText */ public abstract class DialogPreference extends Preference implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener, PreferenceManager.OnActivityDestroyListener { private AlertDialog.Builder mBuilder; private CharSequence mDialogTitle; private CharSequence mDialogMessage; private Drawable mDialogIcon; private CharSequence mPositiveButtonText; private CharSequence mNegativeButtonText; private int mDialogLayoutResId; /** The dialog, if it is showing. */ private Dialog mDialog; /** Which button was clicked. */ private int mWhichButtonClicked; public DialogPreference( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); final TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.DialogPreference, defStyleAttr, defStyleRes); mDialogTitle = a.getString(com.android.internal.R.styleable.DialogPreference_dialogTitle); if (mDialogTitle == null) { // Fallback on the regular title of the preference // (the one that is seen in the list) mDialogTitle = getTitle(); } mDialogMessage = a.getString(com.android.internal.R.styleable.DialogPreference_dialogMessage); mDialogIcon = a.getDrawable(com.android.internal.R.styleable.DialogPreference_dialogIcon); mPositiveButtonText = a.getString(com.android.internal.R.styleable.DialogPreference_positiveButtonText); mNegativeButtonText = a.getString(com.android.internal.R.styleable.DialogPreference_negativeButtonText); mDialogLayoutResId = a.getResourceId(com.android.internal.R.styleable.DialogPreference_dialogLayout, mDialogLayoutResId); a.recycle(); } public DialogPreference(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public DialogPreference(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle); } public DialogPreference(Context context) { this(context, null); } /** * Sets the title of the dialog. This will be shown on subsequent dialogs. * * @param dialogTitle The title. */ public void setDialogTitle(CharSequence dialogTitle) { mDialogTitle = dialogTitle; } /** * @see #setDialogTitle(CharSequence) * @param dialogTitleResId The dialog title as a resource. */ public void setDialogTitle(int dialogTitleResId) { setDialogTitle(getContext().getString(dialogTitleResId)); } /** * Returns the title to be shown on subsequent dialogs. * @return The title. */ public CharSequence getDialogTitle() { return mDialogTitle; } /** * Sets the message of the dialog. This will be shown on subsequent dialogs. *
* This message forms the content View of the dialog and conflicts with * list-based dialogs, for example. If setting a custom View on a dialog via * {@link #setDialogLayoutResource(int)}, include a text View with ID * {@link android.R.id#message} and it will be populated with this message. * * @param dialogMessage The message. */ public void setDialogMessage(CharSequence dialogMessage) { mDialogMessage = dialogMessage; } /** * @see #setDialogMessage(CharSequence) * @param dialogMessageResId The dialog message as a resource. */ public void setDialogMessage(int dialogMessageResId) { setDialogMessage(getContext().getString(dialogMessageResId)); } /** * Returns the message to be shown on subsequent dialogs. * @return The message. */ public CharSequence getDialogMessage() { return mDialogMessage; } /** * Sets the icon of the dialog. This will be shown on subsequent dialogs. * * @param dialogIcon The icon, as a {@link Drawable}. */ public void setDialogIcon(Drawable dialogIcon) { mDialogIcon = dialogIcon; } /** * Sets the icon (resource ID) of the dialog. This will be shown on * subsequent dialogs. * * @param dialogIconRes The icon, as a resource ID. */ public void setDialogIcon(int dialogIconRes) { mDialogIcon = getContext().getDrawable(dialogIconRes); } /** * Returns the icon to be shown on subsequent dialogs. * @return The icon, as a {@link Drawable}. */ public Drawable getDialogIcon() { return mDialogIcon; } /** * Sets the text of the positive button of the dialog. This will be shown on * subsequent dialogs. * * @param positiveButtonText The text of the positive button. */ public void setPositiveButtonText(CharSequence positiveButtonText) { mPositiveButtonText = positiveButtonText; } /** * @see #setPositiveButtonText(CharSequence) * @param positiveButtonTextResId The positive button text as a resource. */ public void setPositiveButtonText(int positiveButtonTextResId) { setPositiveButtonText(getContext().getString(positiveButtonTextResId)); } /** * Returns the text of the positive button to be shown on subsequent * dialogs. * * @return The text of the positive button. */ public CharSequence getPositiveButtonText() { return mPositiveButtonText; } /** * Sets the text of the negative button of the dialog. This will be shown on * subsequent dialogs. * * @param negativeButtonText The text of the negative button. */ public void setNegativeButtonText(CharSequence negativeButtonText) { mNegativeButtonText = negativeButtonText; } /** * @see #setNegativeButtonText(CharSequence) * @param negativeButtonTextResId The negative button text as a resource. */ public void setNegativeButtonText(int negativeButtonTextResId) { setNegativeButtonText(getContext().getString(negativeButtonTextResId)); } /** * Returns the text of the negative button to be shown on subsequent * dialogs. * * @return The text of the negative button. */ public CharSequence getNegativeButtonText() { return mNegativeButtonText; } /** * Sets the layout resource that is inflated as the {@link View} to be shown * as the content View of subsequent dialogs. * * @param dialogLayoutResId The layout resource ID to be inflated. * @see #setDialogMessage(CharSequence) */ public void setDialogLayoutResource(int dialogLayoutResId) { mDialogLayoutResId = dialogLayoutResId; } /** * Returns the layout resource that is used as the content View for * subsequent dialogs. * * @return The layout resource. */ public int getDialogLayoutResource() { return mDialogLayoutResId; } /** * Prepares the dialog builder to be shown when the preference is clicked. * Use this to set custom properties on the dialog. *
* Do not {@link AlertDialog.Builder#create()} or * {@link AlertDialog.Builder#show()}. */ protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { } @Override protected void onClick() { if (mDialog != null && mDialog.isShowing()) return; showDialog(null); } /** * Shows the dialog associated with this Preference. This is normally initiated * automatically on clicking on the preference. Call this method if you need to * show the dialog on some other event. * * @param state Optional instance state to restore on the dialog */ protected void showDialog(Bundle state) { Context context = getContext(); mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE; mBuilder = new AlertDialog.Builder(context) .setTitle(mDialogTitle) .setIcon(mDialogIcon) .setPositiveButton(mPositiveButtonText, this) .setNegativeButton(mNegativeButtonText, this); View contentView = onCreateDialogView(); if (contentView != null) { onBindDialogView(contentView); mBuilder.setView(contentView); } else { mBuilder.setMessage(mDialogMessage); } onPrepareDialogBuilder(mBuilder); getPreferenceManager().registerOnActivityDestroyListener(this); // Create the dialog final Dialog dialog = mDialog = mBuilder.create(); if (state != null) { dialog.onRestoreInstanceState(state); } if (needInputMethod()) { requestInputMethod(dialog); } dialog.setOnDismissListener(this); dialog.show(); } /** * Returns whether the preference needs to display a soft input method when the dialog * is displayed. Default is false. Subclasses should override this method if they need * the soft input method brought up automatically. * @hide */ protected boolean needInputMethod() { return false; } /** * Sets the required flags on the dialog window to enable input method window to show up. */ private void requestInputMethod(Dialog dialog) { Window window = dialog.getWindow(); window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); } /** * Creates the content view for the dialog (if a custom content view is * required). By default, it inflates the dialog layout resource if it is * set. * * @return The content View for the dialog. * @see #setLayoutResource(int) */ protected View onCreateDialogView() { if (mDialogLayoutResId == 0) { return null; } LayoutInflater inflater = LayoutInflater.from(mBuilder.getContext()); return inflater.inflate(mDialogLayoutResId, null); } /** * Binds views in the content View of the dialog to data. *
* Make sure to call through to the superclass implementation.
*
* @param view The content View of the dialog, if it is custom.
*/
protected void onBindDialogView(View view) {
View dialogMessageView = view.findViewById(com.android.internal.R.id.message);
if (dialogMessageView != null) {
final CharSequence message = getDialogMessage();
int newVisibility = View.GONE;
if (!TextUtils.isEmpty(message)) {
if (dialogMessageView instanceof TextView) {
((TextView) dialogMessageView).setText(message);
}
newVisibility = View.VISIBLE;
}
if (dialogMessageView.getVisibility() != newVisibility) {
dialogMessageView.setVisibility(newVisibility);
}
}
}
public void onClick(DialogInterface dialog, int which) {
mWhichButtonClicked = which;
}
public void onDismiss(DialogInterface dialog) {
getPreferenceManager().unregisterOnActivityDestroyListener(this);
mDialog = null;
onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
}
/**
* Called when the dialog is dismissed and should be used to save data to
* the {@link SharedPreferences}.
*
* @param positiveResult Whether the positive button was clicked (true), or
* the negative button was clicked or the dialog was canceled (false).
*/
protected void onDialogClosed(boolean positiveResult) {
}
/**
* Gets the dialog that is shown by this preference.
*
* @return The dialog, or null if a dialog is not being shown.
*/
public Dialog getDialog() {
return mDialog;
}
/**
* {@inheritDoc}
*/
public void onActivityDestroy() {
if (mDialog == null || !mDialog.isShowing()) {
return;
}
mDialog.dismiss();
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
if (mDialog == null || !mDialog.isShowing()) {
return superState;
}
final SavedState myState = new SavedState(superState);
myState.isDialogShowing = true;
myState.dialogBundle = mDialog.onSaveInstanceState();
return myState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {
// Didn't save state for us in onSaveInstanceState
super.onRestoreInstanceState(state);
return;
}
SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());
if (myState.isDialogShowing) {
showDialog(myState.dialogBundle);
}
}
private static class SavedState extends BaseSavedState {
boolean isDialogShowing;
Bundle dialogBundle;
public SavedState(Parcel source) {
super(source);
isDialogShowing = source.readInt() == 1;
dialogBundle = source.readBundle();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(isDialogShowing ? 1 : 0);
dest.writeBundle(dialogBundle);
}
public SavedState(Parcelable superState) {
super(superState);
}
public static final Parcelable.Creator