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