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