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
17package android.media;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.app.ActivityThread;
22import android.app.AppOpsManager;
23import android.content.Context;
24import android.media.VolumeShaper;
25import android.os.Binder;
26import android.os.IBinder;
27import android.os.Parcel;
28import android.os.Parcelable;
29import android.os.Process;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.util.Log;
33
34import com.android.internal.app.IAppOpsCallback;
35import com.android.internal.app.IAppOpsService;
36
37import java.lang.IllegalArgumentException;
38import java.lang.ref.WeakReference;
39import 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 */
47public 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            mAttributes = attr;
131            updateAppOpsPlayAudio_sync();
132        }
133    }
134
135    void baseStart() {
136        if (DEBUG) { Log.v(TAG, "baseStart() piid=" + mPlayerIId); }
137        try {
138            synchronized (mLock) {
139                mState = AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
140                getService().playerEvent(mPlayerIId, mState);
141            }
142        } catch (RemoteException e) {
143            Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e);
144        }
145        synchronized (mLock) {
146            if (isRestricted_sync()) {
147                playerSetVolume(true/*muting*/,0, 0);
148            }
149        }
150    }
151
152    void baseSetStartDelayMs(int delayMs) {
153        synchronized(mLock) {
154            mStartDelayMs = Math.max(delayMs, 0);
155        }
156    }
157
158    protected int getStartDelayMs() {
159        synchronized(mLock) {
160            return mStartDelayMs;
161        }
162    }
163
164    void basePause() {
165        if (DEBUG) { Log.v(TAG, "basePause() piid=" + mPlayerIId); }
166        try {
167            synchronized (mLock) {
168                mState = AudioPlaybackConfiguration.PLAYER_STATE_PAUSED;
169                getService().playerEvent(mPlayerIId, mState);
170            }
171        } catch (RemoteException e) {
172            Log.e(TAG, "Error talking to audio service, PAUSED state will not be tracked", e);
173        }
174    }
175
176    void baseStop() {
177        if (DEBUG) { Log.v(TAG, "baseStop() piid=" + mPlayerIId); }
178        try {
179            synchronized (mLock) {
180                mState = AudioPlaybackConfiguration.PLAYER_STATE_STOPPED;
181                getService().playerEvent(mPlayerIId, mState);
182            }
183        } catch (RemoteException e) {
184            Log.e(TAG, "Error talking to audio service, STOPPED state will not be tracked", e);
185        }
186    }
187
188    void baseSetPan(float pan) {
189        final float p = Math.min(Math.max(-1.0f, pan), 1.0f);
190        synchronized (mLock) {
191            if (p >= 0.0f) {
192                mPanMultiplierL = 1.0f - p;
193                mPanMultiplierR = 1.0f;
194            } else {
195                mPanMultiplierL = 1.0f;
196                mPanMultiplierR = 1.0f + p;
197            }
198        }
199        baseSetVolume(mLeftVolume, mRightVolume);
200    }
201
202    void baseSetVolume(float leftVolume, float rightVolume) {
203        final boolean hasAppOpsPlayAudio;
204        synchronized (mLock) {
205            mLeftVolume = leftVolume;
206            mRightVolume = rightVolume;
207            hasAppOpsPlayAudio = mHasAppOpsPlayAudio;
208            if (isRestricted_sync()) {
209                return;
210            }
211        }
212        playerSetVolume(!hasAppOpsPlayAudio/*muting*/,
213                leftVolume * mPanMultiplierL, rightVolume * mPanMultiplierR);
214    }
215
216    int baseSetAuxEffectSendLevel(float level) {
217        synchronized (mLock) {
218            mAuxEffectSendLevel = level;
219            if (isRestricted_sync()) {
220                return AudioSystem.SUCCESS;
221            }
222        }
223        return playerSetAuxEffectSendLevel(false/*muting*/, level);
224    }
225
226    /**
227     * To be called from a subclass release or finalize method.
228     * Releases AppOps related resources.
229     */
230    void baseRelease() {
231        if (DEBUG) { Log.v(TAG, "baseRelease() piid=" + mPlayerIId + " state=" + mState); }
232        try {
233            synchronized (mLock) {
234                if (mState != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
235                    getService().releasePlayer(mPlayerIId);
236                    mState = AudioPlaybackConfiguration.PLAYER_STATE_RELEASED;
237                }
238            }
239        } catch (RemoteException e) {
240            Log.e(TAG, "Error talking to audio service, the player will still be tracked", e);
241        }
242        try {
243            if (mAppOps != null) {
244                mAppOps.stopWatchingMode(mAppOpsCallback);
245            }
246        } catch (Exception e) {
247            // nothing to do here, the object is supposed to be released anyway
248        }
249    }
250
251    private void updateAppOpsPlayAudio() {
252        synchronized (mLock) {
253            updateAppOpsPlayAudio_sync();
254        }
255    }
256
257    /**
258     * To be called whenever a condition that might affect audibility of this player is updated.
259     * Must be called synchronized on mLock.
260     */
261    void updateAppOpsPlayAudio_sync() {
262        boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio;
263        try {
264            int mode = AppOpsManager.MODE_IGNORED;
265            if (mAppOps != null) {
266                mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
267                    mAttributes.getUsage(),
268                    Process.myUid(), ActivityThread.currentPackageName());
269            }
270            mHasAppOpsPlayAudio = (mode == AppOpsManager.MODE_ALLOWED);
271        } catch (RemoteException e) {
272            mHasAppOpsPlayAudio = false;
273        }
274
275        // AppsOps alters a player's volume; when the restriction changes, reflect it on the actual
276        // volume used by the player
277        try {
278            if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio) {
279                if (mHasAppOpsPlayAudio) {
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