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.content.Context;
     20 import android.media.AudioManager.AudioPlaybackCallback;
     21 import android.media.AudioPlaybackConfiguration;
     22 import android.media.IAudioService;
     23 import android.media.IPlaybackConfigDispatcher;
     24 import android.os.Binder;
     25 import android.os.RemoteException;
     26 import android.os.UserHandle;
     27 import android.util.IntArray;
     28 import android.util.Log;
     29 import android.util.SparseArray;
     30 
     31 import java.io.PrintWriter;
     32 import java.util.ArrayList;
     33 import java.util.HashSet;
     34 import java.util.HashMap;
     35 import java.util.List;
     36 import java.util.Map;
     37 import java.util.Set;
     38 
     39 /**
     40  * Monitors changes in audio playback, and notify the newly started audio playback through the
     41  * {@link OnAudioPlaybackStartedListener} and the activeness change through the
     42  * {@link OnAudioPlaybackActiveStateListener}.
     43  */
     44 class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub {
     45     private static boolean DEBUG = MediaSessionService.DEBUG;
     46     private static String TAG = "AudioPlaybackMonitor";
     47 
     48     private static AudioPlaybackMonitor sInstance;
     49 
     50     /**
     51      * Called when audio playback is started for a given UID.
     52      */
     53     interface OnAudioPlaybackStartedListener {
     54         void onAudioPlaybackStarted(int uid);
     55     }
     56 
     57     /**
     58      * Called when audio player state is changed.
     59      */
     60     interface OnAudioPlayerActiveStateChangedListener {
     61         void onAudioPlayerActiveStateChanged(int uid, boolean active);
     62     }
     63 
     64     private final Object mLock = new Object();
     65     private final Context mContext;
     66     private final List<OnAudioPlaybackStartedListener> mAudioPlaybackStartedListeners
     67             = new ArrayList<>();
     68     private final List<OnAudioPlayerActiveStateChangedListener>
     69             mAudioPlayerActiveStateChangedListeners = new ArrayList<>();
     70     private final Map<Integer, Integer> mAudioPlaybackStates = new HashMap<>();
     71     private final Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>();
     72 
     73     // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
     74     // The UID whose audio playback becomes active at the last comes first.
     75     // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
     76     private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
     77 
     78     static AudioPlaybackMonitor getInstance(Context context, IAudioService audioService) {
     79         if (sInstance == null) {
     80             sInstance = new AudioPlaybackMonitor(context, audioService);
     81         }
     82         return sInstance;
     83     }
     84 
     85     private AudioPlaybackMonitor(Context context, IAudioService audioService) {
     86         mContext = context;
     87         try {
     88             audioService.registerPlaybackCallback(this);
     89         } catch (RemoteException e) {
     90             Log.wtf(TAG, "Failed to register playback callback", e);
     91         }
     92     }
     93 
     94     /**
     95      * Called when the {@link AudioPlaybackConfiguration} is updated.
     96      * <p>If an app starts audio playback, the app's local media session will be the media button
     97      * session. If the app has multiple media sessions, the playback active local session will be
     98      * picked.
     99      *
    100      * @param configs List of the current audio playback configuration
    101      */
    102     @Override
    103     public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
    104             boolean flush) {
    105         if (flush) {
    106             Binder.flushPendingCommands();
    107         }
    108         final long token = Binder.clearCallingIdentity();
    109         try {
    110             List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>();
    111             List<OnAudioPlayerActiveStateChangedListener> audioPlayerActiveStateChangedListeners;
    112             List<OnAudioPlaybackStartedListener> audioPlaybackStartedListeners;
    113             synchronized (mLock) {
    114                 // Update mActiveAudioPlaybackClientUids and mSortedAudioPlaybackClientUids,
    115                 // and find newly activated audio playbacks.
    116                 mActiveAudioPlaybackClientUids.clear();
    117                 for (AudioPlaybackConfiguration config : configs) {
    118                     // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL
    119                     // (i.e. playback from the SoundPool class which is only for sound effects)
    120                     // playback.
    121                     // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM
    122                     // specific audio/video players.
    123                     if (!config.isActive() || config.getPlayerType()
    124                             == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
    125                         continue;
    126                     }
    127 
    128                     mActiveAudioPlaybackClientUids.add(config.getClientUid());
    129                     Integer oldState = mAudioPlaybackStates.get(config.getPlayerInterfaceId());
    130                     if (!isActiveState(oldState)) {
    131                         if (DEBUG) {
    132                             Log.d(TAG, "Found a new active media playback. " +
    133                                     AudioPlaybackConfiguration.toLogFriendlyString(config));
    134                         }
    135                         // New active audio playback.
    136                         newActiveAudioPlaybackClientUids.add(config.getClientUid());
    137                         int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid());
    138                         if (index == 0) {
    139                             // It's the lastly played music app already. Skip updating.
    140                             continue;
    141                         } else if (index > 0) {
    142                             mSortedAudioPlaybackClientUids.remove(index);
    143                         }
    144                         mSortedAudioPlaybackClientUids.add(0, config.getClientUid());
    145                     }
    146                 }
    147                 audioPlayerActiveStateChangedListeners = new ArrayList<>(
    148                         mAudioPlayerActiveStateChangedListeners);
    149                 audioPlaybackStartedListeners = new ArrayList<>(mAudioPlaybackStartedListeners);
    150             }
    151             // Notify the change of audio playback states.
    152             for (AudioPlaybackConfiguration config : configs) {
    153                 boolean wasActive = isActiveState(
    154                         mAudioPlaybackStates.get(config.getPlayerInterfaceId()));
    155                 boolean isActive = config.isActive();
    156                 if (wasActive != isActive) {
    157                     for (OnAudioPlayerActiveStateChangedListener listener
    158                             : audioPlayerActiveStateChangedListeners) {
    159                         listener.onAudioPlayerActiveStateChanged(config.getClientUid(),
    160                                 isActive);
    161                     }
    162                 }
    163             }
    164             // Notify the start of audio playback
    165             for (int uid : newActiveAudioPlaybackClientUids) {
    166                 for (OnAudioPlaybackStartedListener listener : audioPlaybackStartedListeners) {
    167                     listener.onAudioPlaybackStarted(uid);
    168                 }
    169             }
    170             mAudioPlaybackStates.clear();
    171             for (AudioPlaybackConfiguration config : configs) {
    172                 mAudioPlaybackStates.put(config.getPlayerInterfaceId(), config.getPlayerState());
    173             }
    174         } finally {
    175             Binder.restoreCallingIdentity(token);
    176         }
    177     }
    178 
    179     /**
    180      * Registers OnAudioPlaybackStartedListener.
    181      */
    182     public void registerOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
    183         synchronized (mLock) {
    184             mAudioPlaybackStartedListeners.add(listener);
    185         }
    186     }
    187 
    188     /**
    189      * Unregisters OnAudioPlaybackStartedListener.
    190      */
    191     public void unregisterOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
    192         synchronized (mLock) {
    193             mAudioPlaybackStartedListeners.remove(listener);
    194         }
    195     }
    196 
    197     /**
    198      * Registers OnAudioPlayerActiveStateChangedListener.
    199      */
    200     public void registerOnAudioPlayerActiveStateChangedListener(
    201             OnAudioPlayerActiveStateChangedListener listener) {
    202         synchronized (mLock) {
    203             mAudioPlayerActiveStateChangedListeners.add(listener);
    204         }
    205     }
    206 
    207     /**
    208      * Unregisters OnAudioPlayerActiveStateChangedListener.
    209      */
    210     public void unregisterOnAudioPlayerActiveStateChangedListener(
    211             OnAudioPlayerActiveStateChangedListener listener) {
    212         synchronized (mLock) {
    213             mAudioPlayerActiveStateChangedListeners.remove(listener);
    214         }
    215     }
    216 
    217     /**
    218      * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
    219      * audio/video) The UID whose audio playback becomes active at the last comes first.
    220      */
    221     public IntArray getSortedAudioPlaybackClientUids() {
    222         IntArray sortedAudioPlaybackClientUids = new IntArray();
    223         synchronized (mLock) {
    224             sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
    225         }
    226         return sortedAudioPlaybackClientUids;
    227     }
    228 
    229     /**
    230      * Returns if the audio playback is active for the uid.
    231      */
    232     public boolean isPlaybackActive(int uid) {
    233         synchronized (mLock) {
    234             return mActiveAudioPlaybackClientUids.contains(uid);
    235         }
    236     }
    237 
    238     /**
    239      * Cleans up the sorted list of audio playback client UIDs with given {@param
    240      * mediaButtonSessionUid}.
    241      * <p>UIDs whose audio playback started after the media button session's audio playback
    242      * cannot be the lastly played media app. So they won't needed anymore.
    243      *
    244      * @param mediaButtonSessionUid UID of the media button session.
    245      */
    246     public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
    247         synchronized (mLock) {
    248             int userId = UserHandle.getUserId(mediaButtonSessionUid);
    249             for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
    250                 if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
    251                     break;
    252                 }
    253                 int uid = mSortedAudioPlaybackClientUids.get(i);
    254                 if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
    255                     // Clean up unnecessary UIDs.
    256                     // It doesn't need to be managed profile aware because it's just to prevent
    257                     // the list from increasing indefinitely. The media button session updating
    258                     // shouldn't be affected by cleaning up.
    259                     mSortedAudioPlaybackClientUids.remove(i);
    260                 }
    261             }
    262         }
    263     }
    264 
    265     /**
    266      * Dumps {@link AudioPlaybackMonitor}.
    267      */
    268     public void dump(PrintWriter pw, String prefix) {
    269         synchronized (mLock) {
    270             pw.println(prefix + "Audio playback (lastly played comes first)");
    271             String indent = prefix + "  ";
    272             for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
    273                 int uid = mSortedAudioPlaybackClientUids.get(i);
    274                 pw.print(indent + "uid=" + uid + " packages=");
    275                 String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
    276                 if (packages != null && packages.length > 0) {
    277                     for (int j = 0; j < packages.length; j++) {
    278                         pw.print(packages[j] + " ");
    279                     }
    280                 }
    281                 pw.println();
    282             }
    283         }
    284     }
    285 
    286     private boolean isActiveState(Integer state) {
    287         return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
    288     }
    289 }
    290