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