Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 21a73c94 authored by Shunkai Yao's avatar Shunkai Yao Committed by Automerger Merge Worker
Browse files

Merge changes from topic "MusicFx_prostate" into main am: a35f3f45 am: 422e3933

parents 78fbd7d1 422e3933
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -13332,6 +13332,11 @@ public class AudioService extends IAudioService.Stub
        return mDeviceBroker.getDeviceSensorUuid(device);
    }
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    MusicFxHelper getMusicFxHelper() {
        return mMusicFxHelper;
    }
    //======================
    // misc
    //======================
+240 −126
Original line number Diff line number Diff line
@@ -17,9 +17,11 @@
package com.android.server.audio;

import static android.content.pm.PackageManager.MATCH_ANY_USER;

import static com.android.server.audio.AudioService.MUSICFX_HELPER_MSG_START;

import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.IUidObserver;
import android.app.UidObserver;
@@ -48,7 +50,6 @@ import java.util.List;

/**
 * MusicFx management.
 * .
 */
public class MusicFxHelper {
    private static final String TAG = "AS.MusicFxHelper";
@@ -60,14 +61,113 @@ public class MusicFxHelper {
    // Synchronization UidSessionMap access between UidObserver and AudioServiceBroadcastReceiver.
    private final Object mClientUidMapLock = new Object();

    private final String mPackageName = this.getClass().getPackage().getName();

    private final String mMusicFxPackageName = "com.android.musicfx";

    /*package*/ static final int MSG_EFFECT_CLIENT_GONE = MUSICFX_HELPER_MSG_START + 1;

    // The binder token identifying the UidObserver registration.
    private IBinder mUidObserverToken = null;

    // Package name and list of open audio sessions for this package
    private static class PackageSessions {
        String mPackageName;
        List<Integer> mSessions;
    }

    /*
     * Override of SparseArray class to add bind/unbind and UID observer in the put/remove methods.
     *
     * put:
     *  - the first key/value set put into MySparseArray will trigger a procState bump (bindService)
     *  - if no valid observer token exist, will call registerUidObserver for put
     *  - for each new uid put into array, it will be added to uid observer list
     *
     * remove:
     *  - for each uid removed from array, it will be removed from uid observer list as well
     *  - if it's the last uid in array, no more MusicFx procState bump (unbindService), uid
     *    observer will also be removed, and observer token reset to null
     */
    private class MySparseArray extends SparseArray<PackageSessions> {
        private final String mMusicFxPackageName = "com.android.musicfx";

        @RequiresPermission(anyOf = {
                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
                android.Manifest.permission.INTERACT_ACROSS_USERS,
                android.Manifest.permission.INTERACT_ACROSS_PROFILES
        })
        @Override
        public void put(int uid, PackageSessions pkgSessions) {
            if (size() == 0) {
                int procState = ActivityManager.PROCESS_STATE_NONEXISTENT;
                try {
                    procState = ActivityManager.getService().getPackageProcessState(
                            mMusicFxPackageName, mPackageName);
                } catch (RemoteException e) {
                    Log.e(TAG, "RemoteException with getPackageProcessState: " + e);
                }
                if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
                    Intent bindIntent = new Intent().setClassName(mMusicFxPackageName,
                            "com.android.musicfx.KeepAliveService");
                    mContext.bindServiceAsUser(
                            bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE,
                            UserHandle.of(getCurrentUserId()));
                    Log.i(TAG, "bindService to " + mMusicFxPackageName);
                }

                Log.i(TAG, mMusicFxPackageName + " procState " + procState);
            }
            try {
                if (mUidObserverToken == null) {
                    mUidObserverToken = ActivityManager.getService().registerUidObserverForUids(
                            mEffectUidObserver, ActivityManager.UID_OBSERVER_GONE,
                            ActivityManager.PROCESS_STATE_UNKNOWN, mPackageName,
                            new int[]{uid});
                    Log.i(TAG, "registered to observer with UID " + uid);
                } else if (get(uid) == null) { // addUidToObserver if this is a new UID
                    ActivityManager.getService().addUidToObserver(mUidObserverToken, mPackageName,
                            uid);
                    Log.i(TAG, " UID " + uid + " add to observer");
                }
            } catch (RemoteException e) {
                Log.e(TAG, "RemoteException with UID observer add/register: " + e);
            }

            super.put(uid, pkgSessions);
        }

        @Override
        public void remove(int uid) {
            if (get(uid) != null) {
                try {
                    ActivityManager.getService().removeUidFromObserver(mUidObserverToken,
                            mPackageName, uid);
                } catch (RemoteException e) {
                    Log.e(TAG, "RemoteException with removeUidFromObserver: " + e);
                }
            }

            super.remove(uid);

            // stop foreground service delegate and unregister UID observers with the last UID
            if (size() == 0) {
                try {
                    ActivityManager.getService().unregisterUidObserver(mEffectUidObserver);
                } catch (RemoteException e) {
                    Log.e(TAG, "RemoteException with unregisterUidObserver: " + e);
                }
                mUidObserverToken = null;
                mContext.unbindService(mMusicFxBindConnection);
                Log.i(TAG, "last session closed, unregister UID observer, and unbind "
                        + mMusicFxPackageName);
            }
        }
    }

