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