Ringtone.java revision 87d76f6a3e407925c40bdebbfe851919cca58e68
1/* 2 * Copyright (C) 2006 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.media; 18 19import android.content.ContentResolver; 20import android.content.Context; 21import android.content.res.AssetFileDescriptor; 22import android.content.res.Resources.NotFoundException; 23import android.database.Cursor; 24import android.net.Uri; 25import android.os.Binder; 26import android.os.RemoteException; 27import android.provider.MediaStore; 28import android.provider.Settings; 29import android.util.Log; 30 31import java.io.IOException; 32 33/** 34 * Ringtone provides a quick method for playing a ringtone, notification, or 35 * other similar types of sounds. 36 * <p> 37 * For ways of retrieving {@link Ringtone} objects or to show a ringtone 38 * picker, see {@link RingtoneManager}. 39 * 40 * @see RingtoneManager 41 */ 42public class Ringtone { 43 private static final String TAG = "Ringtone"; 44 private static final boolean LOGD = true; 45 46 private static final String[] MEDIA_COLUMNS = new String[] { 47 MediaStore.Audio.Media._ID, 48 MediaStore.Audio.Media.DATA, 49 MediaStore.Audio.Media.TITLE 50 }; 51 52 private final Context mContext; 53 private final AudioManager mAudioManager; 54 55 /** 56 * Flag indicating if we're allowed to fall back to remote playback using 57 * {@link #mRemotePlayer}. Typically this is false when we're the remote 58 * player and there is nobody else to delegate to. 59 */ 60 private final boolean mAllowRemote; 61 private final IRingtonePlayer mRemotePlayer; 62 private final Binder mRemoteToken; 63 64 private MediaPlayer mLocalPlayer; 65 66 private Uri mUri; 67 private String mTitle; 68 69 private AudioAttributes mAudioAttributes = new AudioAttributes.Builder() 70 .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) 71 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 72 .build(); 73 74 /** {@hide} */ 75 public Ringtone(Context context, boolean allowRemote) { 76 mContext = context; 77 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 78 mAllowRemote = allowRemote; 79 mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null; 80 mRemoteToken = allowRemote ? new Binder() : null; 81 } 82 83 /** 84 * Sets the stream type where this ringtone will be played. 85 * 86 * @param streamType The stream, see {@link AudioManager}. 87 * @deprecated use {@link #setAudioAttributes(AudioAttributes)} 88 */ 89 @Deprecated 90 public void setStreamType(int streamType) { 91 setAudioAttributes(new AudioAttributes.Builder() 92 .setInternalLegacyStreamType(streamType) 93 .build()); 94 } 95 96 /** 97 * Gets the stream type where this ringtone will be played. 98 * 99 * @return The stream type, see {@link AudioManager}. 100 * @deprecated use of stream types is deprecated, see 101 * {@link #setAudioAttributes(AudioAttributes)} 102 */ 103 @Deprecated 104 public int getStreamType() { 105 return AudioAttributes.toLegacyStreamType(mAudioAttributes); 106 } 107 108 /** 109 * Sets the {@link AudioAttributes} for this ringtone. 110 * @param attributes the non-null attributes characterizing this ringtone. 111 */ 112 public void setAudioAttributes(AudioAttributes attributes) 113 throws IllegalArgumentException { 114 if (attributes == null) { 115 throw new IllegalArgumentException("Invalid null AudioAttributes for Ringtone"); 116 } 117 mAudioAttributes = attributes; 118 // The audio attributes have to be set before the media player is prepared. 119 // Re-initialize it. 120 setUri(mUri); 121 } 122 123 /** 124 * Returns the {@link AudioAttributes} used by this object. 125 * @return the {@link AudioAttributes} that were set with 126 * {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set. 127 */ 128 public AudioAttributes getAudioAttributes() { 129 return mAudioAttributes; 130 } 131 132 /** 133 * Returns a human-presentable title for ringtone. Looks in media 134 * content provider. If not in either, uses the filename 135 * 136 * @param context A context used for querying. 137 */ 138 public String getTitle(Context context) { 139 if (mTitle != null) return mTitle; 140 return mTitle = getTitle(context, mUri, true); 141 } 142 143 private static String getTitle(Context context, Uri uri, boolean followSettingsUri) { 144 Cursor cursor = null; 145 ContentResolver res = context.getContentResolver(); 146 147 String title = null; 148 149 if (uri != null) { 150 String authority = uri.getAuthority(); 151 152 if (Settings.AUTHORITY.equals(authority)) { 153 if (followSettingsUri) { 154 Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, 155 RingtoneManager.getDefaultType(uri)); 156 String actualTitle = getTitle(context, actualUri, false); 157 title = context 158 .getString(com.android.internal.R.string.ringtone_default_with_actual, 159 actualTitle); 160 } 161 } else { 162 try { 163 if (MediaStore.AUTHORITY.equals(authority)) { 164 cursor = res.query(uri, MEDIA_COLUMNS, null, null, null); 165 } 166 } catch (SecurityException e) { 167 // missing cursor is handled below 168 } 169 170 try { 171 if (cursor != null && cursor.getCount() == 1) { 172 cursor.moveToFirst(); 173 return cursor.getString(2); 174 } else { 175 title = uri.getLastPathSegment(); 176 } 177 } finally { 178 if (cursor != null) { 179 cursor.close(); 180 } 181 } 182 } 183 } 184 185 if (title == null) { 186 title = context.getString(com.android.internal.R.string.ringtone_unknown); 187 188 if (title == null) { 189 title = ""; 190 } 191 } 192 193 return title; 194 } 195 196 /** 197 * Set {@link Uri} to be used for ringtone playback. Attempts to open 198 * locally, otherwise will delegate playback to remote 199 * {@link IRingtonePlayer}. 200 * 201 * @hide 202 */ 203 public void setUri(Uri uri) { 204 destroyLocalPlayer(); 205 206 mUri = uri; 207 if (mUri == null) { 208 return; 209 } 210 211 // TODO: detect READ_EXTERNAL and specific content provider case, instead of relying on throwing 212 213 // try opening uri locally before delegating to remote player 214 mLocalPlayer = new MediaPlayer(); 215 try { 216 mLocalPlayer.setDataSource(mContext, mUri); 217 mLocalPlayer.setAudioAttributes(mAudioAttributes); 218 mLocalPlayer.prepare(); 219 220 } catch (SecurityException | IOException e) { 221 destroyLocalPlayer(); 222 if (!mAllowRemote) { 223 Log.w(TAG, "Remote playback not allowed: " + e); 224 } 225 } 226 227 if (LOGD) { 228 if (mLocalPlayer != null) { 229 Log.d(TAG, "Successfully created local player"); 230 } else { 231 Log.d(TAG, "Problem opening; delegating to remote player"); 232 } 233 } 234 } 235 236 /** {@hide} */ 237 public Uri getUri() { 238 return mUri; 239 } 240 241 /** 242 * Plays the ringtone. 243 */ 244 public void play() { 245 if (mLocalPlayer != null) { 246 // do not play ringtones if stream volume is 0 247 // (typically because ringer mode is silent). 248 if (mAudioManager.getStreamVolume( 249 AudioAttributes.toLegacyStreamType(mAudioAttributes)) != 0) { 250 mLocalPlayer.start(); 251 } 252 } else if (mAllowRemote && (mRemotePlayer != null)) { 253 final Uri canonicalUri = mUri.getCanonicalUri(); 254 try { 255 mRemotePlayer.play(mRemoteToken, canonicalUri, mAudioAttributes); 256 } catch (RemoteException e) { 257 if (!playFallbackRingtone()) { 258 Log.w(TAG, "Problem playing ringtone: " + e); 259 } 260 } 261 } else { 262 if (!playFallbackRingtone()) { 263 Log.w(TAG, "Neither local nor remote playback available"); 264 } 265 } 266 } 267 268 /** 269 * Stops a playing ringtone. 270 */ 271 public void stop() { 272 if (mLocalPlayer != null) { 273 destroyLocalPlayer(); 274 } else if (mAllowRemote && (mRemotePlayer != null)) { 275 try { 276 mRemotePlayer.stop(mRemoteToken); 277 } catch (RemoteException e) { 278 Log.w(TAG, "Problem stopping ringtone: " + e); 279 } 280 } 281 } 282 283 private void destroyLocalPlayer() { 284 if (mLocalPlayer != null) { 285 mLocalPlayer.reset(); 286 mLocalPlayer.release(); 287 mLocalPlayer = null; 288 } 289 } 290 291 /** 292 * Whether this ringtone is currently playing. 293 * 294 * @return True if playing, false otherwise. 295 */ 296 public boolean isPlaying() { 297 if (mLocalPlayer != null) { 298 return mLocalPlayer.isPlaying(); 299 } else if (mAllowRemote && (mRemotePlayer != null)) { 300 try { 301 return mRemotePlayer.isPlaying(mRemoteToken); 302 } catch (RemoteException e) { 303 Log.w(TAG, "Problem checking ringtone: " + e); 304 return false; 305 } 306 } else { 307 Log.w(TAG, "Neither local nor remote playback available"); 308 return false; 309 } 310 } 311 312 private boolean playFallbackRingtone() { 313 if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes)) 314 != 0) { 315 int ringtoneType = RingtoneManager.getDefaultType(mUri); 316 if (ringtoneType == -1 || 317 RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) != null) { 318 // Default ringtone, try fallback ringtone. 319 try { 320 AssetFileDescriptor afd = mContext.getResources().openRawResourceFd( 321 com.android.internal.R.raw.fallbackring); 322 if (afd != null) { 323 mLocalPlayer = new MediaPlayer(); 324 if (afd.getDeclaredLength() < 0) { 325 mLocalPlayer.setDataSource(afd.getFileDescriptor()); 326 } else { 327 mLocalPlayer.setDataSource(afd.getFileDescriptor(), 328 afd.getStartOffset(), 329 afd.getDeclaredLength()); 330 } 331 mLocalPlayer.setAudioAttributes(mAudioAttributes); 332 mLocalPlayer.prepare(); 333 mLocalPlayer.start(); 334 afd.close(); 335 return true; 336 } else { 337 Log.e(TAG, "Could not load fallback ringtone"); 338 } 339 } catch (IOException ioe) { 340 destroyLocalPlayer(); 341 Log.e(TAG, "Failed to open fallback ringtone"); 342 } catch (NotFoundException nfe) { 343 Log.e(TAG, "Fallback ringtone does not exist"); 344 } 345 } else { 346 Log.w(TAG, "not playing fallback for " + mUri); 347 } 348 } 349 return false; 350 } 351 352 void setTitle(String title) { 353 mTitle = title; 354 } 355} 356