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