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