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.annotation.Nullable;
21import android.media.AudioAttributes;
22import android.media.AudioFocusInfo;
23import android.media.AudioManager;
24import android.media.IAudioFocusDispatcher;
25import android.os.IBinder;
26import android.util.Log;
27
28import com.android.server.audio.MediaFocusControl.AudioFocusDeathHandler;
29
30import java.io.PrintWriter;
31
32/**
33 * @hide
34 * Class to handle all the information about a user of audio focus. The lifecycle of each
35 * instance is managed by android.media.MediaFocusControl, from its addition to the audio focus
36 * stack, or the map of focus owners for an external focus policy, to its release.
37 */
38public class FocusRequester {
39
40    // on purpose not using this classe's name, as it will only be used from MediaFocusControl
41    private static final String TAG = "MediaFocusControl";
42    private static final boolean DEBUG = false;
43
44    private AudioFocusDeathHandler mDeathHandler; // may be null
45    private IAudioFocusDispatcher mFocusDispatcher; // may be null
46    private final IBinder mSourceRef; // may be null
47    private final String mClientId;
48    private final String mPackageName;
49    private final int mCallingUid;
50    private final MediaFocusControl mFocusController; // never null
51    private final int mSdkTarget;
52
53    /**
54     * the audio focus gain request that caused the addition of this object in the focus stack.
55     */
56    private final int mFocusGainRequest;
57    /**
58     * the flags associated with the gain request that qualify the type of grant (e.g. accepting
59     * delay vs grant must be immediate)
60     */
61    private final int mGrantFlags;
62    /**
63     * the audio focus loss received my mFocusDispatcher, is AudioManager.AUDIOFOCUS_NONE if
64     *  it never lost focus.
65     */
66    private int mFocusLossReceived;
67    /**
68     * whether this focus owner listener was notified when it lost focus
69     */
70    private boolean mFocusLossWasNotified;
71    /**
72     * the audio attributes associated with the focus request
73     */
74    private final AudioAttributes mAttributes;
75
76    /**
77     * Class constructor
78     * @param aa
79     * @param focusRequest
80     * @param grantFlags
81     * @param afl
82     * @param source
83     * @param id
84     * @param hdlr
85     * @param pn
86     * @param uid
87     * @param ctlr cannot be null
88     */
89    FocusRequester(AudioAttributes aa, int focusRequest, int grantFlags,
90            IAudioFocusDispatcher afl, IBinder source, String id, AudioFocusDeathHandler hdlr,
91            String pn, int uid, @NonNull MediaFocusControl ctlr, int sdk) {
92        mAttributes = aa;
93        mFocusDispatcher = afl;
94        mSourceRef = source;
95        mClientId = id;
96        mDeathHandler = hdlr;
97        mPackageName = pn;
98        mCallingUid = uid;
99        mFocusGainRequest = focusRequest;
100        mGrantFlags = grantFlags;
101        mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
102        mFocusLossWasNotified = true;
103        mFocusController = ctlr;
104        mSdkTarget = sdk;
105    }
106
107    FocusRequester(AudioFocusInfo afi, IAudioFocusDispatcher afl,
108             IBinder source, AudioFocusDeathHandler hdlr, @NonNull MediaFocusControl ctlr) {
109        mAttributes = afi.getAttributes();
110        mClientId = afi.getClientId();
111        mPackageName = afi.getPackageName();
112        mCallingUid = afi.getClientUid();
113        mFocusGainRequest = afi.getGainRequest();
114        mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
115        mFocusLossWasNotified = true;
116        mGrantFlags = afi.getFlags();
117        mSdkTarget = afi.getSdkTarget();
118
119        mFocusDispatcher = afl;
120        mSourceRef = source;
121        mDeathHandler = hdlr;
122        mFocusController = ctlr;
123    }
124
125    boolean hasSameClient(String otherClient) {
126        try {
127            return mClientId.compareTo(otherClient) == 0;
128        } catch (NullPointerException e) {
129            return false;
130        }
131    }
132
133    boolean isLockedFocusOwner() {
134        return ((mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0);
135    }
136
137    boolean hasSameBinder(IBinder ib) {
138        return (mSourceRef != null) && mSourceRef.equals(ib);
139    }
140
141    boolean hasSameDispatcher(IAudioFocusDispatcher fd) {
142        return (mFocusDispatcher != null) && mFocusDispatcher.equals(fd);
143    }
144
145    boolean hasSamePackage(String pack) {
146        try {
147            return mPackageName.compareTo(pack) == 0;
148        } catch (NullPointerException e) {
149            return false;
150        }
151    }
152
153    boolean hasSameUid(int uid) {
154        return mCallingUid == uid;
155    }
156
157    int getClientUid() {
158        return mCallingUid;
159    }
160
161    String getClientId() {
162        return mClientId;
163    }
164
165    int getGainRequest() {
166        return mFocusGainRequest;
167    }
168
169    int getGrantFlags() {
170        return mGrantFlags;
171    }
172
173    AudioAttributes getAudioAttributes() {
174        return mAttributes;
175    }
176
177    int getSdkTarget() {
178        return mSdkTarget;
179    }
180
181    private static String focusChangeToString(int focus) {
182        switch(focus) {
183            case AudioManager.AUDIOFOCUS_NONE:
184                return "none";
185            case AudioManager.AUDIOFOCUS_GAIN:
186                return "GAIN";
187            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
188                return "GAIN_TRANSIENT";
189            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
190                return "GAIN_TRANSIENT_MAY_DUCK";
191            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
192                return "GAIN_TRANSIENT_EXCLUSIVE";
193            case AudioManager.AUDIOFOCUS_LOSS:
194                return "LOSS";
195            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
196                return "LOSS_TRANSIENT";
197            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
198                return "LOSS_TRANSIENT_CAN_DUCK";
199            default:
200                return "[invalid focus change" + focus + "]";
201        }
202    }
203
204    private String focusGainToString() {
205        return focusChangeToString(mFocusGainRequest);
206    }
207
208    private String focusLossToString() {
209        return focusChangeToString(mFocusLossReceived);
210    }
211
212    private static String flagsToString(int flags) {
213        String msg = new String();
214        if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) != 0) {
215            msg += "DELAY_OK";
216        }
217        if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0)     {
218            if (!msg.isEmpty()) { msg += "|"; }
219            msg += "LOCK";
220        }
221        if ((flags & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0) {
222            if (!msg.isEmpty()) { msg += "|"; }
223            msg += "PAUSES_ON_DUCKABLE_LOSS";
224        }
225        return msg;
226    }
227
228    void dump(PrintWriter pw) {
229        pw.println("  source:" + mSourceRef
230                + " -- pack: " + mPackageName
231                + " -- client: " + mClientId
232                + " -- gain: " + focusGainToString()
233                + " -- flags: " + flagsToString(mGrantFlags)
234                + " -- loss: " + focusLossToString()
235                + " -- notified: " + mFocusLossWasNotified
236                + " -- uid: " + mCallingUid
237                + " -- attr: " + mAttributes
238                + " -- sdk:" + mSdkTarget);
239    }
240
241
242    void release() {
243        try {
244            if (mSourceRef != null && mDeathHandler != null) {
245                mSourceRef.unlinkToDeath(mDeathHandler, 0);
246                mDeathHandler = null;
247                mFocusDispatcher = null;
248            }
249        } catch (java.util.NoSuchElementException e) {
250            Log.e(TAG, "FocusRequester.release() hit ", e);
251        }
252    }
253
254    @Override
255    protected void finalize() throws Throwable {
256        release();
257        super.finalize();
258    }
259
260    /**
261     * For a given audio focus gain request, return the audio focus loss type that will result
262     * from it, taking into account any previous focus loss.
263     * @param gainRequest
264     * @return the audio focus loss type that matches the gain request
265     */
266    private int focusLossForGainRequest(int gainRequest) {
267        switch(gainRequest) {
268            case AudioManager.AUDIOFOCUS_GAIN:
269                switch(mFocusLossReceived) {
270                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
271                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
272                    case AudioManager.AUDIOFOCUS_LOSS:
273                    case AudioManager.AUDIOFOCUS_NONE:
274                        return AudioManager.AUDIOFOCUS_LOSS;
275                }
276            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
277            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
278                switch(mFocusLossReceived) {
279                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
280                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
281                    case AudioManager.AUDIOFOCUS_NONE:
282                        return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
283                    case AudioManager.AUDIOFOCUS_LOSS:
284                        return AudioManager.AUDIOFOCUS_LOSS;
285                }
286            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
287                switch(mFocusLossReceived) {
288                    case AudioManager.AUDIOFOCUS_NONE:
289                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
290                        return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
291                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
292                        return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
293                    case AudioManager.AUDIOFOCUS_LOSS:
294                        return AudioManager.AUDIOFOCUS_LOSS;
295                }
296            default:
297                Log.e(TAG, "focusLossForGainRequest() for invalid focus request "+ gainRequest);
298                        return AudioManager.AUDIOFOCUS_NONE;
299        }
300    }
301
302    /**
303     * Called synchronized on MediaFocusControl.mAudioFocusLock
304     */
305    void handleExternalFocusGain(int focusGain, final FocusRequester fr) {
306        int focusLoss = focusLossForGainRequest(focusGain);
307        handleFocusLoss(focusLoss, fr);
308    }
309
310    /**
311     * Called synchronized on MediaFocusControl.mAudioFocusLock
312     */
313    void handleFocusGain(int focusGain) {
314        try {
315            mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
316            mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(),
317                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
318            final IAudioFocusDispatcher fd = mFocusDispatcher;
319            if (fd != null) {
320                if (DEBUG) {
321                    Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
322                        + mClientId);
323                }
324                if (mFocusLossWasNotified) {
325                    fd.dispatchAudioFocusChange(focusGain, mClientId);
326                }
327            }
328            mFocusController.unduckPlayers(this);
329        } catch (android.os.RemoteException e) {
330            Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
331        }
332    }
333
334    /**
335     * Called synchronized on MediaFocusControl.mAudioFocusLock
336     */
337    void handleFocusGainFromRequest(int focusRequestResult) {
338        if (focusRequestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
339            mFocusController.unduckPlayers(this);
340        }
341    }
342
343    /**
344     * Called synchronized on MediaFocusControl.mAudioFocusLock
345     */
346    void handleFocusLoss(int focusLoss, @Nullable final FocusRequester fr) {
347        try {
348            if (focusLoss != mFocusLossReceived) {
349                mFocusLossReceived = focusLoss;
350                mFocusLossWasNotified = false;
351                // before dispatching a focus loss, check if the following conditions are met:
352                // 1/ the framework is not supposed to notify the focus loser on a DUCK loss
353                //    (i.e. it has a focus controller that implements a ducking policy)
354                // 2/ it is a DUCK loss
355                // 3/ the focus loser isn't flagged as pausing in a DUCK loss
356                // if they are, do not notify the focus loser
357                if (!mFocusController.mustNotifyFocusOwnerOnDuck()
358                        && mFocusLossReceived == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
359                        && (mGrantFlags
360                                & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) == 0) {
361                    if (DEBUG) {
362                        Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
363                                + " to " + mClientId + ", to be handled externally");
364                    }
365                    mFocusController.notifyExtPolicyFocusLoss_syncAf(
366                            toAudioFocusInfo(), false /* wasDispatched */);
367                    return;
368                }
369
370                // check enforcement by the framework
371                boolean handled = false;
372                if (focusLoss == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
373                        && MediaFocusControl.ENFORCE_DUCKING
374                        && fr != null) {
375                    // candidate for enforcement by the framework
376                    if (fr.mCallingUid != this.mCallingUid) {
377                        if ((mGrantFlags
378                                & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0) {
379                            // the focus loser declared it would pause instead of duck, let it
380                            // handle it (the framework doesn't pause for apps)
381                            handled = false;
382                            Log.v(TAG, "not ducking uid " + this.mCallingUid + " - flags");
383                        } else if (MediaFocusControl.ENFORCE_DUCKING_FOR_NEW &&
384                                this.getSdkTarget() <= MediaFocusControl.DUCKING_IN_APP_SDK_LEVEL) {
385                            // legacy behavior, apps used to be notified when they should be ducking
386                            handled = false;
387                            Log.v(TAG, "not ducking uid " + this.mCallingUid + " - old SDK");
388                        } else {
389                            handled = mFocusController.duckPlayers(fr, this);
390                        }
391                    } // else: the focus change is within the same app, so let the dispatching
392                      //       happen as if the framework was not involved.
393                }
394
395                if (handled) {
396                    if (DEBUG) {
397                        Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
398                            + " to " + mClientId + ", ducking implemented by framework");
399                    }
400                    mFocusController.notifyExtPolicyFocusLoss_syncAf(
401                            toAudioFocusInfo(), false /* wasDispatched */);
402                    return; // with mFocusLossWasNotified = false
403                }
404
405                final IAudioFocusDispatcher fd = mFocusDispatcher;
406                if (fd != null) {
407                    if (DEBUG) {
408                        Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "
409                            + mClientId);
410                    }
411                    mFocusController.notifyExtPolicyFocusLoss_syncAf(
412                            toAudioFocusInfo(), true /* wasDispatched */);
413                    mFocusLossWasNotified = true;
414                    fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
415                }
416            }
417        } catch (android.os.RemoteException e) {
418            Log.e(TAG, "Failure to signal loss of audio focus due to:", e);
419        }
420    }
421
422    int dispatchFocusChange(int focusChange) {
423        if (mFocusDispatcher == null) {
424            if (MediaFocusControl.DEBUG) { Log.v(TAG, "dispatchFocusChange: no focus dispatcher"); }
425            return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
426        }
427        if (focusChange == AudioManager.AUDIOFOCUS_NONE) {
428            if (MediaFocusControl.DEBUG) { Log.v(TAG, "dispatchFocusChange: AUDIOFOCUS_NONE"); }
429            return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
430        } else if ((focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
431                || focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
432                || focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
433                || focusChange == AudioManager.AUDIOFOCUS_GAIN)
434                && (mFocusGainRequest != focusChange)){
435            Log.w(TAG, "focus gain was requested with " + mFocusGainRequest
436                    + ", dispatching " + focusChange);
437        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
438                || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
439                || focusChange == AudioManager.AUDIOFOCUS_LOSS) {
440            mFocusLossReceived = focusChange;
441        }
442        try {
443            mFocusDispatcher.dispatchAudioFocusChange(focusChange, mClientId);
444        } catch (android.os.RemoteException e) {
445            Log.v(TAG, "dispatchFocusChange: error talking to focus listener", e);
446            return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
447        }
448        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
449    }
450
451    AudioFocusInfo toAudioFocusInfo() {
452        return new AudioFocusInfo(mAttributes, mCallingUid, mClientId, mPackageName,
453                mFocusGainRequest, mFocusLossReceived, mGrantFlags, mSdkTarget);
454    }
455}
456