Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.media;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.app.ActivityThread;
     22 import android.app.AppOpsManager;
     23 import android.content.Context;
     24 import android.media.VolumeShaper;
     25 import android.os.Binder;
     26 import android.os.IBinder;
     27 import android.os.Parcel;
     28 import android.os.Parcelable;
     29 import android.os.Process;
     30 import android.os.RemoteException;
     31 import android.os.ServiceManager;
     32 import android.util.Log;
     33 
     34 import com.android.internal.annotations.GuardedBy;
     35 import com.android.internal.app.IAppOpsCallback;
     36 import com.android.internal.app.IAppOpsService;
     37 
     38 import java.lang.IllegalArgumentException;
     39 import java.lang.ref.WeakReference;
     40 import java.util.Objects;
     41 
     42 /**
     43  * Class to encapsulate a number of common player operations:
     44  *   - AppOps for OP_PLAY_AUDIO
     45  *   - more to come (routing, transport control)
     46  * @hide
     47  */
     48 public abstract class PlayerBase {
     49 
     50     private static final String TAG = "PlayerBase";
     51     /** Debug app ops */
     52     private static final boolean DEBUG_APP_OPS = false;
     53     private static final boolean DEBUG = DEBUG_APP_OPS || false;
     54     private static IAudioService sService; //lazy initialization, use getService()
     55 
     56     // parameters of the player that affect AppOps
     57     protected AudioAttributes mAttributes;
     58     protected float mLeftVolume = 1.0f;
     59     protected float mRightVolume = 1.0f;
     60     protected float mAuxEffectSendLevel = 0.0f;
     61 
     62     // NEVER call into AudioService (see getService()) with mLock held: PlayerBase can run in
     63     // the same process as AudioService, which can synchronously call back into this class,
     64     // causing deadlocks between the two
     65     private final Object mLock = new Object();
     66 
     67     // for AppOps
     68     private @Nullable IAppOpsService mAppOps;
     69     private IAppOpsCallback mAppOpsCallback;
     70     @GuardedBy("mLock")
     71     private boolean mHasAppOpsPlayAudio = true;
     72 
     73     private final int mImplType;
     74     // uniquely identifies the Player Interface throughout the system (P I Id)
     75     private int mPlayerIId = AudioPlaybackConfiguration.PLAYER_PIID_UNASSIGNED;
     76 
     77     @GuardedBy("mLock")
     78     private int mState;
     79     @GuardedBy("mLock")
     80     private int mStartDelayMs = 0;
     81     @GuardedBy("mLock")
     82     private float mPanMultiplierL = 1.0f;
     83     @GuardedBy("mLock")
     84     private float mPanMultiplierR = 1.0f;
     85 
     86     /**
     87      * Constructor. Must be given audio attributes, as they are required for AppOps.
     88      * @param attr non-null audio attributes
     89      * @param class non-null class of the implementation of this abstract class
     90      */
     91     PlayerBase(@NonNull AudioAttributes attr, int implType) {
     92         if (attr == null) {
     93             throw new IllegalArgumentException("Illegal null AudioAttributes");
     94         }
     95         mAttributes = attr;
     96         mImplType = implType;
     97         mState = AudioPlaybackConfiguration.PLAYER_STATE_IDLE;
     98     };
     99 
    100     /**
    101      * Call from derived class when instantiation / initialization is successful
    102      */
    103     protected void baseRegisterPlayer() {
    104         int newPiid = AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
    105         IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
    106         mAppOps = IAppOpsService.Stub.asInterface(b);
    107         // initialize mHasAppOpsPlayAudio
    108         updateAppOpsPlayAudio();
    109         // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
    110         mAppOpsCallback = new IAppOpsCallbackWrapper(this);
    111         try {
    112             mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO,
    113                     ActivityThread.currentPackageName(), mAppOpsCallback);
    114         } catch (RemoteException e) {
    115             Log.e(TAG, "Error registering appOps callback", e);
    116             mHasAppOpsPlayAudio = false;
    117         }
    118         try {
    119             newPiid = getService().trackPlayer(
    120                     new PlayerIdCard(mImplType, mAttributes, new IPlayerWrapper(this)));
    121         } catch (RemoteException e) {
    122             Log.e(TAG, "Error talking to audio service, player will not be tracked", e);
    123         }
    124         mPlayerIId = newPiid;
    125     }
    126 
    127     /**
    128      * To be called whenever the audio attributes of the player change
    129      * @param attr non-null audio attributes
    130      */
    131     void baseUpdateAudioAttributes(@NonNull AudioAttributes attr) {
    132         if (attr == null) {
    133             throw new IllegalArgumentException("Illegal null AudioAttributes");
    134         }
    135         try {
    136             getService().playerAttributes(mPlayerIId, attr);
    137         } catch (RemoteException e) {
    138             Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e);
    139         }
    140         synchronized (mLock) {
    141             boolean attributesChanged = (mAttributes != attr);
    142             mAttributes = attr;
    143             updateAppOpsPlayAudio_sync(attributesChanged);
    144         }
    145     }
    146 
    147     private void updateState(int state) {
    148         final int piid;
    149         synchronized (mLock) {
    150             mState = state;
    151             piid = mPlayerIId;
    152         }
    153         try {
    154             getService().playerEvent(piid, state);
    155         } catch (RemoteException e) {
    156             Log.e(TAG, "Error talking to audio service, "
    157                     + AudioPlaybackConfiguration.toLogFriendlyPlayerState(state)
    158                     + " state will not be tracked for piid=" + piid, e);
    159         }
    160     }
    161 
    162     void baseStart() {
    163         if (DEBUG) { Log.v(TAG, "baseStart() piid=" + mPlayerIId); }
    164         updateState(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
    165         synchronized (mLock) {
    166             if (isRestricted_sync()) {
    167                 playerSetVolume(true/*muting*/,0, 0);
    168             }
    169         }
    170     }
    171 
    172     void baseSetStartDelayMs(int delayMs) {
    173         synchronized(mLock) {
    174             mStartDelayMs = Math.max(delayMs, 0);
    175         }
    176     }
    177 
    178     protected int getStartDelayMs() {
    179         synchronized(mLock) {
    180             return mStartDelayMs;
    181         }
    182     }
    183 
    184     void basePause() {
    185         if (DEBUG) { Log.v(TAG, "basePause() piid=" + mPlayerIId); }
    186         updateState(AudioPlaybackConfiguration.PLAYER_STATE_PAUSED);
    187     }
    188 
    189     void baseStop() {
    190         if (DEBUG) { Log.v(TAG, "baseStop() piid=" + mPlayerIId); }
    191         updateState(AudioPlaybackConfiguration.PLAYER_STATE_STOPPED);
    192     }
    193 
    194     void baseSetPan(float pan) {
    195         final float p = Math.min(Math.max(-1.0f, pan), 1.0f);
    196         synchronized (mLock) {
    197             if (p >= 0.0f) {
    198                 mPanMultiplierL = 1.0f - p;
    199                 mPanMultiplierR = 1.0f;
    200             } else {
    201                 mPanMultiplierL = 1.0f;
    202                 mPanMultiplierR = 1.0f + p;
    203             }
    204         }
    205         baseSetVolume(mLeftVolume, mRightVolume);
    206     }
    207 
    208     void baseSetVolume(float leftVolume, float rightVolume) {
    209         final boolean isRestricted;
    210         synchronized (mLock) {
    211             mLeftVolume = leftVolume;
    212             mRightVolume = rightVolume;
    213             isRestricted = isRestricted_sync();
    214         }
    215         playerSetVolume(isRestricted/*muting*/,
    216                 leftVolume * mPanMultiplierL, rightVolume * mPanMultiplierR);
    217     }
    218 
    219     int baseSetAuxEffectSendLevel(float level) {
    220         synchronized (mLock) {
    221             mAuxEffectSendLevel = level;
    222             if (isRestricted_sync()) {
    223                 return AudioSystem.SUCCESS;
    224             }
    225         }
    226         return playerSetAuxEffectSendLevel(false/*muting*/, level);
    227     }
    228 
    229     /**
    230      * To be called from a subclass release or finalize method.
    231      * Releases AppOps related resources.
    232      */
    233     void baseRelease() {
    234         if (DEBUG) { Log.v(TAG, "baseRelease() piid=" + mPlayerIId + " state=" + mState); }
    235         boolean releasePlayer = false;
    236         synchronized (mLock) {
    237             if (mState != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
    238                 releasePlayer = true;
    239                 mState = AudioPlaybackConfiguration.PLAYER_STATE_RELEASED;
    240             }
    241         }
    242         try {
    243             if (releasePlayer) {
    244                 getService().releasePlayer(mPlayerIId);
    245             }
    246         } catch (RemoteException e) {
    247             Log.e(TAG, "Error talking to audio service, the player will still be tracked", e);
    248         }
    249         try {
    250             if (mAppOps != null) {
    251                 mAppOps.stopWatchingMode(mAppOpsCallback);
    252             }
    253         } catch (Exception e) {
    254             // nothing to do here, the object is supposed to be released anyway
    255         }
    256     }
    257 
    258     private void updateAppOpsPlayAudio() {
    259         synchronized (mLock) {
    260             updateAppOpsPlayAudio_sync(false);
    261         }
    262     }
    263 
    264     /**
    265      * To be called whenever a condition that might affect audibility of this player is updated.
    266      * Must be called synchronized on mLock.
    267      */
    268     void updateAppOpsPlayAudio_sync(boolean attributesChanged) {
    269         boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio;
    270         try {
    271             int mode = AppOpsManager.MODE_IGNORED;
    272             if (mAppOps != null) {
    273                 mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
    274                     mAttributes.getUsage(),
    275                     Process.myUid(), ActivityThread.currentPackageName());
    276             }
    277             mHasAppOpsPlayAudio = (mode == AppOpsManager.MODE_ALLOWED);
    278         } catch (RemoteException e) {
    279             mHasAppOpsPlayAudio = false;
    280         }
    281 
    282         // AppsOps alters a player's volume; when the restriction changes, reflect it on the actual
    283         // volume used by the player
    284         try {
    285             if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio ||
    286                     attributesChanged) {
    287                 getService().playerHasOpPlayAudio(mPlayerIId, mHasAppOpsPlayAudio);
    288                 if (!isRestricted_sync()) {
    289                     if (DEBUG_APP_OPS) {
    290                         Log.v(TAG, "updateAppOpsPlayAudio: unmuting player, vol=" + mLeftVolume
    291                                 + "/" + mRightVolume);
    292                     }
    293                     playerSetVolume(false/*muting*/,
    294                             mLeftVolume * mPanMultiplierL, mRightVolume * mPanMultiplierR);
    295                     playerSetAuxEffectSendLevel(false/*muting*/, mAuxEffectSendLevel);
    296                 } else {
    297                     if (DEBUG_APP_OPS) {
    298                         Log.v(TAG, "updateAppOpsPlayAudio: muting player");
    299                     }
    300                     playerSetVolume(true/*muting*/, 0.0f, 0.0f);
    301                     playerSetAuxEffectSendLevel(true/*muting*/, 0.0f);
    302                 }
    303             }
    304         } catch (Exception e) {
    305             // failing silently, player might not be in right state
    306         }
    307     }
    308 
    309     /**
    310      * To be called by the subclass whenever an operation is potentially restricted.
    311      * As the media player-common behavior are incorporated into this class, the subclass's need
    312      * to call this method should be removed, and this method could become private.
    313      * FIXME can this method be private so subclasses don't have to worry about when to check
    314      *    the restrictions.
    315      * @return
    316      */
    317     boolean isRestricted_sync() {
    318         // check app ops
    319         if (mHasAppOpsPlayAudio) {
    320             return false;
    321         }
    322         // check bypass flag
    323         if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
    324             return false;
    325         }
    326         // check force audibility flag and camera restriction
    327         if (((mAttributes.getAllFlags() & AudioAttributes.FLAG_AUDIBILITY_ENFORCED) != 0)
    328                 && (mAttributes.getUsage() == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)) {
    329             boolean cameraSoundForced = false;
    330             try {
    331                 cameraSoundForced = getService().isCameraSoundForced();
    332             } catch (RemoteException e) {
    333                 Log.e(TAG, "Cannot access AudioService in isRestricted_sync()");
    334             } catch (NullPointerException e) {
    335                 Log.e(TAG, "Null AudioService in isRestricted_sync()");
    336             }
    337             if (cameraSoundForced) {
    338                 return false;
    339             }
    340         }
    341         return true;
    342     }
    343 
    344     private static IAudioService getService()
    345     {
    346         if (sService != null) {
    347             return sService;
    348         }
    349         IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
    350         sService = IAudioService.Stub.asInterface(b);
    351         return sService;
    352     }
    353 
    354     /**
    355      * @hide
    356      * @param delayMs
    357      */
    358     public void setStartDelayMs(int delayMs) {
    359         baseSetStartDelayMs(delayMs);
    360     }
    361 
    362     //=====================================================================
    363     // Abstract methods a subclass needs to implement
    364     /**
    365      * Abstract method for the subclass behavior's for volume and muting commands
    366      * @param muting if true, the player is to be muted, and the volume values can be ignored
    367      * @param leftVolume the left volume to use if muting is false
    368      * @param rightVolume the right volume to use if muting is false
    369      */
    370     abstract void playerSetVolume(boolean muting, float leftVolume, float rightVolume);
    371 
    372     /**
    373      * Abstract method to apply a {@link VolumeShaper.Configuration}
    374      * and a {@link VolumeShaper.Operation} to the Player.
    375      * This should be overridden by the Player to call into the native
    376      * VolumeShaper implementation. Multiple {@code VolumeShapers} may be
    377      * concurrently active for a given Player, each accessible by the
    378      * {@code VolumeShaper} id.
    379      *
    380      * The {@code VolumeShaper} implementation caches the id returned
    381      * when applying a fully specified configuration
    382      * from {VolumeShaper.Configuration.Builder} to track later
    383      * operation changes requested on it.
    384      *
    385      * @param configuration a {@code VolumeShaper.Configuration} object
    386      *        created by {@link VolumeShaper.Configuration.Builder} or
    387      *        an created from a {@code VolumeShaper} id
    388      *        by the {@link VolumeShaper.Configuration} constructor.
    389      * @param operation a {@code VolumeShaper.Operation}.
    390      * @return a negative error status or a
    391      *         non-negative {@code VolumeShaper} id on success.
    392      */
    393     /* package */ abstract int playerApplyVolumeShaper(
    394             @NonNull VolumeShaper.Configuration configuration,
    395             @NonNull VolumeShaper.Operation operation);
    396 
    397     /**
    398      * Abstract method to get the current VolumeShaper state.
    399      * @param id the {@code VolumeShaper} id returned from
    400      *           sending a fully specified {@code VolumeShaper.Configuration}
    401      *           through {@link #playerApplyVolumeShaper}
    402      * @return a {@code VolumeShaper.State} object or null if
    403      *         there is no {@code VolumeShaper} for the id.
    404      */
    405     /* package */ abstract @Nullable VolumeShaper.State playerGetVolumeShaperState(int id);
    406 
    407     abstract int playerSetAuxEffectSendLevel(boolean muting, float level);
    408     abstract void playerStart();
    409     abstract void playerPause();
    410     abstract void playerStop();
    411 
    412     //=====================================================================
    413     private static class IAppOpsCallbackWrapper extends IAppOpsCallback.Stub {
    414         private final WeakReference<PlayerBase> mWeakPB;
    415 
    416         public IAppOpsCallbackWrapper(PlayerBase pb) {
    417             mWeakPB = new WeakReference<PlayerBase>(pb);
    418         }
    419 
    420         @Override
    421         public void opChanged(int op, int uid, String packageName) {
    422             if (op == AppOpsManager.OP_PLAY_AUDIO) {
    423                 if (DEBUG_APP_OPS) { Log.v(TAG, "opChanged: op=PLAY_AUDIO pack=" + packageName); }
    424                 final PlayerBase pb = mWeakPB.get();
    425                 if (pb != null) {
    426                     pb.updateAppOpsPlayAudio();
    427                 }
    428             }
    429         }
    430     }
    431 
    432     //=====================================================================
    433     /**
    434      * Wrapper around an implementation of IPlayer for all subclasses of PlayerBase
    435      * that doesn't keep a strong reference on PlayerBase
    436      */
    437     private static class IPlayerWrapper extends IPlayer.Stub {
    438         private final WeakReference<PlayerBase> mWeakPB;
    439 
    440         public IPlayerWrapper(PlayerBase pb) {
    441             mWeakPB = new WeakReference<PlayerBase>(pb);
    442         }
    443 
    444         @Override
    445         public void start() {
    446             final PlayerBase pb = mWeakPB.get();
    447             if (pb != null) {
    448                 pb.playerStart();
    449             }
    450         }
    451 
    452         @Override
    453         public void pause() {
    454             final PlayerBase pb = mWeakPB.get();
    455             if (pb != null) {
    456                 pb.playerPause();
    457             }
    458         }
    459 
    460         @Override
    461         public void stop() {
    462             final PlayerBase pb = mWeakPB.get();
    463             if (pb != null) {
    464                 pb.playerStop();
    465             }
    466         }
    467 
    468         @Override
    469         public void setVolume(float vol) {
    470             final PlayerBase pb = mWeakPB.get();
    471             if (pb != null) {
    472                 pb.baseSetVolume(vol, vol);
    473             }
    474         }
    475 
    476         @Override
    477         public void setPan(float pan) {
    478             final PlayerBase pb = mWeakPB.get();
    479             if (pb != null) {
    480                 pb.baseSetPan(pan);
    481             }
    482         }
    483 
    484         @Override
    485         public void setStartDelayMs(int delayMs) {
    486             final PlayerBase pb = mWeakPB.get();
    487             if (pb != null) {
    488                 pb.baseSetStartDelayMs(delayMs);
    489             }
    490         }
    491 
    492         @Override
    493         public void applyVolumeShaper(
    494                 @NonNull VolumeShaper.Configuration configuration,
    495                 @NonNull VolumeShaper.Operation operation) {
    496             final PlayerBase pb = mWeakPB.get();
    497             if (pb != null) {
    498                 pb.playerApplyVolumeShaper(configuration, operation);
    499             }
    500         }
    501     }
    502 
    503     //=====================================================================
    504     /**
    505      * Class holding all the information about a player that needs to be known at registration time
    506      */
    507     public static class PlayerIdCard implements Parcelable {
    508         public final int mPlayerType;
    509 
    510         public static final int AUDIO_ATTRIBUTES_NONE = 0;
    511         public static final int AUDIO_ATTRIBUTES_DEFINED = 1;
    512         public final AudioAttributes mAttributes;
    513         public final IPlayer mIPlayer;
    514 
    515         PlayerIdCard(int type, @NonNull AudioAttributes attr, @NonNull IPlayer iplayer) {
    516             mPlayerType = type;
    517             mAttributes = attr;
    518             mIPlayer = iplayer;
    519         }
    520 
    521         @Override
    522         public int hashCode() {
    523             return Objects.hash(mPlayerType);
    524         }
    525 
    526         @Override
    527         public int describeContents() {
    528             return 0;
    529         }
    530 
    531         @Override
    532         public void writeToParcel(Parcel dest, int flags) {
    533             dest.writeInt(mPlayerType);
    534             mAttributes.writeToParcel(dest, 0);
    535             dest.writeStrongBinder(mIPlayer == null ? null : mIPlayer.asBinder());
    536         }
    537 
    538         public static final Parcelable.Creator<PlayerIdCard> CREATOR
    539         = new Parcelable.Creator<PlayerIdCard>() {
    540             /**
    541              * Rebuilds an PlayerIdCard previously stored with writeToParcel().
    542              * @param p Parcel object to read the PlayerIdCard from
    543              * @return a new PlayerIdCard created from the data in the parcel
    544              */
    545             public PlayerIdCard createFromParcel(Parcel p) {
    546                 return new PlayerIdCard(p);
    547             }
    548             public PlayerIdCard[] newArray(int size) {
    549                 return new PlayerIdCard[size];
    550             }
    551         };
    552 
    553         private PlayerIdCard(Parcel in) {
    554             mPlayerType = in.readInt();
    555             mAttributes = AudioAttributes.CREATOR.createFromParcel(in);
    556             // IPlayer can be null if unmarshalling a Parcel coming from who knows where
    557             final IBinder b = in.readStrongBinder();
    558             mIPlayer = (b == null ? null : IPlayer.Stub.asInterface(b));
    559         }
    560 
    561         @Override
    562         public boolean equals(Object o) {
    563             if (this == o) return true;
    564             if (o == null || !(o instanceof PlayerIdCard)) return false;
    565 
    566             PlayerIdCard that = (PlayerIdCard) o;
    567 
    568             // FIXME change to the binder player interface once supported as a member
    569             return ((mPlayerType == that.mPlayerType) && mAttributes.equals(that.mAttributes));
    570         }
    571     }
    572 
    573     //=====================================================================
    574     // Utilities
    575 
    576     /**
    577      * Use to generate warning or exception in legacy code paths that allowed passing stream types
    578      * to qualify audio playback.
    579      * @param streamType the stream type to check
    580      * @throws IllegalArgumentException
    581      */
    582     public static void deprecateStreamTypeForPlayback(int streamType, String className,
    583             String opName) throws IllegalArgumentException {
    584         // STREAM_ACCESSIBILITY was introduced at the same time the use of stream types
    585         // for audio playback was deprecated, so it is not allowed at all to qualify a playback
    586         // use case
    587         if (streamType == AudioManager.STREAM_ACCESSIBILITY) {
    588             throw new IllegalArgumentException("Use of STREAM_ACCESSIBILITY is reserved for "
    589                     + "volume control");
    590         }
    591         Log.w(className, "Use of stream types is deprecated for operations other than " +
    592                 "volume control");
    593         Log.w(className, "See the documentation of " + opName + " for what to use instead with " +
    594                 "android.media.AudioAttributes to qualify your playback use case");
    595     }
    596 }
    597