• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 package com.android.car.audio;
17 
18 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
19 
20 import android.annotation.NonNull;
21 import android.annotation.UserIdInt;
22 import android.car.settings.CarSettings;
23 import android.database.ContentObserver;
24 import android.media.AudioManager;
25 import android.media.AudioManager.FocusRequestResult;
26 import android.net.Uri;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.os.UserHandle;
30 import android.provider.Settings;
31 import android.util.IndentingPrintWriter;
32 import android.util.Slog;
33 
34 import com.android.car.CarLog;
35 import com.android.car.audio.CarAudioContext.AudioContext;
36 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.internal.util.Preconditions;
39 
40 import java.util.List;
41 import java.util.Objects;
42 
43 /**
44  * FocusInteraction is responsible for evaluating how incoming focus requests should be handled
45  * based on pre-defined interaction behaviors for each incoming {@link AudioContext} in relation to
46  * a {@link AudioContext} that is currently holding focus.
47  */
48 final class FocusInteraction {
49 
50     private static final String TAG = CarLog.tagFor(FocusInteraction.class);
51 
52     @VisibleForTesting
53     static final Uri AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL_URI =
54             Settings.Secure.getUriFor(
55                     CarSettings.Secure.KEY_AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL);
56 
57     // Values for the internal interaction matrix we use to make focus decisions
58     @VisibleForTesting
59     static final int INTERACTION_REJECT = 0; // Focus not granted
60     @VisibleForTesting
61     static final int INTERACTION_EXCLUSIVE = 1; // Focus granted, others loose focus
62     @VisibleForTesting
63     static final int INTERACTION_CONCURRENT = 2; // Focus granted, others keep focus
64 
65     private static final int[][] sInteractionMatrix = {
66             // Each Row represents CarAudioContext of current focus holder
67             // Each Column represents CarAudioContext of incoming request (labels along the right)
68             // Cell value is one of INTERACTION_REJECT, INTERACTION_EXCLUSIVE,
69             // or INTERACTION_CONCURRENT
70 
71             // Focus holder: INVALID
72             {
73                     INTERACTION_REJECT, // INVALID
74                     INTERACTION_REJECT, // MUSIC
75                     INTERACTION_REJECT, // NAVIGATION
76                     INTERACTION_REJECT, // VOICE_COMMAND
77                     INTERACTION_REJECT, // CALL_RING
78                     INTERACTION_REJECT, // CALL
79                     INTERACTION_REJECT, // ALARM
80                     INTERACTION_REJECT, // NOTIFICATION
81                     INTERACTION_REJECT, // SYSTEM_SOUND,
82                     INTERACTION_EXCLUSIVE, // EMERGENCY
83                     INTERACTION_EXCLUSIVE, // SAFETY
84                     INTERACTION_REJECT, // VEHICLE_STATUS
85                     INTERACTION_REJECT, // ANNOUNCEMENT
86             },
87             // Focus holder: MUSIC
88             {
89                     INTERACTION_REJECT, // INVALID
90                     INTERACTION_EXCLUSIVE, // MUSIC
91                     INTERACTION_CONCURRENT, // NAVIGATION
92                     INTERACTION_EXCLUSIVE, // VOICE_COMMAND
93                     INTERACTION_EXCLUSIVE, // CALL_RING
94                     INTERACTION_EXCLUSIVE, // CALL
95                     INTERACTION_EXCLUSIVE, // ALARM
96                     INTERACTION_CONCURRENT, // NOTIFICATION
97                     INTERACTION_CONCURRENT, // SYSTEM_SOUND
98                     INTERACTION_EXCLUSIVE, // EMERGENCY
99                     INTERACTION_CONCURRENT, // SAFETY
100                     INTERACTION_CONCURRENT, // VEHICLE_STATUS
101                     INTERACTION_EXCLUSIVE, // ANNOUNCEMENT
102             },
103             // Focus holder: NAVIGATION
104             {
105                     INTERACTION_REJECT, // INVALID
106                     INTERACTION_CONCURRENT, // MUSIC
107                     INTERACTION_CONCURRENT, // NAVIGATION
108                     INTERACTION_EXCLUSIVE, // VOICE_COMMAND
109                     INTERACTION_CONCURRENT, // CALL_RING
110                     INTERACTION_EXCLUSIVE, // CALL
111                     INTERACTION_CONCURRENT, // ALARM
112                     INTERACTION_CONCURRENT, // NOTIFICATION
113                     INTERACTION_CONCURRENT, // SYSTEM_SOUND
114                     INTERACTION_EXCLUSIVE, // EMERGENCY
115                     INTERACTION_CONCURRENT, // SAFETY
116                     INTERACTION_CONCURRENT, // VEHICLE_STATUS
117                     INTERACTION_CONCURRENT, // ANNOUNCEMENT
118             },
119             // Focus holder: VOICE_COMMAND
120             {
121                     INTERACTION_REJECT, // INVALID
122                     INTERACTION_CONCURRENT, // MUSIC
123                     INTERACTION_REJECT, // NAVIGATION
124                     INTERACTION_CONCURRENT, // VOICE_COMMAND
125                     INTERACTION_EXCLUSIVE, // CALL_RING
126                     INTERACTION_EXCLUSIVE, // CALL
127                     INTERACTION_REJECT, // ALARM
128                     INTERACTION_REJECT, // NOTIFICATION
129                     INTERACTION_REJECT, // SYSTEM_SOUND
130                     INTERACTION_EXCLUSIVE, // EMERGENCY
131                     INTERACTION_CONCURRENT, // SAFETY
132                     INTERACTION_CONCURRENT, // VEHICLE_STATUS
133                     INTERACTION_REJECT, // ANNOUNCEMENT
134             },
135             // Focus holder: CALL_RING
136             {
137                     INTERACTION_REJECT, // INVALID
138                     INTERACTION_REJECT, // MUSIC
139                     INTERACTION_CONCURRENT, // NAVIGATION
140                     INTERACTION_CONCURRENT, // VOICE_COMMAND
141                     INTERACTION_CONCURRENT, // CALL_RING
142                     INTERACTION_CONCURRENT, // CALL
143                     INTERACTION_REJECT, // ALARM
144                     INTERACTION_REJECT, // NOTIFICATION
145                     INTERACTION_CONCURRENT, // SYSTEM_SOUND
146                     INTERACTION_EXCLUSIVE, // EMERGENCY
147                     INTERACTION_CONCURRENT, // SAFETY
148                     INTERACTION_CONCURRENT, // VEHICLE_STATUS
149                     INTERACTION_REJECT, // ANNOUNCEMENT
150             },
151             // Focus holder: CALL
152             {
153                     INTERACTION_REJECT, // INVALID
154                     INTERACTION_REJECT, // MUSIC
155                     INTERACTION_CONCURRENT, // NAVIGATION
156                     INTERACTION_REJECT, // VOICE_COMMAND
157                     INTERACTION_CONCURRENT, // CALL_RING
158                     INTERACTION_CONCURRENT, // CALL
159                     INTERACTION_CONCURRENT, // ALARM
160                     INTERACTION_CONCURRENT, // NOTIFICATION
161                     INTERACTION_REJECT, // SYSTEM_SOUND
162                     INTERACTION_CONCURRENT, // EMERGENCY
163                     INTERACTION_CONCURRENT, // SAFETY
164                     INTERACTION_CONCURRENT, // VEHICLE_STATUS
165                     INTERACTION_REJECT, // ANNOUNCEMENT
166             },
167             // Focus holder: ALARM
168             {
169                     INTERACTION_REJECT, // INVALID
170                     INTERACTION_CONCURRENT, // MUSIC
171                     INTERACTION_CONCURRENT, // NAVIGATION
172                     INTERACTION_EXCLUSIVE, // VOICE_COMMAND
173                     INTERACTION_EXCLUSIVE, // CALL_RING
174                     INTERACTION_EXCLUSIVE, // CALL
175                     INTERACTION_CONCURRENT, // ALARM
176                     INTERACTION_CONCURRENT, // NOTIFICATION
177                     INTERACTION_CONCURRENT, // SYSTEM_SOUND
178                     INTERACTION_EXCLUSIVE, // EMERGENCY
179                     INTERACTION_CONCURRENT, // SAFETY
180                     INTERACTION_CONCURRENT, // VEHICLE_STATUS
181                     INTERACTION_REJECT, // ANNOUNCEMENT
182             },
183             // Focus holder: NOTIFICATION
184             {
185                     INTERACTION_REJECT, // INVALID
186                     INTERACTION_CONCURRENT, // MUSIC
187                     INTERACTION_CONCURRENT, // NAVIGATION
188                     INTERACTION_EXCLUSIVE, // VOICE_COMMAND
189                     INTERACTION_EXCLUSIVE, // CALL_RING
190                     INTERACTION_EXCLUSIVE, // CALL
191                     INTERACTION_CONCURRENT, // ALARM
192                     INTERACTION_CONCURRENT, // NOTIFICATION
193                     INTERACTION_CONCURRENT, // SYSTEM_SOUND
194                     INTERACTION_EXCLUSIVE, // EMERGENCY
195                     INTERACTION_CONCURRENT, // SAFETY
196                     INTERACTION_CONCURRENT, // VEHICLE_STATUS
197                     INTERACTION_CONCURRENT, // ANNOUNCEMENT
198             },
199             // Focus holder: SYSTEM_SOUND
200             {
201                     INTERACTION_REJECT, // INVALID
202                     INTERACTION_CONCURRENT, // MUSIC
203                     INTERACTION_CONCURRENT, // NAVIGATION
204                     INTERACTION_EXCLUSIVE, // VOICE_COMMAND
205                     INTERACTION_EXCLUSIVE, // CALL_RING
206                     INTERACTION_EXCLUSIVE, // CALL
207                     INTERACTION_CONCURRENT, // ALARM
208                     INTERACTION_CONCURRENT, // NOTIFICATION
209                     INTERACTION_CONCURRENT, // SYSTEM_SOUND
210                     INTERACTION_EXCLUSIVE, // EMERGENCY
211                     INTERACTION_CONCURRENT, // SAFETY
212                     INTERACTION_CONCURRENT, // VEHICLE_STATUS
213                     INTERACTION_CONCURRENT, // ANNOUNCEMENT
214             },
215             // Focus holder: EMERGENCY
216             {
217                     INTERACTION_REJECT, // INVALID
218                     INTERACTION_REJECT, // MUSIC
219                     INTERACTION_REJECT, // NAVIGATION
220                     INTERACTION_REJECT, // VOICE_COMMAND
221                     INTERACTION_REJECT, // CALL_RING
222                     INTERACTION_CONCURRENT, // CALL
223                     INTERACTION_REJECT, // ALARM
224                     INTERACTION_REJECT, // NOTIFICATION
225                     INTERACTION_REJECT, // SYSTEM_SOUND
226                     INTERACTION_CONCURRENT, // EMERGENCY
227                     INTERACTION_CONCURRENT, // SAFETY
228                     INTERACTION_REJECT, // VEHICLE_STATUS
229                     INTERACTION_REJECT, // ANNOUNCEMENT
230             },
231             // Focus holder: SAFETY
232             {
233                     INTERACTION_REJECT, // INVALID
234                     INTERACTION_CONCURRENT, // MUSIC
235                     INTERACTION_CONCURRENT, // NAVIGATION
236                     INTERACTION_CONCURRENT, // VOICE_COMMAND
237                     INTERACTION_CONCURRENT, // CALL_RING
238                     INTERACTION_CONCURRENT, // CALL
239                     INTERACTION_CONCURRENT, // ALARM
240                     INTERACTION_CONCURRENT, // NOTIFICATION
241                     INTERACTION_CONCURRENT, // SYSTEM_SOUND
242                     INTERACTION_CONCURRENT, // EMERGENCY
243                     INTERACTION_CONCURRENT, // SAFETY
244                     INTERACTION_CONCURRENT, // VEHICLE_STATUS
245                     INTERACTION_CONCURRENT, // ANNOUNCEMENT
246             },
247             // Focus holder: VEHICLE_STATUS
248             {
249                     INTERACTION_REJECT, // INVALID
250                     INTERACTION_CONCURRENT, // MUSIC
251                     INTERACTION_CONCURRENT, // NAVIGATION
252                     INTERACTION_CONCURRENT, // VOICE_COMMAND
253                     INTERACTION_CONCURRENT, // CALL_RING
254                     INTERACTION_CONCURRENT, // CALL
255                     INTERACTION_CONCURRENT, // ALARM
256                     INTERACTION_CONCURRENT, // NOTIFICATION
257                     INTERACTION_CONCURRENT, // SYSTEM_SOUND
258                     INTERACTION_EXCLUSIVE, // EMERGENCY
259                     INTERACTION_CONCURRENT, // SAFETY
260                     INTERACTION_CONCURRENT, // VEHICLE_STATUS
261                     INTERACTION_CONCURRENT, // ANNOUNCEMENT
262             },
263             // Focus holder: ANNOUNCEMENT
264             {
265                     INTERACTION_REJECT, // INVALID
266                     INTERACTION_EXCLUSIVE, // MUSIC
267                     INTERACTION_CONCURRENT, // NAVIGATION
268                     INTERACTION_EXCLUSIVE, // VOICE_COMMAND
269                     INTERACTION_EXCLUSIVE, // CALL_RING
270                     INTERACTION_EXCLUSIVE, // CALL
271                     INTERACTION_EXCLUSIVE, // ALARM
272                     INTERACTION_CONCURRENT, // NOTIFICATION
273                     INTERACTION_CONCURRENT, // SYSTEM_SOUND
274                     INTERACTION_EXCLUSIVE, // EMERGENCY
275                     INTERACTION_CONCURRENT, // SAFETY
276                     INTERACTION_CONCURRENT, // VEHICLE_STATUS
277                     INTERACTION_EXCLUSIVE, // ANNOUNCEMENT
278             },
279     };
280 
281     private final Object mLock = new Object();
282 
283     private final int[][] mInteractionMatrix;
284 
285     private ContentObserver mContentObserver;
286 
287     private final CarAudioSettings mCarAudioFocusSettings;
288 
289     private int mUserId;
290 
291     /**
292      * Constructs a focus interaction instance.
293      */
FocusInteraction(@onNull CarAudioSettings carAudioSettings)294     FocusInteraction(@NonNull CarAudioSettings carAudioSettings) {
295         mCarAudioFocusSettings = Objects.requireNonNull(carAudioSettings);
296         mInteractionMatrix = cloneInteractionMatrix(sInteractionMatrix);
297     }
298 
navigationOnCallSettingChanged()299     private void navigationOnCallSettingChanged() {
300         synchronized (mLock) {
301             if (mUserId != UserHandle.USER_NULL) {
302                 setRejectNavigationOnCallLocked(isRejectNavigationOnCallEnabledInSettings(mUserId));
303             }
304         }
305     }
306 
setRejectNavigationOnCallLocked(boolean navigationRejectedWithCall)307     public void setRejectNavigationOnCallLocked(boolean navigationRejectedWithCall) {
308         mInteractionMatrix[CarAudioContext.CALL][CarAudioContext.NAVIGATION] =
309                 navigationRejectedWithCall ? INTERACTION_REJECT :
310                 sInteractionMatrix[CarAudioContext.CALL][CarAudioContext.NAVIGATION];
311     }
312 
313     /**
314      * Evaluates interaction between incoming focus {@link AudioContext} and the current focus
315      * request based on interaction matrix.
316      *
317      * <p>Note: In addition to returning the {@link FocusRequestResult}
318      * for the incoming request based on this interaction, this method also adds the current {@code
319      * focusHolder} to the {@code focusLosers} list when appropriate.
320      *
321      * @param requestedContext CarAudioContextType of incoming focus request
322      * @param focusHolder      {@link FocusEntry} for current focus holder
323      * @param focusLosers      Mutable array to add focusHolder to if it should lose focus
324      * @return {@link FocusRequestResult} result of focus interaction
325      */
evaluateRequest(@udioContext int requestedContext, FocusEntry focusHolder, List<FocusEntry> focusLosers, boolean allowDucking, boolean allowsDelayedFocus)326     public @FocusRequestResult int evaluateRequest(@AudioContext int requestedContext,
327             FocusEntry focusHolder, List<FocusEntry> focusLosers, boolean allowDucking,
328             boolean allowsDelayedFocus) {
329         @AudioContext int holderContext = focusHolder.getAudioContext();
330         Preconditions.checkArgumentInRange(holderContext, 0, mInteractionMatrix.length - 1,
331                 "holderContext");
332         synchronized (mLock) {
333             int[] holderRow = mInteractionMatrix[holderContext];
334             Preconditions.checkArgumentInRange(requestedContext, 0, holderRow.length - 1,
335                     "requestedContext");
336 
337             switch (holderRow[requestedContext]) {
338                 case INTERACTION_REJECT:
339                     if (allowsDelayedFocus) {
340                         return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
341                     }
342                     return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
343                 case INTERACTION_EXCLUSIVE:
344                     focusLosers.add(focusHolder);
345                     return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
346                 case INTERACTION_CONCURRENT:
347                     // If ducking isn't allowed by the focus requester, then everybody else
348                     // must get a LOSS.
349                     // If a focus holder has set the AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS flag,
350                     // they must get a LOSS message even if ducking would otherwise be allowed.
351                     // If a focus holder holds the RECEIVE_CAR_AUDIO_DUCKING_EVENTS permission,
352                     // they must receive all audio focus losses.
353                     if (!allowDucking
354                             || focusHolder.wantsPauseInsteadOfDucking()
355                             || focusHolder.receivesDuckEvents()) {
356                         focusLosers.add(focusHolder);
357                     }
358                     return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
359                 default:
360                     Slog.e(TAG, String.format("Unsupported CarAudioContext %d - rejecting request",
361                             holderRow[requestedContext]));
362                     return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
363             }
364         }
365     }
366 
367     /**
368      * Sets userId for interaction focus settings
369      */
setUserIdForSettings(@serIdInt int userId)370     void setUserIdForSettings(@UserIdInt int userId) {
371         synchronized (mLock) {
372             mUserId = userId;
373             if (mContentObserver != null) {
374                 mCarAudioFocusSettings.getContentResolver()
375                         .unregisterContentObserver(mContentObserver);
376                 mContentObserver = null;
377             }
378             if (mUserId == UserHandle.USER_NULL) {
379                 setRejectNavigationOnCallLocked(false);
380                 return;
381             }
382             mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
383                 @Override
384                 public void onChange(boolean selfChange, Uri uri) {
385                     if (uri.equals(AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL_URI)) {
386                         navigationOnCallSettingChanged();
387                     }
388                 }
389             };
390             mCarAudioFocusSettings.getContentResolver()
391                     .registerContentObserver(AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL_URI,
392                             false, mContentObserver, userId);
393             setRejectNavigationOnCallLocked(isRejectNavigationOnCallEnabledInSettings(mUserId));
394         }
395     }
396 
isRejectNavigationOnCallEnabledInSettings(@serIdInt int userId)397     private boolean isRejectNavigationOnCallEnabledInSettings(@UserIdInt int userId) {
398         return mCarAudioFocusSettings.isRejectNavigationOnCallEnabledInSettings(userId);
399     }
400 
401     @VisibleForTesting
getInteractionMatrix()402     int[][] getInteractionMatrix() {
403         return cloneInteractionMatrix(mInteractionMatrix);
404     }
405 
cloneInteractionMatrix(int[][] matrixToClone)406     private static int[][] cloneInteractionMatrix(int[][] matrixToClone) {
407         int[][] interactionMatrixClone =
408                 new int[matrixToClone.length][matrixToClone.length];
409         for (int audioContext = 0; audioContext < matrixToClone.length; audioContext++) {
410             System.arraycopy(matrixToClone[audioContext], 0,
411                     interactionMatrixClone[audioContext], 0, matrixToClone.length);
412         }
413         return interactionMatrixClone;
414     }
415 
416     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)417     public void dump(IndentingPrintWriter writer) {
418         boolean rejectNavigationOnCall =
419                 mInteractionMatrix[CarAudioContext.CALL][CarAudioContext.NAVIGATION]
420                 == INTERACTION_REJECT;
421         writer.printf("Reject Navigation on Call: %b\n", rejectNavigationOnCall);
422     }
423 }
424