• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.systemui.media;
18 
19 import android.annotation.Nullable;
20 import android.content.ContentResolver;
21 import android.content.Context;
22 import android.content.pm.PackageManager.NameNotFoundException;
23 import android.database.Cursor;
24 import android.media.AudioAttributes;
25 import android.media.IAudioService;
26 import android.media.IRingtonePlayer;
27 import android.media.Ringtone;
28 import android.media.VolumeShaper;
29 import android.net.Uri;
30 import android.os.Binder;
31 import android.os.IBinder;
32 import android.os.ParcelFileDescriptor;
33 import android.os.Process;
34 import android.os.RemoteException;
35 import android.os.ServiceManager;
36 import android.os.UserHandle;
37 import android.provider.MediaStore;
38 import android.util.Log;
39 
40 import com.android.systemui.SystemUI;
41 
42 import java.io.FileDescriptor;
43 import java.io.IOException;
44 import java.io.PrintWriter;
45 import 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  */
51 public class RingtonePlayer extends SystemUI {
52     private static final String TAG = "RingtonePlayer";
53     private static final boolean LOGD = false;
54 
55     // TODO: support Uri switching under same IBinder
56 
57     private IAudioService mAudioService;
58 
59     private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG);
60     private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
61 
62     @Override
start()63     public void start() {
64         mAsyncPlayer.setUsesWakeLock(mContext);
65 
66         mAudioService = IAudioService.Stub.asInterface(
67                 ServiceManager.getService(Context.AUDIO_SERVICE));
68         try {
69             mAudioService.setRingtonePlayer(mCallback);
70         } catch (RemoteException e) {
71             Log.e(TAG, "Problem registering RingtonePlayer: " + e);
72         }
73     }
74 
75     /**
76      * Represents an active remote {@link Ringtone} client.
77      */
78     private class Client implements IBinder.DeathRecipient {
79         private final IBinder mToken;
80         private final Ringtone mRingtone;
81 
Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa)82         public Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa) {
83             this(token, uri, user, aa, null);
84         }
85 
Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa, @Nullable VolumeShaper.Configuration volumeShaperConfig)86         Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa,
87                 @Nullable VolumeShaper.Configuration volumeShaperConfig) {
88             mToken = token;
89 
90             mRingtone = new Ringtone(getContextForUser(user), false);
91             mRingtone.setAudioAttributes(aa);
92             mRingtone.setUri(uri, volumeShaperConfig);
93         }
94 
95         @Override
binderDied()96         public void binderDied() {
97             if (LOGD) Log.d(TAG, "binderDied() token=" + mToken);
98             synchronized (mClients) {
99                 mClients.remove(mToken);
100             }
101             mRingtone.stop();
102         }
103     }
104 
105     private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {
106         @Override
107         public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)
108                 throws RemoteException {
109             playWithVolumeShaping(token, uri, aa, volume, looping, null);
110         }
111         @Override
112         public void playWithVolumeShaping(IBinder token, Uri uri, AudioAttributes aa, float volume,
113                 boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig)
114                 throws RemoteException {
115             if (LOGD) {
116                 Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
117                         + Binder.getCallingUid() + ")");
118             }
119             Client client;
120             synchronized (mClients) {
121                 client = mClients.get(token);
122                 if (client == null) {
123                     final UserHandle user = Binder.getCallingUserHandle();
124                     client = new Client(token, uri, user, aa, volumeShaperConfig);
125                     token.linkToDeath(client, 0);
126                     mClients.put(token, client);
127                 }
128             }
129             client.mRingtone.setLooping(looping);
130             client.mRingtone.setVolume(volume);
131             client.mRingtone.play();
132         }
133 
134         @Override
135         public void stop(IBinder token) {
136             if (LOGD) Log.d(TAG, "stop(token=" + token + ")");
137             Client client;
138             synchronized (mClients) {
139                 client = mClients.remove(token);
140             }
141             if (client != null) {
142                 client.mToken.unlinkToDeath(client, 0);
143                 client.mRingtone.stop();
144             }
145         }
146 
147         @Override
148         public boolean isPlaying(IBinder token) {
149             if (LOGD) Log.d(TAG, "isPlaying(token=" + token + ")");
150             Client client;
151             synchronized (mClients) {
152                 client = mClients.get(token);
153             }
154             if (client != null) {
155                 return client.mRingtone.isPlaying();
156             } else {
157                 return false;
158             }
159         }
160 
161         @Override
162         public void setPlaybackProperties(IBinder token, float volume, boolean looping) {
163             Client client;
164             synchronized (mClients) {
165                 client = mClients.get(token);
166             }
167             if (client != null) {
168                 client.mRingtone.setVolume(volume);
169                 client.mRingtone.setLooping(looping);
170             }
171             // else no client for token when setting playback properties but will be set at play()
172         }
173 
174         @Override
175         public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
176             if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
177             if (Binder.getCallingUid() != Process.SYSTEM_UID) {
178                 throw new SecurityException("Async playback only available from system UID.");
179             }
180             if (UserHandle.ALL.equals(user)) {
181                 user = UserHandle.SYSTEM;
182             }
183             mAsyncPlayer.play(getContextForUser(user), uri, looping, aa);
184         }
185 
186         @Override
187         public void stopAsync() {
188             if (LOGD) Log.d(TAG, "stopAsync()");
189             if (Binder.getCallingUid() != Process.SYSTEM_UID) {
190                 throw new SecurityException("Async playback only available from system UID.");
191             }
192             mAsyncPlayer.stop();
193         }
194 
195         @Override
196         public String getTitle(Uri uri) {
197             final UserHandle user = Binder.getCallingUserHandle();
198             return Ringtone.getTitle(getContextForUser(user), uri,
199                     false /*followSettingsUri*/, false /*allowRemote*/);
200         }
201 
202         @Override
203         public ParcelFileDescriptor openRingtone(Uri uri) {
204             final UserHandle user = Binder.getCallingUserHandle();
205             final ContentResolver resolver = getContextForUser(user).getContentResolver();
206 
207             // Only open the requested Uri if it's a well-known ringtone or
208             // other sound from the platform media store, otherwise this opens
209             // up arbitrary access to any file on external storage.
210             if (uri.toString().startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
211                 try (Cursor c = resolver.query(uri, new String[] {
212                         MediaStore.Audio.AudioColumns.IS_RINGTONE,
213                         MediaStore.Audio.AudioColumns.IS_ALARM,
214                         MediaStore.Audio.AudioColumns.IS_NOTIFICATION
215                 }, null, null, null)) {
216                     if (c.moveToFirst()) {
217                         if (c.getInt(0) != 0 || c.getInt(1) != 0 || c.getInt(2) != 0) {
218                             try {
219                                 return resolver.openFileDescriptor(uri, "r");
220                             } catch (IOException e) {
221                                 throw new SecurityException(e);
222                             }
223                         }
224                     }
225                 }
226             }
227             throw new SecurityException("Uri is not ringtone, alarm, or notification: " + uri);
228         }
229     };
230 
getContextForUser(UserHandle user)231     private Context getContextForUser(UserHandle user) {
232         try {
233             return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
234         } catch (NameNotFoundException e) {
235             throw new RuntimeException(e);
236         }
237     }
238 
239     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)240     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
241         pw.println("Clients:");
242         synchronized (mClients) {
243             for (Client client : mClients.values()) {
244                 pw.print("  mToken=");
245                 pw.print(client.mToken);
246                 pw.print(" mUri=");
247                 pw.println(client.mRingtone.getUri());
248             }
249         }
250     }
251 }
252