• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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