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