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.List;
     35 import java.util.Set;
     36 
     37 /**
     38  * Monitors changes in audio playback and notify the newly started audio playback through the
     39  * {@link OnAudioPlaybackStartedListener}.
     40  */
     41 class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub {
     42     private static boolean DEBUG = MediaSessionService.DEBUG;
     43     private static String TAG = "AudioPlaybackMonitor";
     44 
     45     /**
     46      * Called when audio playback is started for a given UID.
     47      */
     48     interface OnAudioPlaybackStartedListener {
     49         void onAudioPlaybackStarted(int uid);
     50     }
     51 
     52     private final Object mLock = new Object();
     53     private final Context mContext;
     54     private final OnAudioPlaybackStartedListener mListener;
     55 
     56     private Set<Integer> mActiveAudioPlaybackPlayerInterfaceIds = new HashSet<>();
     57     private Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>();
     58 
     59     // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
     60     // The UID whose audio playback becomes active at the last comes first.
     61     // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
     62     private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
     63 
     64     AudioPlaybackMonitor(Context context, IAudioService audioService,
     65             OnAudioPlaybackStartedListener listener) {
     66         mContext = context;
     67         mListener = listener;
     68         try {
     69             audioService.registerPlaybackCallback(this);
     70         } catch (RemoteException e) {
     71             Log.wtf(TAG, "Failed to register playback callback", e);
     72         }
     73     }
     74 
     75     /**
     76      * Called when the {@link AudioPlaybackConfiguration} is updated.
     77      * <p>If an app starts audio playback, the app's local media session will be the media button
     78      * session. If the app has multiple media sessions, the playback active local session will be
     79      * picked.
     80      *
     81      * @param configs List of the current audio playback configuration
     82      */
     83     @Override
     84     public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
     85         final long token = Binder.clearCallingIdentity();
     86         try {
     87             Set<Integer> newActiveAudioPlaybackPlayerInterfaceIds = new HashSet<>();
     88             List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>();
     89             synchronized (mLock) {
     90                 mActiveAudioPlaybackClientUids.clear();
     91                 for (AudioPlaybackConfiguration config : configs) {
     92                     // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL
     93                     // (i.e. playback from the SoundPool class which is only for sound effects)
     94                     // playback.
     95                     // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM
     96                     // specific audio/video players.
     97                     if (!config.isActive()
     98                             || config.getPlayerType()
     99                             == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
    100                         continue;
    101                     }
    102                     mActiveAudioPlaybackClientUids.add(config.getClientUid());
    103 
    104                     newActiveAudioPlaybackPlayerInterfaceIds.add(config.getPlayerInterfaceId());
    105                     if (!mActiveAudioPlaybackPlayerInterfaceIds.contains(
    106                             config.getPlayerInterfaceId())) {
    107                         if (DEBUG) {
    108                             Log.d(TAG, "Found a new active media playback. " +
    109                                     AudioPlaybackConfiguration.toLogFriendlyString(config));
    110                         }
    111                         // New active audio playback.
    112                         newActiveAudioPlaybackClientUids.add(config.getClientUid());
    113                         int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid());
    114                         if (index == 0) {
    115                             // It's the lastly played music app already. Skip updating.
    116                             continue;
    117                         } else if (index > 0) {
    118                             mSortedAudioPlaybackClientUids.remove(index);
    119                         }
    120                         mSortedAudioPlaybackClientUids.add(0, config.getClientUid());
    121                     }
    122                 }
    123                 mActiveAudioPlaybackPlayerInterfaceIds.clear();
    124                 mActiveAudioPlaybackPlayerInterfaceIds = newActiveAudioPlaybackPlayerInterfaceIds;
    125             }
    126             for (int uid : newActiveAudioPlaybackClientUids) {
    127                 mListener.onAudioPlaybackStarted(uid);
    128             }
    129         } finally {
    130             Binder.restoreCallingIdentity(token);
    131         }
    132     }
    133 
    134     /**
    135      * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
    136      * audio/video) The UID whose audio playback becomes active at the last comes first.
    137      */
    138     public IntArray getSortedAudioPlaybackClientUids() {
    139         IntArray sortedAudioPlaybackClientUids = new IntArray();
    140         synchronized (mLock) {
    141             sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
    142         }
    143         return sortedAudioPlaybackClientUids;
    144     }
    145 
    146     /**
    147      * Returns if the audio playback is active for the uid.
    148      */
    149     public boolean isPlaybackActive(int uid) {
    150         synchronized (mLock) {
    151             return mActiveAudioPlaybackClientUids.contains(uid);
    152         }
    153     }
    154 
    155     /**
    156      * Cleans up the sorted list of audio playback client UIDs with given {@param
    157      * mediaButtonSessionUid}.
    158      * <p>UIDs whose audio playback started after the media button session's audio playback
    159      * cannot be the lastly played media app. So they won't needed anymore.
    160      *
    161      * @param mediaButtonSessionUid UID of the media button session.
    162      */
    163     public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
    164         synchronized (mLock) {
    165             int userId = UserHandle.getUserId(mediaButtonSessionUid);
    166             for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
    167                 if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
    168                     break;
    169                 }
    170                 if (userId == UserHandle.getUserId(mSortedAudioPlaybackClientUids.get(i))) {
    171                     // Clean up unnecessary UIDs.
    172                     // It doesn't need to be managed profile aware because it's just to prevent
    173                     // the list from increasing indefinitely. The media button session updating
    174                     // shouldn't be affected by cleaning up.
    175                     mSortedAudioPlaybackClientUids.remove(i);
    176                 }
    177             }
    178         }
    179     }
    180 
    181     /**
    182      * Dumps {@link AudioPlaybackMonitor}.
    183      */
    184     public void dump(PrintWriter pw, String prefix) {
    185         synchronized (mLock) {
    186             pw.println(prefix + "Audio playback (lastly played comes first)");
    187             String indent = prefix + "  ";
    188             for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
    189                 int uid = mSortedAudioPlaybackClientUids.get(i);
    190                 pw.print(indent + "uid=" + uid + " packages=");
    191                 String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
    192                 if (packages != null && packages.length > 0) {
    193                     for (int j = 0; j < packages.length; j++) {
    194                         pw.print(packages[j] + " ");
    195                     }
    196                 }
    197                 pw.println();
    198             }
    199         }
    200     }
    201 }
    202