1 /*
2  * Copyright (C) 2019 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.car.builtin.util.Slogf;
19 import android.car.media.ICarVolumeCallback;
20 import android.os.Handler;
21 import android.os.HandlerThread;
22 import android.os.IBinder;
23 import android.os.RemoteCallbackList;
24 import android.os.RemoteException;
25 import android.util.SparseArray;
26 
27 import com.android.car.CarLog;
28 import com.android.car.CarServiceUtils;
29 import com.android.internal.annotations.GuardedBy;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 
34 /**
35  * Manages callbacks for changes in car volume
36  */
37 final class CarVolumeCallbackHandler extends RemoteCallbackList<ICarVolumeCallback>  {
38     private static final String REQUEST_HANDLER_THREAD_NAME = "CarVolumeCallback";
39 
40     private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
41             REQUEST_HANDLER_THREAD_NAME);
42     private final Handler mHandler = new Handler(mHandlerThread.getLooper());
43 
44     private final Object mLock = new Object();
45     @GuardedBy("mLock")
46     private final SparseArray<List<IBinder>> mUidToBindersMap = new SparseArray<>();
47 
release()48     void release() {
49     }
50 
registerCallback(IBinder binder, int uid, boolean priority)51     public void registerCallback(IBinder binder, int uid, boolean priority) {
52         ICarVolumeCallback callback = ICarVolumeCallback.Stub.asInterface(binder);
53         synchronized (mLock) {
54             registerCallbackLocked(callback, uid, priority);
55             List<IBinder> binders = mUidToBindersMap.get(uid);
56             if (binders == null) {
57                 binders = new ArrayList<IBinder>();
58                 mUidToBindersMap.put(uid, binders);
59             }
60 
61             if (!binders.contains(binder)) {
62                 binders.add(binder);
63             }
64         }
65     }
66 
unregisterCallback(IBinder binder, int uid)67     public void unregisterCallback(IBinder binder, int uid) {
68         ICarVolumeCallback callback = ICarVolumeCallback.Stub.asInterface(binder);
69         synchronized (mLock) {
70             unregisterCallbackLocked(callback);
71             List<IBinder> binders = mUidToBindersMap.get(uid);
72             if (binders == null) {
73                 // callback is not registered. nothing to remove.
74                 return;
75             }
76 
77             if (binders.contains(binder)) {
78                 binders.remove(binder);
79             }
80 
81             if (binders.isEmpty()) {
82                 mUidToBindersMap.remove(uid);
83             }
84         }
85     }
86 
registerCallbackLocked(ICarVolumeCallback callback, int uid, boolean priority)87     private void registerCallbackLocked(ICarVolumeCallback callback, int uid, boolean priority) {
88         register(callback, new CallerPriorityCookie(uid, priority));
89     }
90 
unregisterCallbackLocked(ICarVolumeCallback callback)91     private void unregisterCallbackLocked(ICarVolumeCallback callback) {
92         unregister(callback);
93     }
94 
95     // handle the special case where an app registers to both ICarVolumeCallback
96     // and ICarVolumeEventCallback.
97     // priority is given to ICarVolumeEventCallback
98     //  - when registered, deprioritize ICarVolumeCallback
99     //  - when unregistered, reprioritize ICarVolumeCallback
checkAndRepriotize(int uid, boolean priority)100     void checkAndRepriotize(int uid, boolean priority) {
101         synchronized (mLock) {
102             if (mUidToBindersMap.contains(uid)) {
103                 List<IBinder> binders = mUidToBindersMap.get(uid);
104                 // Re-register with new priority.
105                 // cannot check the priority without broadcast. forced to unregister and register
106                 // again even if priority is same. optimize in future.
107                 for (int i = 0; i < binders.size(); i++) {
108                     ICarVolumeCallback callback =
109                             ICarVolumeCallback.Stub.asInterface(binders.get(i));
110                     unregisterCallbackLocked(callback);
111                     registerCallbackLocked(callback, uid, priority);
112                 }
113             }
114         }
115     }
116 
onVolumeGroupChange(int zoneId, int groupId, int flags)117     public void onVolumeGroupChange(int zoneId, int groupId, int flags) {
118         mHandler.post(() -> {
119             int count = beginBroadcast();
120             for (int index = 0; index < count; index++) {
121                 CallerPriorityCookie cookie = (CallerPriorityCookie) getBroadcastCookie(index);
122                 ICarVolumeCallback callback = getBroadcastItem(index);
123                 if (!cookie.mPriority) {
124                     continue;
125                 }
126 
127                 try {
128                     callback.onGroupVolumeChanged(zoneId, groupId, flags);
129                 } catch (RemoteException e) {
130                     Slogf.e(CarLog.TAG_AUDIO, "Failed to callback onGroupVolumeChanged", e);
131                 }
132             }
133             finishBroadcast();
134         });
135     }
136 
onGroupMuteChange(int zoneId, int groupId, int flags)137     public void onGroupMuteChange(int zoneId, int groupId, int flags) {
138         mHandler.post(() -> {
139             int count = beginBroadcast();
140             for (int index = 0; index < count; index++) {
141                 CallerPriorityCookie cookie = (CallerPriorityCookie) getBroadcastCookie(index);
142                 ICarVolumeCallback callback = getBroadcastItem(index);
143                 if (!cookie.mPriority) {
144                     continue;
145                 }
146 
147                 try {
148                     callback.onGroupMuteChanged(zoneId, groupId, flags);
149                 } catch (RemoteException e) {
150                     Slogf.e(CarLog.TAG_AUDIO, "Failed to callback onGroupMuteChanged", e);
151                 }
152             }
153             finishBroadcast();
154         });
155     }
156 
onMasterMuteChanged(int zoneId, int flags)157     void onMasterMuteChanged(int zoneId, int flags) {
158         mHandler.post(() -> {
159             int count = beginBroadcast();
160             for (int index = 0; index < count; index++) {
161                 ICarVolumeCallback callback = getBroadcastItem(index);
162                 try {
163                     callback.onMasterMuteChanged(zoneId, flags);
164                 } catch (RemoteException e) {
165                     Slogf.e(CarLog.TAG_AUDIO, "Failed to callback onMasterMuteChanged", e);
166                 }
167             }
168             finishBroadcast();
169         });
170     }
171 
172     @Override
onCallbackDied(ICarVolumeCallback callback, Object cookie)173     public void onCallbackDied(ICarVolumeCallback callback, Object cookie) {
174         CallerPriorityCookie caller = (CallerPriorityCookie) cookie;
175         // when client dies, clean up obsolete user-id from the list
176         synchronized (mLock) {
177             mUidToBindersMap.remove(caller.mUid);
178         }
179     }
180 
181     private static final class CallerPriorityCookie {
182         public final int mUid;
183         public final boolean mPriority;
184 
CallerPriorityCookie(int uid, boolean priority)185         CallerPriorityCookie(int uid, boolean priority) {
186             mUid = uid;
187             mPriority = priority;
188         }
189     }
190 }
191