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 java.lang.IllegalArgumentException;
20
21import android.annotation.NonNull;
22import android.app.ActivityThread;
23import android.app.AppOpsManager;
24import android.content.Context;
25import android.os.IBinder;
26import android.os.Process;
27import android.os.RemoteException;
28import android.os.ServiceManager;
29import android.util.Log;
30
31import com.android.internal.app.IAppOpsCallback;
32import 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 */
40public 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     */
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     */
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
101    void baseStart() {
102        synchronized (mAppOpsLock) {
103            if (isRestricted_sync()) {
104                playerSetVolume(0, 0);
105            }
106        }
107    }
108
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
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     */
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     */
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     */
183    boolean isRestricted_sync() {
184        // check app ops
185        if (mHasAppOpsPlayAudio) {
186            return false;
187        }
188        // check bypass flag
189        if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
190            return false;
191        }
192        return true;
193    }
194
195    // Abstract methods a subclass needs to implement
196    abstract void playerSetVolume(float leftVolume, float rightVolume);
197    abstract int playerSetAuxEffectSendLevel(float level);
198}
199