diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 9d360db384c8c..2fa3a7233f543 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -28,7 +28,6 @@ import java.util.Collection; import android.annotation.IntDef; import android.annotation.NonNull; import android.app.ActivityThread; -import android.app.AppOpsManager; import android.content.Context; import android.os.Handler; import android.os.IBinder; @@ -41,7 +40,6 @@ import android.util.ArrayMap; import android.util.Log; import com.android.internal.annotations.GuardedBy; -import com.android.internal.app.IAppOpsService; /** * The AudioTrack class manages and plays a single audio resource for Java applications. @@ -78,7 +76,8 @@ import com.android.internal.app.IAppOpsService; * * AudioTrack is not final and thus permits subclasses, but such use is not recommended. */ -public class AudioTrack implements AudioRouting +public class AudioTrack extends PlayerBase + implements AudioRouting { //--------------------------------------------------------- // Constants @@ -271,7 +270,6 @@ public class AudioTrack implements AudioRouting */ private int mStreamType = AudioManager.STREAM_MUSIC; - private final AudioAttributes mAttributes; /** * The way audio is consumed by the audio sink, one of MODE_STATIC or MODE_STREAM. */ @@ -297,10 +295,6 @@ public class AudioTrack implements AudioRouting * Audio session ID */ private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE; - /** - * Reference to the app-ops service. - */ - private final IAppOpsService mAppOps; /** * HW_AV_SYNC track AV Sync Header */ @@ -448,11 +442,9 @@ public class AudioTrack implements AudioRouting public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId) throws IllegalArgumentException { + super(attributes); // mState already == STATE_UNINITIALIZED - if (attributes == null) { - throw new IllegalArgumentException("Illegal null AudioAttributes"); - } if (format == null) { throw new IllegalArgumentException("Illegal null AudioFormat"); } @@ -491,10 +483,6 @@ public class AudioTrack implements AudioRouting audioBuffSizeCheck(bufferSizeInBytes); mInitializationLooper = looper; - IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); - mAppOps = IAppOpsService.Stub.asInterface(b); - - mAttributes = new AudioAttributes.Builder(attributes).build(); if (sessionId < 0) { throw new IllegalArgumentException("Invalid audio session ID: "+sessionId); @@ -534,9 +522,8 @@ public class AudioTrack implements AudioRouting * OpenSLES interface is realized. */ /*package*/ AudioTrack(long nativeTrackInJavaObj) { + super(new AudioAttributes.Builder().build()); // "final"s - mAttributes = null; - mAppOps = null; mNativeTrackInJavaObj = 0; mJniData = 0; @@ -961,12 +948,14 @@ public class AudioTrack implements AudioRouting } catch(IllegalStateException ise) { // don't raise an exception, we're releasing the resources. } + baseRelease(); native_release(); mState = STATE_UNINITIALIZED; } @Override protected void finalize() { + baseRelease(); native_finalize(); } @@ -1492,21 +1481,22 @@ public class AudioTrack implements AudioRouting */ @Deprecated public int setStereoVolume(float leftGain, float rightGain) { - if (isRestricted()) { - return SUCCESS; - } if (mState == STATE_UNINITIALIZED) { return ERROR_INVALID_OPERATION; } - leftGain = clampGainOrLevel(leftGain); - rightGain = clampGainOrLevel(rightGain); - - native_setVolume(leftGain, rightGain); - + baseSetVolume(leftGain, rightGain); return SUCCESS; } + @Override + void playerSetVolume(float leftVolume, float rightVolume) { + leftVolume = clampGainOrLevel(leftVolume); + rightVolume = clampGainOrLevel(rightVolume); + + native_setVolume(leftVolume, rightVolume); + } + /** * Sets the specified output gain value on all channels of this track. @@ -1728,29 +1718,13 @@ public class AudioTrack implements AudioRouting if (mState != STATE_INITIALIZED) { throw new IllegalStateException("play() called on uninitialized AudioTrack."); } - if (isRestricted()) { - setVolume(0); - } + baseStart(); synchronized(mPlayStateLock) { native_start(); mPlayState = PLAYSTATE_PLAYING; } } - private boolean isRestricted() { - if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) { - return false; - } - try { - final int usage = AudioAttributes.usageForLegacyStreamType(mStreamType); - final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO, usage, - Process.myUid(), ActivityThread.currentPackageName()); - return mode != AppOpsManager.MODE_ALLOWED; - } catch (RemoteException e) { - return false; - } - } - /** * Stops playing the audio data. * When used on an instance created in {@link #MODE_STREAM} mode, audio will stop playing @@ -2375,12 +2349,14 @@ public class AudioTrack implements AudioRouting * {@link #ERROR_INVALID_OPERATION}, {@link #ERROR} */ public int setAuxEffectSendLevel(float level) { - if (isRestricted()) { - return SUCCESS; - } if (mState == STATE_UNINITIALIZED) { return ERROR_INVALID_OPERATION; } + return baseSetAuxEffectSendLevel(level); + } + + @Override + int playerSetAuxEffectSendLevel(float level) { level = clampGainOrLevel(level); int err = native_setAuxEffectSendLevel(level); return err == 0 ? SUCCESS : ERROR; diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 55d5f42fb78c0..cee7d60a9f368 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -20,7 +20,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; -import android.app.AppOpsManager; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; @@ -34,8 +33,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.Process; import android.os.PowerManager; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemProperties; import android.provider.Settings; import android.system.ErrnoException; @@ -56,7 +53,6 @@ import android.media.SubtitleData; import android.media.SubtitleTrack.RenderingWidget; import android.media.SyncParams; -import com.android.internal.app.IAppOpsService; import com.android.internal.util.Preconditions; import libcore.io.IoBridge; @@ -66,7 +62,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.Runnable; @@ -74,7 +69,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetSocketAddress; import java.util.BitSet; -import java.util.HashSet; import java.util.Map; import java.util.Scanner; import java.util.Set; @@ -561,7 +555,8 @@ import java.lang.ref.WeakReference; * thread by default has a Looper running). * */ -public class MediaPlayer implements SubtitleController.Listener +public class MediaPlayer extends PlayerBase + implements SubtitleController.Listener { /** Constant to retrieve only the new metadata since the last @@ -615,7 +610,6 @@ public class MediaPlayer implements SubtitleController.Listener private PowerManager.WakeLock mWakeLock = null; private boolean mScreenOnWhilePlaying; private boolean mStayAwake; - private final IAppOpsService mAppOps; private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE; private int mUsage = -1; private boolean mBypassInterruptionPolicy; @@ -628,6 +622,7 @@ public class MediaPlayer implements SubtitleController.Listener * result in an exception.

*/ public MediaPlayer() { + super(new AudioAttributes.Builder().build()); Looper looper; if ((looper = Looper.myLooper()) != null) { @@ -640,8 +635,6 @@ public class MediaPlayer implements SubtitleController.Listener mTimeProvider = new TimeProvider(this); mOpenSubtitleSources = new Vector(); - IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); - mAppOps = IAppOpsService.Stub.asInterface(b); /* Native setup requires a weak reference to our object. * It's easier to create it here than in C++. @@ -1211,29 +1204,13 @@ public class MediaPlayer implements SubtitleController.Listener * @throws IllegalStateException if it is called in an invalid state */ public void start() throws IllegalStateException { - if (isRestricted()) { - _setVolume(0, 0); - } + baseStart(); stayAwake(true); _start(); } private native void _start() throws IllegalStateException; - private boolean isRestricted() { - if (mBypassInterruptionPolicy) { - return false; - } - try { - final int usage = mUsage != -1 ? mUsage - : AudioAttributes.usageForLegacyStreamType(getAudioStreamType()); - final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO, usage, - Process.myUid(), ActivityThread.currentPackageName()); - return mode != AppOpsManager.MODE_ALLOWED; - } catch (RemoteException e) { - return false; - } - } private int getAudioStreamType() { if (mStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { @@ -1687,6 +1664,7 @@ public class MediaPlayer implements SubtitleController.Listener * at the same time. */ public void release() { + baseRelease(); stayAwake(false); updateSurfaceScreenOn(); mOnPreparedListener = null; @@ -1756,6 +1734,8 @@ public class MediaPlayer implements SubtitleController.Listener * @see android.media.AudioManager */ public void setAudioStreamType(int streamtype) { + baseUpdateAudioAttributes( + new AudioAttributes.Builder().setInternalLegacyStreamType(streamtype).build()); _setAudioStreamType(streamtype); mStreamType = streamtype; } @@ -1785,6 +1765,7 @@ public class MediaPlayer implements SubtitleController.Listener final String msg = "Cannot set AudioAttributes to null"; throw new IllegalArgumentException(msg); } + baseUpdateAudioAttributes(attributes); mUsage = attributes.getUsage(); mBypassInterruptionPolicy = (attributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0; @@ -1826,9 +1807,11 @@ public class MediaPlayer implements SubtitleController.Listener * to be set independently. */ public void setVolume(float leftVolume, float rightVolume) { - if (isRestricted()) { - return; - } + baseSetVolume(leftVolume, rightVolume); + } + + @Override + void playerSetVolume(float leftVolume, float rightVolume) { _setVolume(leftVolume, rightVolume); } @@ -1898,10 +1881,13 @@ public class MediaPlayer implements SubtitleController.Listener * @param level send level scalar */ public void setAuxEffectSendLevel(float level) { - if (isRestricted()) { - return; - } + baseSetAuxEffectSendLevel(level); + } + + @Override + int playerSetAuxEffectSendLevel(float level) { _setAuxEffectSendLevel(level); + return AudioSystem.SUCCESS; } private native void _setAuxEffectSendLevel(float level); @@ -2795,7 +2781,10 @@ public class MediaPlayer implements SubtitleController.Listener private native final int native_setRetransmitEndpoint(String addrString, int port); @Override - protected void finalize() { native_finalize(); } + protected void finalize() { + baseRelease(); + native_finalize(); + } /* Do not change these values without updating their counterparts * in include/media/mediaplayer.h! diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java new file mode 100644 index 0000000000000..0f7dc9a819c7f --- /dev/null +++ b/media/java/android/media/PlayerBase.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2016 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 android.media; + +import java.lang.IllegalArgumentException; + +import android.annotation.NonNull; +import android.app.ActivityThread; +import android.app.AppOpsManager; +import android.content.Context; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsService; + +/** + * Class to encapsulate a number of common player operations: + * - AppOps for OP_PLAY_AUDIO + * - more to come (routing, transport control) + * @hide + */ +public abstract class PlayerBase { + + // parameters of the player that affect AppOps + protected AudioAttributes mAttributes; + protected float mLeftVolume = 1.0f; + protected float mRightVolume = 1.0f; + protected float mAuxEffectSendLevel = 0.0f; + + // for AppOps + private final IAppOpsService mAppOps; + private final IAppOpsCallback mAppOpsCallback; + private boolean mHasAppOpsPlayAudio = true; + private final Object mAppOpsLock = new Object(); + + + /** + * Constructor. Must be given audio attributes, as they are required for AppOps. + * @param attr non-null audio attributes + */ + PlayerBase(@NonNull AudioAttributes attr) { + if (attr == null) { + throw new IllegalArgumentException("Illegal null AudioAttributes"); + } + mAttributes = attr; + IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); + mAppOps = IAppOpsService.Stub.asInterface(b); + // initialize mHasAppOpsPlayAudio + 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) { + if (op == AppOpsManager.OP_PLAY_AUDIO) { + updateAppOpsPlayAudio_sync(); + } + } + } + }; + try { + mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO, + ActivityThread.currentPackageName(), mAppOpsCallback); + } catch (RemoteException e) { + mHasAppOpsPlayAudio = false; + } + } + + + /** + * To be called whenever the audio attributes of the player change + * @param attr non-null audio attributes + */ + void baseUpdateAudioAttributes(@NonNull AudioAttributes attr) { + if (attr == null) { + throw new IllegalArgumentException("Illegal null AudioAttributes"); + } + synchronized (mAppOpsLock) { + mAttributes = attr; + updateAppOpsPlayAudio_sync(); + } + } + + void baseStart() { + synchronized (mAppOpsLock) { + if (isRestricted_sync()) { + playerSetVolume(0, 0); + } + } + } + + void baseSetVolume(float leftVolume, float rightVolume) { + synchronized (mAppOpsLock) { + mLeftVolume = leftVolume; + mRightVolume = rightVolume; + if (isRestricted_sync()) { + return; + } + } + playerSetVolume(leftVolume, rightVolume); + } + + int baseSetAuxEffectSendLevel(float level) { + synchronized (mAppOpsLock) { + mAuxEffectSendLevel = level; + if (isRestricted_sync()) { + return AudioSystem.SUCCESS; + } + } + return playerSetAuxEffectSendLevel(level); + } + + /** + * To be called from a subclass release or finalize method. + * Releases AppOps related resources. + */ + void baseRelease() { + try { + mAppOps.stopWatchingMode(mAppOpsCallback); + } catch (RemoteException e) { + // nothing to do here, the object is supposed to be released anyway + } + } + + /** + * To be called whenever a condition that might affect audibility of this player is updated. + * Must be called synchronized on mAppOpsLock. + */ + void updateAppOpsPlayAudio_sync() { + boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio; + try { + final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO, + mAttributes.getUsage(), + Process.myUid(), ActivityThread.currentPackageName()); + mHasAppOpsPlayAudio = (mode == AppOpsManager.MODE_ALLOWED); + } catch (RemoteException e) { + mHasAppOpsPlayAudio = false; + } + + // AppsOps alters a player's volume; when the restriction changes, reflect it on the actual + // volume used by the player + try { + if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio) { + if (mHasAppOpsPlayAudio) { + playerSetVolume(mLeftVolume, mRightVolume); + playerSetAuxEffectSendLevel(mAuxEffectSendLevel); + } else { + playerSetVolume(0.0f, 0.0f); + playerSetAuxEffectSendLevel(0.0f); + } + } + } catch (Exception e) { + // failing silently, player might not be in right state + } + } + + + /** + * To be called by the subclass whenever an operation is potentially restricted. + * As the media player-common behavior are incorporated into this class, the subclass's need + * to call this method should be removed, and this method could become private. + * FIXME can this method be private so subclasses don't have to worry about when to check + * the restrictions. + * @return + */ + boolean isRestricted_sync() { + if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) { + return false; + } + return !mHasAppOpsPlayAudio; + } + + // Abstract methods a subclass needs to implement + abstract void playerSetVolume(float leftVolume, float rightVolume); + abstract int playerSetAuxEffectSendLevel(float level); +}