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.database.Cursor;
22import android.net.Uri;
23import android.os.Binder;
24import android.os.RemoteException;
25import android.provider.DrmStore;
26import android.provider.MediaStore;
27import android.provider.Settings;
28import android.util.Log;
29
30import java.io.IOException;
31
32/**
33 * Ringtone provides a quick method for playing a ringtone, notification, or
34 * other similar types of sounds.
35 * <p>
36 * For ways of retrieving {@link Ringtone} objects or to show a ringtone
37 * picker, see {@link RingtoneManager}.
38 *
39 * @see RingtoneManager
40 */
41public class Ringtone {
42    private static final String TAG = "Ringtone";
43    private static final boolean LOGD = true;
44
45    private static final String[] MEDIA_COLUMNS = new String[] {
46        MediaStore.Audio.Media._ID,
47        MediaStore.Audio.Media.DATA,
48        MediaStore.Audio.Media.TITLE
49    };
50
51    private static final String[] DRM_COLUMNS = new String[] {
52        DrmStore.Audio._ID,
53        DrmStore.Audio.DATA,
54        DrmStore.Audio.TITLE
55    };
56
57    private final Context mContext;
58    private final AudioManager mAudioManager;
59    private final boolean mAllowRemote;
60    private final IRingtonePlayer mRemotePlayer;
61    private final Binder mRemoteToken;
62
63    private MediaPlayer mLocalPlayer;
64
65    private Uri mUri;
66    private String mTitle;
67
68    private int mStreamType = AudioManager.STREAM_RING;
69
70    /** {@hide} */
71    public Ringtone(Context context, boolean allowRemote) {
72        mContext = context;
73        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
74        mAllowRemote = allowRemote;
75        mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;
76        mRemoteToken = allowRemote ? new Binder() : null;
77    }
78
79    /**
80     * Sets the stream type where this ringtone will be played.
81     *
82     * @param streamType The stream, see {@link AudioManager}.
83     */
84    public void setStreamType(int streamType) {
85        mStreamType = streamType;
86
87        // The stream type has to be set before the media player is prepared.
88        // Re-initialize it.
89        setUri(mUri);
90    }
91
92    /**
93     * Gets the stream type where this ringtone will be played.
94     *
95     * @return The stream type, see {@link AudioManager}.
96     */
97    public int getStreamType() {
98        return mStreamType;
99    }
100
101    /**
102     * Returns a human-presentable title for ringtone. Looks in media and DRM
103     * content providers. If not in either, uses the filename
104     *
105     * @param context A context used for querying.
106     */
107    public String getTitle(Context context) {
108        if (mTitle != null) return mTitle;
109        return mTitle = getTitle(context, mUri, true);
110    }
111
112    private static String getTitle(Context context, Uri uri, boolean followSettingsUri) {
113        Cursor cursor = null;
114        ContentResolver res = context.getContentResolver();
115
116        String title = null;
117
118        if (uri != null) {
119            String authority = uri.getAuthority();
120
121            if (Settings.AUTHORITY.equals(authority)) {
122                if (followSettingsUri) {
123                    Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context,
124                            RingtoneManager.getDefaultType(uri));
125                    String actualTitle = getTitle(context, actualUri, false);
126                    title = context
127                            .getString(com.android.internal.R.string.ringtone_default_with_actual,
128                                    actualTitle);
129                }
130            } else {
131                try {
132                    if (DrmStore.AUTHORITY.equals(authority)) {
133                        cursor = res.query(uri, DRM_COLUMNS, null, null, null);
134                    } else if (MediaStore.AUTHORITY.equals(authority)) {
135                        cursor = res.query(uri, MEDIA_COLUMNS, null, null, null);
136                    }
137                } catch (SecurityException e) {
138                    // missing cursor is handled below
139                }
140
141                try {
142                    if (cursor != null && cursor.getCount() == 1) {
143                        cursor.moveToFirst();
144                        return cursor.getString(2);
145                    } else {
146                        title = uri.getLastPathSegment();
147                    }
148                } finally {
149                    if (cursor != null) {
150                        cursor.close();
151                    }
152                }
153            }
154        }
155
156        if (title == null) {
157            title = context.getString(com.android.internal.R.string.ringtone_unknown);
158
159            if (title == null) {
160                title = "";
161            }
162        }
163
164        return title;
165    }
166
167    /**
168     * Set {@link Uri} to be used for ringtone playback. Attempts to open
169     * locally, otherwise will delegate playback to remote
170     * {@link IRingtonePlayer}.
171     *
172     * @hide
173     */
174    public void setUri(Uri uri) {
175        destroyLocalPlayer();
176
177        mUri = uri;
178        if (mUri == null) {
179            return;
180        }
181
182        // TODO: detect READ_EXTERNAL and specific content provider case, instead of relying on throwing
183
184        // try opening uri locally before delegating to remote player
185        mLocalPlayer = new MediaPlayer();
186        try {
187            mLocalPlayer.setDataSource(mContext, mUri);
188            mLocalPlayer.setAudioStreamType(mStreamType);
189            mLocalPlayer.prepare();
190
191        } catch (SecurityException e) {
192            destroyLocalPlayer();
193            if (!mAllowRemote) {
194                Log.w(TAG, "Remote playback not allowed: " + e);
195            }
196        } catch (IOException e) {
197            destroyLocalPlayer();
198            if (!mAllowRemote) {
199                Log.w(TAG, "Remote playback not allowed: " + e);
200            }
201        }
202
203        if (LOGD) {
204            if (mLocalPlayer != null) {
205                Log.d(TAG, "Successfully created local player");
206            } else {
207                Log.d(TAG, "Problem opening; delegating to remote player");
208            }
209        }
210    }
211
212    /** {@hide} */
213    public Uri getUri() {
214        return mUri;
215    }
216
217    /**
218     * Plays the ringtone.
219     */
220    public void play() {
221        if (mLocalPlayer != null) {
222            // do not play ringtones if stream volume is 0
223            // (typically because ringer mode is silent).
224            if (mAudioManager.getStreamVolume(mStreamType) != 0) {
225                mLocalPlayer.start();
226            }
227        } else if (mAllowRemote) {
228            final Uri canonicalUri = mUri.getCanonicalUri();
229            try {
230                mRemotePlayer.play(mRemoteToken, canonicalUri, mStreamType);
231            } catch (RemoteException e) {
232                Log.w(TAG, "Problem playing ringtone: " + e);
233            }
234        } else {
235            Log.w(TAG, "Neither local nor remote playback available");
236        }
237    }
238
239    /**
240     * Stops a playing ringtone.
241     */
242    public void stop() {
243        if (mLocalPlayer != null) {
244            destroyLocalPlayer();
245        } else if (mAllowRemote) {
246            try {
247                mRemotePlayer.stop(mRemoteToken);
248            } catch (RemoteException e) {
249                Log.w(TAG, "Problem stopping ringtone: " + e);
250            }
251        }
252    }
253
254    private void destroyLocalPlayer() {
255        if (mLocalPlayer != null) {
256            mLocalPlayer.reset();
257            mLocalPlayer.release();
258            mLocalPlayer = null;
259        }
260    }
261
262    /**
263     * Whether this ringtone is currently playing.
264     *
265     * @return True if playing, false otherwise.
266     */
267    public boolean isPlaying() {
268        if (mLocalPlayer != null) {
269            return mLocalPlayer.isPlaying();
270        } else if (mAllowRemote) {
271            try {
272                return mRemotePlayer.isPlaying(mRemoteToken);
273            } catch (RemoteException e) {
274                Log.w(TAG, "Problem checking ringtone: " + e);
275                return false;
276            }
277        } else {
278            Log.w(TAG, "Neither local nor remote playback available");
279            return false;
280        }
281    }
282
283    void setTitle(String title) {
284        mTitle = title;
285    }
286}
287