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