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.os.BatteryStatsManager;
20 import android.os.Binder;
21 import android.os.IBinder;
22 import android.os.RemoteException;
23 import android.os.WorkSource;
24 import android.util.Log;
25 
26 import com.android.server.wifi.proto.WifiStatsLog;
27 
28 import java.io.PrintWriter;
29 import java.util.ArrayList;
30 import java.util.List;
31 
32 /**
33  * WifiMulticastLockManager tracks holders of multicast locks and
34  * triggers enabling and disabling of filtering.
35  *
36  * @hide
37  */
38 public class WifiMulticastLockManager {
39     private static final String TAG = "WifiMulticastLockManager";
40     private final List<Multicaster> mMulticasters = new ArrayList<>();
41     private int mMulticastEnabled = 0;
42     private int mMulticastDisabled = 0;
43     private boolean mVerboseLoggingEnabled = false;
44     private final BatteryStatsManager mBatteryStats;
45     private final FilterController mFilterController;
46 
47     /** Delegate for handling state change events for multicast filtering. */
48     public interface FilterController {
49         /** Called when multicast filtering should be enabled */
startFilteringMulticastPackets()50         void startFilteringMulticastPackets();
51 
52         /** Called when multicast filtering should be disabled */
stopFilteringMulticastPackets()53         void stopFilteringMulticastPackets();
54     }
55 
WifiMulticastLockManager(FilterController filterController, BatteryStatsManager batteryStats)56     public WifiMulticastLockManager(FilterController filterController,
57             BatteryStatsManager batteryStats) {
58         mBatteryStats = batteryStats;
59         mFilterController = filterController;
60     }
61 
62     private class Multicaster implements IBinder.DeathRecipient {
63         String mTag;
64         int mUid;
65         IBinder mBinder;
66 
Multicaster(String tag, IBinder binder)67         Multicaster(String tag, IBinder binder) {
68             mTag = tag;
69             mUid = Binder.getCallingUid();
70             mBinder = binder;
71             try {
72                 mBinder.linkToDeath(this, 0);
73             } catch (RemoteException e) {
74                 binderDied();
75             }
76         }
77 
78         @Override
binderDied()79         public void binderDied() {
80             Log.e(TAG, "Multicaster binderDied");
81             synchronized (mMulticasters) {
82                 int i = mMulticasters.indexOf(this);
83                 if (i != -1) {
84                     removeMulticasterLocked(i, mUid, mTag);
85                 }
86             }
87         }
88 
unlinkDeathRecipient()89         void unlinkDeathRecipient() {
90             mBinder.unlinkToDeath(this, 0);
91         }
92 
getUid()93         public int getUid() {
94             return mUid;
95         }
96 
getTag()97         public String getTag() {
98             return mTag;
99         }
100 
toString()101         public String toString() {
102             return "Multicaster{" + mTag + " uid=" + mUid  + "}";
103         }
104     }
105 
dump(PrintWriter pw)106     protected void dump(PrintWriter pw) {
107         pw.println("mMulticastEnabled " + mMulticastEnabled);
108         pw.println("mMulticastDisabled " + mMulticastDisabled);
109         pw.println("Multicast Locks held:");
110         for (Multicaster l : mMulticasters) {
111             pw.print("    ");
112             pw.println(l);
113         }
114     }
115 
enableVerboseLogging(int verbose)116     protected void enableVerboseLogging(int verbose) {
117         if (verbose > 0) {
118             mVerboseLoggingEnabled = true;
119         } else {
120             mVerboseLoggingEnabled = false;
121         }
122     }
123 
124     /** Start filtering if  no multicasters exist. */
initializeFiltering()125     public void initializeFiltering() {
126         synchronized (mMulticasters) {
127             // if anybody had requested filters be off, leave off
128             if (mMulticasters.size() != 0) {
129                 return;
130             } else {
131                 mFilterController.startFilteringMulticastPackets();
132             }
133         }
134     }
135 
136     /**
137      * Acquire a multicast lock.
138      * @param binder a binder used to ensure caller is still alive
139      * @param tag string name of the caller.
140      */
acquireLock(IBinder binder, String tag)141     public void acquireLock(IBinder binder, String tag) {
142         synchronized (mMulticasters) {
143             mMulticastEnabled++;
144             mMulticasters.add(new Multicaster(tag, binder));
145             // Note that we could call stopFilteringMulticastPackets only when
146             // our new size == 1 (first call), but this function won't
147             // be called often and by making the stopPacket call each
148             // time we're less fragile and self-healing.
149             mFilterController.stopFilteringMulticastPackets();
150         }
151 
152         int uid = Binder.getCallingUid();
153         final long ident = Binder.clearCallingIdentity();
154         mBatteryStats.reportWifiMulticastEnabled(new WorkSource(uid));
155         WifiStatsLog.write_non_chained(
156                 WifiStatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED, uid, null,
157                 WifiStatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED__STATE__ON, tag);
158         Binder.restoreCallingIdentity(ident);
159     }
160 
161     /** Releases a multicast lock */
releaseLock(String tag)162     public void releaseLock(String tag) {
163         int uid = Binder.getCallingUid();
164         synchronized (mMulticasters) {
165             mMulticastDisabled++;
166             int size = mMulticasters.size();
167             for (int i = size - 1; i >= 0; i--) {
168                 Multicaster m = mMulticasters.get(i);
169                 if ((m != null) && (m.getUid() == uid) && (m.getTag().equals(tag))) {
170                     removeMulticasterLocked(i, uid, tag);
171                     break;
172                 }
173             }
174         }
175     }
176 
removeMulticasterLocked(int i, int uid, String tag)177     private void removeMulticasterLocked(int i, int uid, String tag) {
178         Multicaster removed = mMulticasters.remove(i);
179 
180         if (removed != null) {
181             removed.unlinkDeathRecipient();
182         }
183         if (mMulticasters.size() == 0) {
184             mFilterController.startFilteringMulticastPackets();
185         }
186 
187         final long ident = Binder.clearCallingIdentity();
188         mBatteryStats.reportWifiMulticastDisabled(new WorkSource(uid));
189         WifiStatsLog.write_non_chained(
190                 WifiStatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED, uid, null,
191                 WifiStatsLog.WIFI_MULTICAST_LOCK_STATE_CHANGED__STATE__OFF, tag);
192         Binder.restoreCallingIdentity(ident);
193     }
194 
195     /** Returns whether multicast should be allowed (filterning disabled). */
isMulticastEnabled()196     public boolean isMulticastEnabled() {
197         synchronized (mMulticasters) {
198             return (mMulticasters.size() > 0);
199         }
200     }
201 }
202