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