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