Merge "Fixed NPE during monkey testing."
diff --git a/api/current.txt b/api/current.txt
index ba9b1cc..d030fa9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4171,6 +4171,7 @@
method public abstract void onActivityCreated(android.app.Activity, android.os.Bundle);
method public abstract void onActivityDestroyed(android.app.Activity);
method public abstract void onActivityPaused(android.app.Activity);
+ method public default void onActivityPreCreated(android.app.Activity, android.os.Bundle);
method public abstract void onActivityResumed(android.app.Activity);
method public abstract void onActivitySaveInstanceState(android.app.Activity, android.os.Bundle);
method public abstract void onActivityStarted(android.app.Activity);
diff --git a/api/system-current.txt b/api/system-current.txt
index a0b9500..b834594 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4313,6 +4313,7 @@
method public abstract void onActivityCreated(android.app.Activity, android.os.Bundle);
method public abstract void onActivityDestroyed(android.app.Activity);
method public abstract void onActivityPaused(android.app.Activity);
+ method public default void onActivityPreCreated(android.app.Activity, android.os.Bundle);
method public abstract void onActivityResumed(android.app.Activity);
method public abstract void onActivitySaveInstanceState(android.app.Activity, android.os.Bundle);
method public abstract void onActivityStarted(android.app.Activity);
@@ -22084,6 +22085,7 @@
method public int describeContents();
method public android.media.AudioAttributes getAttributes();
method public java.lang.String getClientId();
+ method public int getClientUid();
method public int getFlags();
method public int getGainRequest();
method public int getLossReceived();
@@ -24268,10 +24270,12 @@
}
public class PlayerProxy {
- method public void pause() throws java.lang.IllegalStateException;
- method public void setVolume(float) throws java.lang.IllegalStateException;
- method public void start() throws java.lang.IllegalStateException;
- method public void stop() throws java.lang.IllegalStateException;
+ method public void pause();
+ method public void setPan(float);
+ method public void setStartDelayMs(int);
+ method public void setVolume(float);
+ method public void start();
+ method public void stop();
}
public final class Rating implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 682c029..ff7e2c2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4181,6 +4181,7 @@
method public abstract void onActivityCreated(android.app.Activity, android.os.Bundle);
method public abstract void onActivityDestroyed(android.app.Activity);
method public abstract void onActivityPaused(android.app.Activity);
+ method public default void onActivityPreCreated(android.app.Activity, android.os.Bundle);
method public abstract void onActivityResumed(android.app.Activity);
method public abstract void onActivitySaveInstanceState(android.app.Activity, android.os.Bundle);
method public abstract void onActivityStarted(android.app.Activity);
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 9fad7bf..c773275 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -107,8 +107,6 @@
mSystemBoot = !property_get_bool(BOOT_COMPLETED_PROP_NAME, 0);
}
-BootAnimation::~BootAnimation() {}
-
void BootAnimation::onFirstRef() {
status_t err = mSession->linkToComposerDeath(this);
ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
@@ -778,11 +776,12 @@
}
// Create and initialize audioplay if there is a wav file in any of the animations.
+ // Do it on a separate thread so we don't hold up the animation intro.
if (partWithAudio != NULL) {
ALOGD("found audio.wav, creating playback engine");
- if (!audioplay::create(partWithAudio->audioData, partWithAudio->audioLength)) {
- return false;
- }
+ mInitAudioThread = new InitAudioThread(partWithAudio->audioData,
+ partWithAudio->audioLength);
+ mInitAudioThread->run("BootAnimation::InitAudioThread", PRIORITY_NORMAL);
}
zip->endIteration(cookie);
@@ -850,9 +849,14 @@
playAnimation(*animation);
- if (mTimeCheckThread != NULL) {
+ if (mTimeCheckThread != nullptr) {
mTimeCheckThread->requestExit();
- mTimeCheckThread = NULL;
+ mTimeCheckThread = nullptr;
+ }
+
+ if (mInitAudioThread != nullptr) {
+ mInitAudioThread->requestExit();
+ mInitAudioThread = nullptr;
}
releaseAnimation(animation);
@@ -892,6 +896,10 @@
// only play audio file the first time we animate the part
if (r == 0 && part.audioData && playSoundsAllowed()) {
ALOGD("playing clip for part%d, size=%d", (int) i, part.audioLength);
+ // Block until the audio engine is finished initializing.
+ if (mInitAudioThread != nullptr) {
+ mInitAudioThread->join();
+ }
audioplay::playClip(part.audioData, part.audioLength);
}
@@ -1185,6 +1193,17 @@
return NO_ERROR;
}
+BootAnimation::InitAudioThread::InitAudioThread(uint8_t* exampleAudioData, int exampleAudioLength)
+ : Thread(false),
+ mExampleAudioData(exampleAudioData),
+ mExampleAudioLength(exampleAudioLength) {}
+
+bool BootAnimation::InitAudioThread::threadLoop() {
+ audioplay::create(mExampleAudioData, mExampleAudioLength);
+ // Exit immediately
+ return false;
+}
+
// ---------------------------------------------------------------------------
}
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index 7a2e4c2..f1fc98e 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -39,8 +39,7 @@
class BootAnimation : public Thread, public IBinder::DeathRecipient
{
public:
- BootAnimation();
- virtual ~BootAnimation();
+ BootAnimation();
sp<SurfaceComposerClient> session() const;
@@ -68,6 +67,16 @@
BootAnimation* mBootAnimation;
};
+ class InitAudioThread : public Thread {
+ public:
+ InitAudioThread(uint8_t* exampleAudioData, int mExampleAudioLength);
+ private:
+ virtual bool threadLoop();
+
+ uint8_t* mExampleAudioData;
+ int mExampleAudioLength;
+ };
+
struct Texture {
GLint w;
GLint h;
@@ -156,7 +165,8 @@
bool mSystemBoot;
String8 mZipFileName;
SortedVector<String8> mLoadedFiles;
- sp<TimeCheckThread> mTimeCheckThread;
+ sp<TimeCheckThread> mTimeCheckThread = nullptr;
+ sp<InitAudioThread> mInitAudioThread = nullptr;
};
// ---------------------------------------------------------------------------
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 6f95309..0ac30de 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -969,7 +969,9 @@
? mLastNonConfigurationInstances.fragments : null);
}
mFragments.dispatchCreate();
- getApplication().dispatchActivityCreated(this, savedInstanceState);
+ if (!isAtLeastO()) {
+ getApplication().dispatchActivityCreated(this, savedInstanceState);
+ }
if (mVoiceInteractor != null) {
mVoiceInteractor.attachActivity(this);
}
@@ -1197,8 +1199,9 @@
mCalled = true;
mFragments.doLoaderStart();
-
- getApplication().dispatchActivityStarted(this);
+ if (!isAtLeastO()) {
+ getApplication().dispatchActivityStarted(this);
+ }
}
/**
@@ -1259,7 +1262,9 @@
@CallSuper
protected void onResume() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
- getApplication().dispatchActivityResumed(this);
+ if (!isAtLeastO()) {
+ getApplication().dispatchActivityResumed(this);
+ }
mActivityTransitionState.onResume(this, isTopOfTask());
mCalled = true;
}
@@ -1426,6 +1431,9 @@
saveManagedDialogs(outState);
mActivityTransitionState.saveState(outState);
storeHasCurrentPermissionRequest(outState);
+ if (isAtLeastO()) {
+ getApplication().dispatchActivitySaveInstanceState(this, outState);
+ }
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
}
@@ -1442,6 +1450,9 @@
onSaveInstanceState(outState, outPersistentState);
saveManagedDialogs(outState);
storeHasCurrentPermissionRequest(outState);
+ if (isAtLeastO()) {
+ getApplication().dispatchActivitySaveInstanceState(this, outState);
+ }
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState +
", " + outPersistentState);
}
@@ -1497,7 +1508,9 @@
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
- getApplication().dispatchActivitySaveInstanceState(this, outState);
+ if (!isAtLeastO()) {
+ getApplication().dispatchActivitySaveInstanceState(this, outState);
+ }
}
/**
@@ -1595,7 +1608,9 @@
@CallSuper
protected void onPause() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this);
- getApplication().dispatchActivityPaused(this);
+ if (!isAtLeastO()) {
+ getApplication().dispatchActivityPaused(this);
+ }
mCalled = true;
}
@@ -1795,7 +1810,9 @@
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
mActivityTransitionState.onStop();
- getApplication().dispatchActivityStopped(this);
+ if (!isAtLeastO()) {
+ getApplication().dispatchActivityStopped(this);
+ }
mTranslucentCallback = null;
mCalled = true;
}
@@ -1865,8 +1882,9 @@
if (mActionBar != null) {
mActionBar.onDestroy();
}
-
- getApplication().dispatchActivityDestroyed(this);
+ if (!isAtLeastO()) {
+ getApplication().dispatchActivityDestroyed(this);
+ }
}
/**
@@ -6750,25 +6768,33 @@
return mParent != null ? mParent.getActivityToken() : mToken;
}
- final void performCreateCommon() {
+ final void performCreateCommon(Bundle icicle) {
+ mActivityTransitionState.readState(icicle);
mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, false);
mFragments.dispatchActivityCreated();
mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
+ if (isAtLeastO()) {
+ getApplication().dispatchActivityCreated(this, icicle);
+ }
}
final void performCreate(Bundle icicle) {
restoreHasCurrentPermissionRequest(icicle);
+ if (isAtLeastO()) {
+ getApplication().dispatchActivityPreCreated(this, icicle);
+ }
onCreate(icicle);
- mActivityTransitionState.readState(icicle);
- performCreateCommon();
+ performCreateCommon(icicle);
}
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
restoreHasCurrentPermissionRequest(icicle);
+ if (isAtLeastO()) {
+ getApplication().dispatchActivityPreCreated(this, icicle);
+ }
onCreate(icicle, persistentState);
- mActivityTransitionState.readState(icicle);
- performCreateCommon();
+ performCreateCommon(icicle);
}
final void performStart() {
@@ -6811,6 +6837,9 @@
}
mActivityTransitionState.enterReady(this);
+ if (isAtLeastO()) {
+ getApplication().dispatchActivityStarted(this);
+ }
}
final void performRestart() {
@@ -6886,7 +6915,9 @@
mFragments.dispatchResume();
mFragments.execPendingActions();
-
+ if (isAtLeastO()) {
+ getApplication().dispatchActivityResumed(this);
+ }
onPostResume();
if (!mCalled) {
throw new SuperNotCalledException(
@@ -6901,13 +6932,15 @@
mCalled = false;
onPause();
mResumed = false;
+ if (isAtLeastO()) {
+ getApplication().dispatchActivityPaused(this);
+ }
if (!mCalled && getApplicationInfo().targetSdkVersion
>= android.os.Build.VERSION_CODES.GINGERBREAD) {
throw new SuperNotCalledException(
"Activity " + mComponent.toShortString() +
" did not call through to super.onPause()");
}
- mResumed = false;
}
final void performUserLeaving() {
@@ -6918,7 +6951,7 @@
final void performStop(boolean preserveWindow) {
mDoReportFullyDrawn = false;
mFragments.doLoaderStop(mChangingConfigurations /*retain*/);
-
+ boolean dispatchActivityStopped = !mStopped;
if (!mStopped) {
if (mWindow != null) {
mWindow.closeAllPanels();
@@ -6955,6 +6988,9 @@
mStopped = true;
}
mResumed = false;
+ if (dispatchActivityStopped && isAtLeastO()) {
+ getApplication().dispatchActivityStopped(this);
+ }
}
final void performDestroy() {
@@ -6966,6 +7002,13 @@
if (mVoiceInteractor != null) {
mVoiceInteractor.detachActivity();
}
+ if (isAtLeastO()) {
+ getApplication().dispatchActivityDestroyed(this);
+ }
+ }
+
+ private boolean isAtLeastO() {
+ return getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O;
}
final void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 156df36..03efe68 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -55,6 +55,7 @@
public LoadedApk mLoadedApk;
public interface ActivityLifecycleCallbacks {
+ default void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {}
void onActivityCreated(Activity activity, Bundle savedInstanceState);
void onActivityStarted(Activity activity);
void onActivityResumed(Activity activity);
@@ -190,6 +191,16 @@
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
+ /* package */ void dispatchActivityPreCreated(Activity activity, Bundle savedInstanceState) {
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = 0; i < callbacks.length; i++) {
+ ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPreCreated(activity,
+ savedInstanceState);
+ }
+ }
+ }
+
/* package */ void dispatchActivityCreated(Activity activity, Bundle savedInstanceState) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
diff --git a/core/tests/coretests/src/android/provider/SettingsTest.java b/core/tests/coretests/src/android/provider/SettingsTest.java
index 3d21817..d76980a 100644
--- a/core/tests/coretests/src/android/provider/SettingsTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsTest.java
@@ -244,6 +244,7 @@
Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES,
Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE,
Settings.Global.NETWORK_AVOID_BAD_WIFI,
+ Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
Settings.Global.NETWORK_PREFERENCE,
Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
Settings.Global.NETWORK_SCORER_APP,
@@ -331,6 +332,7 @@
Settings.Global.WFC_IMS_MODE,
Settings.Global.WFC_IMS_ROAMING_ENABLED,
Settings.Global.WFC_IMS_ROAMING_MODE,
+ Settings.Global.WIFI_BADGING_THRESHOLDS,
Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS,
Settings.Global.WIFI_COUNTRY_CODE,
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
diff --git a/data/keyboards/qwerty.kl b/data/keyboards/qwerty.kl
index 4186007..2fd99ab 100644
--- a/data/keyboards/qwerty.kl
+++ b/data/keyboards/qwerty.kl
@@ -123,3 +123,9 @@
key 166 MEDIA_STOP
key 167 MEDIA_RECORD
key 168 MEDIA_REWIND
+
+key 142 SLEEP
+key 581 STEM_PRIMARY
+key 582 STEM_1
+key 583 STEM_2
+key 584 STEM_3
diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java
index 540c328..60dbe00 100644
--- a/media/java/android/media/AudioFocusInfo.java
+++ b/media/java/android/media/AudioFocusInfo.java
@@ -29,9 +29,10 @@
@SystemApi
public final class AudioFocusInfo implements Parcelable {
- private AudioAttributes mAttributes;
- private String mClientId;
- private String mPackageName;
+ private final AudioAttributes mAttributes;
+ private final int mClientUid;
+ private final String mClientId;
+ private final String mPackageName;
private int mGainRequest;
private int mLossReceived;
private int mFlags;
@@ -47,9 +48,10 @@
* @param flags
* @hide
*/
- public AudioFocusInfo(AudioAttributes aa, String clientId, String packageName,
+ public AudioFocusInfo(AudioAttributes aa, int clientUid, String clientId, String packageName,
int gainRequest, int lossReceived, int flags) {
mAttributes = aa == null ? new AudioAttributes.Builder().build() : aa;
+ mClientUid = clientUid;
mClientId = clientId == null ? "" : clientId;
mPackageName = packageName == null ? "" : packageName;
mGainRequest = gainRequest;
@@ -66,6 +68,9 @@
public AudioAttributes getAttributes() { return mAttributes; }
@SystemApi
+ public int getClientUid() { return mClientUid; }
+
+ @SystemApi
public String getClientId() { return mClientId; }
@SystemApi
@@ -111,6 +116,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
mAttributes.writeToParcel(dest, flags);
+ dest.writeInt(mClientUid);
dest.writeString(mClientId);
dest.writeString(mPackageName);
dest.writeInt(mGainRequest);
@@ -121,7 +127,7 @@
@SystemApi
@Override
public int hashCode() {
- return Objects.hash(mAttributes, mClientId, mPackageName, mGainRequest, mFlags);
+ return Objects.hash(mAttributes, mClientUid, mClientId, mPackageName, mGainRequest, mFlags);
}
@SystemApi
@@ -137,6 +143,9 @@
if (!mAttributes.equals(other.mAttributes)) {
return false;
}
+ if (mClientUid != other.mClientUid) {
+ return false;
+ }
if (!mClientId.equals(other.mClientId)) {
return false;
}
@@ -161,6 +170,7 @@
public AudioFocusInfo createFromParcel(Parcel in) {
return new AudioFocusInfo(
AudioAttributes.CREATOR.createFromParcel(in), //AudioAttributes aa
+ in.readInt(), // int clientUid
in.readString(), //String clientId
in.readString(), //String packageName
in.readInt(), //int gainRequest
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index fa904cd..fb3f5b3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2430,6 +2430,23 @@
/**
* @hide
+ * Return the volume ramping time for a sound to be played after the given focus request,
+ * and to play a sound of the given attributes
+ * @param focusGain
+ * @param attr
+ * @return
+ */
+ public int getFocusRampTimeMs(int focusGain, AudioAttributes attr) {
+ IAudioService service = getService();
+ try {
+ return service.getFocusRampTimeMs(focusGain, attr);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
* Used internally by telephony package to abandon audio focus, typically after a call or
* when ringing ends and the call is rejected or not answered.
* Should match one or more calls to {@link #requestAudioFocusForCall(int, int)}.
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index b23f5fd..5c9f270 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1880,6 +1880,26 @@
if (mState != STATE_INITIALIZED) {
throw new IllegalStateException("play() called on uninitialized AudioTrack.");
}
+ //FIXME use lambda to pass startImpl to superclass
+ final int delay = getStartDelayMs();
+ if (delay == 0) {
+ startImpl();
+ } else {
+ new Thread() {
+ public void run() {
+ try {
+ Thread.sleep(delay);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ baseSetStartDelayMs(0);
+ startImpl();
+ }
+ }.start();
+ }
+ }
+
+ private void startImpl() {
synchronized(mPlayStateLock) {
baseStart();
native_start();
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index a76a328..fa4796a 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -194,5 +194,7 @@
void disableRingtoneSync();
+ int getFocusRampTimeMs(in int focusGain, in AudioAttributes attr);
+
// WARNING: read warning at top of file, it is recommended to add new methods at the end
}
diff --git a/media/java/android/media/IPlayer.aidl b/media/java/android/media/IPlayer.aidl
index ccb60f7..f068a0a 100644
--- a/media/java/android/media/IPlayer.aidl
+++ b/media/java/android/media/IPlayer.aidl
@@ -25,4 +25,6 @@
oneway void pause();
oneway void stop();
oneway void setVolume(float vol);
+ oneway void setPan(float pan);
+ oneway void setStartDelayMs(int delayMs);
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 62abdefd..03dc2ea 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1245,6 +1245,26 @@
* @throws IllegalStateException if it is called in an invalid state
*/
public void start() throws IllegalStateException {
+ //FIXME use lambda to pass startImpl to superclass
+ final int delay = getStartDelayMs();
+ if (delay == 0) {
+ startImpl();
+ } else {
+ new Thread() {
+ public void run() {
+ try {
+ Thread.sleep(delay);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ baseSetStartDelayMs(0);
+ startImpl();
+ }
+ }.start();
+ }
+ }
+
+ private void startImpl() {
baseStart();
stayAwake(true);
_start();
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index bd0a1b4..d2b052a 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -58,14 +58,17 @@
// for AppOps
private IAppOpsService mAppOps;
private IAppOpsCallback mAppOpsCallback;
- private boolean mHasAppOpsPlayAudio = true;
- private final Object mAppOpsLock = new Object();
+ private boolean mHasAppOpsPlayAudio = true; // sync'd on mLock
+ private final Object mLock = new Object();
private final int mImplType;
// uniquely identifies the Player Interface throughout the system (P I Id)
private int mPlayerIId;
- private int mState;
+ private int mState; // sync'd on mLock
+ private int mStartDelayMs = 0; // sync'd on mLock
+ private float mPanMultiplierL = 1.0f; // sync'd on mLock
+ private float mPanMultiplierR = 1.0f; // sync'd on mLock
/**
* Constructor. Must be given audio attributes, as they are required for AppOps.
@@ -89,11 +92,13 @@
IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
mAppOps = IAppOpsService.Stub.asInterface(b);
// initialize mHasAppOpsPlayAudio
- updateAppOpsPlayAudio_sync();
+ synchronized (mLock) {
+ updateAppOpsPlayAudio_sync();
+ }
// register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
mAppOpsCallback = new IAppOpsCallback.Stub() {
public void opChanged(int op, int uid, String packageName) {
- synchronized (mAppOpsLock) {
+ synchronized (mLock) {
if (op == AppOpsManager.OP_PLAY_AUDIO) {
updateAppOpsPlayAudio_sync();
}
@@ -130,7 +135,7 @@
} catch (RemoteException e) {
Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e);
}
- synchronized (mAppOpsLock) {
+ synchronized (mLock) {
mAttributes = attr;
updateAppOpsPlayAudio_sync();
}
@@ -139,23 +144,39 @@
void baseStart() {
if (DEBUG) { Log.v(TAG, "baseStart() piid=" + mPlayerIId); }
try {
- mState = AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
- getService().playerEvent(mPlayerIId, mState);
+ synchronized (mLock) {
+ mState = AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
+ getService().playerEvent(mPlayerIId, mState);
+ }
} catch (RemoteException e) {
Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e);
}
- synchronized (mAppOpsLock) {
+ synchronized (mLock) {
if (isRestricted_sync()) {
playerSetVolume(true/*muting*/,0, 0);
}
}
}
+ void baseSetStartDelayMs(int delayMs) {
+ synchronized(mLock) {
+ mStartDelayMs = Math.max(delayMs, 0);
+ }
+ }
+
+ protected int getStartDelayMs() {
+ synchronized(mLock) {
+ return mStartDelayMs;
+ }
+ }
+
void basePause() {
if (DEBUG) { Log.v(TAG, "basePause() piid=" + mPlayerIId); }
try {
- mState = AudioPlaybackConfiguration.PLAYER_STATE_PAUSED;
- getService().playerEvent(mPlayerIId, mState);
+ synchronized (mLock) {
+ mState = AudioPlaybackConfiguration.PLAYER_STATE_PAUSED;
+ getService().playerEvent(mPlayerIId, mState);
+ }
} catch (RemoteException e) {
Log.e(TAG, "Error talking to audio service, PAUSED state will not be tracked", e);
}
@@ -164,26 +185,45 @@
void baseStop() {
if (DEBUG) { Log.v(TAG, "baseStop() piid=" + mPlayerIId); }
try {
- mState = AudioPlaybackConfiguration.PLAYER_STATE_STOPPED;
- getService().playerEvent(mPlayerIId, mState);
+ synchronized (mLock) {
+ mState = AudioPlaybackConfiguration.PLAYER_STATE_STOPPED;
+ getService().playerEvent(mPlayerIId, mState);
+ }
} catch (RemoteException e) {
Log.e(TAG, "Error talking to audio service, STOPPED state will not be tracked", e);
}
}
+ void baseSetPan(float pan) {
+ final float p = Math.min(Math.max(-1.0f, pan), 1.0f);
+ synchronized (mLock) {
+ if (p >= 0.0f) {
+ mPanMultiplierL = 1.0f - p;
+ mPanMultiplierR = 1.0f;
+ } else {
+ mPanMultiplierL = 1.0f;
+ mPanMultiplierR = 1.0f + p;
+ }
+ }
+ baseSetVolume(mLeftVolume, mRightVolume);
+ }
+
void baseSetVolume(float leftVolume, float rightVolume) {
- synchronized (mAppOpsLock) {
+ final boolean hasAppOpsPlayAudio;
+ synchronized (mLock) {
mLeftVolume = leftVolume;
mRightVolume = rightVolume;
+ hasAppOpsPlayAudio = mHasAppOpsPlayAudio;
if (isRestricted_sync()) {
return;
}
}
- playerSetVolume(false/*muting*/,leftVolume, rightVolume);
+ playerSetVolume(!hasAppOpsPlayAudio/*muting*/,
+ leftVolume * mPanMultiplierL, rightVolume * mPanMultiplierR);
}
int baseSetAuxEffectSendLevel(float level) {
- synchronized (mAppOpsLock) {
+ synchronized (mLock) {
mAuxEffectSendLevel = level;
if (isRestricted_sync()) {
return AudioSystem.SUCCESS;
@@ -199,9 +239,11 @@
void baseRelease() {
if (DEBUG) { Log.v(TAG, "baseRelease() piid=" + mPlayerIId + " state=" + mState); }
try {
- if (mState != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
- getService().releasePlayer(mPlayerIId);
- mState = AudioPlaybackConfiguration.PLAYER_STATE_RELEASED;
+ synchronized (mLock) {
+ if (mState != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
+ getService().releasePlayer(mPlayerIId);
+ mState = AudioPlaybackConfiguration.PLAYER_STATE_RELEASED;
+ }
}
} catch (RemoteException e) {
Log.e(TAG, "Error talking to audio service, the player will still be tracked", e);
@@ -215,7 +257,7 @@
/**
* To be called whenever a condition that might affect audibility of this player is updated.
- * Must be called synchronized on mAppOpsLock.
+ * Must be called synchronized on mLock.
*/
void updateAppOpsPlayAudio_sync() {
boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio;
@@ -237,7 +279,8 @@
Log.v(TAG, "updateAppOpsPlayAudio: unmuting player, vol=" + mLeftVolume
+ "/" + mRightVolume);
}
- playerSetVolume(false/*muting*/, mLeftVolume, mRightVolume);
+ playerSetVolume(false/*muting*/,
+ mLeftVolume * mPanMultiplierL, mRightVolume * mPanMultiplierR);
playerSetAuxEffectSendLevel(false/*muting*/, mAuxEffectSendLevel);
} else {
if (DEBUG_APP_OPS) {
@@ -297,6 +340,14 @@
return sService;
}
+ /**
+ * @hide
+ * @param delayMs
+ */
+ public void setStartDelayMs(int delayMs) {
+ baseSetStartDelayMs(delayMs);
+ }
+
//=====================================================================
// Abstract methods a subclass needs to implement
/**
@@ -335,6 +386,16 @@
public void setVolume(float vol) {
baseSetVolume(vol, vol);
}
+
+ @Override
+ public void setPan(float pan) {
+ baseSetPan(pan);
+ }
+
+ @Override
+ public void setStartDelayMs(int delayMs) {
+ baseSetStartDelayMs(delayMs);
+ }
};
//=====================================================================
diff --git a/media/java/android/media/PlayerProxy.java b/media/java/android/media/PlayerProxy.java
index 171be27..1a2c668 100644
--- a/media/java/android/media/PlayerProxy.java
+++ b/media/java/android/media/PlayerProxy.java
@@ -52,10 +52,9 @@
// Methods matching the IPlayer interface
/**
* @hide
- * @throws IllegalStateException
*/
@SystemApi
- public void start() throws IllegalStateException {
+ public void start() {
try {
mConf.getIPlayer().start();
} catch (NullPointerException|RemoteException e) {
@@ -66,10 +65,9 @@
/**
* @hide
- * @throws IllegalStateException
*/
@SystemApi
- public void pause() throws IllegalStateException {
+ public void pause() {
try {
mConf.getIPlayer().pause();
} catch (NullPointerException|RemoteException e) {
@@ -80,10 +78,9 @@
/**
* @hide
- * @throws IllegalStateException
*/
@SystemApi
- public void stop() throws IllegalStateException {
+ public void stop() {
try {
mConf.getIPlayer().stop();
} catch (NullPointerException|RemoteException e) {
@@ -94,10 +91,10 @@
/**
* @hide
- * @throws IllegalStateException
+ * @param vol
*/
@SystemApi
- public void setVolume(float vol) throws IllegalStateException {
+ public void setVolume(float vol) {
try {
mConf.getIPlayer().setVolume(vol);
} catch (NullPointerException|RemoteException e) {
@@ -106,4 +103,33 @@
}
}
+ /**
+ * @hide
+ * @param pan
+ */
+ @SystemApi
+ public void setPan(float pan) {
+ try {
+ mConf.getIPlayer().setPan(pan);
+ } catch (NullPointerException|RemoteException e) {
+ throw new IllegalStateException(
+ "No player to proxy for setPan operation, player already released?", e);
+ }
+ }
+
+ /**
+ * @hide
+ * @param delayMs
+ */
+ @SystemApi
+ public void setStartDelayMs(int delayMs) {
+ try {
+ mConf.getIPlayer().setStartDelayMs(delayMs);
+ } catch (NullPointerException|RemoteException e) {
+ throw new IllegalStateException(
+ "No player to proxy for setStartDelayMs operation, player already released?",
+ e);
+ }
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index 9788903..5cd7e41 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -41,7 +41,7 @@
public class NotificationPlayer implements OnCompletionListener, OnErrorListener {
private static final int PLAY = 1;
private static final int STOP = 2;
- private static final boolean mDebug = false;
+ private static final boolean DEBUG = false;
private static final class Command {
int code;
@@ -97,17 +97,18 @@
if (!audioManager.isMusicActiveRemotely()) {
synchronized(mQueueAudioFocusLock) {
if (mAudioManagerWithAudioFocus == null) {
- if (mDebug) Log.d(mTag, "requesting AudioFocus");
+ if (DEBUG) Log.d(mTag, "requesting AudioFocus");
+ int focusGain = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
if (mCmd.looping) {
- audioManager.requestAudioFocus(null, mCmd.attributes,
- AudioManager.AUDIOFOCUS_GAIN, 0);
- } else {
- audioManager.requestAudioFocus(null, mCmd.attributes,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
+ focusGain = AudioManager.AUDIOFOCUS_GAIN;
}
+ mNotificationRampTimeMs = audioManager.getFocusRampTimeMs(
+ focusGain, mCmd.attributes);
+ audioManager.requestAudioFocus(null, mCmd.attributes,
+ focusGain, 0);
mAudioManagerWithAudioFocus = audioManager;
} else {
- if (mDebug) Log.d(mTag, "AudioFocus was previously requested");
+ if (DEBUG) Log.d(mTag, "AudioFocus was previously requested");
}
}
}
@@ -119,6 +120,9 @@
// command are issued, and on which it receives the completion callbacks.
player.setOnCompletionListener(NotificationPlayer.this);
player.setOnErrorListener(NotificationPlayer.this);
+ if (DEBUG) { Log.d(mTag, "notification will be delayed by "
+ + mNotificationRampTimeMs + "ms"); }
+ player.setStartDelayMs(mNotificationRampTimeMs);
player.start();
if (mPlayer != null) {
mPlayer.release();
@@ -139,7 +143,7 @@
// is playing, let it continue until we're done, so there
// is less of a glitch.
try {
- if (mDebug) Log.d(mTag, "Starting playback");
+ if (DEBUG) Log.d(mTag, "Starting playback");
//-----------------------------------
// This is were we deviate from the AsyncPlayer implementation and create the
// MediaPlayer in a new thread with which we're synchronized
@@ -179,17 +183,17 @@
Command cmd = null;
synchronized (mCmdQueue) {
- if (mDebug) Log.d(mTag, "RemoveFirst");
+ if (DEBUG) Log.d(mTag, "RemoveFirst");
cmd = mCmdQueue.removeFirst();
}
switch (cmd.code) {
case PLAY:
- if (mDebug) Log.d(mTag, "PLAY");
+ if (DEBUG) Log.d(mTag, "PLAY");
startSound(cmd);
break;
case STOP:
- if (mDebug) Log.d(mTag, "STOP");
+ if (DEBUG) Log.d(mTag, "STOP");
if (mPlayer != null) {
long delay = SystemClock.uptimeMillis() - cmd.requestTime;
if (delay > 1000) {
@@ -232,11 +236,11 @@
public void onCompletion(MediaPlayer mp) {
synchronized(mQueueAudioFocusLock) {
if (mAudioManagerWithAudioFocus != null) {
- if (mDebug) Log.d(mTag, "onCompletion() abandonning AudioFocus");
+ if (DEBUG) Log.d(mTag, "onCompletion() abandonning AudioFocus");
mAudioManagerWithAudioFocus.abandonAudioFocus(null);
mAudioManagerWithAudioFocus = null;
} else {
- if (mDebug) Log.d(mTag, "onCompletion() no need to abandon AudioFocus");
+ if (DEBUG) Log.d(mTag, "onCompletion() no need to abandon AudioFocus");
}
}
// if there are no more sounds to play, end the Looper to listen for media completion
@@ -267,6 +271,7 @@
private PowerManager.WakeLock mWakeLock;
private final Object mQueueAudioFocusLock = new Object();
private AudioManager mAudioManagerWithAudioFocus; // synchronized on mQueueAudioFocusLock
+ private int mNotificationRampTimeMs = 0;
// The current state according to the caller. Reality lags behind
// because of the asynchronous nature of this class.
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 0292db9..8020f0e 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -1660,15 +1660,15 @@
@Override
public void updateStatusIcon(IBinder token, String packageName, int iconId) {
- long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mMethodMap) {
- if (!calledWithValidToken(token)) {
- final int uid = Binder.getCallingUid();
- Slog.e(TAG, "Ignoring updateStatusIcon due to an invalid token. uid:" + uid
- + " token:" + token);
- return;
- }
+ synchronized (mMethodMap) {
+ if (!calledWithValidToken(token)) {
+ final int uid = Binder.getCallingUid();
+ Slog.e(TAG, "Ignoring updateStatusIcon due to an invalid token. uid:" + uid
+ + " token:" + token);
+ return;
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
if (iconId == 0) {
if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
if (mStatusBar != null) {
@@ -1693,9 +1693,9 @@
mStatusBar.setIconVisibility(mSlotIme, true);
}
}
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
- } finally {
- Binder.restoreCallingIdentity(ident);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index b2b3e61..4411794 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -4093,18 +4093,6 @@
void setDockedStackMinimized(boolean minimized) {
mIsDockMinimized = minimized;
- if (minimized) {
- // Docked stack is not visible, no need to confirm credentials for its top activity.
- return;
- }
- final ActivityStack dockedStack = getStack(StackId.DOCKED_STACK_ID);
- if (dockedStack == null) {
- return;
- }
- final ActivityRecord top = dockedStack.topRunningActivityLocked();
- if (top != null && mService.mUserController.shouldConfirmCredentials(top.userId)) {
- mService.mActivityStarter.showConfirmDeviceCredential(top.userId);
- }
}
private final class ActivityStackSupervisorHandler extends Handler {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 96f732e..73ef88b 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -608,50 +608,6 @@
}
}
- void showConfirmDeviceCredential(int userId) {
- // First, retrieve the stack that we want to resume after credential is confirmed.
- ActivityStack targetStack;
- ActivityStack fullscreenStack =
- mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID);
- if (fullscreenStack != null &&
- fullscreenStack.getStackVisibilityLocked(null) != ActivityStack.STACK_INVISIBLE) {
- // Single window case and the case that the docked stack is shown with fullscreen stack.
- targetStack = fullscreenStack;
- } else {
- // The case that the docked stack is shown with recent.
- targetStack = mSupervisor.getStack(HOME_STACK_ID);
- }
- if (targetStack == null) {
- return;
- }
- final KeyguardManager km = (KeyguardManager) mService.mContext
- .getSystemService(Context.KEYGUARD_SERVICE);
- final Intent credential =
- km.createConfirmDeviceCredentialIntent(null, null, userId);
- // For safety, check null here in case users changed the setting after the checking.
- if (credential == null) {
- return;
- }
- final ActivityRecord activityRecord = targetStack.topRunningActivityLocked();
- if (activityRecord != null) {
- final IIntentSender target = mService.getIntentSenderLocked(
- ActivityManager.INTENT_SENDER_ACTIVITY,
- activityRecord.launchedFromPackage,
- activityRecord.launchedFromUid,
- activityRecord.userId,
- null, null, 0,
- new Intent[] { activityRecord.intent },
- new String[] { activityRecord.resolvedType },
- PendingIntent.FLAG_CANCEL_CURRENT |
- PendingIntent.FLAG_ONE_SHOT |
- PendingIntent.FLAG_IMMUTABLE,
- null);
- credential.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
- // Show confirm credentials activity.
- startConfirmCredentialIntent(credential, null);
- }
- }
-
void startConfirmCredentialIntent(Intent intent, Bundle optionsBundle) {
intent.addFlags(FLAG_ACTIVITY_NEW_TASK |
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS |
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 213041e..ef792b0 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -688,7 +688,7 @@
mSettingsObserver = new SettingsObserver();
createStreamStates();
- mMediaFocusControl = new MediaFocusControl(mContext);
+ mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
readAndSetLowRamDevice();
@@ -5581,6 +5581,10 @@
return mMediaFocusControl.getCurrentAudioFocus();
}
+ public int getFocusRampTimeMs(int focusGain, AudioAttributes attr) {
+ return mMediaFocusControl.getFocusRampTimeMs(focusGain, attr);
+ }
+
private boolean readCameraSoundForced() {
return SystemProperties.getBoolean("audio.camerasound.force", false) ||
mContext.getResources().getBoolean(
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index cc181141..5275c05 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -17,6 +17,7 @@
package com.android.server.audio;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
import android.media.AudioManager;
@@ -47,6 +48,7 @@
private final String mPackageName;
private final int mCallingUid;
private final MediaFocusControl mFocusController; // never null
+
/**
* the audio focus gain request that caused the addition of this object in the focus stack.
*/
@@ -62,6 +64,10 @@
*/
private int mFocusLossReceived;
/**
+ * whether this focus owner listener was notified when it lost focus
+ */
+ private boolean mFocusLossWasNotified;
+ /**
* the audio attributes associated with the focus request
*/
private final AudioAttributes mAttributes;
@@ -124,6 +130,10 @@
return mCallingUid == uid;
}
+ int getClientUid() {
+ return mCallingUid;
+ }
+
String getClientId() {
return mClientId;
}
@@ -195,6 +205,7 @@
+ " -- gain: " + focusGainToString()
+ " -- flags: " + flagsToString(mGrantFlags)
+ " -- loss: " + focusLossToString()
+ + " -- notified: " + mFocusLossWasNotified
+ " -- uid: " + mCallingUid
+ " -- attr: " + mAttributes);
}
@@ -263,9 +274,9 @@
/**
* Called synchronized on MediaFocusControl.mAudioFocusLock
*/
- void handleExternalFocusGain(int focusGain) {
+ void handleExternalFocusGain(int focusGain, final FocusRequester fr) {
int focusLoss = focusLossForGainRequest(focusGain);
- handleFocusLoss(focusLoss);
+ handleFocusLoss(focusLoss, fr);
}
/**
@@ -273,6 +284,7 @@
*/
void handleFocusGain(int focusGain) {
try {
+ final int oldLoss = mFocusLossReceived;
mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(),
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
@@ -282,8 +294,13 @@
Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
+ mClientId);
}
- fd.dispatchAudioFocusChange(focusGain, mClientId);
+ if (mFocusLossWasNotified) {
+ fd.dispatchAudioFocusChange(focusGain, mClientId);
+ } else if (oldLoss == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
+ mFocusController.unduckPlayers(this);
+ }
}
+ mFocusLossWasNotified = false;
} catch (android.os.RemoteException e) {
Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
}
@@ -292,10 +309,11 @@
/**
* Called synchronized on MediaFocusControl.mAudioFocusLock
*/
- void handleFocusLoss(int focusLoss) {
+ void handleFocusLoss(int focusLoss, @Nullable final FocusRequester fr) {
try {
if (focusLoss != mFocusLossReceived) {
mFocusLossReceived = focusLoss;
+ mFocusLossWasNotified = false;
// before dispatching a focus loss, check if the following conditions are met:
// 1/ the framework is not supposed to notify the focus loser on a DUCK loss
// 2/ it is a DUCK loss
@@ -313,6 +331,27 @@
toAudioFocusInfo(), false /* wasDispatched */);
return;
}
+
+ // check enforcement by the framework
+ boolean handled = false;
+ if (focusLoss == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
+ && MediaFocusControl.ENFORCE_DUCKING
+ && fr != null) {
+ // candidate for enforcement by the framework
+ if (fr.mCallingUid != this.mCallingUid) {
+ handled = mFocusController.duckPlayers(fr, this);
+ } // else: the focus change is within the same app, so let the dispatching
+ // happen as if the framework was not involved.
+ }
+
+ if (handled) {
+ if (DEBUG) {
+ Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
+ + " to " + mClientId + ", ducking implemented by framework");
+ }
+ return; // with mFocusLossWasNotified = false
+ }
+
final IAudioFocusDispatcher fd = mFocusDispatcher;
if (fd != null) {
if (DEBUG) {
@@ -321,6 +360,7 @@
}
mFocusController.notifyExtPolicyFocusLoss_syncAf(
toAudioFocusInfo(), true /* wasDispatched */);
+ mFocusLossWasNotified = true;
fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
}
}
@@ -330,7 +370,7 @@
}
AudioFocusInfo toAudioFocusInfo() {
- return new AudioFocusInfo(mAttributes, mClientId, mPackageName,
+ return new AudioFocusInfo(mAttributes, mCallingUid, mClientId, mPackageName,
mFocusGainRequest, mFocusLossReceived, mGrantFlags);
}
}
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 206834e..a1c5653 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -40,16 +40,24 @@
* @hide
*
*/
-public class MediaFocusControl {
+public class MediaFocusControl implements PlayerFocusEnforcer {
private static final String TAG = "MediaFocusControl";
+ /**
+ * set to true so the framework enforces ducking itself, without communicating to apps
+ * that they lost focus.
+ */
+ static final boolean ENFORCE_DUCKING = false;
+
private final Context mContext;
private final AppOpsManager mAppOps;
+ private PlayerFocusEnforcer mFocusEnforcer; // never null
- protected MediaFocusControl(Context cntxt) {
+ protected MediaFocusControl(Context cntxt, PlayerFocusEnforcer pfe) {
mContext = cntxt;
mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
+ mFocusEnforcer = pfe;
}
protected void dump(PrintWriter pw) {
@@ -58,6 +66,17 @@
dumpFocusStack(pw);
}
+ //=================================================================
+ // PlayerFocusEnforcer implementation
+ @Override
+ public boolean duckPlayers(FocusRequester winner, FocusRequester loser) {
+ return mFocusEnforcer.duckPlayers(winner, loser);
+ }
+
+ @Override
+ public void unduckPlayers(FocusRequester winner) {
+ mFocusEnforcer.unduckPlayers(winner);
+ }
//==========================================================================================
// AudioFocus
@@ -75,7 +94,7 @@
if (!mFocusStack.empty()) {
// notify the current focus owner it lost focus after removing it from stack
final FocusRequester exFocusOwner = mFocusStack.pop();
- exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS);
+ exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null);
exFocusOwner.release();
}
}
@@ -97,12 +116,12 @@
* Focus is requested, propagate the associated loss throughout the stack.
* @param focusGain the new focus gain that will later be added at the top of the stack
*/
- private void propagateFocusLossFromGain_syncAf(int focusGain) {
+ private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr) {
// 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
Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
while(stackIterator.hasNext()) {
- stackIterator.next().handleExternalFocusGain(focusGain);
+ stackIterator.next().handleExternalFocusGain(focusGain, fr);
}
}
@@ -237,7 +256,7 @@
Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
new Exception());
// no exclusive owner, push at top of stack, focus is granted, propagate change
- propagateFocusLossFromGain_syncAf(nfr.getGainRequest());
+ propagateFocusLossFromGain_syncAf(nfr.getGainRequest(), nfr);
mFocusStack.push(nfr);
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
} else {
@@ -381,6 +400,38 @@
}
}
+ /**
+ * Return the volume ramp time expected before playback with the given AudioAttributes would
+ * start after gaining audio focus.
+ * @param attr attributes of the sound about to start playing
+ * @return time in ms
+ */
+ protected int getFocusRampTimeMs(int focusGain, AudioAttributes attr) {
+ switch (attr.getUsage()) {
+ case AudioAttributes.USAGE_MEDIA:
+ case AudioAttributes.USAGE_GAME:
+ return 1000;
+ case AudioAttributes.USAGE_ALARM:
+ case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
+ case AudioAttributes.USAGE_ASSISTANT:
+ case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
+ case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+ return 700;
+ case AudioAttributes.USAGE_VOICE_COMMUNICATION:
+ case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING:
+ case AudioAttributes.USAGE_NOTIFICATION:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+ case AudioAttributes.USAGE_NOTIFICATION_EVENT:
+ case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
+ return 500;
+ case AudioAttributes.USAGE_UNKNOWN:
+ default:
+ return 0;
+ }
+ }
+
/** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */
protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) {
@@ -463,7 +514,7 @@
} else {
// propagate the focus change through the stack
if (!mFocusStack.empty()) {
- propagateFocusLossFromGain_syncAf(focusChangeHint);
+ propagateFocusLossFromGain_syncAf(focusChangeHint, nfr);
}
// push focus requester at the top of the audio focus stack
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index c6b2cf6..816d5fe 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -37,15 +37,16 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
/**
* Class to receive and dispatch updates from AudioSystem about recording configurations.
*/
public final class PlaybackActivityMonitor
- implements AudioPlaybackConfiguration.PlayerDeathMonitor {
+ implements AudioPlaybackConfiguration.PlayerDeathMonitor, PlayerFocusEnforcer {
public final static String TAG = "AudioService.PlaybackActivityMonitor";
- private final static boolean DEBUG = false;
+ private final static boolean DEBUG = true;
private ArrayList<PlayMonitorClient> mClients = new ArrayList<PlayMonitorClient>();
// a public client is one that needs an anonymized version of the playback configurations, we
@@ -134,12 +135,18 @@
}
protected void dump(PrintWriter pw) {
+ // players
pw.println("\nPlaybackActivityMonitor dump time: "
+ DateFormat.getTimeInstance().format(new Date()));
synchronized(mPlayerLock) {
for (AudioPlaybackConfiguration conf : mPlayers.values()) {
conf.dump(pw);
}
+ // ducked players
+ pw.println("\n ducked player piids:");
+ for (int piid : mDuckedPlayers) {
+ pw.println(" " + piid);
+ }
}
}
@@ -211,7 +218,7 @@
List<AudioPlaybackConfiguration> sysConfigs) {
ArrayList<AudioPlaybackConfiguration> publicConfigs =
new ArrayList<AudioPlaybackConfiguration>();
- // only add active anonymized configurations,
+ // only add active anonymized configurations,
for (AudioPlaybackConfiguration config : sysConfigs) {
if (config.isActive()) {
publicConfigs.add(AudioPlaybackConfiguration.anonymizedCopy(config));
@@ -220,6 +227,82 @@
return publicConfigs;
}
+
+ //=================================================================
+ // PlayerFocusEnforcer implementation
+ private final ArrayList<Integer> mDuckedPlayers = new ArrayList<Integer>();
+
+ @Override
+ public boolean duckPlayers(FocusRequester winner, FocusRequester loser) {
+ if (DEBUG) {
+ Log.v(TAG, String.format("duckPlayers: uids winner=%d loser=%d",
+ winner.getClientUid(), loser.getClientUid())); }
+ synchronized (mPlayerLock) {
+ if (mPlayers.isEmpty()) {
+ return true;
+ }
+ final Set<Integer> piidSet = mPlayers.keySet();
+ final Iterator<Integer> piidIterator = piidSet.iterator();
+ // find which players to duck
+ while (piidIterator.hasNext()) {
+ final Integer piid = piidIterator.next();
+ final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+ if (!winner.hasSameUid(apc.getClientUid())
+ && loser.hasSameUid(apc.getClientUid())
+ && apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED)
+ {
+ if (mDuckedPlayers.contains(piid)) {
+ if (DEBUG) { Log.v(TAG, "player " + piid + " already ducked"); }
+ } else if (apc.getAudioAttributes().getContentType() ==
+ AudioAttributes.CONTENT_TYPE_SPEECH) {
+ // the player is speaking, ducking will make the speech unintelligible
+ // so let the app handle it instead
+ return false;
+ } else {
+ try {
+ if (DEBUG) { Log.v(TAG, "ducking player " + piid); }
+ //FIXME just a test before we have VolumeShape
+ apc.getPlayerProxy().setPan(-1.0f);
+ mDuckedPlayers.add(piid);
+ } catch (Exception e) {
+ Log.e(TAG, "Error ducking player " + piid, e);
+ // something went wrong trying to duck, so let the app handle it
+ // instead, it may know things we don't
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void unduckPlayers(FocusRequester winner) {
+ if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); }
+ synchronized (mPlayerLock) {
+ if (mDuckedPlayers.isEmpty()) {
+ return;
+ }
+ for (int piid : mDuckedPlayers) {
+ final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+ if (apc != null
+ && winner.hasSameUid(apc.getClientUid())) {
+ try {
+ if (DEBUG) { Log.v(TAG, "unducking player" + piid); }
+ //FIXME just a test before we have VolumeShape
+ apc.getPlayerProxy().setPan(0.0f);
+ mDuckedPlayers.remove(new Integer(piid));
+ } catch (Exception e) {
+ Log.e(TAG, "Error unducking player " + piid, e);
+ }
+ } else {
+ Log.e(TAG, "Error unducking player " + piid + ", player not found");
+ }
+ }
+ }
+ }
+
//=================================================================
// Track playback activity listeners
diff --git a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
new file mode 100644
index 0000000..acb4f0d
--- /dev/null
+++ b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 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;
+
+public interface PlayerFocusEnforcer {
+
+ /**
+ * Ducks the players associated with the "loser" focus owner (i.e. same UID). Returns true if
+ * at least one active player was found and ducked, false otherwise.
+ * @param winner
+ * @param loser
+ * @return
+ */
+ public boolean duckPlayers(FocusRequester winner, FocusRequester loser);
+
+ public void unduckPlayers(FocusRequester winner);
+}
\ No newline at end of file