Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2017 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.server.media;
     18 
     19 import android.annotation.NonNull;
     20 import android.content.Context;
     21 import android.media.AudioPlaybackConfiguration;
     22 import android.media.IAudioService;
     23 import android.media.IPlaybackConfigDispatcher;
     24 import android.os.Binder;
     25 import android.os.Handler;
     26 import android.os.Looper;
     27 import android.os.Message;
     28 import android.os.RemoteException;
     29 import android.os.UserHandle;
     30 import android.util.ArrayMap;
     31 import android.util.ArraySet;
     32 import android.util.IntArray;
     33 import android.util.Log;
     34 
     35 import com.android.internal.annotations.GuardedBy;
     36 
     37 import java.io.PrintWriter;
     38 import java.util.List;
     39 import java.util.Map;
     40 import java.util.Set;
     41 
     42 /**
     43  * Monitors the state changes of audio players.
     44  */
     45 class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub {
     46     private static boolean DEBUG = MediaSessionService.DEBUG;
     47     private static String TAG = "AudioPlayerStateMonitor";
     48 
     49     private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor();
     50 
     51     /**
     52      * Listener for handling the active state changes of audio players.
     53      */
     54     interface OnAudioPlayerActiveStateChangedListener {
     55         /**
     56          * Called when the active state of audio player is changed.
     57          *
     58          * @param config The audio playback configuration for the audio player for which active
     59          *              state was changed. If {@param isRemoved} is {@code true}, this holds
     60          *              outdated information.
     61          * @param isRemoved {@code true} if the audio player is removed.
     62          */
     63         void onAudioPlayerActiveStateChanged(
     64                 @NonNull AudioPlaybackConfiguration config, boolean isRemoved);
     65     }
     66 
     67     private final static class MessageHandler extends Handler {
     68         private static final int MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED = 1;
     69 
     70         private final OnAudioPlayerActiveStateChangedListener mListener;
     71 
     72         MessageHandler(Looper looper, OnAudioPlayerActiveStateChangedListener listener) {
     73             super(looper);
     74             mListener = listener;
     75         }
     76 
     77         @Override
     78         public void handleMessage(Message msg) {
     79             switch (msg.what) {
     80                 case MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED:
     81                     mListener.onAudioPlayerActiveStateChanged((AudioPlaybackConfiguration) msg.obj,
     82                             msg.arg1 != 0);
     83                     break;
     84             }
     85         }
     86 
     87         void sendAudioPlayerActiveStateChangedMessage(
     88                 final AudioPlaybackConfiguration config, final boolean isRemoved) {
     89             obtainMessage(MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED,
     90                     isRemoved ? 1 : 0, 0 /* unused */, config).sendToTarget();
     91         }
     92     }
     93 
     94     private final Object mLock = new Object();
     95     @GuardedBy("mLock")
     96     private final Map<OnAudioPlayerActiveStateChangedListener, MessageHandler> mListenerMap =
     97             new ArrayMap<>();
     98     @GuardedBy("mLock")
     99     private final Set<Integer> mActiveAudioUids = new ArraySet<>();
    100     @GuardedBy("mLock")
    101     private ArrayMap<Integer, AudioPlaybackConfiguration> mPrevActiveAudioPlaybackConfigs =
    102             new ArrayMap<>();
    103     // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
    104     // The UID whose audio playback becomes active at the last comes first.
    105     // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
    106     @GuardedBy("mLock")
    107     private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
    108 
    109     @GuardedBy("mLock")
    110     private boolean mRegisteredToAudioService;
    111 
    112     static AudioPlayerStateMonitor getInstance() {
    113         return sInstance;
    114     }
    115 
    116     private AudioPlayerStateMonitor() {
    117     }
    118 
    119     /**
    120      * Called when the {@link AudioPlaybackConfiguration} is updated.
    121      * <p>If an app starts audio playback, the app's local media session will be the media button
    122      * session. If the app has multiple media sessions, the playback active local session will be
    123      * picked.
    124      *
    125      * @param configs List of the current audio playback configuration
    126      */
    127     @Override
    128     public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
    129             boolean flush) {
    130         if (flush) {
    131             Binder.flushPendingCommands();
    132         }
    133         final long token = Binder.clearCallingIdentity();
    134         try {
    135             synchronized (mLock) {
    136                 // Update mActiveAudioUids
    137                 mActiveAudioUids.clear();
    138                 ArrayMap<Integer, AudioPlaybackConfiguration> activeAudioPlaybackConfigs =
    139                         new ArrayMap<>();
    140                 for (AudioPlaybackConfiguration config : configs) {
    141                     if (config.isActive()) {
    142                         mActiveAudioUids.add(config.getClientUid());
    143                         activeAudioPlaybackConfigs.put(config.getPlayerInterfaceId(), config);
    144                     }
    145                 }
    146 
    147                 // Update mSortedAuioPlaybackClientUids.
    148                 for (int i = 0; i < activeAudioPlaybackConfigs.size(); ++i) {
    149                     AudioPlaybackConfiguration config = activeAudioPlaybackConfigs.valueAt(i);
    150                     final int uid = config.getClientUid();
    151                     if (!mPrevActiveAudioPlaybackConfigs.containsKey(
    152                             config.getPlayerInterfaceId())) {
    153                         if (DEBUG) {
    154                             Log.d(TAG, "Found a new active media playback. " +
    155                                     AudioPlaybackConfiguration.toLogFriendlyString(config));
    156                         }
    157                         // New active audio playback.
    158                         int index = mSortedAudioPlaybackClientUids.indexOf(uid);
    159                         if (index == 0) {
    160                             // It's the lastly played music app already. Skip updating.
    161                             continue;
    162                         } else if (index > 0) {
    163                             mSortedAudioPlaybackClientUids.remove(index);
    164                         }
    165                         mSortedAudioPlaybackClientUids.add(0, uid);
    166                     }
    167                 }
    168                 // Notify the active state change of audio players.
    169                 for (AudioPlaybackConfiguration config : configs) {
    170                     final int pii = config.getPlayerInterfaceId();
    171                     boolean wasActive = mPrevActiveAudioPlaybackConfigs.remove(pii) != null;
    172                     if (wasActive != config.isActive()) {
    173                         sendAudioPlayerActiveStateChangedMessageLocked(
    174                                 config, /* isRemoved */ false);
    175                     }
    176                 }
    177                 for (AudioPlaybackConfiguration config : mPrevActiveAudioPlaybackConfigs.values()) {
    178                     sendAudioPlayerActiveStateChangedMessageLocked(config, /* isRemoved */ true);
    179                 }
    180 
    181                 // Update mPrevActiveAudioPlaybackConfigs
    182                 mPrevActiveAudioPlaybackConfigs = activeAudioPlaybackConfigs;
    183             }
    184         } finally {
    185             Binder.restoreCallingIdentity(token);
    186         }
    187     }
    188 
    189     /**
    190      * Registers OnAudioPlayerActiveStateChangedListener.
    191      */
    192     public void registerListener(
    193             OnAudioPlayerActiveStateChangedListener listener, Handler handler) {
    194         synchronized (mLock) {
    195             mListenerMap.put(listener, new MessageHandler((handler == null) ?
    196                     Looper.myLooper() : handler.getLooper(), listener));
    197         }
    198     }
    199 
    200     /**
    201      * Unregisters OnAudioPlayerActiveStateChangedListener.
    202      */
    203     public void unregisterListener(OnAudioPlayerActiveStateChangedListener listener) {
    204         synchronized (mLock) {
    205             mListenerMap.remove(listener);
    206         }
    207     }
    208 
    209     /**
    210      * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
    211      * audio/video) The UID whose audio playback becomes active at the last comes first.
    212      */
    213     public IntArray getSortedAudioPlaybackClientUids() {
    214         IntArray sortedAudioPlaybackClientUids = new IntArray();
    215         synchronized (mLock) {
    216             sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
    217         }
    218         return sortedAudioPlaybackClientUids;
    219     }
    220 
    221     /**
    222      * Returns if the audio playback is active for the uid.
    223      */
    224     public boolean isPlaybackActive(int uid) {
    225         synchronized (mLock) {
    226             return mActiveAudioUids.contains(uid);
    227         }
    228     }
    229 
    230     /**
    231      * Cleans up the sorted list of audio playback client UIDs with given {@param
    232      * mediaButtonSessionUid}.
    233      * <p>UIDs whose audio playback are inactive and have started before the media button session's
    234      * audio playback cannot be the lastly played media app. So they won't needed anymore.
    235      *
    236      * @param mediaButtonSessionUid UID of the media button session.
    237      */
    238     public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
    239         synchronized (mLock) {
    240             int userId = UserHandle.getUserId(mediaButtonSessionUid);
    241             for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
    242                 if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
    243                     break;
    244                 }
    245                 int uid = mSortedAudioPlaybackClientUids.get(i);
    246                 if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
    247                     // Clean up unnecessary UIDs.
    248                     // It doesn't need to be managed profile aware because it's just to prevent
    249                     // the list from increasing indefinitely. The media button session updating
    250                     // shouldn't be affected by cleaning up.
    251                     mSortedAudioPlaybackClientUids.remove(i);
    252                 }
    253             }
    254         }
    255     }
    256 
    257     /**
    258      * Dumps {@link AudioPlayerStateMonitor}.
    259      */
    260     public void dump(Context context, PrintWriter pw, String prefix) {
    261         synchronized (mLock) {
    262             pw.println(prefix + "Audio playback (lastly played comes first)");
    263             String indent = prefix + "  ";
    264             for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
    265                 int uid = mSortedAudioPlaybackClientUids.get(i);
    266                 pw.print(indent + "uid=" + uid + " packages=");
    267                 String[] packages = context.getPackageManager().getPackagesForUid(uid);
    268                 if (packages != null && packages.length > 0) {
    269                     for (int j = 0; j < packages.length; j++) {
    270                         pw.print(packages[j] + " ");
    271                     }
    272                 }
    273                 pw.println();
    274             }
    275         }
    276     }
    277 
    278     public void registerSelfIntoAudioServiceIfNeeded(IAudioService audioService) {
    279         synchronized (mLock) {
    280             try {
    281                 if (!mRegisteredToAudioService) {
    282                     audioService.registerPlaybackCallback(this);
    283                     mRegisteredToAudioService = true;
    284                 }
    285             } catch (RemoteException e) {
    286                 Log.wtf(TAG, "Failed to register playback callback", e);
    287                 mRegisteredToAudioService = false;
    288             }
    289         }
    290     }
    291 
    292     @GuardedBy("mLock")
    293     private void sendAudioPlayerActiveStateChangedMessageLocked(
    294             final AudioPlaybackConfiguration config, final boolean isRemoved) {
    295         for (MessageHandler messageHandler : mListenerMap.values()) {
    296             messageHandler.sendAudioPlayerActiveStateChangedMessage(config, isRemoved);
    297         }
    298     }
    299 }
    300