WakeupController.java revision 5aad8ece7e38a80db917d49b5245f6b8c6dca273
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.Log;
30
31import com.android.internal.annotations.VisibleForTesting;
32
33import java.io.FileDescriptor;
34import java.io.PrintWriter;
35import java.util.Arrays;
36import java.util.Collection;
37import java.util.HashSet;
38import java.util.List;
39import java.util.Set;
40import java.util.stream.Collectors;
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    private final WakeupConfigStoreData mWakeupConfigStoreData;
64    private final WifiWakeMetrics mWifiWakeMetrics;
65
66    private final WifiScanner.ScanListener mScanListener = new WifiScanner.ScanListener() {
67        @Override
68        public void onPeriodChanged(int periodInMs) {
69            // no-op
70        }
71
72        @Override
73        public void onResults(WifiScanner.ScanData[] results) {
74            if (results.length == 1 && results[0].isAllChannelsScanned()) {
75                handleScanResults(Arrays.asList(results[0].getResults()));
76            }
77        }
78
79        @Override
80        public void onFullResult(ScanResult fullScanResult) {
81            // no-op
82        }
83
84        @Override
85        public void onSuccess() {
86            // no-op
87        }
88
89        @Override
90        public void onFailure(int reason, String description) {
91            Log.e(TAG, "ScanListener onFailure: " + reason + ": " + description);
92        }
93    };
94
95    /** Whether this feature is enabled in Settings. */
96    private boolean mWifiWakeupEnabled;
97
98    /** Whether the WakeupController is currently active. */
99    private boolean mIsActive = false;
100
101    /** The number of scans that have been handled by the controller since last {@link #reset()}. */
102    private int mNumScansHandled = 0;
103
104    /** Whether Wifi verbose logging is enabled. */
105    private boolean mVerboseLoggingEnabled;
106
107    public WakeupController(
108            Context context,
109            Looper looper,
110            WakeupLock wakeupLock,
111            WakeupEvaluator wakeupEvaluator,
112            WakeupOnboarding wakeupOnboarding,
113            WifiConfigManager wifiConfigManager,
114            WifiConfigStore wifiConfigStore,
115            WifiWakeMetrics wifiWakeMetrics,
116            WifiInjector wifiInjector,
117            FrameworkFacade frameworkFacade) {
118        mContext = context;
119        mHandler = new Handler(looper);
120        mWakeupLock = wakeupLock;
121        mWakeupEvaluator = wakeupEvaluator;
122        mWakeupOnboarding = wakeupOnboarding;
123        mWifiConfigManager = wifiConfigManager;
124        mWifiWakeMetrics = wifiWakeMetrics;
125        mFrameworkFacade = frameworkFacade;
126        mWifiInjector = wifiInjector;
127        mContentObserver = new ContentObserver(mHandler) {
128            @Override
129            public void onChange(boolean selfChange) {
130                readWifiWakeupEnabledFromSettings();
131                mWakeupOnboarding.setOnboarded();
132            }
133        };
134        mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor(
135                Settings.Global.WIFI_WAKEUP_ENABLED), true, mContentObserver);
136        readWifiWakeupEnabledFromSettings();
137
138        // registering the store data here has the effect of reading the persisted value of the
139        // data sources after system boot finishes
140        mWakeupConfigStoreData = new WakeupConfigStoreData(
141                new IsActiveDataSource(),
142                mWakeupOnboarding.getIsOnboadedDataSource(),
143                mWakeupOnboarding.getNotificationsDataSource(),
144                mWakeupLock.getDataSource());
145        wifiConfigStore.registerStoreData(mWakeupConfigStoreData);
146    }
147
148    private void readWifiWakeupEnabledFromSettings() {
149        mWifiWakeupEnabled = mFrameworkFacade.getIntegerSetting(
150                mContext, Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1;
151        Log.d(TAG, "WifiWake " + (mWifiWakeupEnabled ? "enabled" : "disabled"));
152    }
153
154    private void setActive(boolean isActive) {
155        if (mIsActive != isActive) {
156            Log.d(TAG, "Setting active to " + isActive);
157            mIsActive = isActive;
158            mWifiConfigManager.saveToStore(false /* forceWrite */);
159        }
160    }
161
162    /**
163     * Starts listening for incoming scans.
164     *
165     * <p>Should only be called upon entering ScanMode. WakeupController registers its listener with
166     * the WifiScanner. If the WakeupController is already active, then it returns early. Otherwise
167     * it performs its initialization steps and sets {@link #mIsActive} to true.
168     */
169    public void start() {
170        Log.d(TAG, "start()");
171        mWifiInjector.getWifiScanner().registerScanListener(mScanListener);
172
173        // If already active, we don't want to re-initialize the lock, so return early.
174        if (mIsActive) {
175            // TODO record metric for calls to start() when already active
176            return;
177        }
178        setActive(true);
179
180        // ensure feature is enabled and store data has been read before performing work
181        if (isEnabled()) {
182            mWakeupOnboarding.maybeShowNotification();
183
184            Set<ScanResultMatchInfo> savedNetworksFromLatestScan = getSavedNetworksFromLatestScan();
185            if (mVerboseLoggingEnabled) {
186                Log.d(TAG, "Saved networks in most recent scan:" + savedNetworksFromLatestScan);
187            }
188
189            mWifiWakeMetrics.recordStartEvent(savedNetworksFromLatestScan.size());
190            mWakeupLock.setLock(savedNetworksFromLatestScan);
191            // TODO(b/77291248): request low latency scan here
192        }
193    }
194
195    /**
196     * Stops listening for scans.
197     *
198     * <p>Should only be called upon leaving ScanMode. It deregisters the listener from
199     * WifiScanner.
200     */
201    public void stop() {
202        Log.d(TAG, "stop()");
203        mWifiInjector.getWifiScanner().deregisterScanListener(mScanListener);
204        mWakeupOnboarding.onStop();
205    }
206
207    /** Resets the WakeupController, setting {@link #mIsActive} to false. */
208    public void reset() {
209        Log.d(TAG, "reset()");
210        mWifiWakeMetrics.recordResetEvent(mNumScansHandled);
211        mNumScansHandled = 0;
212        setActive(false);
213    }
214
215    /** Sets verbose logging flag based on verbose level. */
216    public void enableVerboseLogging(int verbose) {
217        mVerboseLoggingEnabled = verbose > 0;
218        mWakeupLock.enableVerboseLogging(mVerboseLoggingEnabled);
219    }
220
221    /** Returns a filtered list of saved networks from the last full scan. */
222    private Set<ScanResultMatchInfo> getSavedNetworksFromLatestScan() {
223        Set<ScanResult> filteredScanResults =
224                filterScanResults(mWifiInjector.getWifiScanner().getSingleScanResults());
225        Set<ScanResultMatchInfo> goodMatchInfos  = toMatchInfos(filteredScanResults);
226        goodMatchInfos.retainAll(getGoodSavedNetworks());
227
228        return goodMatchInfos;
229    }
230
231    /** Returns a set of ScanResults with all DFS channels removed. */
232    private Set<ScanResult> filterScanResults(Collection<ScanResult> scanResults) {
233        int[] dfsChannels = mWifiInjector.getWifiNative()
234                .getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY);
235        if (dfsChannels == null) {
236            dfsChannels = new int[0];
237        }
238
239        final Set<Integer> dfsChannelSet = Arrays.stream(dfsChannels).boxed()
240                .collect(Collectors.toSet());
241
242        return scanResults.stream()
243                .filter(scanResult -> !dfsChannelSet.contains(scanResult.frequency))
244                .collect(Collectors.toSet());
245    }
246
247    /** Returns a filtered list of saved networks from WifiConfigManager. */
248    private Set<ScanResultMatchInfo> getGoodSavedNetworks() {
249        List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks();
250
251        Set<ScanResultMatchInfo> goodSavedNetworks = new HashSet<>(savedNetworks.size());
252        for (WifiConfiguration config : savedNetworks) {
253            if (isWideAreaNetwork(config)
254                    || config.hasNoInternetAccess()
255                    || config.noInternetAccessExpected
256                    || !config.getNetworkSelectionStatus().getHasEverConnected()) {
257                continue;
258            }
259            goodSavedNetworks.add(ScanResultMatchInfo.fromWifiConfiguration(config));
260        }
261
262        return goodSavedNetworks;
263    }
264
265    //TODO(b/69271702) implement WAN filtering
266    private static boolean isWideAreaNetwork(WifiConfiguration config) {
267        return false;
268    }
269
270    /**
271     * Handles incoming scan results.
272     *
273     * <p>The controller updates the WakeupLock with the incoming scan results. If WakeupLock is not
274     * yet fully initialized, it adds the current scanResults to the lock and returns. If WakeupLock
275     * is initialized but not empty, the controller updates the lock with the current scan. If it is
276     * both initialized and empty, it evaluates scan results for a match with saved networks. If a
277     * match exists, it enables wifi.
278     *
279     * <p>The feature must be enabled and the store data must be loaded in order for the controller
280     * to handle scan results.
281     *
282     * @param scanResults The scan results with which to update the controller
283     */
284    private void handleScanResults(Collection<ScanResult> scanResults) {
285        if (!isEnabled()) {
286            Log.d(TAG, "Attempted to handleScanResults while not enabled");
287            return;
288        }
289
290        // only count scan as handled if isEnabled
291        mNumScansHandled++;
292        if (mVerboseLoggingEnabled) {
293            Log.d(TAG, "Incoming scan #" + mNumScansHandled);
294        }
295
296        // need to show notification here in case user turns phone on while wifi is off
297        mWakeupOnboarding.maybeShowNotification();
298
299        Set<ScanResult> filteredScanResults = filterScanResults(scanResults);
300
301        mWakeupLock.update(toMatchInfos(filteredScanResults));
302        if (!mWakeupLock.isUnlocked()) {
303            return;
304        }
305
306        ScanResult network =
307                mWakeupEvaluator.findViableNetwork(filteredScanResults, getGoodSavedNetworks());
308
309        if (network != null) {
310            Log.d(TAG, "Enabling wifi for network: " + network.SSID);
311            enableWifi();
312        }
313    }
314
315    /**
316     * Converts ScanResults to ScanResultMatchInfos.
317     */
318    private static Set<ScanResultMatchInfo> toMatchInfos(Collection<ScanResult> scanResults) {
319        return scanResults.stream()
320                .map(ScanResultMatchInfo::fromScanResult)
321                .collect(Collectors.toSet());
322    }
323
324    /**
325     * Enables wifi.
326     *
327     * <p>This method ignores all checks and assumes that {@link WifiStateMachine} is currently
328     * in ScanModeState.
329     */
330    private void enableWifi() {
331        if (USE_PLATFORM_WIFI_WAKE) {
332            // TODO(b/72180295): ensure that there is no race condition with WifiServiceImpl here
333            if (mWifiInjector.getWifiSettingsStore().handleWifiToggled(true /* wifiEnabled */)) {
334                mWifiInjector.getWifiController().sendMessage(CMD_WIFI_TOGGLED);
335                mWifiWakeMetrics.recordWakeupEvent(mNumScansHandled);
336            }
337        }
338    }
339
340    /**
341     * Whether the feature is currently enabled.
342     *
343     * <p>This method checks both the Settings value and the store data to ensure that it has been
344     * read.
345     */
346    @VisibleForTesting
347    boolean isEnabled() {
348        return mWifiWakeupEnabled && mWakeupConfigStoreData.hasBeenRead();
349    }
350
351    /** Dumps wakeup controller state. */
352    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
353        pw.println("Dump of WakeupController");
354        pw.println("USE_PLATFORM_WIFI_WAKE: " + USE_PLATFORM_WIFI_WAKE);
355        pw.println("mWifiWakeupEnabled: " + mWifiWakeupEnabled);
356        pw.println("isOnboarded: " + mWakeupOnboarding.isOnboarded());
357        pw.println("configStore hasBeenRead: " + mWakeupConfigStoreData.hasBeenRead());
358        pw.println("mIsActive: " + mIsActive);
359        pw.println("mNumScansHandled: " + mNumScansHandled);
360
361        mWakeupLock.dump(fd, pw, args);
362    }
363
364    private class IsActiveDataSource implements WakeupConfigStoreData.DataSource<Boolean> {
365
366        @Override
367        public Boolean getData() {
368            return mIsActive;
369        }
370
371        @Override
372        public void setData(Boolean data) {
373            mIsActive = data;
374        }
375    }
376}
377