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