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 com.android.internal.app;
18
19import com.android.internal.app.AlertActivity;
20import com.android.internal.app.AlertController;
21
22import android.content.DialogInterface;
23import android.content.Intent;
24import android.database.Cursor;
25import android.media.Ringtone;
26import android.media.RingtoneManager;
27import android.net.Uri;
28import android.os.Bundle;
29import android.os.Handler;
30import android.provider.MediaStore;
31import android.provider.Settings;
32import android.view.View;
33import android.widget.AdapterView;
34import android.widget.ListView;
35import android.widget.TextView;
36
37/**
38 * The {@link RingtonePickerActivity} allows the user to choose one from all of the
39 * available ringtones. The chosen ringtone's URI will be persisted as a string.
40 *
41 * @see RingtoneManager#ACTION_RINGTONE_PICKER
42 */
43public final class RingtonePickerActivity extends AlertActivity implements
44        AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener,
45        AlertController.AlertParams.OnPrepareListViewListener {
46
47    private static final String TAG = "RingtonePickerActivity";
48
49    private static final int DELAY_MS_SELECTION_PLAYED = 300;
50
51    private static final String SAVE_CLICKED_POS = "clicked_pos";
52
53    private RingtoneManager mRingtoneManager;
54
55    private Cursor mCursor;
56    private Handler mHandler;
57
58    /** The position in the list of the 'Silent' item. */
59    private int mSilentPos = -1;
60
61    /** The position in the list of the 'Default' item. */
62    private int mDefaultRingtonePos = -1;
63
64    /** The position in the list of the last clicked item. */
65    private int mClickedPos = -1;
66
67    /** The position in the list of the ringtone to sample. */
68    private int mSampleRingtonePos = -1;
69
70    /** Whether this list has the 'Silent' item. */
71    private boolean mHasSilentItem;
72
73    /** The Uri to place a checkmark next to. */
74    private Uri mExistingUri;
75
76    /** The number of static items in the list. */
77    private int mStaticItemCount;
78
79    /** Whether this list has the 'Default' item. */
80    private boolean mHasDefaultItem;
81
82    /** The Uri to play when the 'Default' item is clicked. */
83    private Uri mUriForDefaultItem;
84
85    /**
86     * A Ringtone for the default ringtone. In most cases, the RingtoneManager
87     * will stop the previous ringtone. However, the RingtoneManager doesn't
88     * manage the default ringtone for us, so we should stop this one manually.
89     */
90    private Ringtone mDefaultRingtone;
91
92    private DialogInterface.OnClickListener mRingtoneClickListener =
93            new DialogInterface.OnClickListener() {
94
95        /*
96         * On item clicked
97         */
98        public void onClick(DialogInterface dialog, int which) {
99            // Save the position of most recently clicked item
100            mClickedPos = which;
101
102            // Play clip
103            playRingtone(which, 0);
104        }
105
106    };
107
108    @Override
109    protected void onCreate(Bundle savedInstanceState) {
110        super.onCreate(savedInstanceState);
111
112        mHandler = new Handler();
113
114        Intent intent = getIntent();
115
116        /*
117         * Get whether to show the 'Default' item, and the URI to play when the
118         * default is clicked
119         */
120        mHasDefaultItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
121        mUriForDefaultItem = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
122        if (mUriForDefaultItem == null) {
123            mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
124        }
125
126        if (savedInstanceState != null) {
127            mClickedPos = savedInstanceState.getInt(SAVE_CLICKED_POS, -1);
128        }
129        // Get whether to show the 'Silent' item
130        mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
131
132        // Give the Activity so it can do managed queries
133        mRingtoneManager = new RingtoneManager(this);
134
135        // Get whether to include DRM ringtones
136        boolean includeDrm = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_INCLUDE_DRM,
137                true);
138        mRingtoneManager.setIncludeDrm(includeDrm);
139
140        // Get the types of ringtones to show
141        int types = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, -1);
142        if (types != -1) {
143            mRingtoneManager.setType(types);
144        }
145
146        mCursor = mRingtoneManager.getCursor();
147
148        // The volume keys will control the stream that we are choosing a ringtone for
149        setVolumeControlStream(mRingtoneManager.inferStreamType());
150
151        // Get the URI whose list item should have a checkmark
152        mExistingUri = intent
153                .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
154
155        final AlertController.AlertParams p = mAlertParams;
156        p.mCursor = mCursor;
157        p.mOnClickListener = mRingtoneClickListener;
158        p.mLabelColumn = MediaStore.Audio.Media.TITLE;
159        p.mIsSingleChoice = true;
160        p.mOnItemSelectedListener = this;
161        p.mPositiveButtonText = getString(com.android.internal.R.string.ok);
162        p.mPositiveButtonListener = this;
163        p.mNegativeButtonText = getString(com.android.internal.R.string.cancel);
164        p.mPositiveButtonListener = this;
165        p.mOnPrepareListViewListener = this;
166
167        p.mTitle = intent.getCharSequenceExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
168        if (p.mTitle == null) {
169            p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title);
170        }
171
172        setupAlert();
173    }
174
175    @Override
176    public void onSaveInstanceState(Bundle outState) {
177        super.onSaveInstanceState(outState);
178        outState.putInt(SAVE_CLICKED_POS, mClickedPos);
179    }
180
181    public void onPrepareListView(ListView listView) {
182
183        if (mHasDefaultItem) {
184            mDefaultRingtonePos = addDefaultRingtoneItem(listView);
185
186            if (RingtoneManager.isDefault(mExistingUri)) {
187                mClickedPos = mDefaultRingtonePos;
188            }
189        }
190
191        if (mHasSilentItem) {
192            mSilentPos = addSilentItem(listView);
193
194            // The 'Silent' item should use a null Uri
195            if (mExistingUri == null) {
196                mClickedPos = mSilentPos;
197            }
198        }
199
200        if (mClickedPos == -1) {
201            mClickedPos = getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri));
202        }
203
204        // Put a checkmark next to an item.
205        mAlertParams.mCheckedItem = mClickedPos;
206    }
207
208    /**
209     * Adds a static item to the top of the list. A static item is one that is not from the
210     * RingtoneManager.
211     *
212     * @param listView The ListView to add to.
213     * @param textResId The resource ID of the text for the item.
214     * @return The position of the inserted item.
215     */
216    private int addStaticItem(ListView listView, int textResId) {
217        TextView textView = (TextView) getLayoutInflater().inflate(
218                com.android.internal.R.layout.select_dialog_singlechoice_holo, listView, false);
219        textView.setText(textResId);
220        listView.addHeaderView(textView);
221        mStaticItemCount++;
222        return listView.getHeaderViewsCount() - 1;
223    }
224
225    private int addDefaultRingtoneItem(ListView listView) {
226        return addStaticItem(listView, com.android.internal.R.string.ringtone_default);
227    }
228
229    private int addSilentItem(ListView listView) {
230        return addStaticItem(listView, com.android.internal.R.string.ringtone_silent);
231    }
232
233    /*
234     * On click of Ok/Cancel buttons
235     */
236    public void onClick(DialogInterface dialog, int which) {
237        boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE;
238
239        // Stop playing the previous ringtone
240        mRingtoneManager.stopPreviousRingtone();
241
242        if (positiveResult) {
243            Intent resultIntent = new Intent();
244            Uri uri = null;
245
246            if (mClickedPos == mDefaultRingtonePos) {
247                // Set it to the default Uri that they originally gave us
248                uri = mUriForDefaultItem;
249            } else if (mClickedPos == mSilentPos) {
250                // A null Uri is for the 'Silent' item
251                uri = null;
252            } else {
253                uri = mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(mClickedPos));
254            }
255
256            resultIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, uri);
257            setResult(RESULT_OK, resultIntent);
258        } else {
259            setResult(RESULT_CANCELED);
260        }
261
262        getWindow().getDecorView().post(new Runnable() {
263            public void run() {
264                mCursor.deactivate();
265            }
266        });
267
268        finish();
269    }
270
271    /*
272     * On item selected via keys
273     */
274    public void onItemSelected(AdapterView parent, View view, int position, long id) {
275        playRingtone(position, DELAY_MS_SELECTION_PLAYED);
276    }
277
278    public void onNothingSelected(AdapterView parent) {
279    }
280
281    private void playRingtone(int position, int delayMs) {
282        mHandler.removeCallbacks(this);
283        mSampleRingtonePos = position;
284        mHandler.postDelayed(this, delayMs);
285    }
286
287    public void run() {
288
289        if (mSampleRingtonePos == mSilentPos) {
290            mRingtoneManager.stopPreviousRingtone();
291            return;
292        }
293
294        /*
295         * Stop the default ringtone, if it's playing (other ringtones will be
296         * stopped by the RingtoneManager when we get another Ringtone from it.
297         */
298        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
299            mDefaultRingtone.stop();
300            mDefaultRingtone = null;
301        }
302
303        Ringtone ringtone;
304        if (mSampleRingtonePos == mDefaultRingtonePos) {
305            if (mDefaultRingtone == null) {
306                mDefaultRingtone = RingtoneManager.getRingtone(this, mUriForDefaultItem);
307            }
308            ringtone = mDefaultRingtone;
309
310            /*
311             * Normally the non-static RingtoneManager.getRingtone stops the
312             * previous ringtone, but we're getting the default ringtone outside
313             * of the RingtoneManager instance, so let's stop the previous
314             * ringtone manually.
315             */
316            mRingtoneManager.stopPreviousRingtone();
317
318        } else {
319            ringtone = mRingtoneManager.getRingtone(getRingtoneManagerPosition(mSampleRingtonePos));
320        }
321
322        if (ringtone != null) {
323            ringtone.play();
324        }
325    }
326
327    @Override
328    protected void onStop() {
329        super.onStop();
330        stopAnyPlayingRingtone();
331    }
332
333    @Override
334    protected void onPause() {
335        super.onPause();
336        stopAnyPlayingRingtone();
337    }
338
339    private void stopAnyPlayingRingtone() {
340
341        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
342            mDefaultRingtone.stop();
343        }
344
345        if (mRingtoneManager != null) {
346            mRingtoneManager.stopPreviousRingtone();
347        }
348    }
349
350    private int getRingtoneManagerPosition(int listPos) {
351        return listPos - mStaticItemCount;
352    }
353
354    private int getListPosition(int ringtoneManagerPos) {
355
356        // If the manager position is -1 (for not found), return that
357        if (ringtoneManagerPos < 0) return ringtoneManagerPos;
358
359        return ringtoneManagerPos + mStaticItemCount;
360    }
361
362}
363