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.v7.preference;
18
19import android.app.Dialog;
20import android.content.Context;
21import android.content.DialogInterface;
22import android.graphics.Bitmap;
23import android.graphics.Canvas;
24import android.graphics.drawable.BitmapDrawable;
25import android.graphics.drawable.Drawable;
26import android.os.Bundle;
27import android.support.annotation.LayoutRes;
28import android.support.annotation.NonNull;
29import android.support.v4.app.DialogFragment;
30import android.support.v4.app.Fragment;
31import android.support.v7.app.AlertDialog;
32import android.text.TextUtils;
33import android.view.LayoutInflater;
34import android.view.View;
35import android.view.Window;
36import android.view.WindowManager;
37import 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 */
46public 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
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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
253    public void onClick(DialogInterface dialog, int which) {
254        mWhichButtonClicked = which;
255    }
256
257    @Override
258    public void onDismiss(DialogInterface dialog) {
259        super.onDismiss(dialog);
260        onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
261    }
262
263    public abstract void onDialogClosed(boolean positiveResult);
264}
265