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
17package com.android.systemui.media;
18
19import android.content.Context;
20import android.content.pm.PackageManager.NameNotFoundException;
21import android.media.AudioAttributes;
22import android.media.IAudioService;
23import android.media.IRingtonePlayer;
24import android.media.Ringtone;
25import android.net.Uri;
26import android.os.Binder;
27import android.os.IBinder;
28import android.os.Process;
29import android.os.RemoteException;
30import android.os.ServiceManager;
31import android.os.UserHandle;
32import android.util.Log;
33
34import com.android.systemui.SystemUI;
35
36import java.io.FileDescriptor;
37import java.io.PrintWriter;
38import 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 */
44public 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
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
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
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
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
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