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.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} */
     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
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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} */
    317     public Uri getUri() {
    318         return mUri;
    319     }
    320 
    321     /**
    322      * Plays the ringtone.
    323      */
    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      */
    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 
    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 
    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      */
    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 
    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 
    455     void setTitle(String title) {
    456         mTitle = title;
    457     }
    458 
    459     @Override
    460     protected void finalize() {
    461         if (mLocalPlayer != null) {
    462             mLocalPlayer.release();
    463         }
    464     }
    465 
    466     class MyOnCompletionListener implements MediaPlayer.OnCompletionListener {
    467         public void onCompletion(MediaPlayer mp)
    468         {
    469             synchronized (sActiveRingtones) {
    470                 sActiveRingtones.remove(Ringtone.this);
    471             }
    472         }
    473     }
    474 }
    475