1 /* 2 * Copyright (C) 2016 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 17 package com.android.server.wifi; 18 19 import android.annotation.Nullable; 20 import android.app.ActivityManager; 21 import android.content.Context; 22 import android.os.BatteryStatsManager; 23 import android.os.Binder; 24 import android.os.Handler; 25 import android.os.IBinder; 26 import android.os.Looper; 27 import android.os.RemoteException; 28 import android.os.WorkSource; 29 import android.util.Log; 30 31 import com.android.server.wifi.proto.WifiStatsLog; 32 import com.android.server.wifi.util.WifiPermissionsUtil; 33 34 import java.io.PrintWriter; 35 import java.util.ArrayList; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 40 /** 41 * WifiMulticastLockManager tracks holders of multicast locks and 42 * triggers enabling and disabling of filtering. 43 */ 44 public class WifiMulticastLockManager { 45 private static final String TAG = "WifiMulticastLockManager"; 46 private static final int IMPORTANCE_THRESHOLD = 47 ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; 48 private final List<Multicaster> mMulticasters = new ArrayList<>(); 49 private final Map<Integer, Integer> mNumLocksPerActiveOwner = new HashMap<>(); 50 private final Map<Integer, Integer> mNumLocksPerInactiveOwner = new HashMap<>(); 51 private int mMulticastEnabled = 0; 52 private int mMulticastDisabled = 0; 53 private boolean mIsFilterDisableSessionActive = false; 54 private long mFilterDisableSessionStartTime; 55 private final Handler mHandler; 56 private final Object mLock = new Object(); 57 private boolean mVerboseLoggingEnabled = false; 58 private final BatteryStatsManager mBatteryStats; 59 private final ActiveModeWarden mActiveModeWarden; 60 private final Clock mClock; 61 private final WifiMetrics mWifiMetrics; 62 private final WifiPermissionsUtil mWifiPermissionsUtil; 63 64 /** Delegate for handling state change events for multicast filtering. */ 65 public interface FilterController { 66 /** Called when multicast filtering should be enabled */ startFilteringMulticastPackets()67 void startFilteringMulticastPackets(); 68 69 /** Called when multicast filtering should be disabled */ stopFilteringMulticastPackets()70 void stopFilteringMulticastPackets(); 71 } 72 WifiMulticastLockManager( ActiveModeWarden activeModeWarden, BatteryStatsManager batteryStats, Looper looper, Context context, Clock clock, WifiMetrics wifiMetrics, WifiPermissionsUtil wifiPermissionsUtil)73 public WifiMulticastLockManager( 74 ActiveModeWarden activeModeWarden, 75 BatteryStatsManager batteryStats, 76 Looper looper, 77 Context context, 78 Clock clock, 79 WifiMetrics wifiMetrics, 80 WifiPermissionsUtil wifiPermissionsUtil) { 81 mBatteryStats = batteryStats; 82 mActiveModeWarden = activeModeWarden; 83 mHandler = new Handler(looper); 84 mClock = clock; 85 mWifiMetrics = wifiMetrics; 86 mWifiPermissionsUtil = wifiPermissionsUtil; 87 88 mActiveModeWarden.registerPrimaryClientModeManagerChangedCallback( 89 new PrimaryClientModeManagerChangedCallback()); 90 91 ActivityManager activityManager = context.getSystemService(ActivityManager.class); 92 activityManager.addOnUidImportanceListener(new ActivityManager.OnUidImportanceListener() { 93 @Override 94 public void onUidImportance(final int uid, final int importance) { 95 handleImportanceChanged(uid, importance); 96 } 97 }, IMPORTANCE_THRESHOLD); 98 } 99 100 private class Multicaster implements IBinder.DeathRecipient { 101 String mTag; 102 int mUid; 103 IBinder mBinder; 104 String mAttributionTag; 105 String mPackageName; 106 long mAcquireTime; 107 Multicaster(int uid, IBinder binder, String tag, String attributionTag, String packageName)108 Multicaster(int uid, IBinder binder, String tag, String attributionTag, 109 String packageName) { 110 mTag = tag; 111 mUid = uid; 112 mBinder = binder; 113 mAttributionTag = attributionTag; 114 mPackageName = packageName; 115 mAcquireTime = mClock.getElapsedSinceBootMillis(); 116 try { 117 mBinder.linkToDeath(this, 0); 118 } catch (RemoteException e) { 119 binderDied(); 120 } 121 } 122 123 @Override binderDied()124 public void binderDied() { 125 mHandler.post(() -> { 126 Log.e(TAG, "Multicaster binderDied"); 127 synchronized (mLock) { 128 int i = mMulticasters.indexOf(this); 129 if (i != -1) { 130 removeMulticasterLocked(i, mUid, mTag); 131 } 132 } 133 }); 134 } 135 unlinkDeathRecipient()136 void unlinkDeathRecipient() { 137 mBinder.unlinkToDeath(this, 0); 138 } 139 getUid()140 public int getUid() { 141 return mUid; 142 } 143 getTag()144 public String getTag() { 145 return mTag; 146 } 147 getBinder()148 public IBinder getBinder() { 149 return mBinder; 150 } 151 getAttributionTag()152 public String getAttributionTag() { 153 return mAttributionTag; 154 } 155 getPackageName()156 public String getPackageName() { 157 return mPackageName; 158 } 159 getAcquireTime()160 public long getAcquireTime() { 161 return mAcquireTime; 162 } 163 toString()164 public String toString() { 165 return "Multicaster{" + mTag + " uid=" + mUid + "}"; 166 } 167 } 168 uidIsLockOwner(int uid)169 private boolean uidIsLockOwner(int uid) { 170 return mNumLocksPerActiveOwner.containsKey(uid) 171 || mNumLocksPerInactiveOwner.containsKey(uid); 172 } 173 transitionUidToActive(int uid)174 private void transitionUidToActive(int uid) { 175 if (mNumLocksPerInactiveOwner.containsKey(uid)) { 176 mNumLocksPerActiveOwner.put(uid, mNumLocksPerInactiveOwner.get(uid)); 177 mNumLocksPerInactiveOwner.remove(uid); 178 } 179 } 180 transitionUidToInactive(int uid)181 private void transitionUidToInactive(int uid) { 182 if (mNumLocksPerActiveOwner.containsKey(uid)) { 183 mNumLocksPerInactiveOwner.put(uid, mNumLocksPerActiveOwner.get(uid)); 184 mNumLocksPerActiveOwner.remove(uid); 185 } 186 } 187 handleImportanceChanged(int uid, int importance)188 private void handleImportanceChanged(int uid, int importance) { 189 mHandler.post(() -> { 190 synchronized (mLock) { 191 if (!uidIsLockOwner(uid)) { 192 return; 193 } 194 195 boolean uidIsNowActive = importance < IMPORTANCE_THRESHOLD; 196 boolean prevIsMulticastEnabled = isMulticastEnabled(); 197 Log.i(TAG, "Handling importance changed for uid=" + uid 198 + ", isNowActive=" + uidIsNowActive + ", importance=" + importance); 199 if (uidIsNowActive) { 200 transitionUidToActive(uid); 201 } else { 202 transitionUidToInactive(uid); 203 } 204 205 boolean currentIsMulticastEnabled = isMulticastEnabled(); 206 if (prevIsMulticastEnabled != currentIsMulticastEnabled) { 207 if (currentIsMulticastEnabled) { 208 // Filtering should be stopped if multicast is enabled 209 stopFilteringMulticastPackets(); 210 } else { 211 startFilteringMulticastPackets(); 212 } 213 } 214 } 215 }); 216 } 217 218 protected void dump(PrintWriter pw) { 219 pw.println("mMulticastEnabled " + mMulticastEnabled); 220 pw.println("mMulticastDisabled " + mMulticastDisabled); 221 synchronized (mLock) { 222 pw.println("Active lock owners: " + mNumLocksPerActiveOwner); 223 pw.println("Inactive lock owners: " + mNumLocksPerInactiveOwner); 224 pw.println("Multicast Locks held:"); 225 for (Multicaster l : mMulticasters) { 226 pw.print(" "); 227 pw.println(l); 228 } 229 } 230 } 231 232 protected void enableVerboseLogging(boolean verboseEnabled) { 233 mVerboseLoggingEnabled = verboseEnabled; 234 } 235 236 /** Start filtering multicast packets if no locks are actively held */ 237 public void startFilteringMulticastPackets() { 238 synchronized (mLock) { 239 if (!isMulticastEnabled()) { 240 if (mIsFilterDisableSessionActive) { 241 // Log the end of the filtering disabled session, 242 // since we're about to re-enable multicast packet filtering 243 mWifiMetrics.addMulticastLockManagerActiveSession( 244 mClock.getElapsedSinceBootMillis() - mFilterDisableSessionStartTime); 245 } 246 mActiveModeWarden.getPrimaryClientModeManager() 247 .getMcastLockManagerFilterController() 248 .startFilteringMulticastPackets(); 249 mIsFilterDisableSessionActive = false; 250 } 251 } 252 } 253 254 private void stopFilteringMulticastPackets() { 255 synchronized (mLock) { 256 if (!mIsFilterDisableSessionActive) { 257 // Mark the beginning of a filtering disabled session, 258 // since we're about to disable multicast packet filtering 259 mFilterDisableSessionStartTime = mClock.getElapsedSinceBootMillis(); 260 } 261 mActiveModeWarden.getPrimaryClientModeManager() 262 .getMcastLockManagerFilterController() 263 .stopFilteringMulticastPackets(); 264 mIsFilterDisableSessionActive = true; 265 } 266 } 267 268 /** 269 * Acquire a multicast lock. 270 * @param uid uid of the calling application 271 * @param binder a binder used to ensure caller is still alive 272 * @param lockTag caller-provided tag to identify this lock 273 * @param attributionTag attribution tag of the calling application 274 * @param packageName package name of the calling application 275 */ 276 public void acquireLock(int uid, IBinder binder, String lockTag, String attributionTag, 277 String packageName) { 278 synchronized (mLock) { 279 mMulticastEnabled++; 280 281 // Assume that the application is active if it is requesting a lock 282 if (mNumLocksPerInactiveOwner.containsKey(uid)) { 283 transitionUidToActive(uid); 284 } 285 int numLocksHeldByUid = mNumLocksPerActiveOwner.getOrDefault(uid, 0); 286 mNumLocksPerActiveOwner.put(uid, numLocksHeldByUid + 1); 287 mMulticasters.add(new Multicaster(uid, binder, lockTag, attributionTag, packageName)); 288 289 // Note that we could call stopFilteringMulticastPackets only when 290 // our new size == 1 (first call), but this function won't 291 // be called often and by making the stopPacket call each 292 // time we're less fragile and self-healing. 293 stopFilteringMulticastPackets(); 294 } 295 296 final long ident = Binder.clearCallingIdentity(); 297 mBatteryStats.reportWifiMulticastEnabled(new WorkSource(uid)); 298 WifiStatsLog.write_non_chained( 299 WifiStatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED, uid, null, 300 WifiStatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED__STATE__ON, lockTag); 301 Binder.restoreCallingIdentity(ident); 302 } 303 304 /** Releases a multicast lock */ 305 public void releaseLock(int uid, IBinder binder, String lockTag) { 306 synchronized (mLock) { 307 mMulticastDisabled++; 308 int size = mMulticasters.size(); 309 for (int i = size - 1; i >= 0; i--) { 310 Multicaster m = mMulticasters.get(i); 311 if ((m != null) && (m.getUid() == uid) && (m.getTag().equals(lockTag)) 312 && (m.getBinder() == binder)) { 313 removeMulticasterLocked(i, uid, lockTag); 314 break; 315 } 316 } 317 } 318 } 319 decrementNumLocksForUid(int uid, Map<Integer, Integer> map)320 private void decrementNumLocksForUid(int uid, Map<Integer, Integer> map) { 321 int numLocksHeldByUid = map.get(uid) - 1; 322 if (numLocksHeldByUid == 0) { 323 map.remove(uid); 324 } else { 325 map.put(uid, numLocksHeldByUid); 326 } 327 } 328 removeMulticasterLocked(int i, int uid, String tag)329 private void removeMulticasterLocked(int i, int uid, String tag) { 330 Multicaster removed = mMulticasters.remove(i); 331 if (removed != null) { 332 removed.unlinkDeathRecipient(); 333 mWifiMetrics.addMulticastLockManagerAcqSession( 334 uid, removed.getAttributionTag(), 335 mWifiPermissionsUtil.getWifiCallerType(uid, removed.getPackageName()), 336 mClock.getElapsedSinceBootMillis() - removed.getAcquireTime()); 337 } 338 339 if (mNumLocksPerActiveOwner.containsKey(uid)) { 340 decrementNumLocksForUid(uid, mNumLocksPerActiveOwner); 341 } else if (mNumLocksPerInactiveOwner.containsKey(uid)) { 342 decrementNumLocksForUid(uid, mNumLocksPerInactiveOwner); 343 } 344 345 if (!isMulticastEnabled()) { 346 startFilteringMulticastPackets(); 347 } 348 349 final long ident = Binder.clearCallingIdentity(); 350 mBatteryStats.reportWifiMulticastDisabled(new WorkSource(uid)); 351 WifiStatsLog.write_non_chained( 352 WifiStatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED, uid, null, 353 WifiStatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED__STATE__OFF, tag); 354 Binder.restoreCallingIdentity(ident); 355 } 356 357 /** Returns whether multicast should be allowed (filtering disabled). */ isMulticastEnabled()358 public boolean isMulticastEnabled() { 359 synchronized (mLock) { 360 // Multicast is enabled if any active lock owners exist 361 return !mNumLocksPerActiveOwner.isEmpty(); 362 } 363 } 364 365 private class PrimaryClientModeManagerChangedCallback 366 implements ActiveModeWarden.PrimaryClientModeManagerChangedCallback { 367 368 @Override onChange( @ullable ConcreteClientModeManager prevPrimaryClientModeManager, @Nullable ConcreteClientModeManager newPrimaryClientModeManager)369 public void onChange( 370 @Nullable ConcreteClientModeManager prevPrimaryClientModeManager, 371 @Nullable ConcreteClientModeManager newPrimaryClientModeManager) { 372 if (prevPrimaryClientModeManager != null) { 373 // no longer primary => start filtering out multicast packets 374 prevPrimaryClientModeManager.getMcastLockManagerFilterController() 375 .startFilteringMulticastPackets(); 376 } 377 if (newPrimaryClientModeManager != null 378 && isMulticastEnabled()) { // this call is synchronized 379 // new primary and multicast enabled => stop filtering out multicast packets 380 newPrimaryClientModeManager.getMcastLockManagerFilterController() 381 .stopFilteringMulticastPackets(); 382 } 383 } 384 } 385 } 386