1/* 2 * Copyright (C) 2015 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 17package android.support.v14.preference; 18 19import android.app.AlertDialog; 20import android.app.Dialog; 21import android.app.DialogFragment; 22import android.app.Fragment; 23import android.content.Context; 24import android.content.DialogInterface; 25import android.graphics.Bitmap; 26import android.graphics.Canvas; 27import android.graphics.drawable.BitmapDrawable; 28import android.graphics.drawable.Drawable; 29import android.os.Bundle; 30import android.support.annotation.LayoutRes; 31import android.support.annotation.NonNull; 32import android.support.v7.preference.DialogPreference; 33import android.text.TextUtils; 34import android.view.LayoutInflater; 35import android.view.View; 36import android.view.Window; 37import android.view.WindowManager; 38import android.widget.TextView; 39 40/** 41 * Abstract base class which presents a dialog associated with a 42 * {@link android.support.v7.preference.DialogPreference}. Since the preference object may 43 * not be available during fragment re-creation, the necessary information for displaying the dialog 44 * is read once during the initial call to {@link #onCreate(Bundle)} and saved/restored in the saved 45 * instance state. Custom subclasses should also follow this pattern. 46 */ 47public abstract class PreferenceDialogFragment extends DialogFragment implements 48 DialogInterface.OnClickListener { 49 50 protected static final String ARG_KEY = "key"; 51 52 private static final String SAVE_STATE_TITLE = "PreferenceDialogFragment.title"; 53 private static final String SAVE_STATE_POSITIVE_TEXT = "PreferenceDialogFragment.positiveText"; 54 private static final String SAVE_STATE_NEGATIVE_TEXT = "PreferenceDialogFragment.negativeText"; 55 private static final String SAVE_STATE_MESSAGE = "PreferenceDialogFragment.message"; 56 private static final String SAVE_STATE_LAYOUT = "PreferenceDialogFragment.layout"; 57 private static final String SAVE_STATE_ICON = "PreferenceDialogFragment.icon"; 58 59 private DialogPreference mPreference; 60 61 private CharSequence mDialogTitle; 62 private CharSequence mPositiveButtonText; 63 private CharSequence mNegativeButtonText; 64 private CharSequence mDialogMessage; 65 private @LayoutRes int mDialogLayoutRes; 66 67 private BitmapDrawable mDialogIcon; 68 69 /** Which button was clicked. */ 70 private int mWhichButtonClicked; 71 72 @Override 73 public void onCreate(Bundle savedInstanceState) { 74 super.onCreate(savedInstanceState); 75 76 final Fragment rawFragment = getTargetFragment(); 77 if (!(rawFragment instanceof DialogPreference.TargetFragment)) { 78 throw new IllegalStateException("Target fragment must implement TargetFragment" + 79 " interface"); 80 } 81 82 final DialogPreference.TargetFragment fragment = 83 (DialogPreference.TargetFragment) rawFragment; 84 85 final String key = getArguments().getString(ARG_KEY); 86 if (savedInstanceState == null) { 87 mPreference = (DialogPreference) fragment.findPreference(key); 88 mDialogTitle = mPreference.getDialogTitle(); 89 mPositiveButtonText = mPreference.getPositiveButtonText(); 90 mNegativeButtonText = mPreference.getNegativeButtonText(); 91 mDialogMessage = mPreference.getDialogMessage(); 92 mDialogLayoutRes = mPreference.getDialogLayoutResource(); 93 94 final Drawable icon = mPreference.getDialogIcon(); 95 if (icon == null || icon instanceof BitmapDrawable) { 96 mDialogIcon = (BitmapDrawable) icon; 97 } else { 98 final Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), 99 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 100 final Canvas canvas = new Canvas(bitmap); 101 icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 102 icon.draw(canvas); 103 mDialogIcon = new BitmapDrawable(getResources(), bitmap); 104 } 105 } else { 106 mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE); 107 mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT); 108 mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT); 109 mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE); 110 mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0); 111 final Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON); 112 if (bitmap != null) { 113 mDialogIcon = new BitmapDrawable(getResources(), bitmap); 114 } 115 } 116 } 117 118 @Override 119 public void onSaveInstanceState(@NonNull Bundle outState) { 120 super.onSaveInstanceState(outState); 121 122 outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle); 123 outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText); 124 outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText); 125 outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage); 126 outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes); 127 if (mDialogIcon != null) { 128 outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap()); 129 } 130 } 131 132 @Override 133 public @NonNull 134 Dialog onCreateDialog(Bundle savedInstanceState) { 135 final Context context = getActivity(); 136 mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE; 137 138 final AlertDialog.Builder builder = new AlertDialog.Builder(context) 139 .setTitle(mDialogTitle) 140 .setIcon(mDialogIcon) 141 .setPositiveButton(mPositiveButtonText, this) 142 .setNegativeButton(mNegativeButtonText, this); 143 144 View contentView = onCreateDialogView(context); 145 if (contentView != null) { 146 onBindDialogView(contentView); 147 builder.setView(contentView); 148 } else { 149 builder.setMessage(mDialogMessage); 150 } 151 152 onPrepareDialogBuilder(builder); 153 154 // Create the dialog 155 final Dialog dialog = builder.create(); 156 if (needInputMethod()) { 157 requestInputMethod(dialog); 158 } 159 160 return dialog; 161 } 162 163 /** 164 * Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has 165 * been called on the {@link PreferenceFragment} which launched this dialog. 166 * 167 * @return The {@link DialogPreference} associated with this 168 * dialog. 169 */ 170 public DialogPreference getPreference() { 171 if (mPreference == null) { 172 final String key = getArguments().getString(ARG_KEY); 173 final DialogPreference.TargetFragment fragment = 174 (DialogPreference.TargetFragment) getTargetFragment(); 175 mPreference = (DialogPreference) fragment.findPreference(key); 176 } 177 return mPreference; 178 } 179 180 /** 181 * Prepares the dialog builder to be shown when the preference is clicked. 182 * Use this to set custom properties on the dialog. 183 * <p> 184 * Do not {@link AlertDialog.Builder#create()} or 185 * {@link AlertDialog.Builder#show()}. 186 */ 187 protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {} 188 189 /** 190 * Returns whether the preference needs to display a soft input method when the dialog 191 * is displayed. Default is false. Subclasses should override this method if they need 192 * the soft input method brought up automatically. 193 * @hide 194 */ 195 protected boolean needInputMethod() { 196 return false; 197 } 198 199 /** 200 * Sets the required flags on the dialog window to enable input method window to show up. 201 */ 202 private void requestInputMethod(Dialog dialog) { 203 Window window = dialog.getWindow(); 204 window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); 205 } 206 207 /** 208 * Creates the content view for the dialog (if a custom content view is 209 * required). By default, it inflates the dialog layout resource if it is 210 * set. 211 * 212 * @return The content View for the dialog. 213 * @see DialogPreference#setLayoutResource(int) 214 */ 215 protected View onCreateDialogView(Context context) { 216 final int resId = mDialogLayoutRes; 217 if (resId == 0) { 218 return null; 219 } 220 221 LayoutInflater inflater = LayoutInflater.from(context); 222 return inflater.inflate(resId, null); 223 } 224 225 /** 226 * Binds views in the content View of the dialog to data. 227 * <p> 228 * Make sure to call through to the superclass implementation. 229 * 230 * @param view The content View of the dialog, if it is custom. 231 */ 232 protected void onBindDialogView(View view) { 233 View dialogMessageView = view.findViewById(android.R.id.message); 234 235 if (dialogMessageView != null) { 236 final CharSequence message = mDialogMessage; 237 int newVisibility = View.GONE; 238 239 if (!TextUtils.isEmpty(message)) { 240 if (dialogMessageView instanceof TextView) { 241 ((TextView) dialogMessageView).setText(message); 242 } 243 244 newVisibility = View.VISIBLE; 245 } 246 247 if (dialogMessageView.getVisibility() != newVisibility) { 248 dialogMessageView.setVisibility(newVisibility); 249 } 250 } 251 } 252 253 @Override 254 public void onClick(DialogInterface dialog, int which) { 255 mWhichButtonClicked = which; 256 } 257 258 @Override 259 public void onDismiss(DialogInterface dialog) { 260 super.onDismiss(dialog); 261 onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE); 262 } 263 264 public abstract void onDialogClosed(boolean positiveResult); 265} 266