    // Hashmap of UID and list of open sessions for this UID.
    @GuardedBy("mClientUidMapLock")
    private SparseArray<List<Integer>> mClientUidSessionMap = new SparseArray<>();

    /*package*/ static final int MSG_EFFECT_CLIENT_GONE = MUSICFX_HELPER_MSG_START + 1;
    private MySparseArray mClientUidSessionMap = new MySparseArray();

    // UID observer for effect MusicFx clients
    private final IUidObserver mEffectUidObserver = new UidObserver() {
@@ -102,23 +202,27 @@ public class MusicFxHelper {
     * Handle the broadcast {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and
     * {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents.
     *
     * Only intents without target application package {@link android.content.Intent#getPackage}
     * will be handled by the MusicFxHelper, all intents handled and forwarded by MusicFxHelper
     * will have the target application package.
     *
     * If the intent is {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION}:
     *  - If the MusicFx process is not running, call bindService with AUTO_CREATE to create.
     *  - If this is the first audio session in MusicFx, call set foreground service delegate.
     *  - If the MusicFx process is not running, call bindServiceAsUser with AUTO_CREATE to create.
     *  - If this is the first audio session of MusicFx, call set foreground service delegate.
     *  - If this is the first audio session for a given UID, add the UID into observer.
     *
     * If the intent is {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION}:
     *  - MusicFx will not be foreground delegated anymore.
     *  - The KeepAliveService of MusicFx will be unbound.
     *  - The UidObserver will be removed.
     * If the intent is {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION}
     *  - The KeepAliveService of MusicFx will be unbound, and MusicFx will not be foreground
     *    delegated anymore if the last session of the last package was closed.
     *  - The Uid Observer will be removed when the last session of a package was closed.
     */
    @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS})
    public void handleAudioEffectBroadcast(Context context, Intent intent) {
        String target = intent.getPackage();
        if (target != null) {
            Log.w(TAG, "effect broadcast already targeted to " + target);
            return;
        }
        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
        final PackageManager pm = context.getPackageManager();
        // TODO this should target a user-selected panel
        List<ResolveInfo> ril = pm.queryBroadcastReceivers(intent, 0 /* flags */);
@@ -126,14 +230,14 @@ public class MusicFxHelper {
            ResolveInfo ri = ril.get(0);
            final String senderPackageName = intent.getStringExtra(AudioEffect.EXTRA_PACKAGE_NAME);
            try {
                if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) {
                    final int senderUid = pm.getPackageUidAsUser(senderPackageName,
                            PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId());
                if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) {
                    intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
                    intent.setPackage(ri.activityInfo.packageName);
                    synchronized (mClientUidMapLock) {
                        setMusicFxServiceWithObserver(context, intent, senderUid);
                    }
                    if (setMusicFxServiceWithObserver(intent, senderUid, senderPackageName)) {
                        context.sendBroadcastAsUser(intent, UserHandle.ALL);
                    }
                    return;
                }
            } catch (PackageManager.NameNotFoundException e) {
@@ -144,127 +248,105 @@ public class MusicFxHelper {
        Log.w(TAG, "couldn't find receiver package for effect intent");
    }

    /**
     * Handle the UidObserver onUidGone callback of MusicFx clients.
     * All open audio sessions of this UID will be closed.
     * If this is the last UID for MusicFx:
     *  - MusicFx will not be foreground delegated anymore.
     *  - The KeepAliveService of MusicFx will be unbound.
     *  - The UidObserver will be removed.
     */
    public void handleEffectClientUidGone(int uid) {
        synchronized (mClientUidMapLock) {
            Log.w(TAG, " inside handle MSG_EFFECT_CLIENT_GONE");
            // Once the uid is no longer running, close all remain audio session(s) for this UID
            if (mClientUidSessionMap.get(Integer.valueOf(uid)) != null) {
                final List<Integer> sessions =
                        new ArrayList(mClientUidSessionMap.get(Integer.valueOf(uid)));
                Log.i(TAG, "UID " + uid + " gone, closing " + sessions.size() + " sessions");
                for (Integer session : sessions) {
                    Intent intent = new Intent(
                            AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
                    intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, session);
                    setMusicFxServiceWithObserver(mContext, intent, uid);
                    Log.i(TAG, "Close session " + session + " of UID " + uid);
                }
                mClientUidSessionMap.remove(Integer.valueOf(uid));
    @RequiresPermission(anyOf = {
            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
            android.Manifest.permission.INTERACT_ACROSS_USERS,
            android.Manifest.permission.INTERACT_ACROSS_PROFILES
    })
    @GuardedBy("mClientUidMapLock")
    private boolean handleAudioEffectSessionOpen(
            int senderUid, String senderPackageName, int sessionId) {
        Log.d(TAG, senderPackageName + " UID " + senderUid + " open MusicFx session " + sessionId);

        PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
        if (pkgSessions != null && pkgSessions.mSessions != null) {
            if (pkgSessions.mSessions.contains(sessionId)) {
                Log.e(TAG, "Audio session " + sessionId + " already open for UID: "
                        + senderUid + ", package: " + senderPackageName + ", abort");
                return false;
            }
            if (pkgSessions.mPackageName != senderPackageName) {
                Log.w(TAG, "Inconsistency package names for UID open: " + senderUid + " prev: "
                        + pkgSessions.mPackageName + ", now: " + senderPackageName);
                return false;
            }
        } else {
            // first session for this UID, create a new Package/Sessions pair
            pkgSessions = new PackageSessions();
            pkgSessions.mSessions = new ArrayList();
            pkgSessions.mPackageName = senderPackageName;
        }

    @GuardedBy("mClientUidMapLock")
    private void setMusicFxServiceWithObserver(Context context, Intent intent, int senderUid) {
        PackageManager pm = context.getPackageManager();
        try {
            final int audioSession = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION,
                    AudioManager.AUDIO_SESSION_ID_GENERATE);
            if (AudioManager.AUDIO_SESSION_ID_GENERATE == audioSession) {
                Log.e(TAG, "Intent missing audio session: " + audioSession);
                return;
        pkgSessions.mSessions.add(Integer.valueOf(sessionId));
        mClientUidSessionMap.put(Integer.valueOf(senderUid), pkgSessions);
        return true;
    }

            // only apply to com.android.musicfx and KeepAliveService for now
            final String musicFxPackageName = "com.android.musicfx";
            final String musicFxKeepAliveService = "com.android.musicfx.KeepAliveService";
            final int musicFxUid = pm.getPackageUidAsUser(musicFxPackageName,
                    PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId());

            if (intent.getAction().equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)) {
                List<Integer> sessions = new ArrayList<>();
                Log.d(TAG, "UID " + senderUid + ", open MusicFx session " + audioSession);
                // start foreground service delegate and register UID observer with the first
                // session of first UID open
                if (0 == mClientUidSessionMap.size()) {
                    final int procState = ActivityManager.getService().getPackageProcessState(
                            musicFxPackageName, this.getClass().getPackage().getName());
                    // if musicfx process not in binding state, call bindService with AUTO_CREATE
                    if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
                        Intent bindIntent = new Intent().setClassName(musicFxPackageName,
                                musicFxKeepAliveService);
                        context.bindServiceAsUser(
                                bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE,
                                UserHandle.of(getCurrentUserId()));
                        Log.i(TAG, "bindService to " + musicFxPackageName);
                    }
    @RequiresPermission(anyOf = {
            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
            android.Manifest.permission.INTERACT_ACROSS_USERS,
            android.Manifest.permission.INTERACT_ACROSS_PROFILES
    })
    @GuardedBy("mClientUidMapLock")
    private boolean handleAudioEffectSessionClose(
            int senderUid, String senderPackageName, int sessionId) {
        Log.d(TAG, senderPackageName + " UID " + senderUid + " close MusicFx session " + sessionId);

                    Log.i(TAG, "Package " + musicFxPackageName + " uid " + musicFxUid
                            + " procState " + procState);
                } else if (mClientUidSessionMap.get(Integer.valueOf(senderUid)) != null) {
                    sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
                    if (sessions.contains(audioSession)) {
                        Log.e(TAG, "Audio session " + audioSession + " already exist for UID "
                                + senderUid + ", abort");
                        return;
        PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
        if (pkgSessions == null) {
            Log.e(TAG, senderPackageName + " UID " + senderUid + " does not exist in map, abort");
            return false;
        }
        if (pkgSessions.mPackageName != senderPackageName) {
            Log.w(TAG, "Inconsistency package names for UID " + senderUid + " close, prev: "
                    + pkgSessions.mPackageName + ", now: " + senderPackageName);
            return false;
        }
                // first session of this UID
                if (sessions.size() == 0) {
                    // call registerUidObserverForUids with the first UID and first session
                    if (mClientUidSessionMap.size() == 0 || mUidObserverToken == null) {
                        mUidObserverToken = ActivityManager.getService().registerUidObserverForUids(
                                mEffectUidObserver, ActivityManager.UID_OBSERVER_GONE,
                                ActivityManager.PROCESS_STATE_UNKNOWN, null, new int[]{senderUid});
                        Log.i(TAG, "UID " + senderUid + " registered to observer");
                    } else {
                        // add UID to observer for each new UID
                        ActivityManager.getService().addUidToObserver(mUidObserverToken, TAG,
                                senderUid);
                        Log.i(TAG, "UID " + senderUid + " addeded to observer");

        if (pkgSessions.mSessions != null && pkgSessions.mSessions.size() != 0) {
            if (!pkgSessions.mSessions.contains(sessionId)) {
                Log.e(TAG, senderPackageName + " UID " + senderUid + " session " + sessionId
                        + " does not exist in map, abort");
                return false;
            }

            pkgSessions.mSessions.remove(Integer.valueOf(sessionId));
        }

                sessions.add(Integer.valueOf(audioSession));
                mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions);
            } else {
                if (mClientUidSessionMap.get(senderUid) != null) {
                    Log.d(TAG, "UID " + senderUid + ", close MusicFx session " + audioSession);
                    List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid));
                    sessions.remove(Integer.valueOf(audioSession));
                    if (0 == sessions.size()) {
        if (pkgSessions.mSessions == null || pkgSessions.mSessions.size() == 0) {
            // remove UID from map as well as the UID observer with the last session close
            mClientUidSessionMap.remove(Integer.valueOf(senderUid));
        } else {
                        mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions);
            mClientUidSessionMap.put(Integer.valueOf(senderUid), pkgSessions);
        }

                    // stop foreground service delegate and unregister UID observer with the
                    // last session of last UID close
                    if (0 == mClientUidSessionMap.size()) {
                        ActivityManager.getService().unregisterUidObserver(mEffectUidObserver);
                        mClientUidSessionMap.clear();
                        context.unbindService(mMusicFxBindConnection);
                        Log.i(TAG, " remove all sessions, unregister UID observer, and unbind "
                                + musicFxPackageName);
        return true;
    }
                } else {
                    // if the audio session already closed, print an error
                    Log.e(TAG, "UID " + senderUid + " close audio session " + audioSession
                            + " which does not exist");

    /**
     * @return true if the intent is validated and handled successfully, false with any error
     * (invalid sender/intent for example).
     */
    @RequiresPermission(anyOf = {
            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
            android.Manifest.permission.INTERACT_ACROSS_USERS,
            android.Manifest.permission.INTERACT_ACROSS_PROFILES
    })
    private boolean setMusicFxServiceWithObserver(
            Intent intent, int senderUid, String packageName) {
        final int session = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION,
                AudioManager.AUDIO_SESSION_ID_GENERATE);
        if (AudioManager.AUDIO_SESSION_ID_GENERATE == session) {
            Log.e(TAG, packageName + " intent have no invalid audio session");
            return false;
        }

        synchronized (mClientUidMapLock) {
            if (intent.getAction().equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)) {
                return handleAudioEffectSessionOpen(senderUid, packageName, session);
            } else {
                return handleAudioEffectSessionClose(senderUid, packageName, session);
            }
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "Not able to find UID from package: " + e);
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException " + e + " with handling intent");
        }
    }

@@ -281,6 +363,39 @@ public class MusicFxHelper {
        return UserHandle.USER_SYSTEM;
    }


    /**
     * Handle the UidObserver onUidGone callback of MusicFx clients.
     * Send close intent for all open audio sessions of this UID. The mClientUidSessionMap will be
     * updated with the handling of close intent in setMusicFxServiceWithObserver.
     */
    @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS})
    private void handleEffectClientUidGone(int uid) {
        synchronized (mClientUidMapLock) {
            Log.d(TAG, "handle MSG_EFFECT_CLIENT_GONE uid: " + uid + " mapSize: "
                    + mClientUidSessionMap.size());
            // Once the uid is no longer running, close all remain audio session(s) for this UID
            final PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(uid));
            if (pkgSessions != null) {
                Log.i(TAG, "UID " + uid + " gone, closing all sessions");

                // send close intent for each open session of the gone UID
                for (Integer sessionId : pkgSessions.mSessions) {
                    Intent closeIntent =
                            new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
                    closeIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, pkgSessions.mPackageName);
                    closeIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId);
                    closeIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
                    // set broadcast target
                    closeIntent.setPackage(mMusicFxPackageName);
                    mContext.sendBroadcastAsUser(closeIntent, UserHandle.ALL);
                }
                mClientUidSessionMap.remove(Integer.valueOf(uid));
            }
        }
    }

    @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS})
    /*package*/ void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_EFFECT_CLIENT_GONE:
@@ -292,5 +407,4 @@ public class MusicFxHelper {
                break;
        }
    }

}