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