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