Home | History | Annotate | Download | only in volume
      1 /*
      2  * Copyright (C) 2015 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.volume;
     18 
     19 import android.app.PendingIntent;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.pm.ApplicationInfo;
     23 import android.content.pm.PackageManager;
     24 import android.content.pm.PackageManager.NameNotFoundException;
     25 import android.content.pm.ResolveInfo;
     26 import android.media.IRemoteVolumeController;
     27 import android.media.MediaMetadata;
     28 import android.media.session.ISessionController;
     29 import android.media.session.MediaController;
     30 import android.media.session.MediaController.PlaybackInfo;
     31 import android.media.session.MediaSession.QueueItem;
     32 import android.media.session.MediaSession.Token;
     33 import android.media.session.MediaSessionManager;
     34 import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
     35 import android.media.session.PlaybackState;
     36 import android.os.Bundle;
     37 import android.os.Handler;
     38 import android.os.Looper;
     39 import android.os.Message;
     40 import android.os.RemoteException;
     41 import android.util.Log;
     42 
     43 import java.io.PrintWriter;
     44 import java.io.StringWriter;
     45 import java.util.HashMap;
     46 import java.util.HashSet;
     47 import java.util.List;
     48 import java.util.Map;
     49 import java.util.Objects;
     50 import java.util.Set;
     51 
     52 /**
     53  * Convenience client for all media session updates.  Provides a callback interface for events
     54  * related to remote media sessions.
     55  */
     56 public class MediaSessions {
     57     private static final String TAG = Util.logTag(MediaSessions.class);
     58 
     59     private static final boolean USE_SERVICE_LABEL = false;
     60 
     61     private final Context mContext;
     62     private final H mHandler;
     63     private final MediaSessionManager mMgr;
     64     private final Map<Token, MediaControllerRecord> mRecords = new HashMap<>();
     65     private final Callbacks mCallbacks;
     66 
     67     private boolean mInit;
     68 
     69     public MediaSessions(Context context, Looper looper, Callbacks callbacks) {
     70         mContext = context;
     71         mHandler = new H(looper);
     72         mMgr = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
     73         mCallbacks = callbacks;
     74     }
     75 
     76     public void dump(PrintWriter writer) {
     77         writer.println(getClass().getSimpleName() + " state:");
     78         writer.print("  mInit: "); writer.println(mInit);
     79         writer.print("  mRecords.size: "); writer.println(mRecords.size());
     80         int i = 0;
     81         for (MediaControllerRecord r : mRecords.values()) {
     82             dump(++i, writer, r.controller);
     83         }
     84     }
     85 
     86     public void init() {
     87         if (D.BUG) Log.d(TAG, "init");
     88         // will throw if no permission
     89         mMgr.addOnActiveSessionsChangedListener(mSessionsListener, null, mHandler);
     90         mInit = true;
     91         postUpdateSessions();
     92         mMgr.setRemoteVolumeController(mRvc);
     93     }
     94 
     95     protected void postUpdateSessions() {
     96         if (!mInit) return;
     97         mHandler.sendEmptyMessage(H.UPDATE_SESSIONS);
     98     }
     99 
    100     public void destroy() {
    101         if (D.BUG) Log.d(TAG, "destroy");
    102         mInit = false;
    103         mMgr.removeOnActiveSessionsChangedListener(mSessionsListener);
    104     }
    105 
    106     public void setVolume(Token token, int level) {
    107         final MediaControllerRecord r = mRecords.get(token);
    108         if (r == null) {
    109             Log.w(TAG, "setVolume: No record found for token " + token);
    110             return;
    111         }
    112         if (D.BUG) Log.d(TAG, "Setting level to " + level);
    113         r.controller.setVolumeTo(level, 0);
    114     }
    115 
    116     private void onRemoteVolumeChangedH(ISessionController session, int flags) {
    117         final MediaController controller = new MediaController(mContext, session);
    118         if (D.BUG) Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " "
    119                 + Util.audioManagerFlagsToString(flags));
    120         final Token token = controller.getSessionToken();
    121         mCallbacks.onRemoteVolumeChanged(token, flags);
    122     }
    123 
    124     private void onUpdateRemoteControllerH(ISessionController session) {
    125         final MediaController controller = session != null ? new MediaController(mContext, session)
    126                 : null;
    127         final String pkg = controller != null ? controller.getPackageName() : null;
    128         if (D.BUG) Log.d(TAG, "updateRemoteControllerH " + pkg);
    129         // this may be our only indication that a remote session is changed, refresh
    130         postUpdateSessions();
    131     }
    132 
    133     protected void onActiveSessionsUpdatedH(List<MediaController> controllers) {
    134         if (D.BUG) Log.d(TAG, "onActiveSessionsUpdatedH n=" + controllers.size());
    135         final Set<Token> toRemove = new HashSet<Token>(mRecords.keySet());
    136         for (MediaController controller : controllers) {
    137             final Token token = controller.getSessionToken();
    138             final PlaybackInfo pi = controller.getPlaybackInfo();
    139             toRemove.remove(token);
    140             if (!mRecords.containsKey(token)) {
    141                 final MediaControllerRecord r = new MediaControllerRecord(controller);
    142                 r.name = getControllerName(controller);
    143                 mRecords.put(token, r);
    144                 controller.registerCallback(r, mHandler);
    145             }
    146             final MediaControllerRecord r = mRecords.get(token);
    147             final boolean remote = isRemote(pi);
    148             if (remote) {
    149                 updateRemoteH(token, r.name, pi);
    150                 r.sentRemote = true;
    151             }
    152         }
    153         for (Token t : toRemove) {
    154             final MediaControllerRecord r = mRecords.get(t);
    155             r.controller.unregisterCallback(r);
    156             mRecords.remove(t);
    157             if (D.BUG) Log.d(TAG, "Removing " + r.name + " sentRemote=" + r.sentRemote);
    158             if (r.sentRemote) {
    159                 mCallbacks.onRemoteRemoved(t);
    160                 r.sentRemote = false;
    161             }
    162         }
    163     }
    164 
    165     private static boolean isRemote(PlaybackInfo pi) {
    166         return pi != null && pi.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
    167     }
    168 
    169     protected String getControllerName(MediaController controller) {
    170         final PackageManager pm = mContext.getPackageManager();
    171         final String pkg = controller.getPackageName();
    172         try {
    173             if (USE_SERVICE_LABEL) {
    174                 final List<ResolveInfo> ris = pm.queryIntentServices(
    175                         new Intent("android.media.MediaRouteProviderService").setPackage(pkg), 0);
    176                 if (ris != null) {
    177                     for (ResolveInfo ri : ris) {
    178                         if (ri.serviceInfo == null) continue;
    179                         if (pkg.equals(ri.serviceInfo.packageName)) {
    180                             final String serviceLabel =
    181                                     Objects.toString(ri.serviceInfo.loadLabel(pm), "").trim();
    182                             if (serviceLabel.length() > 0) {
    183                                 return serviceLabel;
    184                             }
    185                         }
    186                     }
    187                 }
    188             }
    189             final ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
    190             final String appLabel = Objects.toString(ai.loadLabel(pm), "").trim();
    191             if (appLabel.length() > 0) {
    192                 return appLabel;
    193             }
    194         } catch (NameNotFoundException e) { }
    195         return pkg;
    196     }
    197 
    198     private void updateRemoteH(Token token, String name, PlaybackInfo pi) {
    199         if (mCallbacks != null) {
    200             mCallbacks.onRemoteUpdate(token, name, pi);
    201         }
    202     }
    203 
    204     private static void dump(int n, PrintWriter writer, MediaController c) {
    205         writer.println("  Controller " + n + ": " + c.getPackageName());
    206         final Bundle extras = c.getExtras();
    207         final long flags = c.getFlags();
    208         final MediaMetadata mm = c.getMetadata();
    209         final PlaybackInfo pi = c.getPlaybackInfo();
    210         final PlaybackState playbackState = c.getPlaybackState();
    211         final List<QueueItem> queue = c.getQueue();
    212         final CharSequence queueTitle = c.getQueueTitle();
    213         final int ratingType = c.getRatingType();
    214         final PendingIntent sessionActivity = c.getSessionActivity();
    215 
    216         writer.println("    PlaybackState: " + Util.playbackStateToString(playbackState));
    217         writer.println("    PlaybackInfo: " + Util.playbackInfoToString(pi));
    218         if (mm != null) {
    219             writer.println("  MediaMetadata.desc=" + mm.getDescription());
    220         }
    221         writer.println("    RatingType: " + ratingType);
    222         writer.println("    Flags: " + flags);
    223         if (extras != null) {
    224             writer.println("    Extras:");
    225             for (String key : extras.keySet()) {
    226                 writer.println("      " + key + "=" + extras.get(key));
    227             }
    228         }
    229         if (queueTitle != null) {
    230             writer.println("    QueueTitle: " + queueTitle);
    231         }
    232         if (queue != null && !queue.isEmpty()) {
    233             writer.println("    Queue:");
    234             for (QueueItem qi : queue) {
    235                 writer.println("      " + qi);
    236             }
    237         }
    238         if (pi != null) {
    239             writer.println("    sessionActivity: " + sessionActivity);
    240         }
    241     }
    242 
    243     public static void dumpMediaSessions(Context context) {
    244         final MediaSessionManager mgr = (MediaSessionManager) context
    245                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
    246         try {
    247             final List<MediaController> controllers = mgr.getActiveSessions(null);
    248             final int N = controllers.size();
    249             if (D.BUG) Log.d(TAG, N + " controllers");
    250             for (int i = 0; i < N; i++) {
    251                 final StringWriter sw = new StringWriter();
    252                 final PrintWriter pw = new PrintWriter(sw, true);
    253                 dump(i + 1, pw, controllers.get(i));
    254                 if (D.BUG) Log.d(TAG, sw.toString());
    255             }
    256         } catch (SecurityException e) {
    257             Log.w(TAG, "Not allowed to get sessions", e);
    258         }
    259     }
    260 
    261     private final class MediaControllerRecord extends MediaController.Callback {
    262         private final MediaController controller;
    263 
    264         private boolean sentRemote;
    265         private String name;
    266 
    267         private MediaControllerRecord(MediaController controller) {
    268             this.controller = controller;
    269         }
    270 
    271         private String cb(String method) {
    272             return method + " " + controller.getPackageName() + " ";
    273         }
    274 
    275         @Override
    276         public void onAudioInfoChanged(PlaybackInfo info) {
    277             if (D.BUG) Log.d(TAG, cb("onAudioInfoChanged") + Util.playbackInfoToString(info)
    278                     + " sentRemote=" + sentRemote);
    279             final boolean remote = isRemote(info);
    280             if (!remote && sentRemote) {
    281                 mCallbacks.onRemoteRemoved(controller.getSessionToken());
    282                 sentRemote = false;
    283             } else if (remote) {
    284                 updateRemoteH(controller.getSessionToken(), name, info);
    285                 sentRemote = true;
    286             }
    287         }
    288 
    289         @Override
    290         public void onExtrasChanged(Bundle extras) {
    291             if (D.BUG) Log.d(TAG, cb("onExtrasChanged") + extras);
    292         }
    293 
    294         @Override
    295         public void onMetadataChanged(MediaMetadata metadata) {
    296             if (D.BUG) Log.d(TAG, cb("onMetadataChanged") + Util.mediaMetadataToString(metadata));
    297         }
    298 
    299         @Override
    300         public void onPlaybackStateChanged(PlaybackState state) {
    301             if (D.BUG) Log.d(TAG, cb("onPlaybackStateChanged") + Util.playbackStateToString(state));
    302         }
    303 
    304         @Override
    305         public void onQueueChanged(List<QueueItem> queue) {
    306             if (D.BUG) Log.d(TAG, cb("onQueueChanged") + queue);
    307         }
    308 
    309         @Override
    310         public void onQueueTitleChanged(CharSequence title) {
    311             if (D.BUG) Log.d(TAG, cb("onQueueTitleChanged") + title);
    312         }
    313 
    314         @Override
    315         public void onSessionDestroyed() {
    316             if (D.BUG) Log.d(TAG, cb("onSessionDestroyed"));
    317         }
    318 
    319         @Override
    320         public void onSessionEvent(String event, Bundle extras) {
    321             if (D.BUG) Log.d(TAG, cb("onSessionEvent") + "event=" + event + " extras=" + extras);
    322         }
    323     }
    324 
    325     private final OnActiveSessionsChangedListener mSessionsListener =
    326             new OnActiveSessionsChangedListener() {
    327         @Override
    328         public void onActiveSessionsChanged(List<MediaController> controllers) {
    329             onActiveSessionsUpdatedH(controllers);
    330         }
    331     };
    332 
    333     private final IRemoteVolumeController mRvc = new IRemoteVolumeController.Stub() {
    334         @Override
    335         public void remoteVolumeChanged(ISessionController session, int flags)
    336                 throws RemoteException {
    337             mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0, session).sendToTarget();
    338         }
    339 
    340         @Override
    341         public void updateRemoteController(final ISessionController session)
    342                 throws RemoteException {
    343             mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, session).sendToTarget();
    344         }
    345     };
    346 
    347     private final class H extends Handler {
    348         private static final int UPDATE_SESSIONS = 1;
    349         private static final int REMOTE_VOLUME_CHANGED = 2;
    350         private static final int UPDATE_REMOTE_CONTROLLER = 3;
    351 
    352         private H(Looper looper) {
    353             super(looper);
    354         }
    355 
    356         @Override
    357         public void handleMessage(Message msg) {
    358             switch (msg.what) {
    359                 case UPDATE_SESSIONS:
    360                     onActiveSessionsUpdatedH(mMgr.getActiveSessions(null));
    361                     break;
    362                 case REMOTE_VOLUME_CHANGED:
    363                     onRemoteVolumeChangedH((ISessionController) msg.obj, msg.arg1);
    364                     break;
    365                 case UPDATE_REMOTE_CONTROLLER:
    366                     onUpdateRemoteControllerH((ISessionController) msg.obj);
    367                     break;
    368             }
    369         }
    370     }
    371 
    372     public interface Callbacks {
    373         void onRemoteUpdate(Token token, String name, PlaybackInfo pi);
    374         void onRemoteRemoved(Token t);
    375         void onRemoteVolumeChanged(Token token, int flags);
    376     }
    377 
    378 }
    379