diff options
3 files changed, 887 insertions, 126 deletions
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index f7b7aaa60a35..44cb1367928d 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -13779,6 +13779,11 @@ public class AudioService extends IAudioService.Stub return mDeviceBroker.getDeviceAddresses(device); } + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + MusicFxHelper getMusicFxHelper() { + return mMusicFxHelper; + } + //====================== // misc //====================== diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java index 5f4e4c3bc4e0..85b3b49ecf78 100644 --- a/services/core/java/com/android/server/audio/MusicFxHelper.java +++ b/services/core/java/com/android/server/audio/MusicFxHelper.java @@ -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 { - final int senderUid = pm.getPackageUidAsUser(senderPackageName, - PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId()); if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) { + final int senderUid = pm.getPackageUidAsUser(senderPackageName, + PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId()); + 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); } - 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; } + + pkgSessions.mSessions.add(Integer.valueOf(sessionId)); + mClientUidSessionMap.put(Integer.valueOf(senderUid), pkgSessions); + return true; } + @RequiresPermission(anyOf = { + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + android.Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.INTERACT_ACROSS_PROFILES + }) @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; + private boolean handleAudioEffectSessionClose( + int senderUid, String senderPackageName, int sessionId) { + Log.d(TAG, senderPackageName + " UID " + senderUid + " close MusicFx session " + sessionId); + + 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; + } + + 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; } - // 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()); + pkgSessions.mSessions.remove(Integer.valueOf(sessionId)); + } - 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); - } + 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), pkgSessions); + } - 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; - } - } - // 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"); - } - } + return true; + } - 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()) { - mClientUidSessionMap.remove(Integer.valueOf(senderUid)); - } else { - mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions); - } + /** + * @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; + } - // 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); - } - } else { - // if the audio session already closed, print an error - Log.e(TAG, "UID " + senderUid + " close audio session " + audioSession - + " which does not exist"); - } + 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; } } - } diff --git a/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java new file mode 100644 index 000000000000..472a82c02937 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java @@ -0,0 +1,642 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyObject; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.media.audiofx.AudioEffect; +import android.os.Message; +import android.util.Log; + +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@MediumTest +@RunWith(AndroidJUnit4.class) +public class MusicFxHelperTest { + private static final String TAG = "MusicFxHelperTest"; + + @Mock private AudioService mMockAudioService; + @Mock private Context mMockContext; + @Mock private PackageManager mMockPackageManager; + + private ResolveInfo mResolveInfo1 = new ResolveInfo(); + private ResolveInfo mResolveInfo2 = new ResolveInfo(); + private final String mTestPkg1 = "testPkg1", mTestPkg2 = "testPkg2", mTestPkg3 = "testPkg3"; + private final String mMusicFxPkgName = "com.android.musicfx"; + private final int mTestUid1 = 1, mTestUid2 = 2, mTestUid3 = 3, mMusicFxUid = 78; + private final int mTestSession1 = 11, mTestSession2 = 22, mTestSession3 = 33; + + private List<ResolveInfo> mEmptyList = new ArrayList<>(); + private List<ResolveInfo> mSingleList = new ArrayList<>(); + private List<ResolveInfo> mDoubleList = new ArrayList<>(); + + // the class being unit-tested here + @InjectMocks private MusicFxHelper mMusicFxHelper; + + @Before + @SuppressWarnings("DirectInvocationOnMock") + public void setUp() throws Exception { + mMockAudioService = mock(AudioService.class); + mMusicFxHelper = mMockAudioService.getMusicFxHelper(); + MockitoAnnotations.initMocks(this); + + mResolveInfo1.activityInfo = new ActivityInfo(); + mResolveInfo1.activityInfo.packageName = mTestPkg1; + mResolveInfo2.activityInfo = new ActivityInfo(); + mResolveInfo2.activityInfo.packageName = mTestPkg2; + + mSingleList.add(mResolveInfo1); + mDoubleList.add(mResolveInfo1); + mDoubleList.add(mResolveInfo2); + + Assert.assertNotNull(mMusicFxHelper); + } + + private Intent newIntent(String action, String packageName, int sessionId) { + Intent intent = new Intent(action); + if (packageName != null) { + intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName); + } + intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId); + return intent; + } + + /** + * Helper function to send ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION intent with verification. + * + * @throws NameNotFoundException if no such package is available to the caller. + */ + private void openSessionWithResList( + List<ResolveInfo> list, int bind, int broadcast, String packageName, int audioSession, + int uid) { + doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); + doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(anyObject(), anyInt()); + if (list != null && list.size() != 0) { + try { + doReturn(uid).when(mMockPackageManager) + .getPackageUidAsUser(eq(packageName), anyObject(), anyInt()); + doReturn(mMusicFxUid).when(mMockPackageManager) + .getPackageUidAsUser(eq(mMusicFxPkgName), anyObject(), anyInt()); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "NameNotFoundException: " + e); + } + } + + Intent intent = newIntent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION, + packageName, audioSession); + mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent); + verify(mMockContext, times(bind)) + .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject()); + verify(mMockContext, times(broadcast)).sendBroadcastAsUser(anyObject(), anyObject()); + } + + /** + * Helper function to send ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION intent with verification. + * + * @throws NameNotFoundException if no such package is available to the caller. + */ + private void closeSessionWithResList( + List<ResolveInfo> list, int unBind, int broadcast, String packageName, + int audioSession, int uid) { + doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); + doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(anyObject(), anyInt()); + if (list != null && list.size() != 0) { + try { + doReturn(uid).when(mMockPackageManager) + .getPackageUidAsUser(eq(packageName), anyObject(), anyInt()); + doReturn(mMusicFxUid).when(mMockPackageManager) + .getPackageUidAsUser(eq(mMusicFxPkgName), anyObject(), anyInt()); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "NameNotFoundException: " + e); + } + } + + Intent intent = newIntent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION, + packageName, audioSession); + mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent); + verify(mMockContext, times(unBind)).unbindService(anyObject()); + verify(mMockContext, times(broadcast)).sendBroadcastAsUser(anyObject(), anyObject()); + } + + /** + * Helper function to send MSG_EFFECT_CLIENT_GONE message with verification. + */ + private void sendMessage(int msgId, int uid, int unBinds, int broadcasts) { + mMusicFxHelper.handleMessage(Message.obtain(null, msgId, uid /* arg1 */, 0 /* arg2 */)); + verify(mMockContext, times(broadcasts)).sendBroadcastAsUser(anyObject(), anyObject()); + verify(mMockContext, times(unBinds)).unbindService(anyObject()); + } + + /** + * Send invalid message to MusicFxHelper. + */ + @Test + public void testInvalidMessage() { + Log.i(TAG, "running testInvalidMessage"); + + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE - 1, 0, 0, 0); + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE + 1, 0, 0, 0); + } + + /** + * Send client gone message to MusicFxHelper when no client exist. + */ + @Test + public void testGoneMessageWhenNoClient() { + Log.i(TAG, "running testGoneMessageWhenNoClient"); + + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, 0, 0, 0); + } + + /** + * Send ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION intent to MusicFxHelper when no session exist. + */ + @Test + public void testCloseBroadcastIntent() { + Log.i(TAG, "running testCloseBroadcastIntent"); + + closeSessionWithResList(null, 0, 0, null, mTestSession1, mTestUid1); + } + + /** + * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION intent when target application package was set. + * When the target application package was set for an intent, it means this intent is limited + * to a specific target application, as a result MusicFxHelper will not handle this intent. + */ + @Test + public void testBroadcastIntentWithPackage() { + Log.i(TAG, "running testBroadcastIntentWithPackage"); + + Intent intent = newIntent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION, null, 1); + intent.setPackage(mTestPkg1); + mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent); + verify(mMockContext, times(0)) + .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject()); + verify(mMockContext, times(0)).sendBroadcastAsUser(anyObject(), anyObject()); + + intent = newIntent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION, null, 1); + intent.setPackage(mTestPkg2); + mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent); + verify(mMockContext, times(0)) + .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject()); + verify(mMockContext, times(0)).sendBroadcastAsUser(anyObject(), anyObject()); + } + + /** + * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION with no broadcast receiver. + */ + @Test + public void testBroadcastIntentWithNoPackageAndNoBroadcastReceiver() { + Log.i(TAG, "running testBroadcastIntentWithNoPackageAndNoBroadcastReceiver"); + + openSessionWithResList(mEmptyList, 0, 0, null, mTestSession1, mTestUid1); + closeSessionWithResList(mEmptyList, 0, 0, null, mTestSession1, mTestUid1); + } + + /** + * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION with one broadcast receiver. + */ + @Test + public void testBroadcastIntentWithNoPackageAndOneBroadcastReceiver() { + Log.i(TAG, "running testBroadcastIntentWithNoPackageAndOneBroadcastReceiver"); + + int broadcasts = 1, bind = 1, unbind = 1; + openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession1, mTestUid1); + + // repeat with different session ID + broadcasts = broadcasts + 1; + bind = bind + 1; + unbind = unbind + 1; + openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession2, mTestUid1); + broadcasts = broadcasts + 1; + closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession2, mTestUid1); + + // repeat with different UID + broadcasts = broadcasts + 1; + bind = bind + 1; + unbind = unbind + 1; + openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession1, mTestUid2); + broadcasts = broadcasts + 1; + closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession1, mTestUid2); + } + + /** + * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION with two broadcast receivers. + */ + @Test + public void testBroadcastIntentWithNoPackageAndTwoBroadcastReceivers() { + Log.i(TAG, "running testBroadcastIntentWithNoPackageAndTwoBroadcastReceivers"); + + openSessionWithResList(mDoubleList, 1, 1, null, mTestSession1, mTestUid1); + closeSessionWithResList(mDoubleList, 1, 2, null, mTestSession1, mTestUid1); + } + + /** + * Open/close session UID not matching. + * No broadcast for mismatching sessionID/UID/packageName. + */ + @Test + public void testBroadcastBadIntents() { + Log.i(TAG, "running testBroadcastBadIntents"); + + int broadcasts = 1; + openSessionWithResList(mSingleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + // mismatch UID + closeSessionWithResList(mSingleList, 0, broadcasts, mTestPkg1, mTestSession1, mTestUid2); + // mismatch AudioSession + closeSessionWithResList(mSingleList, 0, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + // mismatch packageName + closeSessionWithResList(mSingleList, 0, broadcasts, mTestPkg2, mTestSession1, mTestUid1); + + // cleanup with correct UID and session ID + broadcasts = broadcasts + 1; + closeSessionWithResList(mSingleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + } + + /** + * Open/close sessions with one UID, some with correct intents some with illegal intents. + * No broadcast for mismatching sessionID/UID/packageName. + */ + @Test + public void testBroadcastGoodAndBadIntents() { + Log.i(TAG, "running testBroadcastGoodAndBadIntents"); + + int broadcasts = 1, bind = 1, unbind = 0; + openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + // mismatch packageName, session ID and UID + closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg2, mTestSession2, + mTestUid2); + // mismatch session ID and mismatch UID + closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg1, mTestSession2, + mTestUid2); + // mismatch packageName and mismatch UID + closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg2, mTestSession1, + mTestUid2); + // mismatch packageName and sessionID + closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg2, mTestSession2, + mTestUid1); + // inconsistency package name for same UID + openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid1); + // open session2 with good intent + broadcasts = broadcasts + 1; + openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + + // cleanup with correct UID and session ID + broadcasts = broadcasts + 1; + closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + broadcasts = broadcasts + 1; + unbind = unbind + 1; + closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg1, mTestSession2, + mTestUid1); + } + + /** + * Send ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION when there is no listener. + */ + @Test + public void testBroadcastOpenSessionWithValidPackageNameAndNoListener() { + Log.i(TAG, "running testBroadcastOpenSessionWithValidPackageNameAndNoListener"); + + // null listener list should not trigger any action + openSessionWithResList(null, 0, 0, mTestPkg1, mTestSession1, mTestUid1); + // empty listener list should not trigger any action + openSessionWithResList(mEmptyList, 0, 0, mTestPkg1, mTestSession1, mTestUid1); + } + + /** + * One MusicFx client, open session and close. + */ + @Test + public void testOpenCloseAudioSession() { + Log.i(TAG, "running testOpenCloseAudioSession"); + + int broadcasts = 1; + openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + } + + /** + * One MusicFx client, open session and close, then gone. + */ + @Test + public void testOpenCloseAudioSessionAndGone() { + Log.i(TAG, "running testOpenCloseAudioSessionAndGone"); + + int broadcasts = 1; + openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, 0, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + + broadcasts = broadcasts + 1; // 1 open session left + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, 1, broadcasts); + } + + /** + * One MusicFx client, open session, then UID gone without close. + */ + @Test + public void testOpenOneSessionAndGo() { + Log.i(TAG, "running testOpenOneSessionAndGo"); + + int broadcasts = 1; + openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + + broadcasts = broadcasts + 1; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, 1, broadcasts); + } + + /** + * Two MusicFx clients open and close sessions. + */ + @Test + public void testOpenTwoSessionsAndClose() { + Log.i(TAG, "running testOpenTwoSessionsAndClose"); + + int broadcasts = 1, bind = 1, unbind = 0; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession2, + mTestUid2); + broadcasts = broadcasts + 1; + unbind = unbind + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + + broadcasts = broadcasts + 1; + bind = bind + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + broadcasts = broadcasts + 1; + unbind = unbind + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession2, + mTestUid2); + } + + /** + * Two MusicFx clients open sessions, then both UID gone without close. + */ + @Test + public void testOpenTwoSessionsAndGo() { + Log.i(TAG, "running testOpenTwoSessionsAndGo"); + + int broadcasts = 1, bind = 1, unbind = 0; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + + broadcasts = broadcasts + 1; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, unbind, broadcasts); + + broadcasts = broadcasts + 1; + unbind = unbind + 1; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts); + } + + /** + * Two MusicFx clients open sessions, one close but not gone, the other one gone without close. + */ + @Test + public void testTwoSessionsOpenOneCloseOneGo() { + Log.i(TAG, "running testTwoSessionsOpneAndOneCloseOneGo"); + + int broadcasts = 1, bind = 1, unbind = 0; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + + broadcasts = broadcasts + 1; + unbind = unbind + 1; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts); + } + + /** + * One MusicFx client, open multiple audio sessions, and close all sessions. + */ + @Test + public void testTwoSessionsInSameUidOpenClose() { + Log.i(TAG, "running testTwoSessionsOpneAndOneCloseOneGo"); + + int broadcasts = 1, bind = 1, unbind = 0; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + broadcasts = broadcasts + 1; + unbind = unbind + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2, + mTestUid1); + } + + /** + * Three MusicFx clients, each with multiple audio sessions, and close all sessions. + */ + @Test + public void testThreeSessionsInThreeUidOpenClose() { + Log.i(TAG, "running testThreeSessionsInThreeUidOpenClose"); + + int broadcasts = 1, bind = 1, unbind = 0; + //client1 + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + // client2 + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession3, mTestUid2); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + // client3 + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession1, mTestUid3); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession3, mTestUid3); + + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg3, mTestSession3, + mTestUid3); + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession2, + mTestUid2); + // all sessions of client1 closed + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2, + mTestUid1); + // all sessions of client3 closed + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg3, mTestSession1, + mTestUid3); + // all sessions of client2 closed + broadcasts = broadcasts + 1; + // now expect unbind to happen + unbind = unbind + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession3, + mTestUid2); + } + + /** + * Two MusicFx clients, with multiple audio sessions, one close all sessions, and other gone. + */ + @Test + public void testTwoUidOneCloseOneGo() { + Log.i(TAG, "running testTwoUidOneCloseOneGo"); + + int broadcasts = 1, bind = 1, unbind = 0; + //client1 + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + // client2 + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession1, mTestUid2); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + // client2 gone + broadcasts = broadcasts + 2; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts); + // client 1 close all sessions + broadcasts = broadcasts + 1; + unbind = unbind + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2, + mTestUid1); + } + + /** + * Three MusicFx clients, with multiple audio sessions, all UID gone. + */ + @Test + public void testThreeUidAllGo() { + Log.i(TAG, "running testThreeUidAllGo"); + + int broadcasts = 1, bind = 1, unbind = 0; + //client1 + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + // client2 + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession3, mTestUid2); + // client3 + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession3, mTestUid3); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession1, mTestUid3); + + // client2 gone + broadcasts = broadcasts + 2; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts); + // client3 gone + broadcasts = broadcasts + 2; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid3, unbind, broadcasts); + // client 1 gone + broadcasts = broadcasts + 2; + // now expect unbindService to happen + unbind = unbind + 1; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, unbind, broadcasts); + } + + /** + * Three MusicFx clients, multiple audio sessions, open and UID gone in difference sequence. + */ + @Test + public void testThreeUidDiffSequence() { + Log.i(TAG, "running testThreeUidDiffSequence"); + + int broadcasts = 1, bind = 1, unbind = 0; + //client1 + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + // client2 + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + // client1 close one session + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + // client2 open another session + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession3, mTestUid2); + // client3 open one session + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession3, mTestUid3); + // client2 gone + broadcasts = broadcasts + 2; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts); + // client3 open another session + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession1, mTestUid3); + // client1 close another session, and gone + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2, + mTestUid1); + // last UID client3 gone, unbind + broadcasts = broadcasts + 2; + unbind = unbind + 1; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid3, unbind, broadcasts); + } +} |