• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.media;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.res.AssetFileDescriptor;
22 import android.content.res.Resources.NotFoundException;
23 import android.database.Cursor;
24 import android.media.MediaPlayer.OnCompletionListener;
25 import android.net.Uri;
26 import android.os.Binder;
27 import android.os.RemoteException;
28 import android.provider.MediaStore;
29 import android.provider.Settings;
30 import android.provider.MediaStore.MediaColumns;
31 import android.util.Log;
32 
33 import java.io.IOException;
34 import java.util.ArrayList;
35 
36 /**
37  * Ringtone provides a quick method for playing a ringtone, notification, or
38  * other similar types of sounds.
39  * <p>
40  * For ways of retrieving {@link Ringtone} objects or to show a ringtone
41  * picker, see {@link RingtoneManager}.
42  *
43  * @see RingtoneManager
44  */
45 public class Ringtone {
46     private static final String TAG = "Ringtone";
47     private static final boolean LOGD = true;
48 
49     private static final String[] MEDIA_COLUMNS = new String[] {
50         MediaStore.Audio.Media._ID,
51         MediaStore.Audio.Media.DATA,
52         MediaStore.Audio.Media.TITLE
53     };
54     /** Selection that limits query results to just audio files */
55     private static final String MEDIA_SELECTION = MediaColumns.MIME_TYPE + " LIKE 'audio/%' OR "
56             + MediaColumns.MIME_TYPE + " IN ('application/ogg', 'application/x-flac')";
57 
58     // keep references on active Ringtones until stopped or completion listener called.
59     private static final ArrayList<Ringtone> sActiveRingtones = new ArrayList<Ringtone>();
60 
61     private final Context mContext;
62     private final AudioManager mAudioManager;
63 
64     /**
65      * Flag indicating if we're allowed to fall back to remote playback using
66      * {@link #mRemotePlayer}. Typically this is false when we're the remote
67      * player and there is nobody else to delegate to.
68      */
69     private final boolean mAllowRemote;
70     private final IRingtonePlayer mRemotePlayer;
71     private final Binder mRemoteToken;
72 
73     private MediaPlayer mLocalPlayer;
74     private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
75 
76     private Uri mUri;
77     private String mTitle;
78 
79     private AudioAttributes mAudioAttributes = new AudioAttributes.Builder()
80             .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
81             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
82             .build();
83     // playback properties, use synchronized with mPlaybackSettingsLock
84     private boolean mIsLooping = false;
85     private float mVolume = 1.0f;
86     private final Object mPlaybackSettingsLock = new Object();
87 
88     /** {@hide} */
Ringtone(Context context, boolean allowRemote)89     public Ringtone(Context context, boolean allowRemote) {
90         mContext = context;
91         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
92         mAllowRemote = allowRemote;
93         mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;
94         mRemoteToken = allowRemote ? new Binder() : null;
95     }
96 
97     /**
98      * Sets the stream type where this ringtone will be played.
99      *
100      * @param streamType The stream, see {@link AudioManager}.
101      * @deprecated use {@link #setAudioAttributes(AudioAttributes)}
102      */
103     @Deprecated
setStreamType(int streamType)104     public void setStreamType(int streamType) {
105         setAudioAttributes(new AudioAttributes.Builder()
106                 .setInternalLegacyStreamType(streamType)
107                 .build());
108     }
109 
110     /**
111      * Gets the stream type where this ringtone will be played.
112      *
113      * @return The stream type, see {@link AudioManager}.
114      * @deprecated use of stream types is deprecated, see
115      *     {@link #setAudioAttributes(AudioAttributes)}
116      */
117     @Deprecated
getStreamType()118     public int getStreamType() {
119         return AudioAttributes.toLegacyStreamType(mAudioAttributes);
120     }
121 
122     /**
123      * Sets the {@link AudioAttributes} for this ringtone.
124      * @param attributes the non-null attributes characterizing this ringtone.
125      */
setAudioAttributes(AudioAttributes attributes)126     public void setAudioAttributes(AudioAttributes attributes)
127             throws IllegalArgumentException {
128         if (attributes == null) {
129             throw new IllegalArgumentException("Invalid null AudioAttributes for Ringtone");
130         }
131         mAudioAttributes = attributes;
132         // The audio attributes have to be set before the media player is prepared.
133         // Re-initialize it.
134         setUri(mUri);
135     }
136 
137     /**
138      * Returns the {@link AudioAttributes} used by this object.
139      * @return the {@link AudioAttributes} that were set with
140      *     {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set.
141      */
getAudioAttributes()142     public AudioAttributes getAudioAttributes() {
143         return mAudioAttributes;
144     }
145 
146     /**
147      * @hide
148      * Sets the player to be looping or non-looping.
149      * @param looping whether to loop or not
150      */
setLooping(boolean looping)151     public void setLooping(boolean looping) {
152         synchronized (mPlaybackSettingsLock) {
153             mIsLooping = looping;
154             applyPlaybackProperties_sync();
155         }
156     }
157 
158     /**
159      * @hide
160      * Sets the volume on this player.
161      * @param volume a raw scalar in range 0.0 to 1.0, where 0.0 mutes this player, and 1.0
162      *   corresponds to no attenuation being applied.
163      */
setVolume(float volume)164     public void setVolume(float volume) {
165         synchronized (mPlaybackSettingsLock) {
166             if (volume < 0.0f) { volume = 0.0f; }
167             if (volume > 1.0f) { volume = 1.0f; }
168             mVolume = volume;
169             applyPlaybackProperties_sync();
170         }
171     }
172 
173     /**
174      * Must be called synchronized on mPlaybackSettingsLock
175      */
applyPlaybackProperties_sync()176     private void applyPlaybackProperties_sync() {
177         if (mLocalPlayer != null) {
178             mLocalPlayer.setVolume(mVolume);
179             mLocalPlayer.setLooping(mIsLooping);
180         } else if (mAllowRemote && (mRemotePlayer != null)) {
181             try {
182                 mRemotePlayer.setPlaybackProperties(mRemoteToken, mVolume, mIsLooping);
183             } catch (RemoteException e) {
184                 Log.w(TAG, "Problem setting playback properties: ", e);
185             }
186         } else {
187             Log.w(TAG,
188                     "Neither local nor remote player available when applying playback properties");
189         }
190     }
191 
192     /**
193      * Returns a human-presentable title for ringtone. Looks in media
194      * content provider. If not in either, uses the filename
195      *
196      * @param context A context used for querying.
197      */
getTitle(Context context)198     public String getTitle(Context context) {
199         if (mTitle != null) return mTitle;
200         return mTitle = getTitle(context, mUri, true /*followSettingsUri*/, mAllowRemote);
201     }
202 
203     /**
204      * @hide
205      */
getTitle( Context context, Uri uri, boolean followSettingsUri, boolean allowRemote)206     public static String getTitle(
207             Context context, Uri uri, boolean followSettingsUri, boolean allowRemote) {
208         ContentResolver res = context.getContentResolver();
209 
210         String title = null;
211 
212         if (uri != null) {
213             String authority = uri.getAuthority();
214 
215             if (Settings.AUTHORITY.equals(authority)) {
216                 if (followSettingsUri) {
217                     Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context,
218                             RingtoneManager.getDefaultType(uri));
219                     String actualTitle = getTitle(
220                             context, actualUri, false /*followSettingsUri*/, allowRemote);
221                     title = context
222                             .getString(com.android.internal.R.string.ringtone_default_with_actual,
223                                     actualTitle);
224                 }
225             } else {
226                 Cursor cursor = null;
227                 try {
228                     if (MediaStore.AUTHORITY.equals(authority)) {
229                         final String mediaSelection = allowRemote ? null : MEDIA_SELECTION;
230                         cursor = res.query(uri, MEDIA_COLUMNS, mediaSelection, null, null);
231                         if (cursor != null && cursor.getCount() == 1) {
232                             cursor.moveToFirst();
233                             return cursor.getString(2);
234                         }
235                         // missing cursor is handled below
236                     }
237                 } catch (SecurityException e) {
238                     IRingtonePlayer mRemotePlayer = null;
239                     if (allowRemote) {
240                         AudioManager audioManager =
241                                 (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
242                         mRemotePlayer = audioManager.getRingtonePlayer();
243                     }
244                     if (mRemotePlayer != null) {
245                         try {
246                             title = mRemotePlayer.getTitle(uri);
247                         } catch (RemoteException re) {
248                         }
249                     }
250                 } finally {
251                     if (cursor != null) {
252                         cursor.close();
253                     }
254                     cursor = null;
255                 }
256                 if (title == null) {
257                     title = uri.getLastPathSegment();
258                 }
259             }
260         }
261 
262         if (title == null) {
263             title = context.getString(com.android.internal.R.string.ringtone_unknown);
264 
265             if (title == null) {
266                 title = "";
267             }
268         }
269 
270         return title;
271     }
272 
273     /**
274      * Set {@link Uri} to be used for ringtone playback. Attempts to open
275      * locally, otherwise will delegate playback to remote
276      * {@link IRingtonePlayer}.
277      *
278      * @hide
279      */
setUri(Uri uri)280     public void setUri(Uri uri) {
281         destroyLocalPlayer();
282 
283         mUri = uri;
284         if (mUri == null) {
285             return;
286         }
287 
288         // TODO: detect READ_EXTERNAL and specific content provider case, instead of relying on throwing
289 
290         // try opening uri locally before delegating to remote player
291         mLocalPlayer = new MediaPlayer();
292         try {
293             mLocalPlayer.setDataSource(mContext, mUri);
294             mLocalPlayer.setAudioAttributes(mAudioAttributes);
295             synchronized (mPlaybackSettingsLock) {
296                 applyPlaybackProperties_sync();
297             }
298             mLocalPlayer.prepare();
299 
300         } catch (SecurityException | IOException e) {
301             destroyLocalPlayer();
302             if (!mAllowRemote) {
303                 Log.w(TAG, "Remote playback not allowed: " + e);
304             }
305         }
306 
307         if (LOGD) {
308             if (mLocalPlayer != null) {
309                 Log.d(TAG, "Successfully created local player");
310             } else {
311                 Log.d(TAG, "Problem opening; delegating to remote player");
312             }
313         }
314     }
315 
316     /** {@hide} */
getUri()317     public Uri getUri() {
318         return mUri;
319     }
320 
321     /**
322      * Plays the ringtone.
323      */
play()324     public void play() {
325         if (mLocalPlayer != null) {
326             // do not play ringtones if stream volume is 0
327             // (typically because ringer mode is silent).
328             if (mAudioManager.getStreamVolume(
329                     AudioAttributes.toLegacyStreamType(mAudioAttributes)) != 0) {
330                 startLocalPlayer();
331             }
332         } else if (mAllowRemote && (mRemotePlayer != null)) {
333             final Uri canonicalUri = mUri.getCanonicalUri();
334             final boolean looping;
335             final float volume;
336             synchronized (mPlaybackSettingsLock) {
337                 looping = mIsLooping;
338                 volume = mVolume;
339             }
340             try {
341                 mRemotePlayer.play(mRemoteToken, canonicalUri, mAudioAttributes, volume, looping);
342             } catch (RemoteException e) {
343                 if (!playFallbackRingtone()) {
344                     Log.w(TAG, "Problem playing ringtone: " + e);
345                 }
346             }
347         } else {
348             if (!playFallbackRingtone()) {
349                 Log.w(TAG, "Neither local nor remote playback available");
350             }
351         }
352     }
353 
354     /**
355      * Stops a playing ringtone.
356      */
stop()357     public void stop() {
358         if (mLocalPlayer != null) {
359             destroyLocalPlayer();
360         } else if (mAllowRemote && (mRemotePlayer != null)) {
361             try {
362                 mRemotePlayer.stop(mRemoteToken);
363             } catch (RemoteException e) {
364                 Log.w(TAG, "Problem stopping ringtone: " + e);
365             }
366         }
367     }
368 
destroyLocalPlayer()369     private void destroyLocalPlayer() {
370         if (mLocalPlayer != null) {
371             mLocalPlayer.reset();
372             mLocalPlayer.release();
373             mLocalPlayer = null;
374             synchronized (sActiveRingtones) {
375                 sActiveRingtones.remove(this);
376             }
377         }
378     }
379 
startLocalPlayer()380     private void startLocalPlayer() {
381         if (mLocalPlayer == null) {
382             return;
383         }
384         synchronized (sActiveRingtones) {
385             sActiveRingtones.add(this);
386         }
387         mLocalPlayer.setOnCompletionListener(mCompletionListener);
388         mLocalPlayer.start();
389     }
390 
391     /**
392      * Whether this ringtone is currently playing.
393      *
394      * @return True if playing, false otherwise.
395      */
isPlaying()396     public boolean isPlaying() {
397         if (mLocalPlayer != null) {
398             return mLocalPlayer.isPlaying();
399         } else if (mAllowRemote && (mRemotePlayer != null)) {
400             try {
401                 return mRemotePlayer.isPlaying(mRemoteToken);
402             } catch (RemoteException e) {
403                 Log.w(TAG, "Problem checking ringtone: " + e);
404                 return false;
405             }
406         } else {
407             Log.w(TAG, "Neither local nor remote playback available");
408             return false;
409         }
410     }
411 
playFallbackRingtone()412     private boolean playFallbackRingtone() {
413         if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes))
414                 != 0) {
415             int ringtoneType = RingtoneManager.getDefaultType(mUri);
416             if (ringtoneType == -1 ||
417                     RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) != null) {
418                 // Default ringtone, try fallback ringtone.
419                 try {
420                     AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(
421                             com.android.internal.R.raw.fallbackring);
422                     if (afd != null) {
423                         mLocalPlayer = new MediaPlayer();
424                         if (afd.getDeclaredLength() < 0) {
425                             mLocalPlayer.setDataSource(afd.getFileDescriptor());
426                         } else {
427                             mLocalPlayer.setDataSource(afd.getFileDescriptor(),
428                                     afd.getStartOffset(),
429                                     afd.getDeclaredLength());
430                         }
431                         mLocalPlayer.setAudioAttributes(mAudioAttributes);
432                         synchronized (mPlaybackSettingsLock) {
433                             applyPlaybackProperties_sync();
434                         }
435                         mLocalPlayer.prepare();
436                         startLocalPlayer();
437                         afd.close();
438                         return true;
439                     } else {
440                         Log.e(TAG, "Could not load fallback ringtone");
441                     }
442                 } catch (IOException ioe) {
443                     destroyLocalPlayer();
444                     Log.e(TAG, "Failed to open fallback ringtone");
445                 } catch (NotFoundException nfe) {
446                     Log.e(TAG, "Fallback ringtone does not exist");
447                 }
448             } else {
449                 Log.w(TAG, "not playing fallback for " + mUri);
450             }
451         }
452         return false;
453     }
454 
setTitle(String title)455     void setTitle(String title) {
456         mTitle = title;
457     }
458 
459     @Override
finalize()460     protected void finalize() {
461         if (mLocalPlayer != null) {
462             mLocalPlayer.release();
463         }
464     }
465 
466     class MyOnCompletionListener implements MediaPlayer.OnCompletionListener {
onCompletion(MediaPlayer mp)467         public void onCompletion(MediaPlayer mp)
468         {
469             synchronized (sActiveRingtones) {
470                 sActiveRingtones.remove(Ringtone.this);
471             }
472         }
473     }
474 }
475