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) throws RemoteException {
96             if (LOGD) {
97                 Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
98                         + Binder.getCallingUid() + ")");
99             }
100             Client client;
101             synchronized (mClients) {
102                 client = mClients.get(token);
103                 if (client == null) {
104                     final UserHandle user = Binder.getCallingUserHandle();
105                     client = new Client(token, uri, user, aa);
106                     token.linkToDeath(client, 0);
107                     mClients.put(token, client);
108                 }
109             }
110             client.mRingtone.play();
111         }
112 
113         @Override
114         public void stop(IBinder token) {
115             if (LOGD) Log.d(TAG, "stop(token=" + token + ")");
116             Client client;
117             synchronized (mClients) {
118                 client = mClients.remove(token);
119             }
120             if (client != null) {
121                 client.mToken.unlinkToDeath(client, 0);
122                 client.mRingtone.stop();
123             }
124         }
125 
126         @Override
127         public boolean isPlaying(IBinder token) {
128             if (LOGD) Log.d(TAG, "isPlaying(token=" + token + ")");
129             Client client;
130             synchronized (mClients) {
131                 client = mClients.get(token);
132             }
133             if (client != null) {
134                 return client.mRingtone.isPlaying();
135             } else {
136                 return false;
137             }
138         }
139 
140         @Override
141         public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
142             if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
143             if (Binder.getCallingUid() != Process.SYSTEM_UID) {
144                 throw new SecurityException("Async playback only available from system UID.");
145             }
146 
147             mAsyncPlayer.play(getContextForUser(user), uri, looping, aa);
148         }
149 
150         @Override
151         public void stopAsync() {
152             if (LOGD) Log.d(TAG, "stopAsync()");
153             if (Binder.getCallingUid() != Process.SYSTEM_UID) {
154                 throw new SecurityException("Async playback only available from system UID.");
155             }
156             mAsyncPlayer.stop();
157         }
158     };
159 
getContextForUser(UserHandle user)160     private Context getContextForUser(UserHandle user) {
161         try {
162             return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
163         } catch (NameNotFoundException e) {
164             throw new RuntimeException(e);
165         }
166     }
167 
168     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)169     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
170         pw.println("Clients:");
171         synchronized (mClients) {
172             for (Client client : mClients.values()) {
173                 pw.print("  mToken=");
174                 pw.print(client.mToken);
175                 pw.print(" mUri=");
176                 pw.println(client.mRingtone.getUri());
177             }
178         }
179     }
180 }
181