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 java.lang.IllegalArgumentException;
20 
21 import android.annotation.NonNull;
22 import android.app.ActivityThread;
23 import android.app.AppOpsManager;
24 import android.content.Context;
25 import android.os.IBinder;
26 import android.os.Process;
27 import android.os.RemoteException;
28 import android.os.ServiceManager;
29 import android.util.Log;
30 
31 import com.android.internal.app.IAppOpsCallback;
32 import com.android.internal.app.IAppOpsService;
33 
34 /**
35  * Class to encapsulate a number of common player operations:
36  *   - AppOps for OP_PLAY_AUDIO
37  *   - more to come (routing, transport control)
38  * @hide
39  */
40 public abstract class PlayerBase {
41 
42     // parameters of the player that affect AppOps
43     protected AudioAttributes mAttributes;
44     protected float mLeftVolume = 1.0f;
45     protected float mRightVolume = 1.0f;
46     protected float mAuxEffectSendLevel = 0.0f;
47 
48     // for AppOps
49     private final IAppOpsService mAppOps;
50     private final IAppOpsCallback mAppOpsCallback;
51     private boolean mHasAppOpsPlayAudio = true;
52     private final Object mAppOpsLock = new Object();
53 
54 
55     /**
56      * Constructor. Must be given audio attributes, as they are required for AppOps.
57      * @param attr non-null audio attributes
58      */
PlayerBase(@onNull AudioAttributes attr)59     PlayerBase(@NonNull AudioAttributes attr) {
60         if (attr == null) {
61             throw new IllegalArgumentException("Illegal null AudioAttributes");
62         }
63         mAttributes = attr;
64         IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
65         mAppOps = IAppOpsService.Stub.asInterface(b);
66         // initialize mHasAppOpsPlayAudio
67         updateAppOpsPlayAudio_sync();
68         // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
69         mAppOpsCallback = new IAppOpsCallback.Stub() {
70             public void opChanged(int op, int uid, String packageName) {
71                 synchronized (mAppOpsLock) {
72                     if (op == AppOpsManager.OP_PLAY_AUDIO) {
73                         updateAppOpsPlayAudio_sync();
74                     }
75                 }
76             }
77         };
78         try {
79             mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO,
80                     ActivityThread.currentPackageName(), mAppOpsCallback);
81         } catch (RemoteException e) {
82             mHasAppOpsPlayAudio = false;
83         }
84     }
85 
86 
87     /**
88      * To be called whenever the audio attributes of the player change
89      * @param attr non-null audio attributes
90      */
baseUpdateAudioAttributes(@onNull AudioAttributes attr)91     void baseUpdateAudioAttributes(@NonNull AudioAttributes attr) {
92         if (attr == null) {
93             throw new IllegalArgumentException("Illegal null AudioAttributes");
94         }
95         synchronized (mAppOpsLock) {
96             mAttributes = attr;
97             updateAppOpsPlayAudio_sync();
98         }
99     }
100 
baseStart()101     void baseStart() {
102         synchronized (mAppOpsLock) {
103             if (isRestricted_sync()) {
104                 playerSetVolume(0, 0);
105             }
106         }
107     }
108 
baseSetVolume(float leftVolume, float rightVolume)109     void baseSetVolume(float leftVolume, float rightVolume) {
110         synchronized (mAppOpsLock) {
111             mLeftVolume = leftVolume;
112             mRightVolume = rightVolume;
113             if (isRestricted_sync()) {
114                 return;
115             }
116         }
117         playerSetVolume(leftVolume, rightVolume);
118     }
119 
baseSetAuxEffectSendLevel(float level)120     int baseSetAuxEffectSendLevel(float level) {
121         synchronized (mAppOpsLock) {
122             mAuxEffectSendLevel = level;
123             if (isRestricted_sync()) {
124                 return AudioSystem.SUCCESS;
125             }
126         }
127         return playerSetAuxEffectSendLevel(level);
128     }
129 
130     /**
131      * To be called from a subclass release or finalize method.
132      * Releases AppOps related resources.
133      */
baseRelease()134     void baseRelease() {
135         try {
136             mAppOps.stopWatchingMode(mAppOpsCallback);
137         } catch (RemoteException e) {
138             // nothing to do here, the object is supposed to be released anyway
139         }
140     }
141 
142     /**
143      * To be called whenever a condition that might affect audibility of this player is updated.
144      * Must be called synchronized on mAppOpsLock.
145      */
updateAppOpsPlayAudio_sync()146     void updateAppOpsPlayAudio_sync() {
147         boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio;
148         try {
149             final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
150                     mAttributes.getUsage(),
151                     Process.myUid(), ActivityThread.currentPackageName());
152             mHasAppOpsPlayAudio = (mode == AppOpsManager.MODE_ALLOWED);
153         } catch (RemoteException e) {
154             mHasAppOpsPlayAudio = false;
155         }
156 
157         // AppsOps alters a player's volume; when the restriction changes, reflect it on the actual
158         // volume used by the player
159         try {
160             if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio) {
161                 if (mHasAppOpsPlayAudio) {
162                     playerSetVolume(mLeftVolume, mRightVolume);
163                     playerSetAuxEffectSendLevel(mAuxEffectSendLevel);
164                 } else {
165                     playerSetVolume(0.0f, 0.0f);
166                     playerSetAuxEffectSendLevel(0.0f);
167                 }
168             }
169         } catch (Exception e) {
170             // failing silently, player might not be in right state
171         }
172     }
173 
174 
175     /**
176      * To be called by the subclass whenever an operation is potentially restricted.
177      * As the media player-common behavior are incorporated into this class, the subclass's need
178      * to call this method should be removed, and this method could become private.
179      * FIXME can this method be private so subclasses don't have to worry about when to check
180      *    the restrictions.
181      * @return
182      */
isRestricted_sync()183     boolean isRestricted_sync() {
184         if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
185             return false;
186         }
187         return !mHasAppOpsPlayAudio;
188     }
189 
190     // Abstract methods a subclass needs to implement
playerSetVolume(float leftVolume, float rightVolume)191     abstract void playerSetVolume(float leftVolume, float rightVolume);
playerSetAuxEffectSendLevel(float level)192     abstract int playerSetAuxEffectSendLevel(float level);
193 }
194