diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java old mode 100644 new mode 100755 index 541883346e23d..fe340c46e3c46 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4747,6 +4747,14 @@ public final class Settings { */ public static final String SHOW_BATTERY_PERCENT = "status_bar_show_battery_percent"; + /** + * Whether or not to enable multiple audio focus. + * When enabled, requires more management by user over application playback activity, + * for instance pausing media apps when another starts. + * @hide + */ + public static final String MULTI_AUDIO_FOCUS_ENABLED = "multi_audio_focus_enabled"; + /** * IMPORTANT: If you add a new public settings you also have to add it to * PUBLIC_SETTINGS below. If the new setting is hidden you have to add diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java old mode 100644 new mode 100755 index 8477aa3ca26ec..3e1f72da87314 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6145,6 +6145,17 @@ public class AudioManager { } } + /** @hide + * TODO: make this a @SystemApi */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void setMultiAudioFocusEnabled(boolean enabled) { + try { + getService().setMultiAudioFocusEnabled(enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + //--------------------------------------------------------- // Inner classes //-------------------- diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl old mode 100644 new mode 100755 index bb10e1fe2f2c1..e3b67f86a3673 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -301,4 +301,6 @@ interface IAudioService { // WARNING: read warning at top of file, new methods that need to be used by native // code via IAudioManager.h need to be added to the top section. + + oneway void setMultiAudioFocusEnabled(in boolean enabled); } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 01a2b6952f2ac..03f6df0d5d2dc 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -101,7 +101,8 @@ public class SettingsBackupTest { Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities Settings.System.SCREEN_BRIGHTNESS_FLOAT, - Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT + Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT, + Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific ); private static final Set BACKUP_BLACKLISTED_GLOBAL_SETTINGS = diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java old mode 100644 new mode 100755 index 02d8571c2dcd7..ae22e69f5b1b0 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -8418,6 +8418,23 @@ public class AudioService extends IAudioService.Stub AudioSystem.setAudioHalPids(pidsArray); } + //====================== + // Multi Audio Focus + //====================== + public void setMultiAudioFocusEnabled(boolean enabled) { + enforceModifyAudioRoutingPermission(); + if (mMediaFocusControl != null) { + boolean mafEnabled = mMediaFocusControl.getMultiAudioFocusEnabled(); + if (mafEnabled != enabled) { + mMediaFocusControl.updateMultiAudioFocus(enabled); + if (!enabled) { + mDeviceBroker.postBroadcastBecomingNoisy(); + } + } + } + } + + //====================== // misc //====================== diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java old mode 100644 new mode 100755 index 7bf2bc842176f..bfab9919258cb --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -30,6 +30,7 @@ import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; +import android.provider.Settings; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -80,6 +81,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { private final Context mContext; private final AppOpsManager mAppOps; private PlayerFocusEnforcer mFocusEnforcer; // never null + private boolean mMultiAudioFocusEnabled = false; private boolean mRingOrCallActive = false; @@ -91,6 +93,8 @@ public class MediaFocusControl implements PlayerFocusEnforcer { mContext = cntxt; mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE); mFocusEnforcer = pfe; + mMultiAudioFocusEnabled = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.MULTI_AUDIO_FOCUS_ENABLED, 0) != 0; } protected void dump(PrintWriter pw) { @@ -100,6 +104,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { pw.println("\n"); // log mEventLogger.dump(pw); + dumpMultiAudioFocus(pw); } //================================================================= @@ -194,6 +199,14 @@ public class MediaFocusControl implements PlayerFocusEnforcer { mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN); } } + + if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) { + for (FocusRequester multifr : mMultiAudioFocusList) { + if (isLockedFocusOwner(multifr)) { + multifr.handleFocusGain(AudioManager.AUDIOFOCUS_GAIN); + } + } + } } /** @@ -203,17 +216,30 @@ public class MediaFocusControl implements PlayerFocusEnforcer { */ @GuardedBy("mAudioFocusLock") private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr, - boolean forceDuck) { + boolean forceDuck) { final List clientsToRemove = new LinkedList(); // going through the audio focus stack to signal new focus, traversing order doesn't // matter as all entries respond to the same external focus gain - for (FocusRequester focusLoser : mFocusStack) { - final boolean isDefinitiveLoss = - focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck); - if (isDefinitiveLoss) { - clientsToRemove.add(focusLoser.getClientId()); + if (!mFocusStack.empty()) { + for (FocusRequester focusLoser : mFocusStack) { + final boolean isDefinitiveLoss = + focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck); + if (isDefinitiveLoss) { + clientsToRemove.add(focusLoser.getClientId()); + } } } + + if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) { + for (FocusRequester multifocusLoser : mMultiAudioFocusList) { + final boolean isDefinitiveLoss = + multifocusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck); + if (isDefinitiveLoss) { + clientsToRemove.add(multifocusLoser.getClientId()); + } + } + } + for (String clientToRemove : clientsToRemove) { removeFocusStackEntry(clientToRemove, false /*signal*/, true /*notifyFocusFollowers*/); @@ -222,6 +248,8 @@ public class MediaFocusControl implements PlayerFocusEnforcer { private final Stack mFocusStack = new Stack(); + ArrayList mMultiAudioFocusList = new ArrayList(); + /** * Helper function: * Display in the log the current entries in the audio focus stack @@ -287,6 +315,22 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } } } + + if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) { + Iterator listIterator = mMultiAudioFocusList.iterator(); + while (listIterator.hasNext()) { + FocusRequester fr = listIterator.next(); + if (fr.hasSameClient(clientToRemove)) { + listIterator.remove(); + fr.release(); + } + } + + if (signal) { + // notify the new top of the stack it gained focus + notifyTopOfAudioFocusStack(); + } + } } /** @@ -410,6 +454,16 @@ public class MediaFocusControl implements PlayerFocusEnforcer { removeFocusEntryForExtPolicy(mCb); } else { removeFocusStackEntryOnDeath(mCb); + if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) { + Iterator listIterator = mMultiAudioFocusList.iterator(); + while (listIterator.hasNext()) { + FocusRequester fr = listIterator.next(); + if (fr.hasSameBinder(mCb)) { + listIterator.remove(); + fr.release(); + } + } + } } } } @@ -867,6 +921,35 @@ public class MediaFocusControl implements PlayerFocusEnforcer { final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb, clientId, afdh, callingPackageName, Binder.getCallingUid(), this, sdk); + + if (mMultiAudioFocusEnabled + && (focusChangeHint == AudioManager.AUDIOFOCUS_GAIN)) { + if (enteringRingOrCall) { + if (!mMultiAudioFocusList.isEmpty()) { + for (FocusRequester multifr : mMultiAudioFocusList) { + multifr.handleFocusLossFromGain(focusChangeHint, nfr, forceDuck); + } + } + } else { + boolean needAdd = true; + if (!mMultiAudioFocusList.isEmpty()) { + for (FocusRequester multifr : mMultiAudioFocusList) { + if (multifr.getClientUid() == Binder.getCallingUid()) { + needAdd = false; + break; + } + } + } + if (needAdd) { + mMultiAudioFocusList.add(nfr); + } + nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), + AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; + } + } + if (focusGrantDelayed) { // focusGrantDelayed being true implies we can't reassign focus right now // which implies the focus stack is not empty. @@ -877,9 +960,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { return requestResult; } else { // propagate the focus change through the stack - if (!mFocusStack.empty()) { - propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck); - } + propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck); // push focus requester at the top of the audio focus stack mFocusStack.push(nfr); @@ -970,4 +1051,39 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } }.start(); } + + public void updateMultiAudioFocus(boolean enabled) { + Log.d(TAG, "updateMultiAudioFocus( " + enabled + " )"); + mMultiAudioFocusEnabled = enabled; + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.MULTI_AUDIO_FOCUS_ENABLED, enabled ? 1 : 0); + if (!mFocusStack.isEmpty()) { + final FocusRequester fr = mFocusStack.peek(); + fr.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null, false); + } + if (!enabled) { + if (!mMultiAudioFocusList.isEmpty()) { + for (FocusRequester multifr : mMultiAudioFocusList) { + multifr.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null, false); + } + mMultiAudioFocusList.clear(); + } + } + } + + public boolean getMultiAudioFocusEnabled() { + return mMultiAudioFocusEnabled; + } + + private void dumpMultiAudioFocus(PrintWriter pw) { + pw.println("Multi Audio Focus enabled :" + mMultiAudioFocusEnabled); + if (!mMultiAudioFocusList.isEmpty()) { + pw.println("Multi Audio Focus List:"); + pw.println("------------------------------"); + for (FocusRequester multifr : mMultiAudioFocusList) { + multifr.dump(pw); + } + pw.println("------------------------------"); + } + } }