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