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