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.content.Context;
20import android.net.wifi.WifiManager;
21import android.os.Binder;
22import android.os.IBinder;
23import android.os.RemoteException;
24import android.os.WorkSource;
25import android.util.Slog;
26
27import com.android.internal.app.IBatteryStats;
28
29import java.io.PrintWriter;
30import java.util.ArrayList;
31import java.util.List;
32
33/**
34 * WifiLockManager maintains the list of wake locks held by different applications.
35 */
36public class WifiLockManager {
37    private static final String TAG = "WifiLockManager";
38    private boolean mVerboseLoggingEnabled = false;
39
40    private final Context mContext;
41    private final IBatteryStats mBatteryStats;
42
43    private final List<WifiLock> mWifiLocks = new ArrayList<>();
44    // some wifi lock statistics
45    private int mFullHighPerfLocksAcquired;
46    private int mFullHighPerfLocksReleased;
47    private int mFullLocksAcquired;
48    private int mFullLocksReleased;
49    private int mScanLocksAcquired;
50    private int mScanLocksReleased;
51
52    WifiLockManager(Context context, IBatteryStats batteryStats) {
53        mContext = context;
54        mBatteryStats = batteryStats;
55    }
56
57    /**
58     * Method allowing a calling app to acquire a Wifi WakeLock in the supplied mode.
59     *
60     * This method verifies that the caller has permission to make the call and that the lock mode
61     * is a valid WifiLock mode.
62     * @param lockMode int representation of the Wifi WakeLock type.
63     * @param tag String passed to WifiManager.WifiLock
64     * @param binder IBinder for the calling app
65     * @param ws WorkSource of the calling app
66     *
67     * @return true if the lock was successfully acquired, false if the lockMode was invalid.
68     */
69    public boolean acquireWifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
70        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
71        if (!isValidLockMode(lockMode)) {
72            throw new IllegalArgumentException("lockMode =" + lockMode);
73        }
74        if (ws == null || ws.size() == 0) {
75            ws = new WorkSource(Binder.getCallingUid());
76        } else {
77            mContext.enforceCallingOrSelfPermission(
78                    android.Manifest.permission.UPDATE_DEVICE_STATS, null);
79        }
80        return addLock(new WifiLock(lockMode, tag, binder, ws));
81    }
82
83    /**
84     * Method used by applications to release a WiFi Wake lock.  This method checks permissions for
85     * the caller and if allowed, releases the underlying WifiLock(s).
86     *
87     * @param binder IBinder for the calling app.
88     * @return true if the lock was released, false if the caller did not hold any locks
89     */
90    public boolean releaseWifiLock(IBinder binder) {
91        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
92        return releaseLock(binder);
93    }
94
95    /**
96     * Method used to get the strongest lock type currently held by the WifiLockManager.
97     *
98     * If no locks are held, WifiManager.WIFI_MODE_NO_LOCKS_HELD is returned.
99     *
100     * @return int representing the currently held (highest power consumption) lock.
101     */
102    public synchronized int getStrongestLockMode() {
103        if (mWifiLocks.isEmpty()) {
104            return WifiManager.WIFI_MODE_NO_LOCKS_HELD;
105        }
106
107        if (mFullHighPerfLocksAcquired > mFullHighPerfLocksReleased) {
108            return WifiManager.WIFI_MODE_FULL_HIGH_PERF;
109        }
110
111        if (mFullLocksAcquired > mFullLocksReleased) {
112            return WifiManager.WIFI_MODE_FULL;
113        }
114
115        return WifiManager.WIFI_MODE_SCAN_ONLY;
116    }
117
118    /**
119     * Method to create a WorkSource containing all active WifiLock WorkSources.
120     */
121    public synchronized WorkSource createMergedWorkSource() {
122        WorkSource mergedWS = new WorkSource();
123        for (WifiLock lock : mWifiLocks) {
124            mergedWS.add(lock.getWorkSource());
125        }
126        return mergedWS;
127    }
128
129    /**
130     * Method used to update WifiLocks with a new WorkSouce.
131     *
132     * @param binder IBinder for the calling application.
133     * @param ws WorkSource to add to the existing WifiLock(s).
134     */
135    public synchronized void updateWifiLockWorkSource(IBinder binder, WorkSource ws) {
136        // Does the caller have permission to make this call?
137        mContext.enforceCallingOrSelfPermission(
138                android.Manifest.permission.UPDATE_DEVICE_STATS, null);
139
140        // Now check if there is an active lock
141        WifiLock wl = findLockByBinder(binder);
142        if (wl == null) {
143            throw new IllegalArgumentException("Wifi lock not active");
144        }
145
146        WorkSource newWorkSource;
147        if (ws == null || ws.size() == 0) {
148            newWorkSource = new WorkSource(Binder.getCallingUid());
149        } else {
150            // Make a copy of the WorkSource before adding it to the WakeLock
151            newWorkSource = new WorkSource(ws);
152        }
153
154        long ident = Binder.clearCallingIdentity();
155        try {
156            mBatteryStats.noteFullWifiLockReleasedFromSource(wl.mWorkSource);
157            wl.mWorkSource = newWorkSource;
158            mBatteryStats.noteFullWifiLockAcquiredFromSource(wl.mWorkSource);
159        } catch (RemoteException e) {
160        } finally {
161            Binder.restoreCallingIdentity(ident);
162        }
163    }
164
165    private static boolean isValidLockMode(int lockMode) {
166        if (lockMode != WifiManager.WIFI_MODE_FULL
167                && lockMode != WifiManager.WIFI_MODE_SCAN_ONLY
168                && lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF) {
169            return false;
170        }
171        return true;
172    }
173
174    private synchronized boolean addLock(WifiLock lock) {
175        if (mVerboseLoggingEnabled) {
176            Slog.d(TAG, "addLock: " + lock);
177        }
178
179        if (findLockByBinder(lock.getBinder()) != null) {
180            if (mVerboseLoggingEnabled) {
181                Slog.d(TAG, "attempted to add a lock when already holding one");
182            }
183            return false;
184        }
185
186        mWifiLocks.add(lock);
187
188        boolean lockAdded = false;
189        long ident = Binder.clearCallingIdentity();
190        try {
191            mBatteryStats.noteFullWifiLockAcquiredFromSource(lock.mWorkSource);
192            switch(lock.mMode) {
193                case WifiManager.WIFI_MODE_FULL:
194                    ++mFullLocksAcquired;
195                    break;
196                case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
197                    ++mFullHighPerfLocksAcquired;
198                    break;
199                case WifiManager.WIFI_MODE_SCAN_ONLY:
200                    ++mScanLocksAcquired;
201                    break;
202            }
203            lockAdded = true;
204        } catch (RemoteException e) {
205        } finally {
206            Binder.restoreCallingIdentity(ident);
207        }
208        return lockAdded;
209    }
210
211    private synchronized WifiLock removeLock(IBinder binder) {
212        WifiLock lock = findLockByBinder(binder);
213        if (lock != null) {
214            mWifiLocks.remove(lock);
215            lock.unlinkDeathRecipient();
216        }
217        return lock;
218    }
219
220    private synchronized boolean releaseLock(IBinder binder) {
221        WifiLock wifiLock = removeLock(binder);
222        if (wifiLock == null) {
223            // attempting to release a lock that is not active.
224            return false;
225        }
226
227        if (mVerboseLoggingEnabled) {
228            Slog.d(TAG, "releaseLock: " + wifiLock);
229        }
230
231        long ident = Binder.clearCallingIdentity();
232        try {
233            mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
234            switch(wifiLock.mMode) {
235                case WifiManager.WIFI_MODE_FULL:
236                    ++mFullLocksReleased;
237                    break;
238                case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
239                    ++mFullHighPerfLocksReleased;
240                    break;
241                case WifiManager.WIFI_MODE_SCAN_ONLY:
242                    ++mScanLocksReleased;
243                    break;
244            }
245        } catch (RemoteException e) {
246        } finally {
247            Binder.restoreCallingIdentity(ident);
248        }
249        return true;
250    }
251
252
253    private synchronized WifiLock findLockByBinder(IBinder binder) {
254        for (WifiLock lock : mWifiLocks) {
255            if (lock.getBinder() == binder) {
256                return lock;
257            }
258        }
259        return null;
260    }
261
262    protected void dump(PrintWriter pw) {
263        pw.println("Locks acquired: " + mFullLocksAcquired + " full, "
264                + mFullHighPerfLocksAcquired + " full high perf, "
265                + mScanLocksAcquired + " scan");
266        pw.println("Locks released: " + mFullLocksReleased + " full, "
267                + mFullHighPerfLocksReleased + " full high perf, "
268                + mScanLocksReleased + " scan");
269        pw.println();
270        pw.println("Locks held:");
271        for (WifiLock lock : mWifiLocks) {
272            pw.print("    ");
273            pw.println(lock);
274        }
275    }
276
277    protected void enableVerboseLogging(int verbose) {
278        if (verbose > 0) {
279            mVerboseLoggingEnabled = true;
280        } else {
281            mVerboseLoggingEnabled = false;
282        }
283    }
284
285    private class WifiLock implements IBinder.DeathRecipient {
286        String mTag;
287        int mUid;
288        IBinder mBinder;
289        int mMode;
290        WorkSource mWorkSource;
291
292        WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
293            mTag = tag;
294            mBinder = binder;
295            mUid = Binder.getCallingUid();
296            mMode = lockMode;
297            mWorkSource = ws;
298            try {
299                mBinder.linkToDeath(this, 0);
300            } catch (RemoteException e) {
301                binderDied();
302            }
303        }
304
305        protected WorkSource getWorkSource() {
306            return mWorkSource;
307        }
308
309        protected int getUid() {
310            return mUid;
311        }
312
313        protected IBinder getBinder() {
314            return mBinder;
315        }
316
317        public void binderDied() {
318            releaseLock(mBinder);
319        }
320
321        public void unlinkDeathRecipient() {
322            mBinder.unlinkToDeath(this, 0);
323        }
324
325        public String toString() {
326            return "WifiLock{" + this.mTag + " type=" + this.mMode + " uid=" + mUid + "}";
327        }
328    }
329}
330