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