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