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