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