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.content.Context;
20 import android.content.pm.PackageManager.NameNotFoundException;
21 import android.media.AudioAttributes;
22 import android.media.IAudioService;
23 import android.media.IRingtonePlayer;
24 import android.media.Ringtone;
25 import android.net.Uri;
26 import android.os.Binder;
27 import android.os.IBinder;
28 import android.os.Process;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.os.UserHandle;
32 import android.util.Log;
33 
34 import com.android.systemui.SystemUI;
35 
36 import java.io.FileDescriptor;
37 import java.io.PrintWriter;
38 import java.util.HashMap;
39 
40 /**
41  * Service that offers to play ringtones by {@link Uri}, since our process has
42  * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}.
43  */
44 public class RingtonePlayer extends SystemUI {
45     private static final String TAG = "RingtonePlayer";
46     private static final boolean LOGD = false;
47 
48     // TODO: support Uri switching under same IBinder
49 
50     private IAudioService mAudioService;
51 
52     private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG);
53     private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
54 
55     @Override
start()56     public void start() {
57         mAsyncPlayer.setUsesWakeLock(mContext);
58 
59         mAudioService = IAudioService.Stub.asInterface(
60                 ServiceManager.getService(Context.AUDIO_SERVICE));
61         try {
62             mAudioService.setRingtonePlayer(mCallback);
63         } catch (RemoteException e) {
64             Log.e(TAG, "Problem registering RingtonePlayer: " + e);
65         }
66     }
67 
68     /**
69      * Represents an active remote {@link Ringtone} client.
70      */
71     private class Client implements IBinder.DeathRecipient {
72         private final IBinder mToken;
73         private final Ringtone mRingtone;
74 
Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa)75         public Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa) {
76             mToken = token;
77 
78             mRingtone = new Ringtone(getContextForUser(user), false);
79             mRingtone.setAudioAttributes(aa);
80             mRingtone.setUri(uri);
81         }
82 
83         @Override
binderDied()84         public void binderDied() {
85             if (LOGD) Log.d(TAG, "binderDied() token=" + mToken);
86             synchronized (mClients) {
87                 mClients.remove(mToken);
88             }
89             mRingtone.stop();
90         }
91     }
92 
93     private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {
94         @Override
95         public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)
96                 throws RemoteException {
97             if (LOGD) {
98                 Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
99                         + Binder.getCallingUid() + ")");
100             }
101             Client client;
102             synchronized (mClients) {
103                 client = mClients.get(token);
104                 if (client == null) {
105                     final UserHandle user = Binder.getCallingUserHandle();
106                     client = new Client(token, uri, user, aa);
107                     token.linkToDeath(client, 0);
108                     mClients.put(token, client);
109                 }
110             }
111             client.mRingtone.setLooping(looping);
112             client.mRingtone.setVolume(volume);
113             client.mRingtone.play();
114         }
115 
116         @Override
117         public void stop(IBinder token) {
118             if (LOGD) Log.d(TAG, "stop(token=" + token + ")");
119             Client client;
120             synchronized (mClients) {
121                 client = mClients.remove(token);
122             }
123             if (client != null) {
124                 client.mToken.unlinkToDeath(client, 0);
125                 client.mRingtone.stop();
126             }
127         }
128 
129         @Override
130         public boolean isPlaying(IBinder token) {
131             if (LOGD) Log.d(TAG, "isPlaying(token=" + token + ")");
132             Client client;
133             synchronized (mClients) {
134                 client = mClients.get(token);
135             }
136             if (client != null) {
137                 return client.mRingtone.isPlaying();
138             } else {
139                 return false;
140             }
141         }
142 
143         @Override
144         public void setPlaybackProperties(IBinder token, float volume, boolean looping) {
145             Client client;
146             synchronized (mClients) {
147                 client = mClients.get(token);
148             }
149             if (client != null) {
150                 client.mRingtone.setVolume(volume);
151                 client.mRingtone.setLooping(looping);
152             }
153             // else no client for token when setting playback properties but will be set at play()
154         }
155 
156         @Override
157         public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
158             if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
159             if (Binder.getCallingUid() != Process.SYSTEM_UID) {
160                 throw new SecurityException("Async playback only available from system UID.");
161             }
162             if (UserHandle.ALL.equals(user)) {
163                 user = UserHandle.OWNER;
164             }
165             mAsyncPlayer.play(getContextForUser(user), uri, looping, aa);
166         }
167 
168         @Override
169         public void stopAsync() {
170             if (LOGD) Log.d(TAG, "stopAsync()");
171             if (Binder.getCallingUid() != Process.SYSTEM_UID) {
172                 throw new SecurityException("Async playback only available from system UID.");
173             }
174             mAsyncPlayer.stop();
175         }
176 
177         @Override
178         public String getTitle(Uri uri) {
179             final UserHandle user = Binder.getCallingUserHandle();
180             return Ringtone.getTitle(getContextForUser(user), uri,
181                     false /*followSettingsUri*/, false /*allowRemote*/);
182         }
183     };
184 
getContextForUser(UserHandle user)185     private Context getContextForUser(UserHandle user) {
186         try {
187             return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
188         } catch (NameNotFoundException e) {
189             throw new RuntimeException(e);
190         }
191     }
192 
193     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)194     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
195         pw.println("Clients:");
196         synchronized (mClients) {
197             for (Client client : mClients.values()) {
198                 pw.print("  mToken=");
199                 pw.print(client.mToken);
200                 pw.print(" mUri=");
201                 pw.println(client.mRingtone.getUri());
202             }
203         }
204     }
205 }
206