1/*
2 * Copyright (C) 2007 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.preference;
18
19import android.content.Context;
20import android.content.Intent;
21import android.content.res.TypedArray;
22import android.media.AudioAttributes;
23import android.media.RingtoneManager;
24import android.net.Uri;
25import android.provider.Settings.System;
26import android.text.TextUtils;
27import android.util.AttributeSet;
28
29/**
30 * A {@link Preference} that allows the user to choose a ringtone from those on the device.
31 * The chosen ringtone's URI will be persisted as a string.
32 * <p>
33 * If the user chooses the "Default" item, the saved string will be one of
34 * {@link System#DEFAULT_RINGTONE_URI},
35 * {@link System#DEFAULT_NOTIFICATION_URI}, or
36 * {@link System#DEFAULT_ALARM_ALERT_URI}. If the user chooses the "Silent"
37 * item, the saved string will be an empty string.
38 *
39 * @attr ref android.R.styleable#RingtonePreference_ringtoneType
40 * @attr ref android.R.styleable#RingtonePreference_showDefault
41 * @attr ref android.R.styleable#RingtonePreference_showSilent
42 */
43public class RingtonePreference extends Preference implements
44        PreferenceManager.OnActivityResultListener {
45
46    private static final String TAG = "RingtonePreference";
47
48    private int mRingtoneType;
49    private boolean mShowDefault;
50    private boolean mShowSilent;
51
52    private int mRequestCode;
53
54    public RingtonePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
55        super(context, attrs, defStyleAttr, defStyleRes);
56
57        final TypedArray a = context.obtainStyledAttributes(attrs,
58                com.android.internal.R.styleable.RingtonePreference, defStyleAttr, defStyleRes);
59        mRingtoneType = a.getInt(com.android.internal.R.styleable.RingtonePreference_ringtoneType,
60                RingtoneManager.TYPE_RINGTONE);
61        mShowDefault = a.getBoolean(com.android.internal.R.styleable.RingtonePreference_showDefault,
62                true);
63        mShowSilent = a.getBoolean(com.android.internal.R.styleable.RingtonePreference_showSilent,
64                true);
65        a.recycle();
66    }
67
68    public RingtonePreference(Context context, AttributeSet attrs, int defStyleAttr) {
69        this(context, attrs, defStyleAttr, 0);
70    }
71
72    public RingtonePreference(Context context, AttributeSet attrs) {
73        this(context, attrs, com.android.internal.R.attr.ringtonePreferenceStyle);
74    }
75
76    public RingtonePreference(Context context) {
77        this(context, null);
78    }
79
80    /**
81     * Returns the sound type(s) that are shown in the picker.
82     *
83     * @return The sound type(s) that are shown in the picker.
84     * @see #setRingtoneType(int)
85     */
86    public int getRingtoneType() {
87        return mRingtoneType;
88    }
89
90    /**
91     * Sets the sound type(s) that are shown in the picker.
92     *
93     * @param type The sound type(s) that are shown in the picker.
94     * @see RingtoneManager#EXTRA_RINGTONE_TYPE
95     */
96    public void setRingtoneType(int type) {
97        mRingtoneType = type;
98    }
99
100    /**
101     * Returns whether to a show an item for the default sound/ringtone.
102     *
103     * @return Whether to show an item for the default sound/ringtone.
104     */
105    public boolean getShowDefault() {
106        return mShowDefault;
107    }
108
109    /**
110     * Sets whether to show an item for the default sound/ringtone. The default
111     * to use will be deduced from the sound type(s) being shown.
112     *
113     * @param showDefault Whether to show the default or not.
114     * @see RingtoneManager#EXTRA_RINGTONE_SHOW_DEFAULT
115     */
116    public void setShowDefault(boolean showDefault) {
117        mShowDefault = showDefault;
118    }
119
120    /**
121     * Returns whether to a show an item for 'Silent'.
122     *
123     * @return Whether to show an item for 'Silent'.
124     */
125    public boolean getShowSilent() {
126        return mShowSilent;
127    }
128
129    /**
130     * Sets whether to show an item for 'Silent'.
131     *
132     * @param showSilent Whether to show 'Silent'.
133     * @see RingtoneManager#EXTRA_RINGTONE_SHOW_SILENT
134     */
135    public void setShowSilent(boolean showSilent) {
136        mShowSilent = showSilent;
137    }
138
139    @Override
140    protected void onClick() {
141        // Launch the ringtone picker
142        Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
143        onPrepareRingtonePickerIntent(intent);
144        PreferenceFragment owningFragment = getPreferenceManager().getFragment();
145        if (owningFragment != null) {
146            owningFragment.startActivityForResult(intent, mRequestCode);
147        } else {
148            getPreferenceManager().getActivity().startActivityForResult(intent, mRequestCode);
149        }
150    }
151
152    /**
153     * Prepares the intent to launch the ringtone picker. This can be modified
154     * to adjust the parameters of the ringtone picker.
155     *
156     * @param ringtonePickerIntent The ringtone picker intent that can be
157     *            modified by putting extras.
158     */
159    protected void onPrepareRingtonePickerIntent(Intent ringtonePickerIntent) {
160
161        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI,
162                onRestoreRingtone());
163
164        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, mShowDefault);
165        if (mShowDefault) {
166            ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
167                    RingtoneManager.getDefaultUri(getRingtoneType()));
168        }
169
170        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, mShowSilent);
171        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, mRingtoneType);
172        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, getTitle());
173        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
174                AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
175    }
176
177    /**
178     * Called when a ringtone is chosen.
179     * <p>
180     * By default, this saves the ringtone URI to the persistent storage as a
181     * string.
182     *
183     * @param ringtoneUri The chosen ringtone's {@link Uri}. Can be null.
184     */
185    protected void onSaveRingtone(Uri ringtoneUri) {
186        persistString(ringtoneUri != null ? ringtoneUri.toString() : "");
187    }
188
189    /**
190     * Called when the chooser is about to be shown and the current ringtone
191     * should be marked. Can return null to not mark any ringtone.
192     * <p>
193     * By default, this restores the previous ringtone URI from the persistent
194     * storage.
195     *
196     * @return The ringtone to be marked as the current ringtone.
197     */
198    protected Uri onRestoreRingtone() {
199        final String uriString = getPersistedString(null);
200        return !TextUtils.isEmpty(uriString) ? Uri.parse(uriString) : null;
201    }
202
203    @Override
204    protected Object onGetDefaultValue(TypedArray a, int index) {
205        return a.getString(index);
206    }
207
208    @Override
209    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValueObj) {
210        String defaultValue = (String) defaultValueObj;
211
212        /*
213         * This method is normally to make sure the internal state and UI
214         * matches either the persisted value or the default value. Since we
215         * don't show the current value in the UI (until the dialog is opened)
216         * and we don't keep local state, if we are restoring the persisted
217         * value we don't need to do anything.
218         */
219        if (restorePersistedValue) {
220            return;
221        }
222
223        // If we are setting to the default value, we should persist it.
224        if (!TextUtils.isEmpty(defaultValue)) {
225            onSaveRingtone(Uri.parse(defaultValue));
226        }
227    }
228
229    @Override
230    protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
231        super.onAttachedToHierarchy(preferenceManager);
232
233        preferenceManager.registerOnActivityResultListener(this);
234        mRequestCode = preferenceManager.getNextRequestCode();
235    }
236
237    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
238
239        if (requestCode == mRequestCode) {
240
241            if (data != null) {
242                Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
243
244                if (callChangeListener(uri != null ? uri.toString() : "")) {
245                    onSaveRingtone(uri);
246                }
247            }
248
249            return true;
250        }
251
252        return false;
253    }
254
255}
256