blob: 4c96de23281009cc08f96fa316f0480962978ed4 [file] [log] [blame]
Jeff Sharkey098d5802012-04-26 17:30:34 -07001/*
2 * Copyright (C) 2012 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 com.android.systemui.media;
18
Yiwen Cheneac542e2019-02-10 14:23:25 -080019import android.annotation.Nullable;
Jeff Sharkey783ee0c2016-03-05 17:23:28 -070020import android.content.ContentResolver;
Jeff Sharkey098d5802012-04-26 17:30:34 -070021import android.content.Context;
Jeff Sharkey65c4a2b2012-09-25 17:22:27 -070022import android.content.pm.PackageManager.NameNotFoundException;
Jeff Sharkey783ee0c2016-03-05 17:23:28 -070023import android.database.Cursor;
Jean-Michel Trivi81f871e2014-08-06 16:32:38 -070024import android.media.AudioAttributes;
Jeff Sharkey098d5802012-04-26 17:30:34 -070025import android.media.IAudioService;
26import android.media.IRingtonePlayer;
27import android.media.Ringtone;
Yiwen Cheneac542e2019-02-10 14:23:25 -080028import android.media.VolumeShaper;
Jeff Sharkey098d5802012-04-26 17:30:34 -070029import android.net.Uri;
30import android.os.Binder;
31import android.os.IBinder;
Jeff Sharkey783ee0c2016-03-05 17:23:28 -070032import android.os.ParcelFileDescriptor;
Jeff Sharkey098d5802012-04-26 17:30:34 -070033import android.os.Process;
34import android.os.RemoteException;
35import android.os.ServiceManager;
Jeff Sharkey65c4a2b2012-09-25 17:22:27 -070036import android.os.UserHandle;
Jeff Sharkey783ee0c2016-03-05 17:23:28 -070037import android.provider.MediaStore;
John Spurlockcd686b52013-06-05 10:13:46 -040038import android.util.Log;
Jeff Sharkey098d5802012-04-26 17:30:34 -070039
40import com.android.systemui.SystemUI;
Jeff Sharkey098d5802012-04-26 17:30:34 -070041
42import java.io.FileDescriptor;
Jeff Sharkey783ee0c2016-03-05 17:23:28 -070043import java.io.IOException;
Jeff Sharkey098d5802012-04-26 17:30:34 -070044import java.io.PrintWriter;
45import java.util.HashMap;
46
47/**
48 * Service that offers to play ringtones by {@link Uri}, since our process has
49 * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}.
50 */
51public class RingtonePlayer extends SystemUI {
52 private static final String TAG = "RingtonePlayer";
Jeff Sharkeyb6e404a2012-05-15 11:36:11 -070053 private static final boolean LOGD = false;
Jeff Sharkey098d5802012-04-26 17:30:34 -070054
55 // TODO: support Uri switching under same IBinder
56
57 private IAudioService mAudioService;
58
59 private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG);
John Spurlockb8baccc2013-06-05 12:59:01 -040060 private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
Jeff Sharkey098d5802012-04-26 17:30:34 -070061
Dave Mankoffa5d8a392019-10-10 12:21:09 -040062 public RingtonePlayer(Context context) {
63 super(context);
64 }
65
Jeff Sharkey098d5802012-04-26 17:30:34 -070066 @Override
67 public void start() {
68 mAsyncPlayer.setUsesWakeLock(mContext);
69
70 mAudioService = IAudioService.Stub.asInterface(
71 ServiceManager.getService(Context.AUDIO_SERVICE));
72 try {
73 mAudioService.setRingtonePlayer(mCallback);
74 } catch (RemoteException e) {
John Spurlockcd686b52013-06-05 10:13:46 -040075 Log.e(TAG, "Problem registering RingtonePlayer: " + e);
Jeff Sharkey098d5802012-04-26 17:30:34 -070076 }
77 }
78
79 /**
80 * Represents an active remote {@link Ringtone} client.
81 */
82 private class Client implements IBinder.DeathRecipient {
83 private final IBinder mToken;
84 private final Ringtone mRingtone;
85
Jean-Michel Trivi81f871e2014-08-06 16:32:38 -070086 public Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa) {
Yiwen Cheneac542e2019-02-10 14:23:25 -080087 this(token, uri, user, aa, null);
88 }
89
90 Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa,
91 @Nullable VolumeShaper.Configuration volumeShaperConfig) {
Jeff Sharkey098d5802012-04-26 17:30:34 -070092 mToken = token;
Jeff Sharkey65c4a2b2012-09-25 17:22:27 -070093
94 mRingtone = new Ringtone(getContextForUser(user), false);
Jean-Michel Trivi81f871e2014-08-06 16:32:38 -070095 mRingtone.setAudioAttributes(aa);
Yiwen Cheneac542e2019-02-10 14:23:25 -080096 mRingtone.setUri(uri, volumeShaperConfig);
Jeff Sharkey098d5802012-04-26 17:30:34 -070097 }
98
99 @Override
100 public void binderDied() {
John Spurlockcd686b52013-06-05 10:13:46 -0400101 if (LOGD) Log.d(TAG, "binderDied() token=" + mToken);
Jeff Sharkey098d5802012-04-26 17:30:34 -0700102 synchronized (mClients) {
103 mClients.remove(mToken);
104 }
105 mRingtone.stop();
106 }
107 }
108
109 private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {
110 @Override
Jean-Michel Trivi462045e2015-06-30 12:34:48 -0700111 public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)
112 throws RemoteException {
Yiwen Cheneac542e2019-02-10 14:23:25 -0800113 playWithVolumeShaping(token, uri, aa, volume, looping, null);
114 }
115 @Override
116 public void playWithVolumeShaping(IBinder token, Uri uri, AudioAttributes aa, float volume,
117 boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig)
118 throws RemoteException {
Jeff Sharkey65c4a2b2012-09-25 17:22:27 -0700119 if (LOGD) {
John Spurlockcd686b52013-06-05 10:13:46 -0400120 Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
Jeff Sharkey65c4a2b2012-09-25 17:22:27 -0700121 + Binder.getCallingUid() + ")");
122 }
Jeff Sharkey098d5802012-04-26 17:30:34 -0700123 Client client;
124 synchronized (mClients) {
125 client = mClients.get(token);
126 if (client == null) {
Jeff Sharkey65c4a2b2012-09-25 17:22:27 -0700127 final UserHandle user = Binder.getCallingUserHandle();
Yiwen Cheneac542e2019-02-10 14:23:25 -0800128 client = new Client(token, uri, user, aa, volumeShaperConfig);
Jeff Sharkey098d5802012-04-26 17:30:34 -0700129 token.linkToDeath(client, 0);
130 mClients.put(token, client);
131 }
132 }
Jean-Michel Trivi462045e2015-06-30 12:34:48 -0700133 client.mRingtone.setLooping(looping);
134 client.mRingtone.setVolume(volume);
Jeff Sharkey098d5802012-04-26 17:30:34 -0700135 client.mRingtone.play();
136 }
137
138 @Override
139 public void stop(IBinder token) {
John Spurlockcd686b52013-06-05 10:13:46 -0400140 if (LOGD) Log.d(TAG, "stop(token=" + token + ")");
Jeff Sharkey098d5802012-04-26 17:30:34 -0700141 Client client;
142 synchronized (mClients) {
143 client = mClients.remove(token);
144 }
145 if (client != null) {
146 client.mToken.unlinkToDeath(client, 0);
147 client.mRingtone.stop();
148 }
149 }
150
151 @Override
152 public boolean isPlaying(IBinder token) {
John Spurlockcd686b52013-06-05 10:13:46 -0400153 if (LOGD) Log.d(TAG, "isPlaying(token=" + token + ")");
Jeff Sharkey098d5802012-04-26 17:30:34 -0700154 Client client;
155 synchronized (mClients) {
156 client = mClients.get(token);
157 }
158 if (client != null) {
159 return client.mRingtone.isPlaying();
160 } else {
161 return false;
162 }
163 }
164
165 @Override
Jean-Michel Trivi462045e2015-06-30 12:34:48 -0700166 public void setPlaybackProperties(IBinder token, float volume, boolean looping) {
167 Client client;
168 synchronized (mClients) {
169 client = mClients.get(token);
170 }
171 if (client != null) {
172 client.mRingtone.setVolume(volume);
173 client.mRingtone.setLooping(looping);
174 }
175 // else no client for token when setting playback properties but will be set at play()
176 }
177
178 @Override
Jean-Michel Trivi81f871e2014-08-06 16:32:38 -0700179 public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
John Spurlockcd686b52013-06-05 10:13:46 -0400180 if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
Jeff Sharkey098d5802012-04-26 17:30:34 -0700181 if (Binder.getCallingUid() != Process.SYSTEM_UID) {
182 throw new SecurityException("Async playback only available from system UID.");
183 }
Sungmin Choi4a05bbcd2015-08-07 18:12:19 -0700184 if (UserHandle.ALL.equals(user)) {
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700185 user = UserHandle.SYSTEM;
Sungmin Choi4a05bbcd2015-08-07 18:12:19 -0700186 }
Jean-Michel Trivi81f871e2014-08-06 16:32:38 -0700187 mAsyncPlayer.play(getContextForUser(user), uri, looping, aa);
Jeff Sharkey098d5802012-04-26 17:30:34 -0700188 }
189
190 @Override
191 public void stopAsync() {
John Spurlockcd686b52013-06-05 10:13:46 -0400192 if (LOGD) Log.d(TAG, "stopAsync()");
Jeff Sharkey098d5802012-04-26 17:30:34 -0700193 if (Binder.getCallingUid() != Process.SYSTEM_UID) {
194 throw new SecurityException("Async playback only available from system UID.");
195 }
196 mAsyncPlayer.stop();
197 }
Todd Kennedy68de1572015-07-20 13:32:40 -0700198
199 @Override
200 public String getTitle(Uri uri) {
201 final UserHandle user = Binder.getCallingUserHandle();
202 return Ringtone.getTitle(getContextForUser(user), uri,
203 false /*followSettingsUri*/, false /*allowRemote*/);
204 }
Jeff Sharkey783ee0c2016-03-05 17:23:28 -0700205
206 @Override
207 public ParcelFileDescriptor openRingtone(Uri uri) {
208 final UserHandle user = Binder.getCallingUserHandle();
209 final ContentResolver resolver = getContextForUser(user).getContentResolver();
210
211 // Only open the requested Uri if it's a well-known ringtone or
212 // other sound from the platform media store, otherwise this opens
213 // up arbitrary access to any file on external storage.
214 if (uri.toString().startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
215 try (Cursor c = resolver.query(uri, new String[] {
216 MediaStore.Audio.AudioColumns.IS_RINGTONE,
217 MediaStore.Audio.AudioColumns.IS_ALARM,
218 MediaStore.Audio.AudioColumns.IS_NOTIFICATION
219 }, null, null, null)) {
220 if (c.moveToFirst()) {
221 if (c.getInt(0) != 0 || c.getInt(1) != 0 || c.getInt(2) != 0) {
222 try {
223 return resolver.openFileDescriptor(uri, "r");
224 } catch (IOException e) {
225 throw new SecurityException(e);
226 }
227 }
228 }
229 }
230 }
231 throw new SecurityException("Uri is not ringtone, alarm, or notification: " + uri);
232 }
Jeff Sharkey098d5802012-04-26 17:30:34 -0700233 };
234
Jeff Sharkey65c4a2b2012-09-25 17:22:27 -0700235 private Context getContextForUser(UserHandle user) {
236 try {
237 return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
238 } catch (NameNotFoundException e) {
239 throw new RuntimeException(e);
240 }
241 }
242
Jeff Sharkey098d5802012-04-26 17:30:34 -0700243 @Override
244 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
245 pw.println("Clients:");
246 synchronized (mClients) {
247 for (Client client : mClients.values()) {
248 pw.print(" mToken=");
249 pw.print(client.mToken);
250 pw.print(" mUri=");
251 pw.println(client.mRingtone.getUri());
252 }
253 }
254 }
255}