1098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey/*
2098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey * Copyright (C) 2012 The Android Open Source Project
3098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey *
4098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
5098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey * you may not use this file except in compliance with the License.
6098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey * You may obtain a copy of the License at
7098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey *
8098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
9098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey *
10098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey * Unless required by applicable law or agreed to in writing, software
11098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
12098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey * See the License for the specific language governing permissions and
14098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey * limitations under the License.
15098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey */
16098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
17098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeypackage com.android.systemui.media;
18098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
19098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeyimport android.content.Context;
2065c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkeyimport android.content.pm.PackageManager.NameNotFoundException;
2181f871e2b96125d57b76c07169e868e516443794Jean-Michel Triviimport android.media.AudioAttributes;
22098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeyimport android.media.IAudioService;
23098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeyimport android.media.IRingtonePlayer;
24098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeyimport android.media.Ringtone;
25098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeyimport android.net.Uri;
26098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeyimport android.os.Binder;
27098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeyimport android.os.IBinder;
28098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeyimport android.os.Process;
29098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeyimport android.os.RemoteException;
30098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeyimport android.os.ServiceManager;
3165c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkeyimport android.os.UserHandle;
32cd686b5b6d4166b510df8e32138479a9559bc117John Spurlockimport android.util.Log;
33098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
34098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeyimport com.android.systemui.SystemUI;
35098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
36098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeyimport java.io.FileDescriptor;
37098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeyimport java.io.PrintWriter;
38098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeyimport java.util.HashMap;
39098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
40098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey/**
41098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey * Service that offers to play ringtones by {@link Uri}, since our process has
42098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}.
43098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey */
44098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkeypublic class RingtonePlayer extends SystemUI {
45098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    private static final String TAG = "RingtonePlayer";
46b6e404a95f31f4681265eb53d2b7969f900151ccJeff Sharkey    private static final boolean LOGD = false;
47098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
48098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    // TODO: support Uri switching under same IBinder
49098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
50098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    private IAudioService mAudioService;
51098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
52098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG);
53b8bacccfc159fe204e5b09b52de55f8ba853f713John Spurlock    private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
54098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
55098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    @Override
56098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    public void start() {
57098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        mAsyncPlayer.setUsesWakeLock(mContext);
58098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
59098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        mAudioService = IAudioService.Stub.asInterface(
60098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                ServiceManager.getService(Context.AUDIO_SERVICE));
61098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        try {
62098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            mAudioService.setRingtonePlayer(mCallback);
63098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        } catch (RemoteException e) {
64cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            Log.e(TAG, "Problem registering RingtonePlayer: " + e);
65098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        }
66098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    }
67098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
68098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    /**
69098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey     * Represents an active remote {@link Ringtone} client.
70098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey     */
71098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    private class Client implements IBinder.DeathRecipient {
72098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        private final IBinder mToken;
73098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        private final Ringtone mRingtone;
74098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
7581f871e2b96125d57b76c07169e868e516443794Jean-Michel Trivi        public Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa) {
76098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            mToken = token;
7765c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey
7865c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey            mRingtone = new Ringtone(getContextForUser(user), false);
7981f871e2b96125d57b76c07169e868e516443794Jean-Michel Trivi            mRingtone.setAudioAttributes(aa);
80098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            mRingtone.setUri(uri);
81098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        }
82098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
83098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        @Override
84098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        public void binderDied() {
85cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (LOGD) Log.d(TAG, "binderDied() token=" + mToken);
86098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            synchronized (mClients) {
87098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                mClients.remove(mToken);
88098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            }
89098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            mRingtone.stop();
90098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        }
91098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    }
92098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
93098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {
94098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        @Override
9581f871e2b96125d57b76c07169e868e516443794Jean-Michel Trivi        public void play(IBinder token, Uri uri, AudioAttributes aa) throws RemoteException {
9665c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey            if (LOGD) {
97cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock                Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
9865c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey                        + Binder.getCallingUid() + ")");
9965c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey            }
100098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            Client client;
101098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            synchronized (mClients) {
102098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                client = mClients.get(token);
103098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                if (client == null) {
10465c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey                    final UserHandle user = Binder.getCallingUserHandle();
10581f871e2b96125d57b76c07169e868e516443794Jean-Michel Trivi                    client = new Client(token, uri, user, aa);
106098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                    token.linkToDeath(client, 0);
107098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                    mClients.put(token, client);
108098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                }
109098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            }
110098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            client.mRingtone.play();
111098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        }
112098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
113098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        @Override
114098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        public void stop(IBinder token) {
115cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (LOGD) Log.d(TAG, "stop(token=" + token + ")");
116098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            Client client;
117098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            synchronized (mClients) {
118098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                client = mClients.remove(token);
119098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            }
120098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            if (client != null) {
121098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                client.mToken.unlinkToDeath(client, 0);
122098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                client.mRingtone.stop();
123098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            }
124098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        }
125098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
126098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        @Override
127098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        public boolean isPlaying(IBinder token) {
128cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (LOGD) Log.d(TAG, "isPlaying(token=" + token + ")");
129098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            Client client;
130098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            synchronized (mClients) {
131098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                client = mClients.get(token);
132098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            }
133098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            if (client != null) {
134098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                return client.mRingtone.isPlaying();
135098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            } else {
136098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                return false;
137098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            }
138098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        }
139098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
140098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        @Override
14181f871e2b96125d57b76c07169e868e516443794Jean-Michel Trivi        public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
142cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
143098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
144098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                throw new SecurityException("Async playback only available from system UID.");
145098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            }
14665c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey
14781f871e2b96125d57b76c07169e868e516443794Jean-Michel Trivi            mAsyncPlayer.play(getContextForUser(user), uri, looping, aa);
148098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        }
149098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
150098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        @Override
151098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        public void stopAsync() {
152cd686b5b6d4166b510df8e32138479a9559bc117John Spurlock            if (LOGD) Log.d(TAG, "stopAsync()");
153098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
154098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                throw new SecurityException("Async playback only available from system UID.");
155098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            }
156098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            mAsyncPlayer.stop();
157098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        }
158098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    };
159098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey
16065c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey    private Context getContextForUser(UserHandle user) {
16165c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey        try {
16265c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey            return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
16365c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey        } catch (NameNotFoundException e) {
16465c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey            throw new RuntimeException(e);
16565c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey        }
16665c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey    }
16765c4a2b26cd8776b0927e9b0e07ecf53bd31b627Jeff Sharkey
168098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    @Override
169098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
170098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        pw.println("Clients:");
171098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        synchronized (mClients) {
172098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            for (Client client : mClients.values()) {
173098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                pw.print("  mToken=");
174098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                pw.print(client.mToken);
175098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                pw.print(" mUri=");
176098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey                pw.println(client.mRingtone.getUri());
177098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey            }
178098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey        }
179098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey    }
180098d580cc2bb6c0891c756a4e5230c6c6b0d2376Jeff Sharkey}
181