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 android.car.media.CarAudioManager.AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS; 19 20 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 21 22 import android.annotation.NonNull; 23 import android.car.Car; 24 import android.car.builtin.util.Slogf; 25 import android.content.pm.PackageManager; 26 import android.media.AudioFocusInfo; 27 import android.media.AudioManager; 28 import android.os.Bundle; 29 import android.util.proto.ProtoOutputStream; 30 31 import com.android.car.CarLog; 32 import com.android.car.audio.CarAudioContext.AudioContext; 33 import com.android.car.audio.CarAudioDumpProto.CarAudioZoneFocusProto.CarAudioFocusProto; 34 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 35 import com.android.car.internal.util.IndentingPrintWriter; 36 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.Objects; 40 41 final class FocusEntry { 42 private static final String TAG = CarLog.tagFor(FocusEntry.class); 43 44 private final AudioFocusInfo mAudioFocusInfo; 45 private final int mAudioContext; 46 47 private final List<FocusEntry> mBlockers; 48 private final PackageManager mPackageManager; 49 private boolean mIsDucked; 50 FocusEntry(@onNull AudioFocusInfo audioFocusInfo, @AudioContext int context, @NonNull PackageManager packageManager)51 FocusEntry(@NonNull AudioFocusInfo audioFocusInfo, @AudioContext int context, 52 @NonNull PackageManager packageManager) { 53 Objects.requireNonNull(audioFocusInfo, "AudioFocusInfo cannot be null"); 54 Objects.requireNonNull(packageManager, "PackageManager cannot be null"); 55 mAudioFocusInfo = audioFocusInfo; 56 mAudioContext = context; 57 mBlockers = new ArrayList<>(); 58 mPackageManager = packageManager; 59 } 60 61 @AudioContext getAudioContext()62 int getAudioContext() { 63 return mAudioContext; 64 } 65 getAudioFocusInfo()66 AudioFocusInfo getAudioFocusInfo() { 67 return mAudioFocusInfo; 68 } 69 isUnblocked()70 boolean isUnblocked() { 71 return mBlockers.isEmpty(); 72 } 73 addBlocker(FocusEntry blocker)74 void addBlocker(FocusEntry blocker) { 75 mBlockers.add(blocker); 76 } 77 removeBlocker(FocusEntry blocker)78 void removeBlocker(FocusEntry blocker) { 79 mBlockers.remove(blocker); 80 } 81 getClientId()82 String getClientId() { 83 return mAudioFocusInfo.getClientId(); 84 } 85 isDucked()86 boolean isDucked() { 87 return mIsDucked; 88 } 89 setDucked(boolean ducked)90 void setDucked(boolean ducked) { 91 mIsDucked = ducked; 92 } 93 wantsPauseInsteadOfDucking()94 boolean wantsPauseInsteadOfDucking() { 95 return (mAudioFocusInfo.getFlags() & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) 96 != 0; 97 } 98 receivesDuckEvents()99 boolean receivesDuckEvents() { 100 Bundle bundle = mAudioFocusInfo.getAttributes().getBundle(); 101 102 if (bundle == null || !bundle.containsKey(AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS)) { 103 return false; 104 } 105 106 if (!bundle.getBoolean(AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS)) { 107 return false; 108 } 109 110 try { 111 return (mPackageManager.checkPermission(Car.PERMISSION_RECEIVE_CAR_AUDIO_DUCKING_EVENTS, 112 mAudioFocusInfo.getPackageName()) == PackageManager.PERMISSION_GRANTED); 113 } catch (Exception e) { 114 Slogf.e(TAG, "receivesDuckEvents check permission error:", e); 115 return false; 116 } 117 } 118 119 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)120 public void dump(IndentingPrintWriter writer) { 121 writer.printf("%s - %s\n", getClientId(), mAudioFocusInfo.getAttributes()); 122 writer.increaseIndent(); 123 // Prints in single line 124 writer.printf("Receives Duck Events: %b, ", receivesDuckEvents()); 125 writer.printf("Wants Pause Instead of Ducking: %b, ", wantsPauseInsteadOfDucking()); 126 writer.printf("Is Ducked: %b\n", isDucked()); 127 writer.printf("Is Unblocked: %b\n", isUnblocked()); 128 writer.increaseIndent(); 129 for (int index = 0; index < mBlockers.size(); index++) { 130 writer.printf("Blocker[%d]: %s\n", index, mBlockers.get(index)); 131 } 132 writer.decreaseIndent(); 133 writer.decreaseIndent(); 134 } 135 136 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(long fieldId, ProtoOutputStream proto)137 public void dumpProto(long fieldId, ProtoOutputStream proto) { 138 long token = proto.start(fieldId); 139 proto.write(CarAudioFocusProto.FocusEntryProto.CLIENT_ID, getClientId()); 140 CarAudioContextInfo.dumpCarAudioAttributesProto(mAudioFocusInfo.getAttributes(), 141 CarAudioFocusProto.FocusEntryProto.ATTRIBUTES, proto); 142 proto.write(CarAudioFocusProto.FocusEntryProto.RECEIVES_DUCK_EVENTS, receivesDuckEvents()); 143 proto.write(CarAudioFocusProto.FocusEntryProto.WANTS_PAUSE_INSTEAD_OF_DUCKING, 144 wantsPauseInsteadOfDucking()); 145 proto.write(CarAudioFocusProto.FocusEntryProto.IS_DUCKED, isDucked()); 146 proto.write(CarAudioFocusProto.FocusEntryProto.IS_UNBLOCKED, isUnblocked()); 147 for (int index = 0; index < mBlockers.size(); index++) { 148 mBlockers.get(index).dumpProto(CarAudioFocusProto.FocusEntryProto.BLOCKERS, proto); 149 } 150 proto.end(token); 151 } 152 153 @Override 154 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) toString()155 public String toString() { 156 StringBuilder stringBuilder = new StringBuilder(); 157 stringBuilder.append("Focus Entry: client id "); 158 stringBuilder.append(getClientId()); 159 stringBuilder.append(", attributes "); 160 stringBuilder.append(mAudioFocusInfo.getAttributes()); 161 stringBuilder.append(", can duck "); 162 stringBuilder.append(receivesDuckEvents()); 163 stringBuilder.append(", wants pause "); 164 stringBuilder.append(wantsPauseInsteadOfDucking()); 165 stringBuilder.append(", is ducked "); 166 stringBuilder.append(isDucked()); 167 stringBuilder.append(", is unblocked "); 168 stringBuilder.append(isUnblocked()); 169 return stringBuilder.toString(); 170 } 171 } 172