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