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