1/*
2 * Copyright (C) 2013 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 com.android.server.audio;
18
19import android.annotation.NonNull;
20import android.media.AudioAttributes;
21import android.media.AudioFocusInfo;
22import android.media.AudioManager;
23import android.media.IAudioFocusDispatcher;
24import android.os.IBinder;
25import android.util.Log;
26
27import com.android.server.audio.MediaFocusControl.AudioFocusDeathHandler;
28
29import java.io.PrintWriter;
30
31/**
32 * @hide
33 * Class to handle all the information about a user of audio focus. The lifecycle of each
34 * instance is managed by android.media.MediaFocusControl, from its addition to the audio focus
35 * stack to its release.
36 */
37public class FocusRequester {
38
39    // on purpose not using this classe's name, as it will only be used from MediaFocusControl
40    private static final String TAG = "MediaFocusControl";
41    private static final boolean DEBUG = false;
42
43    private AudioFocusDeathHandler mDeathHandler;
44    private final IAudioFocusDispatcher mFocusDispatcher; // may be null
45    private final IBinder mSourceRef;
46    private final String mClientId;
47    private final String mPackageName;
48    private final int mCallingUid;
49    private final MediaFocusControl mFocusController; // never null
50    /**
51     * the audio focus gain request that caused the addition of this object in the focus stack.
52     */
53    private final int mFocusGainRequest;
54    /**
55     * the flags associated with the gain request that qualify the type of grant (e.g. accepting
56     * delay vs grant must be immediate)
57     */
58    private final int mGrantFlags;
59    /**
60     * the audio focus loss received my mFocusDispatcher, is AudioManager.AUDIOFOCUS_NONE if
61     *  it never lost focus.
62     */
63    private int mFocusLossReceived;
64    /**
65     * the audio attributes associated with the focus request
66     */
67    private final AudioAttributes mAttributes;
68
69    /**
70     * Class constructor
71     * @param aa
72     * @param focusRequest
73     * @param grantFlags
74     * @param afl
75     * @param source
76     * @param id
77     * @param hdlr
78     * @param pn
79     * @param uid
80     * @param ctlr cannot be null
81     */
82    FocusRequester(AudioAttributes aa, int focusRequest, int grantFlags,
83            IAudioFocusDispatcher afl, IBinder source, String id, AudioFocusDeathHandler hdlr,
84            String pn, int uid, @NonNull MediaFocusControl ctlr) {
85        mAttributes = aa;
86        mFocusDispatcher = afl;
87        mSourceRef = source;
88        mClientId = id;
89        mDeathHandler = hdlr;
90        mPackageName = pn;
91        mCallingUid = uid;
92        mFocusGainRequest = focusRequest;
93        mGrantFlags = grantFlags;
94        mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
95        mFocusController = ctlr;
96    }
97
98
99    boolean hasSameClient(String otherClient) {
100        try {
101            return mClientId.compareTo(otherClient) == 0;
102        } catch (NullPointerException e) {
103            return false;
104        }
105    }
106
107    boolean isLockedFocusOwner() {
108        return ((mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0);
109    }
110
111    boolean hasSameBinder(IBinder ib) {
112        return (mSourceRef != null) && mSourceRef.equals(ib);
113    }
114
115    boolean hasSamePackage(String pack) {
116        try {
117            return mPackageName.compareTo(pack) == 0;
118        } catch (NullPointerException e) {
119            return false;
120        }
121    }
122
123    boolean hasSameUid(int uid) {
124        return mCallingUid == uid;
125    }
126
127    String getClientId() {
128        return mClientId;
129    }
130
131    int getGainRequest() {
132        return mFocusGainRequest;
133    }
134
135    int getGrantFlags() {
136        return mGrantFlags;
137    }
138
139    AudioAttributes getAudioAttributes() {
140        return mAttributes;
141    }
142
143
144    private static String focusChangeToString(int focus) {
145        switch(focus) {
146            case AudioManager.AUDIOFOCUS_NONE:
147                return "none";
148            case AudioManager.AUDIOFOCUS_GAIN:
149                return "GAIN";
150            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
151                return "GAIN_TRANSIENT";
152            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
153                return "GAIN_TRANSIENT_MAY_DUCK";
154            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
155                return "GAIN_TRANSIENT_EXCLUSIVE";
156            case AudioManager.AUDIOFOCUS_LOSS:
157                return "LOSS";
158            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
159                return "LOSS_TRANSIENT";
160            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
161                return "LOSS_TRANSIENT_CAN_DUCK";
162            default:
163                return "[invalid focus change" + focus + "]";
164        }
165    }
166
167    private String focusGainToString() {
168        return focusChangeToString(mFocusGainRequest);
169    }
170
171    private String focusLossToString() {
172        return focusChangeToString(mFocusLossReceived);
173    }
174
175    private static String flagsToString(int flags) {
176        String msg = new String();
177        if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) != 0) {
178            msg += "DELAY_OK";
179        }
180        if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0)     {
181            if (!msg.isEmpty()) { msg += "|"; }
182            msg += "LOCK";
183        }
184        if ((flags & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0) {
185            if (!msg.isEmpty()) { msg += "|"; }
186            msg += "PAUSES_ON_DUCKABLE_LOSS";
187        }
188        return msg;
189    }
190
191    void dump(PrintWriter pw) {
192        pw.println("  source:" + mSourceRef
193                + " -- pack: " + mPackageName
194                + " -- client: " + mClientId
195                + " -- gain: " + focusGainToString()
196                + " -- flags: " + flagsToString(mGrantFlags)
197                + " -- loss: " + focusLossToString()
198                + " -- uid: " + mCallingUid
199                + " -- attr: " + mAttributes);
200    }
201
202
203    void release() {
204        try {
205            if (mSourceRef != null && mDeathHandler != null) {
206                mSourceRef.unlinkToDeath(mDeathHandler, 0);
207                mDeathHandler = null;
208            }
209        } catch (java.util.NoSuchElementException e) {
210            Log.e(TAG, "FocusRequester.release() hit ", e);
211        }
212    }
213
214    @Override
215    protected void finalize() throws Throwable {
216        release();
217        super.finalize();
218    }
219
220    /**
221     * For a given audio focus gain request, return the audio focus loss type that will result
222     * from it, taking into account any previous focus loss.
223     * @param gainRequest
224     * @return the audio focus loss type that matches the gain request
225     */
226    private int focusLossForGainRequest(int gainRequest) {
227        switch(gainRequest) {
228            case AudioManager.AUDIOFOCUS_GAIN:
229                switch(mFocusLossReceived) {
230                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
231                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
232                    case AudioManager.AUDIOFOCUS_LOSS:
233                    case AudioManager.AUDIOFOCUS_NONE:
234                        return AudioManager.AUDIOFOCUS_LOSS;
235                }
236            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
237            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
238                switch(mFocusLossReceived) {
239                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
240                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
241                    case AudioManager.AUDIOFOCUS_NONE:
242                        return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
243                    case AudioManager.AUDIOFOCUS_LOSS:
244                        return AudioManager.AUDIOFOCUS_LOSS;
245                }
246            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
247                switch(mFocusLossReceived) {
248                    case AudioManager.AUDIOFOCUS_NONE:
249                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
250                        return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
251                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
252                        return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
253                    case AudioManager.AUDIOFOCUS_LOSS:
254                        return AudioManager.AUDIOFOCUS_LOSS;
255                }
256            default:
257                Log.e(TAG, "focusLossForGainRequest() for invalid focus request "+ gainRequest);
258                        return AudioManager.AUDIOFOCUS_NONE;
259        }
260    }
261
262    /**
263     * Called synchronized on MediaFocusControl.mAudioFocusLock
264     */
265    void handleExternalFocusGain(int focusGain) {
266        int focusLoss = focusLossForGainRequest(focusGain);
267        handleFocusLoss(focusLoss);
268    }
269
270    /**
271     * Called synchronized on MediaFocusControl.mAudioFocusLock
272     */
273    void handleFocusGain(int focusGain) {
274        try {
275            mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
276            mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(),
277                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
278            if (mFocusDispatcher != null) {
279                if (DEBUG) {
280                    Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
281                        + mClientId);
282                }
283                mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId);
284            }
285        } catch (android.os.RemoteException e) {
286            Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
287        }
288    }
289
290    /**
291     * Called synchronized on MediaFocusControl.mAudioFocusLock
292     */
293    void handleFocusLoss(int focusLoss) {
294        try {
295            if (focusLoss != mFocusLossReceived) {
296                mFocusLossReceived = focusLoss;
297                // before dispatching a focus loss, check if the following conditions are met:
298                // 1/ the framework is not supposed to notify the focus loser on a DUCK loss
299                // 2/ it is a DUCK loss
300                // 3/ the focus loser isn't flagged as pausing in a DUCK loss
301                // if they are, do not notify the focus loser
302                if (!mFocusController.mustNotifyFocusOwnerOnDuck()
303                        && mFocusLossReceived == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
304                        && (mGrantFlags
305                                & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) == 0) {
306                    if (DEBUG) {
307                        Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
308                                + " to " + mClientId + ", to be handled externally");
309                    }
310                    mFocusController.notifyExtPolicyFocusLoss_syncAf(
311                            toAudioFocusInfo(), false /* wasDispatched */);
312                    return;
313                }
314                if (mFocusDispatcher != null) {
315                    if (DEBUG) {
316                        Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "
317                            + mClientId);
318                    }
319                    mFocusController.notifyExtPolicyFocusLoss_syncAf(
320                            toAudioFocusInfo(), true /* wasDispatched */);
321                    mFocusDispatcher.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
322                }
323            }
324        } catch (android.os.RemoteException e) {
325            Log.e(TAG, "Failure to signal loss of audio focus due to:", e);
326        }
327    }
328
329    AudioFocusInfo toAudioFocusInfo() {
330        return new AudioFocusInfo(mAttributes, mClientId, mPackageName,
331                mFocusGainRequest, mFocusLossReceived, mGrantFlags);
332    }
333}
334