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 private final boolean mAllowRemote; 55 private final IRingtonePlayer mRemotePlayer; 56 private final Binder mRemoteToken; 57 58 private MediaPlayer mLocalPlayer; 59 60 private Uri mUri; 61 private String mTitle; 62 63 private int mStreamType = AudioManager.STREAM_RING; 64 65 /** {@hide} */ 66 public Ringtone(Context context, boolean allowRemote) { 67 mContext = context; 68 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 69 mAllowRemote = allowRemote; 70 mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null; 71 mRemoteToken = allowRemote ? new Binder() : null; 72 } 73 74 /** 75 * Sets the stream type where this ringtone will be played. 76 * 77 * @param streamType The stream, see {@link AudioManager}. 78 */ 79 public void setStreamType(int streamType) { 80 mStreamType = streamType; 81 82 // The stream type has to be set before the media player is prepared. 83 // Re-initialize it. 84 setUri(mUri); 85 } 86 87 /** 88 * Gets the stream type where this ringtone will be played. 89 * 90 * @return The stream type, see {@link AudioManager}. 91 */ 92 public int getStreamType() { 93 return mStreamType; 94 } 95 96 /** 97 * Returns a human-presentable title for ringtone. Looks in media 98 * content provider. If not in either, uses the filename 99 * 100 * @param context A context used for querying. 101 */ 102 public String getTitle(Context context) { 103 if (mTitle != null) return mTitle; 104 return mTitle = getTitle(context, mUri, true); 105 } 106 107 private static String getTitle(Context context, Uri uri, boolean followSettingsUri) { 108 Cursor cursor = null; 109 ContentResolver res = context.getContentResolver(); 110 111 String title = null; 112 113 if (uri != null) { 114 String authority = uri.getAuthority(); 115 116 if (Settings.AUTHORITY.equals(authority)) { 117 if (followSettingsUri) { 118 Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, 119 RingtoneManager.getDefaultType(uri)); 120 String actualTitle = getTitle(context, actualUri, false); 121 title = context 122 .getString(com.android.internal.R.string.ringtone_default_with_actual, 123 actualTitle); 124 } 125 } else { 126 try { 127 if (MediaStore.AUTHORITY.equals(authority)) { 128 cursor = res.query(uri, MEDIA_COLUMNS, null, null, null); 129 } 130 } catch (SecurityException e) { 131 // missing cursor is handled below 132 } 133 134 try { 135 if (cursor != null && cursor.getCount() == 1) { 136 cursor.moveToFirst(); 137 return cursor.getString(2); 138 } else { 139 title = uri.getLastPathSegment(); 140 } 141 } finally { 142 if (cursor != null) { 143 cursor.close(); 144 } 145 } 146 } 147 } 148 149 if (title == null) { 150 title = context.getString(com.android.internal.R.string.ringtone_unknown); 151 152 if (title == null) { 153 title = ""; 154 } 155 } 156 157 return title; 158 } 159 160 /** 161 * Set {@link Uri} to be used for ringtone playback. Attempts to open 162 * locally, otherwise will delegate playback to remote 163 * {@link IRingtonePlayer}. 164 * 165 * @hide 166 */ 167 public void setUri(Uri uri) { 168 destroyLocalPlayer(); 169 170 mUri = uri; 171 if (mUri == null) { 172 return; 173 } 174 175 // TODO: detect READ_EXTERNAL and specific content provider case, instead of relying on throwing 176 177 // try opening uri locally before delegating to remote player 178 mLocalPlayer = new MediaPlayer(); 179 try { 180 mLocalPlayer.setDataSource(mContext, mUri); 181 mLocalPlayer.setAudioStreamType(mStreamType); 182 mLocalPlayer.prepare(); 183 184 } catch (SecurityException e) { 185 destroyLocalPlayer(); 186 if (!mAllowRemote) { 187 Log.w(TAG, "Remote playback not allowed: " + e); 188 } 189 } catch (IOException e) { 190 destroyLocalPlayer(); 191 if (!mAllowRemote) { 192 Log.w(TAG, "Remote playback not allowed: " + e); 193 } 194 } 195 196 if (LOGD) { 197 if (mLocalPlayer != null) { 198 Log.d(TAG, "Successfully created local player"); 199 } else { 200 Log.d(TAG, "Problem opening; delegating to remote player"); 201 } 202 } 203 } 204 205 /** {@hide} */ 206 public Uri getUri() { 207 return mUri; 208 } 209 210 /** 211 * Plays the ringtone. 212 */ 213 public void play() { 214 if (mLocalPlayer != null) { 215 // do not play ringtones if stream volume is 0 216 // (typically because ringer mode is silent). 217 if (mAudioManager.getStreamVolume(mStreamType) != 0) { 218 mLocalPlayer.start(); 219 } 220 } else if (mAllowRemote) { 221 final Uri canonicalUri = mUri.getCanonicalUri(); 222 try { 223 mRemotePlayer.play(mRemoteToken, canonicalUri, mStreamType); 224 } catch (RemoteException e) { 225 if (!playFallbackRingtone()) { 226 Log.w(TAG, "Problem playing ringtone: " + e); 227 } 228 } 229 } else { 230 if (!playFallbackRingtone()) { 231 Log.w(TAG, "Neither local nor remote playback available"); 232 } 233 } 234 } 235 236 /** 237 * Stops a playing ringtone. 238 */ 239 public void stop() { 240 if (mLocalPlayer != null) { 241 destroyLocalPlayer(); 242 } else if (mAllowRemote) { 243 try { 244 mRemotePlayer.stop(mRemoteToken); 245 } catch (RemoteException e) { 246 Log.w(TAG, "Problem stopping ringtone: " + e); 247 } 248 } 249 } 250 251 private void destroyLocalPlayer() { 252 if (mLocalPlayer != null) { 253 mLocalPlayer.reset(); 254 mLocalPlayer.release(); 255 mLocalPlayer = null; 256 } 257 } 258 259 /** 260 * Whether this ringtone is currently playing. 261 * 262 * @return True if playing, false otherwise. 263 */ 264 public boolean isPlaying() { 265 if (mLocalPlayer != null) { 266 return mLocalPlayer.isPlaying(); 267 } else if (mAllowRemote) { 268 try { 269 return mRemotePlayer.isPlaying(mRemoteToken); 270 } catch (RemoteException e) { 271 Log.w(TAG, "Problem checking ringtone: " + e); 272 return false; 273 } 274 } else { 275 Log.w(TAG, "Neither local nor remote playback available"); 276 return false; 277 } 278 } 279 280 private boolean playFallbackRingtone() { 281 if (mAudioManager.getStreamVolume(mStreamType) != 0) { 282 int ringtoneType = RingtoneManager.getDefaultType(mUri); 283 if (ringtoneType == -1 || 284 RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) != null) { 285 // Default ringtone, try fallback ringtone. 286 try { 287 AssetFileDescriptor afd = mContext.getResources().openRawResourceFd( 288 com.android.internal.R.raw.fallbackring); 289 if (afd != null) { 290 mLocalPlayer = new MediaPlayer(); 291 if (afd.getDeclaredLength() < 0) { 292 mLocalPlayer.setDataSource(afd.getFileDescriptor()); 293 } else { 294 mLocalPlayer.setDataSource(afd.getFileDescriptor(), 295 afd.getStartOffset(), 296 afd.getDeclaredLength()); 297 } 298 mLocalPlayer.setAudioStreamType(mStreamType); 299 mLocalPlayer.prepare(); 300 mLocalPlayer.start(); 301 afd.close(); 302 return true; 303 } else { 304 Log.e(TAG, "Could not load fallback ringtone"); 305 } 306 } catch (IOException ioe) { 307 destroyLocalPlayer(); 308 Log.e(TAG, "Failed to open fallback ringtone"); 309 } catch (NotFoundException nfe) { 310 Log.e(TAG, "Fallback ringtone does not exist"); 311 } 312 } else { 313 Log.w(TAG, "not playing fallback for " + mUri); 314 } 315 } 316 return false; 317 } 318 319 void setTitle(String title) { 320 mTitle = title; 321 } 322} 323