WakeupController.java revision 32152cdeeefb4d8cc9a1137232892e5360ae1f5b
1/*
2 * Copyright 2017 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 static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
20
21import android.content.Context;
22import android.database.ContentObserver;
23import android.net.wifi.ScanResult;
24import android.net.wifi.WifiConfiguration;
25import android.net.wifi.WifiScanner;
26import android.os.Handler;
27import android.os.Looper;
28import android.provider.Settings;
29import android.util.ArraySet;
30import android.util.Log;
31
32import com.android.internal.annotations.VisibleForTesting;
33
34import java.io.FileDescriptor;
35import java.io.PrintWriter;
36import java.util.Arrays;
37import java.util.Collection;
38import java.util.HashSet;
39import java.util.List;
40import java.util.Set;
41
42
43/**
44 * WakeupController is responsible managing Auto Wifi.
45 *
46 * <p>It determines if and when to re-enable wifi after it has been turned off by the user.
47 */
48public class WakeupController {
49
50    private static final String TAG = "WakeupController";
51
52    private static final boolean USE_PLATFORM_WIFI_WAKE = true;
53
54    private final Context mContext;
55    private final Handler mHandler;
56    private final FrameworkFacade mFrameworkFacade;
57    private final ContentObserver mContentObserver;
58    private final WakeupLock mWakeupLock;
59    private final WakeupEvaluator mWakeupEvaluator;
60    private final WakeupOnboarding mWakeupOnboarding;
61    private final WifiConfigManager mWifiConfigManager;
62    private final WifiInjector mWifiInjector;
63
64    private final WifiScanner.ScanListener mScanListener = new WifiScanner.ScanListener() {
65        @Override
66        public void onPeriodChanged(int periodInMs) {
67            // no-op
68        }
69
70        @Override
71        public void onResults(WifiScanner.ScanData[] results) {
72            if (results.length == 1 && results[0].isAllChannelsScanned()) {
73                handleScanResults(Arrays.asList(results[0].getResults()));
74            }
75        }
76
77        @Override
78        public void onFullResult(ScanResult fullScanResult) {
79            // no-op
80        }
81
82        @Override
83        public void onSuccess() {
84            // no-op
85        }
86
87        @Override
88        public void onFailure(int reason, String description) {
89            Log.e(TAG, "ScanListener onFailure: " + reason + ": " + description);
90        }
91    };
92
93    /** Whether this feature is enabled in Settings. */
94    private boolean mWifiWakeupEnabled;
95
96    /** Whether the WakeupController is currently active. */
97    private boolean mIsActive = false;
98
99    public WakeupController(
100            Context context,
101            Looper looper,
102            WakeupLock wakeupLock,
103            WakeupEvaluator wakeupEvaluator,
104            WakeupOnboarding wakeupOnboarding,
105            WifiConfigManager wifiConfigManager,
106            WifiConfigStore wifiConfigStore,
107            WifiInjector wifiInjector,
108            FrameworkFacade frameworkFacade) {
109        mContext = context;
110        mHandler = new Handler(looper);
111        mWakeupLock = wakeupLock;
112        mWakeupEvaluator = wakeupEvaluator;
113        mWakeupOnboarding = wakeupOnboarding;
114        mWifiConfigManager = wifiConfigManager;
115        mFrameworkFacade = frameworkFacade;
116        mWifiInjector = wifiInjector;
117        mContentObserver = new ContentObserver(mHandler) {
118            @Override
119            public void onChange(boolean selfChange) {
120                mWifiWakeupEnabled = mFrameworkFacade.getIntegerSetting(
121                                mContext, Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1;
122            }
123        };
124        mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor(
125                Settings.Global.WIFI_WAKEUP_ENABLED), true, mContentObserver);
126        mContentObserver.onChange(false /* selfChange */);
127
128        // registering the store data here has the effect of reading the persisted value of the
129        // data sources after system boot finishes
130        WakeupConfigStoreData wakeupConfigStoreData = new WakeupConfigStoreData(
131                new IsActiveDataSource(),
132                mWakeupOnboarding.getDataSource(),
133                mWakeupLock.getDataSource());
134        wifiConfigStore.registerStoreData(wakeupConfigStoreData);
135    }
136
137    private void setActive(boolean isActive) {
138        if (mIsActive != isActive) {
139            Log.d(TAG, "Setting active to " + isActive);
140            mIsActive = isActive;
141            mWifiConfigManager.saveToStore(false /* forceWrite */);
142        }
143    }
144
145    /**
146     * Starts listening for incoming scans.
147     *
148     * <p>Should only be called upon entering ScanMode. WakeupController registers its listener with
149     * the WifiScanner. If the WakeupController is already active, then it returns early. Otherwise
150     * it performs its initialization steps and sets {@link #mIsActive} to true.
151     */
152    public void start() {
153        Log.d(TAG, "start()");
154        mWifiInjector.getWifiScanner().registerScanListener(mScanListener);
155
156        // If already active, we don't want to re-initialize the lock, so return early.
157        if (mIsActive) {
158            return;
159        }
160        setActive(true);
161
162        if (isEnabled()) {
163            mWakeupOnboarding.maybeShowNotification();
164            mWakeupLock.initialize(getMostRecentSavedScanResults());
165        }
166    }
167
168    /**
169     * Stops listening for scans.
170     *
171     * <p>Should only be called upon leaving ScanMode. It deregisters the listener from
172     * WifiScanner.
173     */
174    public void stop() {
175        Log.d(TAG, "stop()");
176        mWifiInjector.getWifiScanner().deregisterScanListener(mScanListener);
177        mWakeupOnboarding.onStop();
178    }
179
180    /** Resets the WakeupController, setting {@link #mIsActive} to false. */
181    public void reset() {
182        Log.d(TAG, "reset()");
183        setActive(false);
184    }
185
186    /** Returns a list of saved networks from the last full scan. */
187    private Set<ScanResultMatchInfo> getMostRecentSavedScanResults() {
188        Set<ScanResultMatchInfo> goodSavedNetworks = getGoodSavedNetworks();
189
190        List<ScanResult> scanResults = mWifiInjector.getWifiScanner().getSingleScanResults();
191        Set<ScanResultMatchInfo> lastSeenNetworks = new HashSet<>(scanResults.size());
192        for (ScanResult scanResult : scanResults) {
193            lastSeenNetworks.add(ScanResultMatchInfo.fromScanResult(scanResult));
194        }
195
196        lastSeenNetworks.retainAll(goodSavedNetworks);
197        return lastSeenNetworks;
198    }
199
200    /** Returns a filtered list of saved networks from WifiConfigManager. */
201    private Set<ScanResultMatchInfo> getGoodSavedNetworks() {
202        List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks();
203
204        Set<ScanResultMatchInfo> goodSavedNetworks = new HashSet<>(savedNetworks.size());
205        for (WifiConfiguration config : savedNetworks) {
206            if (isWideAreaNetwork(config)
207                    || config.hasNoInternetAccess()
208                    || config.noInternetAccessExpected
209                    || !config.getNetworkSelectionStatus().getHasEverConnected()) {
210                continue;
211            }
212            goodSavedNetworks.add(ScanResultMatchInfo.fromWifiConfiguration(config));
213        }
214
215        Log.d(TAG, "getGoodSavedNetworks: " + goodSavedNetworks.size());
216        return goodSavedNetworks;
217    }
218
219    //TODO(b/69271702) implement WAN filtering
220    private boolean isWideAreaNetwork(WifiConfiguration wifiConfiguration) {
221        return false;
222    }
223
224    /**
225     * Handles incoming scan results.
226     *
227     * <p>The controller updates the WakeupLock with the incoming scan results. If WakeupLock is
228     * empty, it evaluates scan results for a match with saved networks. If a match exists, it
229     * enables wifi.
230     *
231     * @param scanResults The scan results with which to update the controller
232     */
233    private void handleScanResults(Collection<ScanResult> scanResults) {
234        if (!isEnabled()) {
235            return;
236        }
237
238        // need to show notification here in case user enables Wifi Wake when Wifi is off
239        mWakeupOnboarding.maybeShowNotification();
240        if (!mWakeupOnboarding.isOnboarded()) {
241            return;
242        }
243
244        // only update the wakeup lock if it's not already empty
245        if (!mWakeupLock.isEmpty()) {
246            Set<ScanResultMatchInfo> networks = new ArraySet<>();
247            for (ScanResult scanResult : scanResults) {
248                networks.add(ScanResultMatchInfo.fromScanResult(scanResult));
249            }
250            mWakeupLock.update(networks);
251
252            // if wakeup lock is still not empty, return
253            if (!mWakeupLock.isEmpty()) {
254                return;
255            }
256
257            Log.d(TAG, "WakeupLock emptied");
258        }
259
260        ScanResult network =
261                mWakeupEvaluator.findViableNetwork(scanResults, getGoodSavedNetworks());
262
263        if (network != null) {
264            Log.d(TAG, "Found viable network: " + network.SSID);
265            onNetworkFound(network);
266        }
267    }
268
269    private void onNetworkFound(ScanResult scanResult) {
270        if (isEnabled() && mIsActive && USE_PLATFORM_WIFI_WAKE) {
271            Log.d(TAG, "Enabling wifi for network: " + scanResult.SSID);
272            enableWifi();
273        }
274    }
275
276    /**
277     * Enables wifi.
278     *
279     * <p>This method ignores all checks and assumes that {@link WifiStateMachine} is currently
280     * in ScanModeState.
281     */
282    private void enableWifi() {
283        // TODO(b/72180295): ensure that there is no race condition with WifiServiceImpl here
284        if (mWifiInjector.getWifiSettingsStore().handleWifiToggled(true /* wifiEnabled */)) {
285            mWifiInjector.getWifiController().sendMessage(CMD_WIFI_TOGGLED);
286        }
287    }
288
289    /** Whether the feature is enabled in settings. */
290    @VisibleForTesting
291    boolean isEnabled() {
292        return mWifiWakeupEnabled;
293    }
294
295    /** Dumps wakeup controller state. */
296    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
297        pw.println("Dump of WakeupController");
298        pw.println("mWifiWakeupEnabled: " + mWifiWakeupEnabled);
299        pw.println("USE_PLATFORM_WIFI_WAKE: " + USE_PLATFORM_WIFI_WAKE);
300        pw.println("mIsActive: " + mIsActive);
301        mWakeupLock.dump(fd, pw, args);
302    }
303
304    private class IsActiveDataSource implements WakeupConfigStoreData.DataSource<Boolean> {
305
306        @Override
307        public Boolean getData() {
308            return mIsActive;
309        }
310
311        @Override
312        public void setData(Boolean data) {
313            mIsActive = data;
314        }
315    }
316}
317