Home | History | Annotate | Download | only in media
      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.IAudioService;
     22 import android.media.IRingtonePlayer;
     23 import android.media.Ringtone;
     24 import android.net.Uri;
     25 import android.os.Binder;
     26 import android.os.IBinder;
     27 import android.os.Process;
     28 import android.os.RemoteException;
     29 import android.os.ServiceManager;
     30 import android.os.UserHandle;
     31 import android.util.Slog;
     32 
     33 import com.android.systemui.SystemUI;
     34 import com.google.android.collect.Maps;
     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 = Maps.newHashMap();
     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             Slog.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, int streamType) {
     76             mToken = token;
     77 
     78             mRingtone = new Ringtone(getContextForUser(user), false);
     79             mRingtone.setStreamType(streamType);
     80             mRingtone.setUri(uri);
     81         }
     82 
     83         @Override
     84         public void binderDied() {
     85             if (LOGD) Slog.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, int streamType) throws RemoteException {
     96             if (LOGD) {
     97                 Slog.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, streamType);
    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) Slog.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) Slog.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, int streamType) {
    142             if (LOGD) Slog.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, streamType);
    148         }
    149 
    150         @Override
    151         public void stopAsync() {
    152             if (LOGD) Slog.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