| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.wifi.scanner; |
| |
| import static android.content.pm.PackageManager.PERMISSION_DENIED; |
| import static android.content.pm.PackageManager.PERMISSION_GRANTED; |
| |
| import android.Manifest; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.AlarmManager; |
| import android.app.PendingIntent; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.net.wifi.IWifiScanner; |
| import android.net.wifi.IWifiScannerListener; |
| import android.net.wifi.ScanResult; |
| import android.net.wifi.WifiAnnotations; |
| import android.net.wifi.WifiManager; |
| import android.net.wifi.WifiScanner; |
| import android.net.wifi.WifiScanner.ChannelSpec; |
| import android.net.wifi.WifiScanner.PnoSettings; |
| import android.net.wifi.WifiScanner.ScanData; |
| import android.net.wifi.WifiScanner.ScanSettings; |
| import android.net.wifi.WifiScanner.WifiBand; |
| import android.net.wifi.util.ScanResultUtil; |
| import android.os.BatteryStatsManager; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.os.WorkSource; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.LocalLog; |
| import android.util.Log; |
| import android.util.Pair; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.Protocol; |
| import com.android.internal.util.State; |
| import com.android.internal.util.StateMachine; |
| import com.android.modules.utils.build.SdkLevel; |
| import com.android.server.wifi.ClientModeImpl; |
| import com.android.server.wifi.Clock; |
| import com.android.server.wifi.DeviceConfigFacade; |
| import com.android.server.wifi.WifiInjector; |
| import com.android.server.wifi.WifiLog; |
| import com.android.server.wifi.WifiMetrics; |
| import com.android.server.wifi.WifiNative; |
| import com.android.server.wifi.WifiThreadRunner; |
| import com.android.server.wifi.proto.WifiStatsLog; |
| import com.android.server.wifi.proto.nano.WifiMetricsProto; |
| import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection; |
| import com.android.server.wifi.util.ArrayUtils; |
| import com.android.server.wifi.util.LastCallerInfoManager; |
| import com.android.server.wifi.util.WifiPermissionsUtil; |
| import com.android.server.wifi.util.WorkSourceUtil; |
| import com.android.wifi.resources.R; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.stream.Collectors; |
| |
| public class WifiScanningServiceImpl extends IWifiScanner.Stub { |
| |
| private static final String TAG = WifiScanningService.TAG; |
| private static final boolean DBG = false; |
| |
| private static final int UNKNOWN_PID = -1; |
| |
| private final LocalLog mLocalLog = new LocalLog(512); |
| |
| private WifiLog mLog; |
| |
| private void localLog(String message) { |
| mLocalLog.log(message); |
| if (isVerboseLoggingEnabled()) { |
| Log.i(TAG, message); |
| } |
| } |
| |
| private void logw(String message) { |
| Log.w(TAG, message); |
| mLocalLog.log(message); |
| } |
| |
| private void loge(String message) { |
| Log.e(TAG, message); |
| mLocalLog.log(message); |
| } |
| |
| private void notifyFailure(IWifiScannerListener listener, int reasonCode, String reason) { |
| try { |
| listener.onFailure(reasonCode, reason); |
| } catch (RemoteException e) { |
| loge(e + "failed to notify listener for failure"); |
| } |
| } |
| |
| @Override |
| public Bundle getAvailableChannels(@WifiBand int band, String packageName, |
| @Nullable String attributionTag) { |
| int uid = Binder.getCallingUid(); |
| long ident = Binder.clearCallingIdentity(); |
| try { |
| enforcePermission(uid, packageName, attributionTag, false, false, false); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| |
| ChannelSpec[][] channelSpecs = mWifiThreadRunner.call(() -> { |
| if (mChannelHelper == null) return new ChannelSpec[0][0]; |
| mChannelHelper.updateChannels(); |
| return mChannelHelper.getAvailableScanChannels(band); |
| }, new ChannelSpec[0][0]); |
| |
| ArrayList<Integer> list = new ArrayList<>(); |
| for (int i = 0; i < channelSpecs.length; i++) { |
| for (ChannelSpec channelSpec : channelSpecs[i]) { |
| list.add(channelSpec.frequency); |
| } |
| } |
| Bundle b = new Bundle(); |
| b.putIntegerArrayList(WifiScanner.GET_AVAILABLE_CHANNELS_EXTRA, list); |
| mLog.trace("getAvailableChannels uid=%").c(Binder.getCallingUid()).flush(); |
| return b; |
| } |
| |
| /** |
| * See {@link WifiScanner#isScanning()} |
| * |
| * @return true if in ScanningState. |
| */ |
| @Override |
| public boolean isScanning() { |
| int uid = Binder.getCallingUid(); |
| if (!mWifiPermissionsUtil.checkCallersHardwareLocationPermission(uid)) { |
| throw new SecurityException("UID " + uid |
| + " does not have hardware Location permission"); |
| } |
| return mIsScanning; |
| } |
| |
| @Override |
| public boolean setScanningEnabled(boolean enable, int tid, String packageName) { |
| int uid = Binder.getCallingUid(); |
| int msgWhat = enable ? WifiScanner.CMD_ENABLE : WifiScanner.CMD_DISABLE; |
| try { |
| enforcePermission(uid, packageName, null, isPrivilegedMessage(msgWhat), |
| false, false); |
| } catch (SecurityException e) { |
| localLog("setScanningEnabled: failed to authorize app: " + packageName + " uid " |
| + uid); |
| return false; |
| } |
| localLog("enable scan: package " + packageName + " tid " + tid + " enable " + enable); |
| mWifiThreadRunner.post(() -> { |
| if (enable) { |
| Log.i(TAG, |
| "Received a request to enable scanning, UID = " + Binder.getCallingUid()); |
| setupScannerImpls(); |
| } else { |
| Log.i(TAG, "Received a request to disable scanning, UID = " + uid); |
| teardownScannerImpls(); |
| } |
| Message msg = Message.obtain(); |
| msg.what = msgWhat; |
| mBackgroundScanStateMachine.sendMessage(Message.obtain(msg)); |
| mSingleScanStateMachine.sendMessage(Message.obtain(msg)); |
| mPnoScanStateMachine.sendMessage(Message.obtain(msg)); |
| mLastCallerInfoManager.put(WifiManager.API_SCANNING_ENABLED, tid, |
| Binder.getCallingUid(), Binder.getCallingPid(), packageName, enable); |
| }); |
| return true; |
| } |
| |
| @Override |
| public void registerScanListener(IWifiScannerListener listener, String packageName, |
| String featureId) { |
| final int uid = Binder.getCallingUid(); |
| try { |
| enforcePermission(uid, packageName, featureId, |
| isPrivilegedMessage(WifiScanner.CMD_REGISTER_SCAN_LISTENER), |
| false, false); |
| } catch (SecurityException e) { |
| localLog("registerScanListener: failed to authorize app: " + packageName + " uid " |
| + uid); |
| notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); |
| return; |
| } |
| mWifiThreadRunner.post(() -> { |
| if (mClients.get(listener) != null) { |
| logw("duplicate client connection: " + uid + ", listener=" + listener); |
| return; |
| } |
| final ExternalClientInfo client = new ExternalClientInfo(uid, packageName, |
| listener); |
| client.register(); |
| localLog("register scan listener: " + client); |
| logScanRequest("registerScanListener", client, null, null, null); |
| mSingleScanListeners.addRequest(client, null, null); |
| client.replySucceeded(); |
| }); |
| } |
| |
| @Override |
| public void unregisterScanListener(IWifiScannerListener listener, String packageName, |
| String featureId) { |
| int uid = Binder.getCallingUid(); |
| try { |
| enforcePermission(uid, packageName, featureId, |
| isPrivilegedMessage(WifiScanner.CMD_DEREGISTER_SCAN_LISTENER), |
| true, false); |
| } catch (SecurityException e) { |
| localLog("unregisterScanListener: failed to authorize app: " + packageName + " uid " |
| + uid); |
| notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); |
| return; |
| } |
| ExternalClientInfo client = (ExternalClientInfo) mClients.get(listener); |
| if (client == null) { |
| logw("no client registered: " + uid + ", listener=" + listener); |
| return; |
| } |
| mWifiThreadRunner.post(() -> { |
| logScanRequest("deregisterScanListener", client, null, null, null); |
| mSingleScanListeners.removeRequest(client); |
| client.cleanup(); |
| }); |
| } |
| |
| @Override |
| public void startBackgroundScan(IWifiScannerListener listener, |
| WifiScanner.ScanSettings settings, |
| WorkSource workSource, String packageName, String featureId) { |
| final int uid = Binder.getCallingUid(); |
| try { |
| enforcePermission(uid, packageName, featureId, |
| isPrivilegedMessage(WifiScanner.CMD_START_BACKGROUND_SCAN), |
| false, false); |
| } catch (SecurityException e) { |
| localLog("startBackgroundScan: failed to authorize app: " + packageName + " uid " |
| + uid); |
| notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); |
| return; |
| } |
| mWifiThreadRunner.post(() -> { |
| ExternalClientInfo client = (ExternalClientInfo) mClients.get(listener); |
| if (client == null) { |
| client = new ExternalClientInfo(uid, packageName, listener); |
| client.register(); |
| } |
| localLog("start background scan: " + client + " package " + packageName); |
| Message msg = Message.obtain(); |
| msg.what = WifiScanner.CMD_START_BACKGROUND_SCAN; |
| msg.obj = new ScanParams(listener, settings, workSource); |
| msg.sendingUid = uid; |
| mBackgroundScanStateMachine.sendMessage(msg); |
| }); |
| } |
| |
| @Override |
| public void stopBackgroundScan(IWifiScannerListener listener, String packageName, |
| String featureId) { |
| final int uid = Binder.getCallingUid(); |
| try { |
| enforcePermission(uid, packageName, featureId, |
| isPrivilegedMessage(WifiScanner.CMD_STOP_BACKGROUND_SCAN), |
| true, false); |
| } catch (SecurityException e) { |
| localLog("stopBackgroundScan: failed to authorize app: " + packageName + " uid " |
| + uid); |
| notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); |
| return; |
| } |
| ExternalClientInfo client = (ExternalClientInfo) mClients.get(listener); |
| if (client == null) { |
| Log.e(TAG, "listener not found " + listener); |
| return; |
| } |
| localLog("stop background scan: " + client); |
| Message msg = Message.obtain(); |
| msg.what = WifiScanner.CMD_STOP_BACKGROUND_SCAN; |
| msg.obj = new ScanParams(listener, null, null); |
| msg.sendingUid = uid; |
| mBackgroundScanStateMachine.sendMessage(msg); |
| } |
| |
| @Override |
| public boolean getScanResults(String packageName, String featureId) { |
| final int uid = Binder.getCallingUid(); |
| try { |
| enforcePermission(uid, packageName, featureId, |
| isPrivilegedMessage(WifiScanner.CMD_GET_SCAN_RESULTS), |
| false, false); |
| } catch (SecurityException e) { |
| localLog("getScanResults: failed to authorize app: " + packageName + " uid " |
| + uid); |
| return false; |
| } |
| localLog("get scan result: " + packageName); |
| mBackgroundScanStateMachine.sendMessage(WifiScanner.CMD_GET_SCAN_RESULTS); |
| return true; |
| } |
| |
| private static class ScanParams { |
| public IWifiScannerListener listener; |
| public WifiScanner.ScanSettings settings; |
| public WifiScanner.PnoSettings pnoSettings; |
| public WorkSource workSource; |
| public String packageName; |
| public String featureId; |
| |
| ScanParams(IWifiScannerListener listener, WifiScanner.ScanSettings settings, |
| WorkSource workSource) { |
| this(listener, settings, null, workSource, null, null); |
| } |
| |
| ScanParams(IWifiScannerListener listener, WifiScanner.ScanSettings settings, |
| WifiScanner.PnoSettings pnoSettings, WorkSource workSource, String packageName, |
| String featureId) { |
| this.listener = listener; |
| this.settings = settings; |
| this.pnoSettings = pnoSettings; |
| this.workSource = workSource; |
| this.packageName = packageName; |
| this.featureId = featureId; |
| } |
| } |
| |
| @Override |
| public void startScan(IWifiScannerListener listener, WifiScanner.ScanSettings settings, |
| WorkSource workSource, String packageName, String featureId) { |
| final int uid = Binder.getCallingUid(); |
| try { |
| enforcePermission(uid, packageName, featureId, |
| isPrivilegedMessage(WifiScanner.CMD_START_SINGLE_SCAN), |
| shouldIgnoreLocationSettingsForSingleScan(settings), |
| shouldHideFromAppsForSingleScan(settings)); |
| } catch (SecurityException e) { |
| localLog("startScan: failed to authorize app: " + packageName + " uid " |
| + uid); |
| notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); |
| return; |
| } |
| mWifiThreadRunner.post(() -> { |
| ExternalClientInfo client = (ExternalClientInfo) mClients.get(listener); |
| if (client == null) { |
| client = new ExternalClientInfo(uid, packageName, listener); |
| client.register(); |
| } |
| localLog("start scan: " + client + " package " + packageName); |
| Message msg = Message.obtain(); |
| msg.what = WifiScanner.CMD_START_SINGLE_SCAN; |
| msg.obj = new ScanParams(listener, settings, workSource); |
| msg.sendingUid = uid; |
| mSingleScanStateMachine.sendMessage(msg); |
| }); |
| } |
| |
| @Override |
| public void stopScan(IWifiScannerListener listener, String packageName, String featureId) { |
| int uid = Binder.getCallingUid(); |
| try { |
| enforcePermission(uid, packageName, featureId, |
| isPrivilegedMessage(WifiScanner.CMD_STOP_SINGLE_SCAN), |
| true, false); |
| } catch (SecurityException e) { |
| localLog("stopScan: failed to authorize app: " + packageName + " uid " |
| + uid); |
| notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); |
| return; |
| } |
| mWifiThreadRunner.post(() -> { |
| ExternalClientInfo client = (ExternalClientInfo) mClients.get(listener); |
| if (client == null) { |
| Log.e(TAG, "listener not found " + listener); |
| return; |
| } |
| localLog("stop scan: " + client); |
| Message msg = Message.obtain(); |
| msg.what = WifiScanner.CMD_STOP_SINGLE_SCAN; |
| msg.obj = new ScanParams(listener, null, null); |
| msg.sendingUid = uid; |
| mSingleScanStateMachine.sendMessage(msg); |
| }); |
| } |
| |
| @Override |
| public List<ScanResult> getSingleScanResults(String packageName, String featureId) { |
| localLog("get single scan result: package " + packageName); |
| final int uid = Binder.getCallingUid(); |
| try { |
| enforcePermission(uid, packageName, featureId, |
| isPrivilegedMessage(WifiScanner.CMD_GET_SCAN_RESULTS), |
| false, false); |
| } catch (SecurityException e) { |
| localLog("getSingleScanResults: failed to authorize app: " + packageName + " uid " |
| + uid); |
| return new ArrayList<>(); |
| } |
| return mWifiThreadRunner.call(() -> mSingleScanStateMachine.filterCachedScanResultsByAge(), |
| new ArrayList<ScanResult>()); |
| } |
| |
| @Override |
| public void startPnoScan(IWifiScannerListener listener, WifiScanner.ScanSettings scanSettings, |
| WifiScanner.PnoSettings pnoSettings, String packageName, String featureId) { |
| final int uid = Binder.getCallingUid(); |
| try { |
| enforcePermission(uid, packageName, featureId, |
| isPrivilegedMessage(WifiScanner.CMD_START_PNO_SCAN), |
| false, false); |
| } catch (SecurityException e) { |
| localLog("startPnoScan: failed to authorize app: " + packageName + " uid " |
| + uid); |
| notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); |
| return; |
| } |
| mWifiThreadRunner.post(() -> { |
| ExternalClientInfo client = (ExternalClientInfo) mClients.get(listener); |
| if (client == null) { |
| client = new ExternalClientInfo(uid, packageName, listener); |
| client.register(); |
| } |
| localLog("start pno scan: " + client); |
| Message msg = Message.obtain(); |
| msg.what = WifiScanner.CMD_START_PNO_SCAN; |
| msg.obj = new ScanParams(listener, scanSettings, pnoSettings, null, null, null); |
| msg.sendingUid = uid; |
| mPnoScanStateMachine.sendMessage(msg); |
| }); |
| } |
| |
| @Override |
| public void stopPnoScan(IWifiScannerListener listener, String packageName, String featureId) { |
| final int uid = Binder.getCallingUid(); |
| try { |
| enforcePermission(uid, packageName, featureId, |
| isPrivilegedMessage(WifiScanner.CMD_STOP_PNO_SCAN), |
| true, false); |
| } catch (SecurityException e) { |
| localLog("stopPnoScan: failed to authorize app: " + packageName + " uid " |
| + uid); |
| notifyFailure(listener, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); |
| return; |
| } |
| mWifiThreadRunner.post(() -> { |
| ExternalClientInfo client = (ExternalClientInfo) mClients.get(listener); |
| if (client == null) { |
| Log.e(TAG, "listener not found " + listener); |
| return; |
| } |
| localLog("stop pno scan: " + client); |
| Message msg = Message.obtain(); |
| msg.what = WifiScanner.CMD_STOP_PNO_SCAN; |
| msg.obj = new ScanParams(listener, null, null); |
| msg.sendingUid = uid; |
| mPnoScanStateMachine.sendMessage(msg); |
| }); |
| } |
| |
| @Override |
| public void enableVerboseLogging(boolean enabled) { |
| if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(Binder.getCallingUid())) { |
| return; |
| } |
| mVerboseLoggingEnabled.set(enabled); |
| localLog("enableVerboseLogging: uid=" + Binder.getCallingUid() + " enabled=" + enabled); |
| } |
| |
| private boolean isVerboseLoggingEnabled() { |
| return mVerboseLoggingEnabled.get(); |
| } |
| |
| private void enforceNetworkStack(int uid) { |
| mContext.enforcePermission( |
| Manifest.permission.NETWORK_STACK, |
| UNKNOWN_PID, uid, |
| "NetworkStack"); |
| } |
| |
| // Helper method to check if the incoming message is for a privileged request. |
| private boolean isPrivilegedMessage(int msgWhat) { |
| boolean isPrivileged = (msgWhat == WifiScanner.CMD_ENABLE |
| || msgWhat == WifiScanner.CMD_DISABLE |
| || msgWhat == WifiScanner.CMD_START_PNO_SCAN |
| || msgWhat == WifiScanner.CMD_STOP_PNO_SCAN); |
| if (!SdkLevel.isAtLeastT()) { |
| isPrivileged = isPrivileged || msgWhat == WifiScanner.CMD_REGISTER_SCAN_LISTENER; |
| } |
| return isPrivileged; |
| } |
| |
| // Check if we should ignore location settings if this is a single scan request. |
| private boolean shouldIgnoreLocationSettingsForSingleScan(ScanSettings scanSettings) { |
| if (scanSettings == null) return false; |
| return scanSettings.ignoreLocationSettings; |
| } |
| |
| // Check if we should hide this request from app-ops if this is a single scan request. |
| private boolean shouldHideFromAppsForSingleScan(ScanSettings scanSettings) { |
| if (scanSettings == null) return false; |
| return scanSettings.hideFromAppOps; |
| } |
| |
| /** |
| * Enforce the necessary client permissions for WifiScanner. |
| * If the client has NETWORK_STACK permission, then it can "always" send "any" request. |
| * If the client has only LOCATION_HARDWARE permission, then it can |
| * a) Only make scan related requests when location is turned on. |
| * b) Can never make one of the privileged requests. |
| * |
| * @param uid uid of the client |
| * @param packageName package name of the client |
| * @param attributionTag The feature in the package of the client |
| * @param isPrivilegedRequest whether we are checking for a privileged request |
| * @param shouldIgnoreLocationSettings override to ignore location settings |
| * @param shouldHideFromApps override to hide request from AppOps |
| */ |
| private void enforcePermission(int uid, String packageName, @Nullable String attributionTag, |
| boolean isPrivilegedRequest, boolean shouldIgnoreLocationSettings, |
| boolean shouldHideFromApps) { |
| try { |
| /** Wifi stack issued requests.*/ |
| enforceNetworkStack(uid); |
| } catch (SecurityException e) { |
| // System-app issued requests |
| if (isPrivilegedRequest) { |
| // Privileged message, only requests from clients with NETWORK_STACK allowed! |
| throw e; |
| } |
| mWifiPermissionsUtil.enforceCanAccessScanResultsForWifiScanner(packageName, |
| attributionTag, uid, shouldIgnoreLocationSettings, shouldHideFromApps); |
| } |
| } |
| |
| private static final int BASE = Protocol.BASE_WIFI_SCANNER_SERVICE; |
| |
| private static final int CMD_SCAN_RESULTS_AVAILABLE = BASE + 0; |
| private static final int CMD_FULL_SCAN_RESULTS = BASE + 1; |
| private static final int CMD_SCAN_PAUSED = BASE + 8; |
| private static final int CMD_SCAN_RESTARTED = BASE + 9; |
| private static final int CMD_SCAN_FAILED = BASE + 10; |
| private static final int CMD_PNO_NETWORK_FOUND = BASE + 11; |
| private static final int CMD_PNO_SCAN_FAILED = BASE + 12; |
| private static final int CMD_SW_PNO_SCAN = BASE + 14; |
| |
| |
| private final Context mContext; |
| private final Looper mLooper; |
| private final WifiThreadRunner mWifiThreadRunner; |
| private final WifiScannerImpl.WifiScannerImplFactory mScannerImplFactory; |
| private final ArrayMap<IWifiScannerListener, ClientInfo> mClients; |
| private final Map<String, WifiScannerImpl> mScannerImpls; |
| |
| |
| private final RequestList<Void> mSingleScanListeners = new RequestList<>(); |
| |
| private ChannelHelper mChannelHelper; |
| private BackgroundScanScheduler mBackgroundScheduler; |
| private WifiNative.ScanSettings mPreviousSchedule; |
| private boolean mIsScanning = false; |
| |
| private WifiBackgroundScanStateMachine mBackgroundScanStateMachine; |
| private WifiSingleScanStateMachine mSingleScanStateMachine; |
| private WifiPnoScanStateMachine mPnoScanStateMachine; |
| private final BatteryStatsManager mBatteryStats; |
| private final AlarmManager mAlarmManager; |
| private final WifiMetrics mWifiMetrics; |
| private final Clock mClock; |
| private final WifiPermissionsUtil mWifiPermissionsUtil; |
| private final WifiNative mWifiNative; |
| private final WifiManager mWifiManager; |
| private final LastCallerInfoManager mLastCallerInfoManager; |
| private final DeviceConfigFacade mDeviceConfigFacade; |
| |
| private AtomicBoolean mVerboseLoggingEnabled = new AtomicBoolean(false); |
| |
| WifiScanningServiceImpl(Context context, Looper looper, |
| WifiScannerImpl.WifiScannerImplFactory scannerImplFactory, |
| BatteryStatsManager batteryStats, WifiInjector wifiInjector) { |
| mContext = context; |
| mLooper = looper; |
| mWifiThreadRunner = new WifiThreadRunner(new Handler(looper)); |
| mScannerImplFactory = scannerImplFactory; |
| mBatteryStats = batteryStats; |
| mClients = new ArrayMap<>(); |
| mScannerImpls = new ArrayMap<>(); |
| mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); |
| mWifiMetrics = wifiInjector.getWifiMetrics(); |
| mClock = wifiInjector.getClock(); |
| mLog = wifiInjector.makeLog(TAG); |
| mWifiPermissionsUtil = wifiInjector.getWifiPermissionsUtil(); |
| mWifiNative = wifiInjector.getWifiNative(); |
| mDeviceConfigFacade = wifiInjector.getDeviceConfigFacade(); |
| // Wifi service is always started before other wifi services. So, there is no problem |
| // obtaining WifiManager in the constructor here. |
| mWifiManager = mContext.getSystemService(WifiManager.class); |
| mPreviousSchedule = null; |
| mLastCallerInfoManager = wifiInjector.getLastCallerInfoManager(); |
| } |
| |
| public void startService() { |
| mWifiThreadRunner.post(() -> { |
| mBackgroundScanStateMachine = new WifiBackgroundScanStateMachine(mLooper); |
| mSingleScanStateMachine = new WifiSingleScanStateMachine(mLooper); |
| mPnoScanStateMachine = new WifiPnoScanStateMachine(mLooper); |
| |
| mBackgroundScanStateMachine.start(); |
| mSingleScanStateMachine.start(); |
| mPnoScanStateMachine.start(); |
| }); |
| } |
| |
| /** |
| * Checks if all the channels provided by the new impl is already satisfied by an existing impl. |
| * |
| * Note: This only handles the cases where the 2 ifaces are on different chips with |
| * distinctly different bands supported on both. If there are cases where |
| * the 2 ifaces support overlapping bands, then we probably need to rework this. |
| * For example: wlan0 supports 2.4G only, wlan1 supports 2.4G + 5G + DFS. |
| * In the above example, we should teardown wlan0 impl when wlan1 impl is created |
| * because wlan1 impl can already handle all the supported bands. |
| * Ignoring this for now since we don't foresee this requirement in the near future. |
| */ |
| private boolean doesAnyExistingImplSatisfy(WifiScannerImpl newImpl) { |
| for (WifiScannerImpl existingImpl : mScannerImpls.values()) { |
| if (existingImpl.getChannelHelper().satisfies(newImpl.getChannelHelper())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void setupScannerImpls() { |
| Set<String> ifaceNames = mWifiNative.getClientInterfaceNames(); |
| if (ArrayUtils.isEmpty(ifaceNames)) { |
| loge("Failed to retrieve client interface names"); |
| return; |
| } |
| Set<String> ifaceNamesOfImplsAlreadySetup = mScannerImpls.keySet(); |
| if (ifaceNames.equals(ifaceNamesOfImplsAlreadySetup)) { |
| // Scanner Impls already exist for all ifaces (back to back CMD_ENABLE sent?). |
| Log.i(TAG, "scanner impls already exists"); |
| return; |
| } |
| // set of impls to teardown. |
| Set<String> ifaceNamesOfImplsToTeardown = new ArraySet<>(ifaceNamesOfImplsAlreadySetup); |
| ifaceNamesOfImplsToTeardown.removeAll(ifaceNames); |
| // set of impls to be considered for setup. |
| Set<String> ifaceNamesOfImplsToSetup = new ArraySet<>(ifaceNames); |
| ifaceNamesOfImplsToSetup.removeAll(ifaceNamesOfImplsAlreadySetup); |
| |
| for (String ifaceName : ifaceNamesOfImplsToTeardown) { |
| WifiScannerImpl impl = mScannerImpls.remove(ifaceName); |
| if (impl == null) continue; // should never happen |
| impl.cleanup(); |
| Log.i(TAG, "Removed an impl for " + ifaceName); |
| } |
| for (String ifaceName : ifaceNamesOfImplsToSetup) { |
| WifiScannerImpl impl = mScannerImplFactory.create(mContext, mLooper, mClock, ifaceName); |
| if (impl == null) { |
| loge("Failed to create scanner impl for " + ifaceName); |
| continue; |
| } |
| // If this new scanner impl does not offer any new bands to scan, then we should |
| // ignore it. |
| if (!doesAnyExistingImplSatisfy(impl)) { |
| mScannerImpls.put(ifaceName, impl); |
| Log.i(TAG, "Created a new impl for " + ifaceName); |
| } else { |
| Log.i(TAG, "All the channels on the new impl for iface " + ifaceName |
| + " are already satisfied by an existing impl. Skipping.."); |
| impl.cleanup(); // cleanup the impl before discarding. |
| } |
| } |
| } |
| |
| private void teardownScannerImpls() { |
| for (Map.Entry<String, WifiScannerImpl> entry : mScannerImpls.entrySet()) { |
| WifiScannerImpl impl = entry.getValue(); |
| String ifaceName = entry.getKey(); |
| if (impl == null) continue; // should never happen |
| impl.cleanup(); |
| Log.i(TAG, "Removed an impl for " + ifaceName); |
| } |
| mScannerImpls.clear(); |
| } |
| |
| private WorkSource computeWorkSource(ClientInfo ci, WorkSource requestedWorkSource) { |
| if (requestedWorkSource != null && !requestedWorkSource.isEmpty()) { |
| return requestedWorkSource.withoutNames(); |
| } |
| |
| if (ci.getUid() > 0) { |
| return new WorkSource(ci.getUid()); |
| } |
| |
| // We can't construct a sensible WorkSource because the one supplied to us was empty and |
| // we don't have a valid UID for the given client. |
| loge("Unable to compute workSource for client: " + ci + ", requested: " |
| + requestedWorkSource); |
| return new WorkSource(); |
| } |
| |
| private class RequestInfo<T> { |
| final ClientInfo clientInfo; |
| final WorkSource workSource; |
| final T settings; |
| |
| RequestInfo(ClientInfo clientInfo, WorkSource requestedWorkSource, T settings) { |
| this.clientInfo = clientInfo; |
| this.settings = settings; |
| this.workSource = computeWorkSource(clientInfo, requestedWorkSource); |
| } |
| } |
| |
| private class RequestList<T> extends ArrayList<RequestInfo<T>> { |
| void addRequest(ClientInfo ci, WorkSource reqworkSource, T settings) { |
| add(new RequestInfo<T>(ci, reqworkSource, settings)); |
| } |
| |
| T removeRequest(ClientInfo ci) { |
| T removed = null; |
| Iterator<RequestInfo<T>> iter = iterator(); |
| while (iter.hasNext()) { |
| RequestInfo<T> entry = iter.next(); |
| if (entry.clientInfo == ci) { |
| removed = entry.settings; |
| iter.remove(); |
| } |
| } |
| return removed; |
| } |
| |
| Collection<T> getAllSettings() { |
| ArrayList<T> settingsList = new ArrayList<>(); |
| Iterator<RequestInfo<T>> iter = iterator(); |
| while (iter.hasNext()) { |
| RequestInfo<T> entry = iter.next(); |
| settingsList.add(entry.settings); |
| } |
| return settingsList; |
| } |
| |
| Collection<T> getAllSettingsForClient(ClientInfo ci) { |
| ArrayList<T> settingsList = new ArrayList<>(); |
| Iterator<RequestInfo<T>> iter = iterator(); |
| while (iter.hasNext()) { |
| RequestInfo<T> entry = iter.next(); |
| if (entry.clientInfo == ci) { |
| settingsList.add(entry.settings); |
| } |
| } |
| return settingsList; |
| } |
| |
| void removeAllForClient(ClientInfo ci) { |
| Iterator<RequestInfo<T>> iter = iterator(); |
| while (iter.hasNext()) { |
| RequestInfo<T> entry = iter.next(); |
| if (entry.clientInfo == ci) { |
| iter.remove(); |
| } |
| } |
| } |
| |
| WorkSource createMergedWorkSource() { |
| WorkSource mergedSource = new WorkSource(); |
| for (RequestInfo<T> entry : this) { |
| mergedSource.add(entry.workSource); |
| } |
| return mergedSource; |
| } |
| } |
| |
| /** |
| * State machine that holds the state of single scans. Scans should only be active in the |
| * ScanningState. The pending scans and active scans maps are swapped when entering |
| * ScanningState. Any requests queued while scanning will be placed in the pending queue and |
| * executed after transitioning back to IdleState. |
| */ |
| class WifiSingleScanStateMachine extends StateMachine { |
| /** |
| * Maximum age of results that we return from our cache via |
| * {@link WifiScanner#getScanResults()}. |
| * This is currently set to 3 minutes to restore parity with the wpa_supplicant's scan |
| * result cache expiration policy. (See b/62253332 for details) |
| */ |
| @VisibleForTesting |
| public static final int CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS = 180 * 1000; |
| /** |
| * Alarm Tag to use for the delayed indication of emergency scan end. |
| */ |
| @VisibleForTesting |
| public static final String EMERGENCY_SCAN_END_INDICATION_ALARM_TAG = |
| TAG + "EmergencyScanEnd"; |
| /** |
| * Alarm timeout to use for the delayed indication of emergency scan end. |
| */ |
| private static final int EMERGENCY_SCAN_END_INDICATION_DELAY_MILLIS = 15_000; |
| /** |
| * Alarm listener to use for the delayed indication of emergency scan end. |
| */ |
| private final AlarmManager.OnAlarmListener mEmergencyScanEndIndicationListener = |
| () -> mWifiManager.setEmergencyScanRequestInProgress(false); |
| |
| private final DefaultState mDefaultState = new DefaultState(); |
| private final DriverStartedState mDriverStartedState = new DriverStartedState(); |
| private final IdleState mIdleState = new IdleState(); |
| private final ScanningState mScanningState = new ScanningState(); |
| |
| private WifiNative.ScanSettings mActiveScanSettings = null; |
| private RequestList<ScanSettings> mActiveScans = new RequestList<>(); |
| private RequestList<ScanSettings> mPendingScans = new RequestList<>(); |
| |
| // Scan results cached from the last full single scan request. |
| private final List<ScanResult> mCachedScanResults = new ArrayList<>(); |
| |
| // Tracks scan requests across multiple scanner impls. |
| private final ScannerImplsTracker mScannerImplsTracker; |
| |
| WifiSingleScanStateMachine(Looper looper) { |
| super("WifiSingleScanStateMachine", looper); |
| |
| mScannerImplsTracker = new ScannerImplsTracker(); |
| |
| setLogRecSize(128); |
| setLogOnlyTransitions(false); |
| |
| // CHECKSTYLE:OFF IndentationCheck |
| addState(mDefaultState); |
| addState(mDriverStartedState, mDefaultState); |
| addState(mIdleState, mDriverStartedState); |
| addState(mScanningState, mDriverStartedState); |
| // CHECKSTYLE:ON IndentationCheck |
| |
| setInitialState(mDefaultState); |
| } |
| |
| /** |
| * Tracks a single scan request across all the available scanner impls. |
| * |
| * a) Initiates the scan using the same ScanSettings across all the available impls. |
| * b) Waits for all the impls to report the status of the scan request (success or failure). |
| * c) Calculates a consolidated scan status and sends the results if successful. |
| * Note: If there are failures on some of the scanner impls, we ignore them since we will |
| * get some scan results from the other successful impls. We don't declare total scan |
| * failures, unless all the scanner impls fail. |
| */ |
| private final class ScannerImplsTracker { |
| private final class ScanEventHandler implements WifiNative.ScanEventHandler { |
| private final String mImplIfaceName; |
| ScanEventHandler(@NonNull String implIfaceName) { |
| mImplIfaceName = implIfaceName; |
| } |
| |
| /** |
| * Called to indicate a change in state for the current scan. |
| * Will dispatch a corresponding event to the state machine |
| */ |
| @Override |
| public void onScanStatus(int event) { |
| if (DBG) { |
| localLog("onScanStatus event received, event=" + event |
| + ", iface=" + mImplIfaceName); |
| } |
| switch (event) { |
| case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE: |
| case WifiNative.WIFI_SCAN_THRESHOLD_NUM_SCANS: |
| case WifiNative.WIFI_SCAN_THRESHOLD_PERCENT: |
| reportScanStatusForImpl(mImplIfaceName, STATUS_SUCCEEDED); |
| break; |
| case WifiNative.WIFI_SCAN_FAILED: |
| reportScanStatusForImpl(mImplIfaceName, STATUS_FAILED); |
| break; |
| default: |
| Log.e(TAG, "Unknown scan status event: " + event); |
| break; |
| } |
| } |
| |
| /** |
| * Called for each full scan result if requested |
| */ |
| @Override |
| public void onFullScanResult(ScanResult fullScanResult, int bucketsScanned) { |
| if (DBG) localLog("onFullScanResult received on iface " + mImplIfaceName); |
| reportFullScanResultForImpl(mImplIfaceName, fullScanResult, bucketsScanned); |
| } |
| |
| @Override |
| public void onScanPaused(ScanData[] scanData) { |
| // should not happen for single scan |
| Log.e(TAG, "Got scan paused for single scan"); |
| } |
| |
| @Override |
| public void onScanRestarted() { |
| // should not happen for single scan |
| Log.e(TAG, "Got scan restarted for single scan"); |
| } |
| } |
| |
| private static final int STATUS_PENDING = 0; |
| private static final int STATUS_SUCCEEDED = 1; |
| private static final int STATUS_FAILED = 2; |
| |
| // Tracks scan status per impl. |
| Map<String, Integer> mStatusPerImpl = new ArrayMap<>(); |
| |
| /** |
| * Triggers a new scan on all the available scanner impls. |
| * @return true if the scan succeeded on any of the impl, false otherwise. |
| */ |
| public boolean startSingleScan(WifiNative.ScanSettings scanSettings) { |
| mStatusPerImpl.clear(); |
| boolean anySuccess = false; |
| for (Map.Entry<String, WifiScannerImpl> entry : mScannerImpls.entrySet()) { |
| String ifaceName = entry.getKey(); |
| WifiScannerImpl impl = entry.getValue(); |
| boolean success = impl.startSingleScan( |
| scanSettings, new ScanEventHandler(ifaceName)); |
| if (!success) { |
| Log.e(TAG, "Failed to start single scan on " + ifaceName); |
| mStatusPerImpl.put(ifaceName, STATUS_FAILED); |
| continue; |
| } |
| mStatusPerImpl.put(ifaceName, STATUS_PENDING); |
| anySuccess = true; |
| } |
| return anySuccess; |
| } |
| |
| /** |
| * Returns the latest scan results from all the available scanner impls. |
| * @return Consolidated list of scan results from all the impl. |
| */ |
| public @Nullable ScanData getLatestSingleScanResults() { |
| ScanData consolidatedScanData = null; |
| for (WifiScannerImpl impl : mScannerImpls.values()) { |
| Integer ifaceStatus = mStatusPerImpl.get(impl.getIfaceName()); |
| if (ifaceStatus == null || ifaceStatus != STATUS_SUCCEEDED) { |
| continue; |
| } |
| ScanData scanData = impl.getLatestSingleScanResults(); |
| if (consolidatedScanData == null) { |
| consolidatedScanData = new ScanData(scanData); |
| } else { |
| consolidatedScanData.addResults(scanData.getResults()); |
| } |
| } |
| return consolidatedScanData; |
| } |
| |
| private void reportFullScanResultForImpl(@NonNull String implIfaceName, |
| ScanResult fullScanResult, int bucketsScanned) { |
| Integer status = mStatusPerImpl.get(implIfaceName); |
| if (status != null && status == STATUS_PENDING) { |
| sendMessage(CMD_FULL_SCAN_RESULTS, 0, bucketsScanned, fullScanResult); |
| } |
| } |
| |
| private int getConsolidatedStatus() { |
| boolean anyPending = mStatusPerImpl.values().stream() |
| .anyMatch(status -> status == STATUS_PENDING); |
| // at-least one impl status is still pending. |
| if (anyPending) return STATUS_PENDING; |
| |
| boolean anySuccess = mStatusPerImpl.values().stream() |
| .anyMatch(status -> status == STATUS_SUCCEEDED); |
| // one success is good enough to declare consolidated success. |
| if (anySuccess) { |
| return STATUS_SUCCEEDED; |
| } else { |
| // all failed. |
| return STATUS_FAILED; |
| } |
| } |
| |
| private void reportScanStatusForImpl(@NonNull String implIfaceName, int newStatus) { |
| Integer currentStatus = mStatusPerImpl.get(implIfaceName); |
| if (currentStatus != null && currentStatus == STATUS_PENDING) { |
| mStatusPerImpl.put(implIfaceName, newStatus); |
| } |
| // Now check if all the scanner impls scan status is available. |
| int consolidatedStatus = getConsolidatedStatus(); |
| if (consolidatedStatus == STATUS_SUCCEEDED) { |
| sendMessage(CMD_SCAN_RESULTS_AVAILABLE); |
| } else if (consolidatedStatus == STATUS_FAILED) { |
| sendMessage(CMD_SCAN_FAILED); |
| } |
| } |
| } |
| |
| /** |
| * Helper method to handle the scan start message. |
| */ |
| private void handleScanStartMessage(ClientInfo ci, ScanParams scanParams) { |
| if (ci == null) { |
| logCallback("singleScanInvalidRequest", ci, "null params"); |
| return; |
| } |
| ScanSettings scanSettings = scanParams.settings; |
| WorkSource workSource = scanParams.workSource; |
| if (validateScanRequest(ci, scanSettings)) { |
| if (getCurrentState() == mDefaultState && !scanSettings.ignoreLocationSettings) { |
| // Reject regular scan requests if scanning is disabled. |
| ci.replyFailed(WifiScanner.REASON_UNSPECIFIED, "not available"); |
| return; |
| } |
| mWifiMetrics.incrementOneshotScanCount(); |
| if ((scanSettings.band & WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY) != 0) { |
| mWifiMetrics.incrementOneshotScanWithDfsCount(); |
| } |
| logScanRequest("addSingleScanRequest", ci, workSource, |
| scanSettings, null); |
| ci.replySucceeded(); |
| |
| if (scanSettings.ignoreLocationSettings) { |
| // Inform wifi manager that an emergency scan is in progress (regardless of |
| // whether scanning is currently enabled or not). This ensures that |
| // the wifi chip remains on for the duration of this scan. |
| mWifiManager.setEmergencyScanRequestInProgress(true); |
| } |
| |
| if (getCurrentState() == mScanningState) { |
| // If there is an active scan that will fulfill the scan request then |
| // mark this request as an active scan, otherwise mark it pending. |
| if (activeScanSatisfies(scanSettings)) { |
| mActiveScans.addRequest(ci, workSource, scanSettings); |
| } else { |
| mPendingScans.addRequest(ci, workSource, scanSettings); |
| } |
| } else if (getCurrentState() == mIdleState) { |
| // If were not currently scanning then try to start a scan. Otherwise |
| // this scan will be scheduled when transitioning back to IdleState |
| // after finishing the current scan. |
| mPendingScans.addRequest(ci, workSource, scanSettings); |
| tryToStartNewScan(); |
| } else if (getCurrentState() == mDefaultState) { |
| // If scanning is disabled and the request is for emergency purposes |
| // (checked above), add to pending list. this scan will be scheduled when |
| // transitioning to IdleState when wifi manager enables scanning as a part of |
| // processing WifiManager.setEmergencyScanRequestInProgress(true) |
| mPendingScans.addRequest(ci, workSource, scanSettings); |
| } |
| } else { |
| logCallback("singleScanInvalidRequest", ci, "bad request"); |
| ci.replyFailed(WifiScanner.REASON_INVALID_REQUEST, "bad request"); |
| mWifiMetrics.incrementScanReturnEntry( |
| WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION, 1); |
| } |
| } |
| |
| class DefaultState extends State { |
| @Override |
| public void enter() { |
| mActiveScans.clear(); |
| mPendingScans.clear(); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| switch (msg.what) { |
| case WifiScanner.CMD_ENABLE: |
| if (mScannerImpls.isEmpty()) { |
| loge("Failed to start single scan state machine because scanner impl" |
| + " is null"); |
| return HANDLED; |
| } |
| transitionTo(mIdleState); |
| return HANDLED; |
| case WifiScanner.CMD_DISABLE: |
| transitionTo(mDefaultState); |
| return HANDLED; |
| case WifiScanner.CMD_START_SINGLE_SCAN: |
| ScanParams scanParams = (ScanParams) msg.obj; |
| if (scanParams != null) { |
| ClientInfo ci = mClients.get(scanParams.listener); |
| handleScanStartMessage(ci, scanParams); |
| } |
| return HANDLED; |
| case WifiScanner.CMD_STOP_SINGLE_SCAN: |
| scanParams = (ScanParams) msg.obj; |
| if (scanParams != null) { |
| ClientInfo ci = mClients.get(scanParams.listener); |
| removeSingleScanRequests(ci); |
| } |
| return HANDLED; |
| case CMD_SCAN_RESULTS_AVAILABLE: |
| if (DBG) localLog("ignored scan results available event"); |
| return HANDLED; |
| case CMD_FULL_SCAN_RESULTS: |
| if (DBG) localLog("ignored full scan result event"); |
| return HANDLED; |
| case WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS: |
| // Should not handled here. |
| return HANDLED; |
| default: |
| return NOT_HANDLED; |
| } |
| } |
| } |
| |
| /** |
| * State representing when the driver is running. This state is not meant to be transitioned |
| * directly, but is instead intended as a parent state of ScanningState and IdleState |
| * to hold common functionality and handle cleaning up scans when the driver is shut down. |
| */ |
| class DriverStartedState extends State { |
| @Override |
| public void exit() { |
| // clear scan results when scan mode is not active |
| mCachedScanResults.clear(); |
| |
| mWifiMetrics.incrementScanReturnEntry( |
| WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED, |
| mPendingScans.size()); |
| sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED, |
| "Scan was interrupted"); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| switch (msg.what) { |
| case WifiScanner.CMD_ENABLE: |
| // Ignore if we're already in driver loaded state. |
| return HANDLED; |
| default: |
| return NOT_HANDLED; |
| } |
| } |
| } |
| |
| class IdleState extends State { |
| @Override |
| public void enter() { |
| tryToStartNewScan(); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| return NOT_HANDLED; |
| } |
| } |
| |
| class ScanningState extends State { |
| private WorkSource mScanWorkSource; |
| |
| @Override |
| public void enter() { |
| mScanWorkSource = mActiveScans.createMergedWorkSource(); |
| mBatteryStats.reportWifiScanStartedFromSource(mScanWorkSource); |
| Pair<int[], String[]> uidsAndTags = |
| WorkSourceUtil.getUidsAndTagsForWs(mScanWorkSource); |
| WifiStatsLog.write(WifiStatsLog.WIFI_SCAN_STATE_CHANGED, |
| uidsAndTags.first, uidsAndTags.second, |
| WifiStatsLog.WIFI_SCAN_STATE_CHANGED__STATE__ON); |
| mIsScanning = true; |
| } |
| |
| @Override |
| public void exit() { |
| mActiveScanSettings = null; |
| mBatteryStats.reportWifiScanStoppedFromSource(mScanWorkSource); |
| Pair<int[], String[]> uidsAndTags = |
| WorkSourceUtil.getUidsAndTagsForWs(mScanWorkSource); |
| WifiStatsLog.write(WifiStatsLog.WIFI_SCAN_STATE_CHANGED, |
| uidsAndTags.first, uidsAndTags.second, |
| WifiStatsLog.WIFI_SCAN_STATE_CHANGED__STATE__OFF); |
| mIsScanning = false; |
| |
| // if any scans are still active (never got results available then indicate failure) |
| mWifiMetrics.incrementScanReturnEntry( |
| WifiMetricsProto.WifiLog.SCAN_UNKNOWN, |
| mActiveScans.size()); |
| sendOpFailedToAllAndClear(mActiveScans, WifiScanner.REASON_UNSPECIFIED, |
| "Scan was interrupted"); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| switch (msg.what) { |
| case CMD_SCAN_RESULTS_AVAILABLE: |
| ScanData latestScanResults = |
| mScannerImplsTracker.getLatestSingleScanResults(); |
| if (latestScanResults != null) { |
| handleScanResults(latestScanResults); |
| } else { |
| Log.e(TAG, "latest scan results null unexpectedly"); |
| } |
| transitionTo(mIdleState); |
| return HANDLED; |
| case CMD_FULL_SCAN_RESULTS: |
| reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2); |
| return HANDLED; |
| case CMD_SCAN_FAILED: |
| mWifiMetrics.incrementScanReturnEntry( |
| WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mActiveScans.size()); |
| mWifiMetrics.getScanMetrics().logScanFailed( |
| WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE); |
| sendOpFailedToAllAndClear(mActiveScans, WifiScanner.REASON_UNSPECIFIED, |
| "Scan failed"); |
| transitionTo(mIdleState); |
| return HANDLED; |
| default: |
| return NOT_HANDLED; |
| } |
| } |
| } |
| |
| boolean validateScanType(@WifiAnnotations.ScanType int type) { |
| return (type == WifiScanner.SCAN_TYPE_LOW_LATENCY |
| || type == WifiScanner.SCAN_TYPE_LOW_POWER |
| || type == WifiScanner.SCAN_TYPE_HIGH_ACCURACY); |
| } |
| |
| boolean validateScanRequest(ClientInfo ci, ScanSettings settings) { |
| if (ci == null) { |
| Log.d(TAG, "Failing single scan request ClientInfo not found " + ci); |
| return false; |
| } |
| if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) { |
| if (settings.channels == null || settings.channels.length == 0) { |
| Log.d(TAG, "Failing single scan because channel list was empty"); |
| return false; |
| } |
| } |
| if (!validateScanType(settings.type)) { |
| Log.e(TAG, "Invalid scan type " + settings.type); |
| return false; |
| } |
| if (mContext.checkPermission( |
| Manifest.permission.NETWORK_STACK, UNKNOWN_PID, ci.getUid()) |
| == PERMISSION_DENIED) { |
| if (!ArrayUtils.isEmpty(settings.hiddenNetworks)) { |
| Log.e(TAG, "Failing single scan because app " + ci.getUid() |
| + " does not have permission to set hidden networks"); |
| return false; |
| } |
| if (settings.type != WifiScanner.SCAN_TYPE_LOW_LATENCY) { |
| Log.e(TAG, "Failing single scan because app " + ci.getUid() |
| + " does not have permission to set type"); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // We can coalesce a LOW_POWER/LOW_LATENCY scan request into an ongoing HIGH_ACCURACY |
| // scan request. But, we can't coalesce a HIGH_ACCURACY scan request into an ongoing |
| // LOW_POWER/LOW_LATENCY scan request. |
| boolean activeScanTypeSatisfies(int requestScanType) { |
| switch(mActiveScanSettings.scanType) { |
| case WifiScanner.SCAN_TYPE_LOW_LATENCY: |
| case WifiScanner.SCAN_TYPE_LOW_POWER: |
| return requestScanType != WifiScanner.SCAN_TYPE_HIGH_ACCURACY; |
| case WifiScanner.SCAN_TYPE_HIGH_ACCURACY: |
| return true; |
| default: |
| // This should never happen because we've validated the incoming type in |
| // |validateScanType|. |
| throw new IllegalArgumentException("Invalid scan type " |
| + mActiveScanSettings.scanType); |
| } |
| } |
| |
| // If there is a HIGH_ACCURACY scan request among the requests being merged, the merged |
| // scan type should be HIGH_ACCURACY. |
| int mergeScanTypes(int existingScanType, int newScanType) { |
| switch(existingScanType) { |
| case WifiScanner.SCAN_TYPE_LOW_LATENCY: |
| case WifiScanner.SCAN_TYPE_LOW_POWER: |
| return newScanType; |
| case WifiScanner.SCAN_TYPE_HIGH_ACCURACY: |
| return existingScanType; |
| default: |
| // This should never happen because we've validated the incoming type in |
| // |validateScanType|. |
| throw new IllegalArgumentException("Invalid scan type " + existingScanType); |
| } |
| } |
| |
| private boolean mergeRnrSetting(boolean enable6GhzRnr, ScanSettings scanSettings) { |
| if (!SdkLevel.isAtLeastS()) { |
| return false; |
| } |
| if (enable6GhzRnr) { |
| return true; |
| } |
| int rnrSetting = scanSettings.getRnrSetting(); |
| if (rnrSetting == WifiScanner.WIFI_RNR_ENABLED) { |
| return true; |
| } |
| if (rnrSetting == WifiScanner.WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED) { |
| return ChannelHelper.is6GhzBandIncluded(scanSettings.band); |
| } |
| return false; |
| } |
| |
| boolean activeScanSatisfies(ScanSettings settings) { |
| if (mActiveScanSettings == null) { |
| return false; |
| } |
| |
| if (!activeScanTypeSatisfies(settings.type)) { |
| return false; |
| } |
| |
| // there is always one bucket for a single scan |
| WifiNative.BucketSettings activeBucket = mActiveScanSettings.buckets[0]; |
| |
| // validate that all requested channels are being scanned |
| ChannelCollection activeChannels = mChannelHelper.createChannelCollection(); |
| activeChannels.addChannels(activeBucket); |
| if (!activeChannels.containsSettings(settings)) { |
| return false; |
| } |
| |
| // if the request is for a full scan, but there is no ongoing full scan |
| if ((settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0 |
| && (activeBucket.report_events & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) |
| == 0) { |
| return false; |
| } |
| |
| if (!ArrayUtils.isEmpty(settings.hiddenNetworks)) { |
| if (ArrayUtils.isEmpty(mActiveScanSettings.hiddenNetworks)) { |
| return false; |
| } |
| List<WifiNative.HiddenNetwork> activeHiddenNetworks = new ArrayList<>(); |
| for (WifiNative.HiddenNetwork hiddenNetwork : mActiveScanSettings.hiddenNetworks) { |
| activeHiddenNetworks.add(hiddenNetwork); |
| } |
| for (ScanSettings.HiddenNetwork hiddenNetwork : settings.hiddenNetworks) { |
| WifiNative.HiddenNetwork nativeHiddenNetwork = new WifiNative.HiddenNetwork(); |
| nativeHiddenNetwork.ssid = hiddenNetwork.ssid; |
| if (!activeHiddenNetworks.contains(nativeHiddenNetwork)) { |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| void removeSingleScanRequests(ClientInfo ci) { |
| if (ci != null) { |
| logScanRequest("removeSingleScanRequests", ci, null, null, null); |
| mPendingScans.removeAllForClient(ci); |
| mActiveScans.removeAllForClient(ci); |
| } |
| } |
| |
| void tryToStartNewScan() { |
| if (mPendingScans.size() == 0) { // no pending requests |
| return; |
| } |
| mChannelHelper.updateChannels(); |
| // TODO move merging logic to a scheduler |
| WifiNative.ScanSettings settings = new WifiNative.ScanSettings(); |
| settings.num_buckets = 1; |
| WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings(); |
| bucketSettings.bucket = 0; |
| bucketSettings.period_ms = 0; |
| bucketSettings.report_events = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; |
| |
| ChannelCollection channels = mChannelHelper.createChannelCollection(); |
| WifiScanner.ChannelSpec[][] available6GhzChannels = |
| mChannelHelper.getAvailableScanChannels(WifiScanner.WIFI_BAND_6_GHZ); |
| boolean are6GhzChannelsAvailable = available6GhzChannels.length > 0 |
| && available6GhzChannels[0].length > 0; |
| List<WifiNative.HiddenNetwork> hiddenNetworkList = new ArrayList<>(); |
| for (RequestInfo<ScanSettings> entry : mPendingScans) { |
| settings.scanType = mergeScanTypes(settings.scanType, entry.settings.type); |
| if (are6GhzChannelsAvailable) { |
| settings.enable6GhzRnr = mergeRnrSetting( |
| settings.enable6GhzRnr, entry.settings); |
| } else { |
| settings.enable6GhzRnr = false; |
| } |
| channels.addChannels(entry.settings); |
| for (ScanSettings.HiddenNetwork srcNetwork : entry.settings.hiddenNetworks) { |
| WifiNative.HiddenNetwork hiddenNetwork = new WifiNative.HiddenNetwork(); |
| hiddenNetwork.ssid = srcNetwork.ssid; |
| hiddenNetworkList.add(hiddenNetwork); |
| } |
| if ((entry.settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) |
| != 0) { |
| bucketSettings.report_events |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; |
| } |
| |
| if (entry.clientInfo != null) { |
| mWifiMetrics.getScanMetrics().setClientUid(entry.clientInfo.mUid); |
| } |
| mWifiMetrics.getScanMetrics().setWorkSource(entry.workSource); |
| } |
| |
| if (hiddenNetworkList.size() > 0) { |
| settings.hiddenNetworks = new WifiNative.HiddenNetwork[hiddenNetworkList.size()]; |
| int numHiddenNetworks = 0; |
| for (WifiNative.HiddenNetwork hiddenNetwork : hiddenNetworkList) { |
| settings.hiddenNetworks[numHiddenNetworks++] = hiddenNetwork; |
| } |
| } |
| |
| channels.fillBucketSettings(bucketSettings, Integer.MAX_VALUE); |
| settings.buckets = new WifiNative.BucketSettings[] {bucketSettings}; |
| |
| if (mScannerImplsTracker.startSingleScan(settings)) { |
| mWifiMetrics.getScanMetrics().logScanStarted( |
| WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE); |
| |
| // store the active scan settings |
| mActiveScanSettings = settings; |
| // swap pending and active scan requests |
| RequestList<ScanSettings> tmp = mActiveScans; |
| mActiveScans = mPendingScans; |
| mPendingScans = tmp; |
| // make sure that the pending list is clear |
| mPendingScans.clear(); |
| transitionTo(mScanningState); |
| } else { |
| mWifiMetrics.incrementScanReturnEntry( |
| WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mPendingScans.size()); |
| mWifiMetrics.getScanMetrics().logScanFailedToStart( |
| WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE); |
| |
| // notify and cancel failed scans |
| sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED, |
| "Failed to start single scan"); |
| } |
| } |
| |
| void sendOpFailedToAllAndClear(RequestList<?> clientHandlers, int reason, |
| String description) { |
| for (RequestInfo<?> entry : clientHandlers) { |
| logCallback("singleScanFailed", entry.clientInfo, |
| "reason=" + reason + ", " + description); |
| try { |
| entry.clientInfo.mListener.onFailure(reason, description); |
| } catch (RemoteException e) { |
| loge("Failed to call onFullResult: " + entry.clientInfo); |
| } |
| } |
| clientHandlers.clear(); |
| } |
| |
| void reportFullScanResult(@NonNull ScanResult result, int bucketsScanned) { |
| for (RequestInfo<ScanSettings> entry : mActiveScans) { |
| if (ScanScheduleUtil.shouldReportFullScanResultForSettings(mChannelHelper, |
| result, bucketsScanned, entry.settings, -1)) { |
| entry.clientInfo.reportEvent((listener) -> { |
| try { |
| listener.onFullResult(result); |
| } catch (RemoteException e) { |
| loge("Failed to call onFullResult: " + entry.clientInfo); |
| } |
| }); |
| } |
| } |
| |
| for (RequestInfo<Void> entry : mSingleScanListeners) { |
| entry.clientInfo.reportEvent((listener) -> { |
| try { |
| listener.onFullResult(result); |
| } catch (RemoteException e) { |
| loge("Failed to call onFullResult: " + entry.clientInfo); |
| } |
| }); |
| } |
| } |
| |
| void reportScanResults(@NonNull ScanData results) { |
| if (results != null && results.getResults() != null) { |
| if (results.getResults().length > 0) { |
| mWifiMetrics.incrementNonEmptyScanResultCount(); |
| } else { |
| mWifiMetrics.incrementEmptyScanResultCount(); |
| } |
| } |
| ScanData[] allResults = new ScanData[] {results}; |
| for (RequestInfo<ScanSettings> entry : mActiveScans) { |
| ScanData[] resultsToDeliver = ScanScheduleUtil.filterResultsForSettings( |
| mChannelHelper, allResults, entry.settings, -1); |
| logCallback("singleScanResults", entry.clientInfo, |
| describeForLog(resultsToDeliver)); |
| entry.clientInfo.reportEvent((listener) -> { |
| try { |
| listener.onResults(resultsToDeliver); |
| // make sure the handler is removed |
| listener.onSingleScanCompleted(); |
| } catch (RemoteException e) { |
| loge("Failed to call onResult: " + entry.clientInfo); |
| } |
| }); |
| } |
| |
| for (RequestInfo<Void> entry : mSingleScanListeners) { |
| logCallback("singleScanResults", entry.clientInfo, |
| describeForLog(allResults)); |
| entry.clientInfo.reportEvent((listener) -> { |
| try { |
| listener.onResults(allResults); |
| } catch (RemoteException e) { |
| loge("Failed to call onResult: " + entry.clientInfo); |
| } |
| }); |
| } |
| } |
| |
| void handleScanResults(@NonNull ScanData results) { |
| mWifiMetrics.getScanMetrics().logScanSucceeded( |
| WifiMetrics.ScanMetrics.SCAN_TYPE_SINGLE, results.getResults().length); |
| mWifiMetrics.incrementScanReturnEntry( |
| WifiMetricsProto.WifiLog.SCAN_SUCCESS, mActiveScans.size()); |
| reportScanResults(results); |
| // Cache full band (with DFS or not) scan results. |
| if (WifiScanner.isFullBandScan(results.getScannedBandsInternal(), true)) { |
| mCachedScanResults.clear(); |
| mCachedScanResults.addAll(Arrays.asList(results.getResults())); |
| } |
| if (mActiveScans.stream().anyMatch(rI -> rI.settings.ignoreLocationSettings)) { |
| // We were processing an emergency scan, post an alarm to inform WifiManager the |
| // end of that scan processing. If another scan is processed before the alarm fires, |
| // this timer is restarted (AlarmManager.set() using the same listener resets the |
| // timer). This delayed indication of emergency scan end prevents |
| // quick wifi toggle on/off if there is a burst of emergency scans when wifi is off. |
| mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| mClock.getElapsedSinceBootMillis() |
| + EMERGENCY_SCAN_END_INDICATION_DELAY_MILLIS, |
| EMERGENCY_SCAN_END_INDICATION_ALARM_TAG, |
| mEmergencyScanEndIndicationListener, getHandler()); |
| } |
| for (RequestInfo<ScanSettings> entry : mActiveScans) { |
| entry.clientInfo.unregister(); |
| } |
| mActiveScans.clear(); |
| } |
| |
| List<ScanResult> getCachedScanResultsAsList() { |
| return mCachedScanResults; |
| } |
| |
| /** |
| * Filter out any scan results that are older than |
| * {@link #CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS}. |
| * |
| * @return Filtered list of scan results. |
| */ |
| public List<ScanResult> filterCachedScanResultsByAge() { |
| // Using ScanResult.timestamp here to ensure that we use the same fields as |
| // WificondScannerImpl for filtering stale results. |
| long currentTimeInMillis = mClock.getElapsedSinceBootMillis(); |
| return mCachedScanResults.stream() |
| .filter(scanResult |
| -> ((currentTimeInMillis - (scanResult.timestamp / 1000)) |
| < CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS)).collect(Collectors.toList()); |
| } |
| } |
| |
| // TODO(b/71855918): Remove this bg scan state machine and its dependencies. |
| // Note: bgscan will not support multiple scanner impls (will pick any). |
| class WifiBackgroundScanStateMachine extends StateMachine { |
| |
| private final DefaultState mDefaultState = new DefaultState(); |
| private final StartedState mStartedState = new StartedState(); |
| private final PausedState mPausedState = new PausedState(); |
| |
| private final RequestList<ScanSettings> mActiveBackgroundScans = new RequestList<>(); |
| |
| private WifiScannerImpl mScannerImpl; |
| |
| WifiBackgroundScanStateMachine(Looper looper) { |
| super("WifiBackgroundScanStateMachine", looper); |
| |
| setLogRecSize(512); |
| setLogOnlyTransitions(false); |
| |
| // CHECKSTYLE:OFF IndentationCheck |
| addState(mDefaultState); |
| addState(mStartedState, mDefaultState); |
| addState(mPausedState, mDefaultState); |
| // CHECKSTYLE:ON IndentationCheck |
| |
| setInitialState(mDefaultState); |
| } |
| |
| public Collection<ScanSettings> getBackgroundScanSettings(ClientInfo ci) { |
| return mActiveBackgroundScans.getAllSettingsForClient(ci); |
| } |
| |
| public void removeBackgroundScanSettings(ClientInfo ci) { |
| mActiveBackgroundScans.removeAllForClient(ci); |
| updateSchedule(); |
| } |
| |
| private final class ScanEventHandler implements WifiNative.ScanEventHandler { |
| private final String mImplIfaceName; |
| |
| ScanEventHandler(@NonNull String implIfaceName) { |
| mImplIfaceName = implIfaceName; |
| } |
| |
| @Override |
| public void onScanStatus(int event) { |
| if (DBG) localLog("onScanStatus event received, event=" + event); |
| switch (event) { |
| case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE: |
| case WifiNative.WIFI_SCAN_THRESHOLD_NUM_SCANS: |
| case WifiNative.WIFI_SCAN_THRESHOLD_PERCENT: |
| sendMessage(CMD_SCAN_RESULTS_AVAILABLE); |
| break; |
| case WifiNative.WIFI_SCAN_FAILED: |
| sendMessage(CMD_SCAN_FAILED); |
| break; |
| default: |
| Log.e(TAG, "Unknown scan status event: " + event); |
| break; |
| } |
| } |
| |
| @Override |
| public void onFullScanResult(ScanResult fullScanResult, int bucketsScanned) { |
| if (DBG) localLog("onFullScanResult received"); |
| sendMessage(CMD_FULL_SCAN_RESULTS, 0, bucketsScanned, fullScanResult); |
| } |
| |
| @Override |
| public void onScanPaused(ScanData[] scanData) { |
| if (DBG) localLog("onScanPaused received"); |
| sendMessage(CMD_SCAN_PAUSED, scanData); |
| } |
| |
| @Override |
| public void onScanRestarted() { |
| if (DBG) localLog("onScanRestarted received"); |
| sendMessage(CMD_SCAN_RESTARTED); |
| } |
| } |
| |
| class DefaultState extends State { |
| @Override |
| public void enter() { |
| if (DBG) localLog("DefaultState"); |
| mActiveBackgroundScans.clear(); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| switch (msg.what) { |
| case WifiScanner.CMD_ENABLE: |
| if (mScannerImpls.isEmpty()) { |
| loge("Failed to start bgscan scan state machine because scanner impl" |
| + " is null"); |
| return HANDLED; |
| } |
| // Pick any impl available and stick to it until disable. |
| mScannerImpl = mScannerImpls.entrySet().iterator().next().getValue(); |
| mChannelHelper = mScannerImpl.getChannelHelper(); |
| |
| mBackgroundScheduler = new BackgroundScanScheduler(mChannelHelper); |
| |
| WifiNative.ScanCapabilities capabilities = |
| new WifiNative.ScanCapabilities(); |
| if (!mScannerImpl.getScanCapabilities(capabilities)) { |
| loge("could not get scan capabilities"); |
| return HANDLED; |
| } |
| if (capabilities.max_scan_buckets <= 0) { |
| loge("invalid max buckets in scan capabilities " |
| + capabilities.max_scan_buckets); |
| return HANDLED; |
| } |
| mBackgroundScheduler.setMaxBuckets(capabilities.max_scan_buckets); |
| mBackgroundScheduler.setMaxApPerScan(capabilities.max_ap_cache_per_scan); |
| |
| Log.i(TAG, "wifi driver loaded with scan capabilities: " |
| + "max buckets=" + capabilities.max_scan_buckets); |
| |
| transitionTo(mStartedState); |
| return HANDLED; |
| case WifiScanner.CMD_DISABLE: |
| Log.i(TAG, "wifi driver unloaded"); |
| transitionTo(mDefaultState); |
| break; |
| case WifiScanner.CMD_START_BACKGROUND_SCAN: |
| case WifiScanner.CMD_STOP_BACKGROUND_SCAN: |
| case WifiScanner.CMD_START_SINGLE_SCAN: |
| case WifiScanner.CMD_STOP_SINGLE_SCAN: |
| case WifiScanner.CMD_GET_SCAN_RESULTS: |
| ScanParams scanParams = (ScanParams) msg.obj; |
| ClientInfo ci = mClients.get(scanParams.listener); |
| ci.replyFailed(WifiScanner.REASON_UNSPECIFIED, "not available"); |
| break; |
| |
| case CMD_SCAN_RESULTS_AVAILABLE: |
| if (DBG) localLog("ignored scan results available event"); |
| break; |
| |
| case CMD_FULL_SCAN_RESULTS: |
| if (DBG) localLog("ignored full scan result event"); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return HANDLED; |
| } |
| } |
| |
| class StartedState extends State { |
| |
| @Override |
| public void enter() { |
| if (DBG) localLog("StartedState"); |
| if (mScannerImpl == null) { |
| // should never happen |
| Log.wtf(TAG, "Scanner impl unexpectedly null"); |
| transitionTo(mDefaultState); |
| } |
| } |
| |
| @Override |
| public void exit() { |
| sendBackgroundScanFailedToAllAndClear( |
| WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); |
| mScannerImpl = null; // reset impl |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| switch (msg.what) { |
| case WifiScanner.CMD_ENABLE: |
| Log.e(TAG, "wifi driver loaded received while already loaded"); |
| // Ignore if we're already in driver loaded state. |
| return HANDLED; |
| case WifiScanner.CMD_DISABLE: |
| return NOT_HANDLED; |
| case WifiScanner.CMD_START_BACKGROUND_SCAN: { |
| ScanParams scanParams = (ScanParams) msg.obj; |
| mWifiMetrics.incrementBackgroundScanCount(); |
| ClientInfo ci = mClients.get(scanParams.listener); |
| if (scanParams.settings == null) { |
| loge("params null"); |
| return HANDLED; |
| } |
| if (addBackgroundScanRequest(ci, msg.arg2, scanParams.settings, |
| scanParams.workSource)) { |
| ci.replySucceeded(); |
| } else { |
| ci.replyFailed(WifiScanner.REASON_INVALID_REQUEST, "bad request"); |
| } |
| break; |
| } |
| case WifiScanner.CMD_STOP_BACKGROUND_SCAN: |
| ScanParams scanParams = (ScanParams) msg.obj; |
| ClientInfo ci = mClients.get(scanParams.listener); |
| removeBackgroundScanRequest(ci); |
| break; |
| case WifiScanner.CMD_GET_SCAN_RESULTS: |
| reportScanResults(mScannerImpl.getLatestBatchedScanResults(true)); |
| break; |
| case CMD_SCAN_RESULTS_AVAILABLE: |
| WifiScanner.ScanData[] results = mScannerImpl.getLatestBatchedScanResults( |
| true); |
| mWifiMetrics.getScanMetrics().logScanSucceeded( |
| WifiMetrics.ScanMetrics.SCAN_TYPE_BACKGROUND, |
| results != null ? results.length : 0); |
| reportScanResults(results); |
| break; |
| case CMD_FULL_SCAN_RESULTS: |
| reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2); |
| break; |
| case CMD_SCAN_PAUSED: |
| reportScanResults((ScanData[]) msg.obj); |
| transitionTo(mPausedState); |
| break; |
| case CMD_SCAN_FAILED: |
| mWifiMetrics.getScanMetrics().logScanFailed( |
| WifiMetrics.ScanMetrics.SCAN_TYPE_BACKGROUND); |
| Log.e(TAG, "WifiScanner background scan gave CMD_SCAN_FAILED"); |
| sendBackgroundScanFailedToAllAndClear( |
| WifiScanner.REASON_UNSPECIFIED, "Background Scan failed"); |
| break; |
| default: |
| return NOT_HANDLED; |
| } |
| |
| return HANDLED; |
| } |
| } |
| |
| class PausedState extends State { |
| @Override |
| public void enter() { |
| if (DBG) localLog("PausedState"); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| switch (msg.what) { |
| case CMD_SCAN_RESTARTED: |
| transitionTo(mStartedState); |
| break; |
| default: |
| deferMessage(msg); |
| break; |
| } |
| return HANDLED; |
| } |
| } |
| |
| private boolean addBackgroundScanRequest(ClientInfo ci, int handler, |
| ScanSettings settings, WorkSource workSource) { |
| if (ci == null) { |
| Log.d(TAG, "Failing scan request ClientInfo not found " + handler); |
| return false; |
| } |
| |
| if (settings.periodInMs < WifiScanner.MIN_SCAN_PERIOD_MS) { |
| loge("Failing scan request because periodInMs is " + settings.periodInMs |
| + ", min scan period is: " + WifiScanner.MIN_SCAN_PERIOD_MS); |
| return false; |
| } |
| |
| if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED && settings.channels == null) { |
| loge("Channels was null with unspecified band"); |
| return false; |
| } |
| |
| if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED |
| && settings.channels.length == 0) { |
| loge("No channels specified"); |
| return false; |
| } |
| |
| int minSupportedPeriodMs = mChannelHelper.estimateScanDuration(settings); |
| if (settings.periodInMs < minSupportedPeriodMs) { |
| loge("Failing scan request because minSupportedPeriodMs is " |
| + minSupportedPeriodMs + " but the request wants " + settings.periodInMs); |
| return false; |
| } |
| |
| // check truncated binary exponential back off scan settings |
| if (settings.maxPeriodInMs != 0 && settings.maxPeriodInMs != settings.periodInMs) { |
| if (settings.maxPeriodInMs < settings.periodInMs) { |
| loge("Failing scan request because maxPeriodInMs is " + settings.maxPeriodInMs |
| + " but less than periodInMs " + settings.periodInMs); |
| return false; |
| } |
| if (settings.maxPeriodInMs > WifiScanner.MAX_SCAN_PERIOD_MS) { |
| loge("Failing scan request because maxSupportedPeriodMs is " |
| + WifiScanner.MAX_SCAN_PERIOD_MS + " but the request wants " |
| + settings.maxPeriodInMs); |
| return false; |
| } |
| if (settings.stepCount < 1) { |
| loge("Failing scan request because stepCount is " + settings.stepCount |
| + " which is less than 1"); |
| return false; |
| } |
| } |
| |
| logScanRequest("addBackgroundScanRequest", ci, null, settings, null); |
| mWifiMetrics.getScanMetrics().setClientUid(ci.mUid); |
| mWifiMetrics.getScanMetrics().setWorkSource(workSource); |
| mActiveBackgroundScans.addRequest(ci, workSource, settings); |
| |
| if (updateSchedule()) { |
| return true; |
| } else { |
| mActiveBackgroundScans.removeRequest(ci); |
| localLog("Failing scan request because failed to reset scan"); |
| return false; |
| } |
| } |
| |
| private boolean updateSchedule() { |
| if (mChannelHelper == null || mBackgroundScheduler == null || mScannerImpl == null) { |
| loge("Failed to update schedule because WifiScanningService is not initialized"); |
| return false; |
| } |
| mChannelHelper.updateChannels(); |
| Collection<ScanSettings> settings = mActiveBackgroundScans.getAllSettings(); |
| |
| mBackgroundScheduler.updateSchedule(settings); |
| WifiNative.ScanSettings schedule = mBackgroundScheduler.getSchedule(); |
| |
| if (ScanScheduleUtil.scheduleEquals(mPreviousSchedule, schedule)) { |
| if (DBG) Log.d(TAG, "schedule updated with no change"); |
| return true; |
| } |
| |
| mPreviousSchedule = schedule; |
| |
| if (schedule.num_buckets == 0) { |
| mScannerImpl.stopBatchedScan(); |
| if (DBG) Log.d(TAG, "scan stopped"); |
| return true; |
| } else { |
| localLog("starting scan: " |
| + "base period=" + schedule.base_period_ms |
| + ", max ap per scan=" + schedule.max_ap_per_scan |
| + ", batched scans=" + schedule.report_threshold_num_scans); |
| for (int b = 0; b < schedule.num_buckets; b++) { |
| WifiNative.BucketSettings bucket = schedule.buckets[b]; |
| localLog("bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)" |
| + "[" + bucket.report_events + "]: " |
| + ChannelHelper.toString(bucket)); |
| } |
| |
| if (mScannerImpl.startBatchedScan(schedule, |
| new ScanEventHandler(mScannerImpl.getIfaceName()))) { |
| if (DBG) { |
| Log.d(TAG, "scan restarted with " + schedule.num_buckets |
| + " bucket(s) and base period: " + schedule.base_period_ms); |
| } |
| mWifiMetrics.getScanMetrics().logScanStarted( |
| WifiMetrics.ScanMetrics.SCAN_TYPE_BACKGROUND); |
| return true; |
| } else { |
| mPreviousSchedule = null; |
| loge("error starting scan: " |
| + "base period=" + schedule.base_period_ms |
| + ", max ap per scan=" + schedule.max_ap_per_scan |
| + ", batched scans=" + schedule.report_threshold_num_scans); |
| for (int b = 0; b < schedule.num_buckets; b++) { |
| WifiNative.BucketSettings bucket = schedule.buckets[b]; |
| loge("bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)" |
| + "[" + bucket.report_events + "]: " |
| + ChannelHelper.toString(bucket)); |
| } |
| mWifiMetrics.getScanMetrics().logScanFailedToStart( |
| WifiMetrics.ScanMetrics.SCAN_TYPE_BACKGROUND); |
| return false; |
| } |
| } |
| } |
| |
| private void removeBackgroundScanRequest(ClientInfo ci) { |
| if (ci != null) { |
| ScanSettings settings = mActiveBackgroundScans.removeRequest(ci); |
| logScanRequest("removeBackgroundScanRequest", ci, null, settings, null); |
| updateSchedule(); |
| } |
| } |
| |
| private void reportFullScanResult(ScanResult result, int bucketsScanned) { |
| for (RequestInfo<ScanSettings> entry : mActiveBackgroundScans) { |
| ClientInfo ci = entry.clientInfo; |
| ScanSettings settings = entry.settings; |
| if (mBackgroundScheduler.shouldReportFullScanResultForSettings( |
| result, bucketsScanned, settings)) { |
| ScanResult newResult = new ScanResult(result); |
| if (result.informationElements != null) { |
| newResult.informationElements = result.informationElements.clone(); |
| } |
| else { |
| newResult.informationElements = null; |
| } |
| entry.clientInfo.reportEvent((listener) -> { |
| try { |
| listener.onFullResult(newResult); |
| } catch (RemoteException e) { |
| loge("Failed to call onFullResult: " + ci); |
| } |
| }); |
| } |
| } |
| } |
| |
| private void reportScanResults(ScanData[] results) { |
| if (results == null) { |
| Log.d(TAG,"The results is null, nothing to report."); |
| return; |
| } |
| for (ScanData result : results) { |
| if (result != null && result.getResults() != null) { |
| if (result.getResults().length > 0) { |
| mWifiMetrics.incrementNonEmptyScanResultCount(); |
| } else { |
| mWifiMetrics.incrementEmptyScanResultCount(); |
| } |
| } |
| } |
| for (RequestInfo<ScanSettings> entry : mActiveBackgroundScans) { |
| ClientInfo ci = entry.clientInfo; |
| ScanSettings settings = entry.settings; |
| ScanData[] resultsToDeliver = |
| mBackgroundScheduler.filterResultsForSettings(results, settings); |
| if (resultsToDeliver != null) { |
| logCallback("backgroundScanResults", ci, |
| describeForLog(resultsToDeliver)); |
| entry.clientInfo.reportEvent((listener) -> { |
| try { |
| listener.onResults(resultsToDeliver); |
| } catch (RemoteException e) { |
| loge("Failed to call onFullResult: " + ci); |
| } |
| }); |
| } |
| } |
| } |
| |
| private void sendBackgroundScanFailedToAllAndClear(int reason, String description) { |
| for (RequestInfo<ScanSettings> entry : mActiveBackgroundScans) { |
| ClientInfo ci = entry.clientInfo; |
| entry.clientInfo.reportEvent((listener) -> { |
| try { |
| listener.onFailure(reason, description); |
| } catch (RemoteException e) { |
| loge("Failed to call onFullResult: " + ci); |
| } |
| }); |
| } |
| mActiveBackgroundScans.clear(); |
| } |
| } |
| |
| /** |
| * PNO scan state machine has 5 states: |
| * -Default State |
| * -Started State |
| * -Hw Pno Scan state |
| * -Single Scan state |
| * |
| * These are the main state transitions: |
| * 1. Start at |Default State| |
| * 2. Move to |Started State| when we get the |WIFI_SCAN_AVAILABLE| broadcast from WifiManager. |
| * 3. When a new PNO scan request comes in: |
| * a.1. Switch to |Hw Pno Scan state| when the device supports HW PNO |
| * (This could either be HAL based ePNO or wificond based PNO). |
| * a.2. In |Hw Pno Scan state| when PNO scan results are received, check if the result |
| * contains IE (information elements). If yes, send the results to the client, else |
| * switch to |Single Scan state| and send the result to the client when the scan result |
| * is obtained. |
| * |
| * Note: PNO scans only work for a single client today. We don't have support in HW to support |
| * multiple requests at the same time, so will need non-trivial changes to support (if at all |
| * possible) in WifiScanningService. |
| */ |
| class WifiPnoScanStateMachine extends StateMachine { |
| |
| private final DefaultState mDefaultState = new DefaultState(); |
| private final StartedState mStartedState = new StartedState(); |
| private final HwPnoScanState mHwPnoScanState = new HwPnoScanState(); |
| private final SwPnoScanState mSwPnoScanState = new SwPnoScanState(); |
| private final SingleScanState mSingleScanState = new SingleScanState(); |
| private InternalClientInfo mInternalClientInfo; |
| |
| private final RequestList<Pair<PnoSettings, ScanSettings>> mActivePnoScans = |
| new RequestList<>(); |
| // Tracks scan requests across multiple scanner impls. |
| private final ScannerImplsTracker mScannerImplsTracker; |
| |
| WifiPnoScanStateMachine(Looper looper) { |
| super("WifiPnoScanStateMachine", looper); |
| |
| mScannerImplsTracker = new ScannerImplsTracker(); |
| |
| setLogRecSize(256); |
| setLogOnlyTransitions(false); |
| |
| // CHECKSTYLE:OFF IndentationCheck |
| addState(mDefaultState); |
| addState(mStartedState, mDefaultState); |
| addState(mHwPnoScanState, mStartedState); |
| addState(mSingleScanState, mHwPnoScanState); |
| addState(mSwPnoScanState, mStartedState); |
| // CHECKSTYLE:ON IndentationCheck |
| |
| setInitialState(mDefaultState); |
| } |
| |
| public void removePnoSettings(ClientInfo ci) { |
| mActivePnoScans.removeAllForClient(ci); |
| } |
| |
| /** |
| * Tracks a PNO scan request across all the available scanner impls. |
| * |
| * Note: If there are failures on some of the scanner impls, we ignore them since we can |
| * get a PNO match from the other successful impls. We don't declare total scan |
| * failures, unless all the scanner impls fail. |
| */ |
| private final class ScannerImplsTracker { |
| private final class PnoEventHandler implements WifiNative.PnoEventHandler { |
| private final String mImplIfaceName; |
| |
| PnoEventHandler(@NonNull String implIfaceName) { |
| mImplIfaceName = implIfaceName; |
| } |
| |
| @Override |
| public void onPnoNetworkFound(ScanResult[] results) { |
| if (DBG) localLog("onWifiPnoNetworkFound event received"); |
| reportPnoNetworkFoundForImpl(mImplIfaceName, results); |
| } |
| |
| @Override |
| public void onPnoScanFailed() { |
| if (DBG) localLog("onWifiPnoScanFailed event received"); |
| reportPnoScanFailedForImpl(mImplIfaceName); |
| } |
| } |
| |
| private static final int STATUS_PENDING = 0; |
| private static final int STATUS_FAILED = 2; |
| |
| // Tracks scan status per impl. |
| Map<String, Integer> mStatusPerImpl = new ArrayMap<>(); |
| |
| /** |
| * Triggers a new PNO with the specified settings on all the available scanner impls. |
| * @return true if the PNO succeeded on any of the impl, false otherwise. |
| */ |
| public boolean setHwPnoList(WifiNative.PnoSettings pnoSettings) { |
| mStatusPerImpl.clear(); |
| boolean anySuccess = false; |
| for (Map.Entry<String, WifiScannerImpl> entry : mScannerImpls.entrySet()) { |
| String ifaceName = entry.getKey(); |
| WifiScannerImpl impl = entry.getValue(); |
| boolean success = impl.setHwPnoList( |
| pnoSettings, new PnoEventHandler(ifaceName)); |
| if (!success) { |
| Log.e(TAG, "Failed to start pno on " + ifaceName); |
| continue; |
| } |
| mStatusPerImpl.put(ifaceName, STATUS_PENDING); |
| anySuccess = true; |
| } |
| return anySuccess; |
| } |
| |
| /** |
| * Resets any ongoing PNO on all the available scanner impls. |
| * @return true if the PNO stop succeeded on all of the impl, false otherwise. |
| */ |
| public boolean resetHwPnoList() { |
| boolean allSuccess = true; |
| for (String ifaceName : mStatusPerImpl.keySet()) { |
| WifiScannerImpl impl = mScannerImpls.get(ifaceName); |
| if (impl == null) continue; |
| boolean success = impl.resetHwPnoList(); |
| if (!success) { |
| Log.e(TAG, "Failed to stop pno on " + ifaceName); |
| allSuccess = false; |
| } |
| } |
| mStatusPerImpl.clear(); |
| return allSuccess; |
| } |
| |
| /** |
| * @return true if HW PNO is supported on all the available scanner impls, |
| * false otherwise. |
| */ |
| public boolean isHwPnoSupported(boolean isConnected) { |
| for (WifiScannerImpl impl : mScannerImpls.values()) { |
| if (!impl.isHwPnoSupported(isConnected)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private void reportPnoNetworkFoundForImpl(@NonNull String implIfaceName, |
| ScanResult[] results) { |
| Integer status = mStatusPerImpl.get(implIfaceName); |
| if (status != null && status == STATUS_PENDING) { |
| sendMessage(CMD_PNO_NETWORK_FOUND, 0, 0, results); |
| } |
| } |
| |
| private int getConsolidatedStatus() { |
| boolean anyPending = mStatusPerImpl.values().stream() |
| .anyMatch(status -> status == STATUS_PENDING); |
| // at-least one impl status is still pending. |
| if (anyPending) { |
| return STATUS_PENDING; |
| } else { |
| // all failed. |
| return STATUS_FAILED; |
| } |
| } |
| |
| private void reportPnoScanFailedForImpl(@NonNull String implIfaceName) { |
| Integer currentStatus = mStatusPerImpl.get(implIfaceName); |
| if (currentStatus != null && currentStatus == STATUS_PENDING) { |
| mStatusPerImpl.put(implIfaceName, STATUS_FAILED); |
| } |
| // Now check if all the scanner impls scan status is available. |
| int consolidatedStatus = getConsolidatedStatus(); |
| if (consolidatedStatus == STATUS_FAILED) { |
| sendMessage(CMD_PNO_SCAN_FAILED); |
| } |
| } |
| } |
| |
| class DefaultState extends State { |
| @Override |
| public void enter() { |
| if (DBG) localLog("DefaultState"); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| switch (msg.what) { |
| case WifiScanner.CMD_ENABLE: |
| if (mScannerImpls.isEmpty()) { |
| loge("Failed to start pno scan state machine because scanner impl" |
| + " is null"); |
| return HANDLED; |
| } |
| transitionTo(mStartedState); |
| break; |
| case WifiScanner.CMD_DISABLE: |
| transitionTo(mDefaultState); |
| break; |
| case WifiScanner.CMD_START_PNO_SCAN: |
| case WifiScanner.CMD_STOP_PNO_SCAN: |
| ScanParams scanParams = (ScanParams) msg.obj; |
| ClientInfo ci = mClients.get(scanParams.listener); |
| ci.replyFailed(WifiScanner.REASON_UNSPECIFIED, "not available"); |
| break; |
| case CMD_PNO_NETWORK_FOUND: |
| case CMD_PNO_SCAN_FAILED: |
| case WifiScanner.CMD_SCAN_RESULT: |
| case WifiScanner.CMD_OP_FAILED: |
| loge("Unexpected message " + msg.what); |
| break; |
| default: |
| return NOT_HANDLED; |
| } |
| return HANDLED; |
| } |
| } |
| |
| class StartedState extends State { |
| @Override |
| public void enter() { |
| if (DBG) localLog("StartedState"); |
| } |
| |
| @Override |
| public void exit() { |
| sendPnoScanFailedToAllAndClear( |
| WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| switch (msg.what) { |
| case WifiScanner.CMD_ENABLE: |
| // Ignore if we're already in driver loaded state. |
| return HANDLED; |
| case WifiScanner.CMD_START_PNO_SCAN: |
| ScanParams scanParams = (ScanParams) msg.obj; |
| if (scanParams == null) { |
| loge("scan params null"); |
| return HANDLED; |
| } |
| if (scanParams.pnoSettings == null || scanParams.settings == null) { |
| Log.e(TAG, "Failed to get parcelable params"); |
| ClientInfo ci = mClients.get(scanParams.listener); |
| ci.replyFailed(WifiScanner.REASON_INVALID_REQUEST, "bad parcel params"); |
| return HANDLED; |
| } |
| if (mScannerImplsTracker.isHwPnoSupported( |
| scanParams.pnoSettings.isConnected)) { |
| deferMessage(msg); |
| transitionTo(mHwPnoScanState); |
| } else if (mContext.getResources().getBoolean( |
| R.bool.config_wifiSwPnoEnabled) |
| && mDeviceConfigFacade.isSoftwarePnoEnabled()) { |
| deferMessage(msg); |
| transitionTo(mSwPnoScanState); |
| } else { |
| Log.w(TAG, "PNO is not available"); |
| ClientInfo ci = mClients.get(scanParams.listener); |
| ci.replyFailed(WifiScanner.REASON_INVALID_REQUEST, "not supported"); |
| } |
| break; |
| case WifiScanner.CMD_STOP_PNO_SCAN: |
| scanParams = (ScanParams) msg.obj; |
| ClientInfo ci = mClients.get(scanParams.listener); |
| if (ci != null) { |
| ci.cleanup(); |
| } |
| break; |
| default: |
| return NOT_HANDLED; |
| } |
| return HANDLED; |
| } |
| } |
| |
| class HwPnoScanState extends State { |
| @Override |
| public void enter() { |
| if (DBG) localLog("HwPnoScanState"); |
| } |
| |
| @Override |
| public void exit() { |
| // Reset PNO scan in ScannerImpl before we exit. |
| mScannerImplsTracker.resetHwPnoList(); |
| removeInternalClient(); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| switch (msg.what) { |
| case WifiScanner.CMD_START_PNO_SCAN: |
| ScanParams scanParams = (ScanParams) msg.obj; |
| ClientInfo ci = mClients.get(scanParams.listener); |
| if (scanParams == null) { |
| loge("params null"); |
| return HANDLED; |
| } |
| if (scanParams.pnoSettings == null || scanParams.settings == null) { |
| Log.e(TAG, "Failed to get parcelable params"); |
| ci.replyFailed(WifiScanner.REASON_INVALID_REQUEST, "bad parcel params"); |
| return HANDLED; |
| } |
| |
| if (addHwPnoScanRequest(ci, scanParams.settings, |
| scanParams.pnoSettings)) { |
| mWifiMetrics.getScanMetrics().logPnoScanEvent( |
| WifiMetrics.ScanMetrics.PNO_SCAN_STATE_STARTED); |
| ci.replySucceeded(); |
| } else { |
| mWifiMetrics.getScanMetrics().logPnoScanEvent( |
| WifiMetrics.ScanMetrics.PNO_SCAN_STATE_FAILED_TO_START); |
| ci.replyFailed(WifiScanner.REASON_INVALID_REQUEST, "bad request"); |
| ci.cleanup(); |
| transitionTo(mStartedState); |
| } |
| break; |
| case WifiScanner.CMD_STOP_PNO_SCAN: |
| scanParams = (ScanParams) msg.obj; |
| ci = mClients.get(scanParams.listener); |
| removeHwPnoScanRequest(ci); |
| transitionTo(mStartedState); |
| break; |
| case CMD_PNO_NETWORK_FOUND: |
| ScanResult[] scanResults = ((ScanResult[]) msg.obj); |
| mWifiMetrics.getScanMetrics().logPnoScanEvent( |
| WifiMetrics.ScanMetrics.PNO_SCAN_STATE_COMPLETED_NETWORK_FOUND); |
| |
| if (isSingleScanNeeded(scanResults)) { |
| ScanSettings activeScanSettings = getScanSettings(); |
| if (activeScanSettings == null) { |
| sendPnoScanFailedToAllAndClear( |
| WifiScanner.REASON_UNSPECIFIED, |
| "couldn't retrieve setting"); |
| transitionTo(mStartedState); |
| } else { |
| addSingleScanRequest(activeScanSettings); |
| transitionTo(mSingleScanState); |
| } |
| } else { |
| reportPnoNetworkFound((ScanResult[]) msg.obj); |
| } |
| break; |
| case CMD_PNO_SCAN_FAILED: |
| mWifiMetrics.getScanMetrics().logPnoScanEvent( |
| WifiMetrics.ScanMetrics.PNO_SCAN_STATE_FAILED); |
| sendPnoScanFailedToAllAndClear( |
| WifiScanner.REASON_UNSPECIFIED, "pno scan failed"); |
| transitionTo(mStartedState); |
| break; |
| default: |
| return NOT_HANDLED; |
| } |
| return HANDLED; |
| } |
| } |
| |
| class SwPnoScanState extends State { |
| private final AlarmManager mSwPnoAlarmManager; |
| private final SwPnoAlarmReceiver mSwPnoAlarmReceiver = new SwPnoAlarmReceiver(); |
| @VisibleForTesting |
| public static final String SW_PNO_ALARM_INTENT_ACTION = |
| "com.android.server.wifi.scanner.WifiPnoScanStateMachine.SwPnoScanState" |
| + ".SW_PNO_ALARM"; |
| PendingIntent mPendingIntentSwPno; |
| private final int mSwPnoTimerMarginMs; |
| @VisibleForTesting |
| public static final String SW_PNO_UPPER_BOUND_ALARM_INTENT_ACTION = |
| "com.android.server.wifi.scanner.WifiPnoScanStateMachine.SwPnoScanState" |
| + ".SW_PNO_UPPERBOUND_ALARM"; |
| PendingIntent mPendingIntentSwPnoUpperBound; |
| private SwPnoScheduler mSwPnoScheduler = null; |
| private ScanParams mScanParams = null; |
| private ClientInfo mClientInfo = null; |
| |
| |
| private final class SwPnoScheduler { |
| |
| private final class SwPnoScheduleInfo { |
| /** |
| * The timer is initially scheduled with an interval equal to mTimerBaseMs. |
| * If mBackoff is true, at each iteration the interval will increase |
| * proportionally to the elapsed iterations. |
| * The schedule is repeated up to mMaxIterations iterations. |
| */ |
| private final int mMaxIterations; |
| private final int mTimerBaseMs; |
| private final boolean mBackoff; |
| /** |
| * Whether the alarm should be exact or not. |
| * Inexact alarms are delivered when the system thinks it is most efficient. |
| */ |
| private final boolean mExact; |
| |
| |
| SwPnoScheduleInfo(int maxIterations, boolean exact, int timerBaseMs, |
| boolean backoff) { |
| if (maxIterations < 1 || timerBaseMs < 1) { |
| Log.wtf(TAG, "Invalid Sw PNO Schedule Info."); |
| throw new IllegalArgumentException("Invalid Sw PNO Schedule Info"); |
| } |
| this.mMaxIterations = maxIterations; |
| this.mExact = exact; |
| this.mTimerBaseMs = timerBaseMs; |
| this.mBackoff = backoff; |
| } |
| } |
| private List<SwPnoScheduleInfo> mSwPnoScheduleInfos = new ArrayList<>(); |
| SwPnoScheduleInfo mCurrentSchedule; |
| Iterator<SwPnoScheduleInfo> mScheduleIterator; |
| int mIteration = 0; |
| |
| /** |
| * Append a new schedule info at the end of the schedule queue. |
| * @param maxIterations Number of times the current schedule must be repeated |
| * @param exact Whether the alarms are scheduled exactly or not |
| * @param timerBaseMs Initial alarm interval |
| * @param backoff Whether the interval should increase at each iteration or not |
| */ |
| void addSchedule(int maxIterations, boolean exact, int timerBaseMs, |
| boolean backoff) { |
| mSwPnoScheduleInfos.add(new SwPnoScheduleInfo(maxIterations, exact, timerBaseMs, |
| backoff)); |
| } |
| |
| boolean start() { |
| if (mSwPnoScheduleInfos.isEmpty()) { |
| Log.wtf(TAG, "No SwPno Schedule Found"); |
| return false; |
| } |
| mScheduleIterator = mSwPnoScheduleInfos.iterator(); |
| mCurrentSchedule = mScheduleIterator.next(); |
| return true; |
| } |
| |
| boolean next() { |
| if (mCurrentSchedule.mMaxIterations > mIteration) { |
| mIteration++; |
| return true; |
| } else if (mScheduleIterator.hasNext()) { |
| mCurrentSchedule = mScheduleIterator.next(); |
| mIteration = 1; |
| return true; |
| } |
| return false; |
| } |
| |
| int getInterval() { |
| int multiplier = mCurrentSchedule.mBackoff ? mIteration : 1; |
| return mCurrentSchedule.mTimerBaseMs * multiplier; |
| } |
| |
| boolean isExact() { |
| return mCurrentSchedule.mExact; |
| } |
| } |
| |
| private class SwPnoAlarmReceiver extends BroadcastReceiver { |
| |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (intent.getAction().equals(SW_PNO_UPPER_BOUND_ALARM_INTENT_ACTION)) { |
| mSwPnoAlarmManager.cancel(mPendingIntentSwPno); |
| } else { |
| mSwPnoAlarmManager.cancel(mPendingIntentSwPnoUpperBound); |
| } |
| Message msg = obtainMessage(); |
| msg.what = CMD_SW_PNO_SCAN; |
| sendMessage(msg); |
| } |
| } |
| |
| SwPnoScanState() { |
| Intent alarmIntent = new Intent(SW_PNO_ALARM_INTENT_ACTION).setPackage( |
| mContext.getPackageName()); |
| Intent alarmIntentUpperBound = new Intent( |
| SW_PNO_UPPER_BOUND_ALARM_INTENT_ACTION).setPackage( |
| mContext.getPackageName()); |
| mSwPnoAlarmManager = mContext.getSystemService(AlarmManager.class); |
| mPendingIntentSwPno = PendingIntent.getBroadcast(mContext, /* requestCode */ 0, |
| alarmIntent, PendingIntent.FLAG_IMMUTABLE); |
| mPendingIntentSwPnoUpperBound = PendingIntent.getBroadcast(mContext, |
| /* requestCode */ 1, alarmIntentUpperBound, PendingIntent.FLAG_IMMUTABLE); |
| mSwPnoTimerMarginMs = mContext.getResources().getInteger( |
| R.integer.config_wifiSwPnoSlowTimerMargin); |
| } |
| |
| @Override |
| public void enter() { |
| if (DBG) localLog("SwPnoScanState"); |
| IntentFilter filter = new IntentFilter(SW_PNO_ALARM_INTENT_ACTION); |
| filter.addAction(SW_PNO_UPPER_BOUND_ALARM_INTENT_ACTION); |
| mContext.registerReceiver(mSwPnoAlarmReceiver, filter, null, |
| getHandler()); |
| } |
| |
| @Override |
| public void exit() { |
| removeInternalClient(); |
| mSwPnoAlarmManager.cancel(mPendingIntentSwPno); |
| mSwPnoAlarmManager.cancel(mPendingIntentSwPnoUpperBound); |
| mContext.unregisterReceiver(mSwPnoAlarmReceiver); |
| mScanParams = null; |
| mClientInfo = null; |
| } |
| |
| boolean initializeSwPnoScheduleInfos(int mobilityIntervalMs) { |
| final int swPnoDefaultTimerFastMs = mContext.getResources().getInteger( |
| R.integer.config_wifiSwPnoFastTimerMs); |
| final int swPnoDefaultTimerSlowMs = mContext.getResources().getInteger( |
| R.integer.config_wifiSwPnoSlowTimerMs); |
| final int swPnoMobilityIterations = mContext.getResources().getInteger( |
| R.integer.config_wifiSwPnoMobilityStateTimerIterations); |
| final int swPnoFastIterations = mContext.getResources().getInteger( |
| R.integer.config_wifiSwPnoFastTimerIterations); |
| final int swPnoSlowIterations = mContext.getResources().getInteger( |
| R.integer.config_wifiSwPnoSlowTimerIterations); |
| |
| mSwPnoScheduler = new SwPnoScheduler(); |
| try { |
| mSwPnoScheduler.addSchedule(swPnoMobilityIterations, |
| /* exact */ true, mobilityIntervalMs, /* backoff */ true); |
| mSwPnoScheduler.addSchedule(swPnoFastIterations, |
| /* exact */ true, swPnoDefaultTimerFastMs, /* backoff */ false); |
| mSwPnoScheduler.addSchedule(swPnoSlowIterations, |
| /* exact */ false, swPnoDefaultTimerSlowMs, /* backoff */ false); |
| } catch (IllegalArgumentException e) { |
| return false; |
| } |
| return mSwPnoScheduler.start(); |
| } |
| |
| private void addSwPnoScanRequest(ClientInfo ci, |
| ScanSettings scanSettings, PnoSettings pnoSettings) { |
| scanSettings.reportEvents |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT |
| | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; |
| addPnoScanRequest(ci, scanSettings, pnoSettings); |
| } |
| |
| private void removeSwPnoScanRequest(ClientInfo ci) { |
| if (ci != null) { |
| Pair<PnoSettings, ScanSettings> settings = removePnoScanRequest(ci); |
| ci.cleanup(); |
| if (settings != null) { |
| logScanRequest("removeSwPnoScanRequest", ci, null, |
| settings.second, settings.first); |
| } |
| } |
| } |
| |
| private void schedulePnoTimer(boolean exact, int timeoutMs) { |
| Log.i(TAG, "Next SwPno scan in: " + timeoutMs); |
| if (exact) { |
| mSwPnoAlarmManager.setExactAndAllowWhileIdle( |
| AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| mClock.getElapsedSinceBootMillis() + timeoutMs, |
| mPendingIntentSwPno); |
| } else { |
| mSwPnoAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME, |
| mClock.getElapsedSinceBootMillis() + timeoutMs, |
| mSwPnoTimerMarginMs, |
| mPendingIntentSwPno); |
| |
| mSwPnoAlarmManager.setExactAndAllowWhileIdle( |
| AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| mClock.getElapsedSinceBootMillis() + timeoutMs |
| + mSwPnoTimerMarginMs, mPendingIntentSwPnoUpperBound); |
| } |
| } |
| |
| private void handleSwPnoScan() { |
| if (mScanParams != null && mScanParams.settings != null && mClientInfo != null) { |
| // The Internal ClientInfo is unregistered by |
| // WifiSingleScanStateMachine#handleScanResults after each scan. We have |
| // therefore to re-create or at least re-register the client before each scan. |
| // For the first scan this is redundant. |
| removeInternalClient(); |
| addInternalClient(mClientInfo); |
| addSingleScanRequest(mScanParams.settings); |
| } |
| } |
| |
| private void handleSwPnoSchedule() { |
| if (mSwPnoScheduler.next()) { |
| schedulePnoTimer(mSwPnoScheduler.isExact(), |
| mSwPnoScheduler.getInterval()); |
| } else { |
| // Nothing more to schedule, stopping SW PNO |
| Message msg = obtainMessage(); |
| msg.what = WifiScanner.CMD_STOP_PNO_SCAN; |
| sendMessage(msg); |
| } |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| switch (msg.what) { |
| case WifiScanner.CMD_START_PNO_SCAN: { |
| Log.i(TAG, "Starting Software PNO"); |
| ScanParams scanParams = (ScanParams) msg.obj; |
| if (scanParams == null) { |
| Log.wtf(TAG, "Received Start PNO request without parameters"); |
| transitionTo(mStartedState); |
| return HANDLED; |
| } |
| |
| ClientInfo clientInfo = mClients.get(scanParams.listener); |
| if (clientInfo == null) { |
| Log.wtf(TAG, "Received Start PNO request without ClientInfo"); |
| transitionTo(mStartedState); |
| return HANDLED; |
| } |
| |
| if (!mActivePnoScans.isEmpty()) { |
| loge("Dropping scan request because there is already an active scan"); |
| clientInfo.replyFailed(WifiScanner.REASON_DUPLICATE_REQEUST, |
| "Failed to add a SW Pno Scan Request"); |
| return HANDLED; |
| } |
| |
| if (scanParams.pnoSettings == null || scanParams.settings == null) { |
| Log.e(TAG, "SwPno Invalid Scan Parameters"); |
| clientInfo.replyFailed(WifiScanner.REASON_INVALID_REQUEST, |
| "invalid settings"); |
| transitionTo(mStartedState); |
| return HANDLED; |
| } |
| |
| if (!initializeSwPnoScheduleInfos(scanParams.settings.periodInMs)) { |
| clientInfo.replyFailed(WifiScanner.REASON_INVALID_REQUEST, |
| "Failed to initialize the Sw PNO Scheduler"); |
| transitionTo(mStartedState); |
| return HANDLED; |
| } |
| |
| addSwPnoScanRequest(clientInfo, scanParams.settings, |
| scanParams.pnoSettings); |
| clientInfo.replySucceeded(); |
| mClientInfo = clientInfo; |
| mScanParams = scanParams; |
| |
| handleSwPnoScan(); |
| handleSwPnoSchedule(); |
| break; |
| } |
| case CMD_SW_PNO_SCAN: |
| // The internal client is registered to mClients when the PNO scan is |
| // started, and is deregistered when the scan is over. By verifying that |
| // the internal client is not registered in mClients can be sure that no |
| // other pno scans are in progress |
| if (mClients.get(mInternalClientInfo.mListener) == null) { |
| handleSwPnoScan(); |
| handleSwPnoSchedule(); |
| } |
| break; |
| case WifiScanner.CMD_STOP_PNO_SCAN: { |
| Log.i(TAG, "Stopping Software PNO"); |
| if (mClientInfo != null) { |
| removeSwPnoScanRequest(mClientInfo); |
| transitionTo(mStartedState); |
| } |
| break; |
| } |
| case WifiScanner.CMD_OP_FAILED: |
| sendPnoScanFailedToAllAndClear( |
| WifiScanner.REASON_UNSPECIFIED, "scan failed"); |
| transitionTo(mStartedState); |
| break; |
| default: |
| return NOT_HANDLED; |
| } |
| return HANDLED; |
| } |
| } |
| |
| class SingleScanState extends State { |
| @Override |
| public void enter() { |
| if (DBG) localLog("SingleScanState"); |
| } |
| |
| @Override |
| public boolean processMessage(Message msg) { |
| switch (msg.what) { |
| case WifiScanner.CMD_SCAN_RESULT: |
| WifiScanner.ParcelableScanData parcelableScanData = |
| (WifiScanner.ParcelableScanData) msg.obj; |
| ScanData[] scanDatas = parcelableScanData.getResults(); |
| ScanData lastScanData = scanDatas[scanDatas.length - 1]; |
| reportPnoNetworkFound(lastScanData.getResults()); |
| transitionTo(mHwPnoScanState); |
| break; |
| case WifiScanner.CMD_OP_FAILED: |
| sendPnoScanFailedToAllAndClear( |
| WifiScanner.REASON_UNSPECIFIED, "single scan failed"); |
| transitionTo(mStartedState); |
| break; |
| default: |
| return NOT_HANDLED; |
| } |
| return HANDLED; |
| } |
| } |
| |
| private WifiNative.PnoSettings convertSettingsToPnoNative(ScanSettings scanSettings, |
| PnoSettings pnoSettings) { |
| WifiNative.PnoSettings nativePnoSetting = new WifiNative.PnoSettings(); |
| nativePnoSetting.periodInMs = scanSettings.periodInMs; |
| nativePnoSetting.min5GHzRssi = pnoSettings.min5GHzRssi; |
| nativePnoSetting.min24GHzRssi = pnoSettings.min24GHzRssi; |
| nativePnoSetting.min6GHzRssi = pnoSettings.min6GHzRssi; |
| nativePnoSetting.isConnected = pnoSettings.isConnected; |
| nativePnoSetting.networkList = |
| new WifiNative.PnoNetwork[pnoSettings.networkList.length]; |
| for (int i = 0; i < pnoSettings.networkList.length; i++) { |
| nativePnoSetting.networkList[i] = new WifiNative.PnoNetwork(); |
| nativePnoSetting.networkList[i].ssid = pnoSettings.networkList[i].ssid; |
| nativePnoSetting.networkList[i].flags = pnoSettings.networkList[i].flags; |
| nativePnoSetting.networkList[i].auth_bit_field = |
| pnoSettings.networkList[i].authBitField; |
| nativePnoSetting.networkList[i].frequencies = |
| pnoSettings.networkList[i].frequencies; |
| } |
| return nativePnoSetting; |
| } |
| |
| // Retrieve the only active scan settings. |
| private ScanSettings getScanSettings() { |
| for (Pair<PnoSettings, ScanSettings> settingsPair : mActivePnoScans.getAllSettings()) { |
| return settingsPair.second; |
| } |
| return null; |
| } |
| |
| private void removeInternalClient() { |
| if (mInternalClientInfo != null) { |
| mInternalClientInfo.cleanup(); |
| mInternalClientInfo = null; |
| } else { |
| Log.w(TAG, "No Internal client for PNO"); |
| } |
| } |
| |
| private void addInternalClient(ClientInfo ci) { |
| if (mInternalClientInfo == null) { |
| mInternalClientInfo = new InternalClientInfo(ci.getUid(), "internal", |
| new InternalListener()); |
| mInternalClientInfo.register(); |
| } else { |
| Log.w(TAG, "Internal client for PNO already exists"); |
| } |
| } |
| |
| private void addPnoScanRequest(ClientInfo ci, ScanSettings scanSettings, |
| PnoSettings pnoSettings) { |
| mActivePnoScans.addRequest(ci, ClientModeImpl.WIFI_WORK_SOURCE, |
| Pair.create(pnoSettings, scanSettings)); |
| addInternalClient(ci); |
| } |
| |
| private Pair<PnoSettings, ScanSettings> removePnoScanRequest(ClientInfo ci) { |
| Pair<PnoSettings, ScanSettings> settings = mActivePnoScans.removeRequest(ci); |
| return settings; |
| } |
| |
| private boolean addHwPnoScanRequest(ClientInfo ci, ScanSettings scanSettings, |
| PnoSettings pnoSettings) { |
| if (ci == null) { |
| Log.d(TAG, "Failing scan request ClientInfo not found "); |
| return false; |
| } |
| if (!mActivePnoScans.isEmpty()) { |
| loge("Failing scan request because there is already an active scan"); |
| return false; |
| } |
| WifiNative.PnoSettings nativePnoSettings = |
| convertSettingsToPnoNative(scanSettings, pnoSettings); |
| if (!mScannerImplsTracker.setHwPnoList(nativePnoSettings)) { |
| return false; |
| } |
| logScanRequest("addHwPnoScanRequest", ci, null, scanSettings, pnoSettings); |
| addPnoScanRequest(ci, scanSettings, pnoSettings); |
| |
| return true; |
| } |
| |
| private void removeHwPnoScanRequest(ClientInfo ci) { |
| if (ci != null) { |
| Pair<PnoSettings, ScanSettings> settings = removePnoScanRequest(ci); |
| ci.cleanup(); |
| if (settings != null) { |
| logScanRequest("removeHwPnoScanRequest", ci, null, |
| settings.second, settings.first); |
| } |
| } |
| } |
| |
| private void reportPnoNetworkFound(ScanResult[] results) { |
| for (RequestInfo<Pair<PnoSettings, ScanSettings>> entry : mActivePnoScans) { |
| ClientInfo ci = entry.clientInfo; |
| logCallback("pnoNetworkFound", ci, describeForLog(results)); |
| ci.reportEvent((listener) -> { |
| try { |
| listener.onPnoNetworkFound(results); |
| } catch (RemoteException e) { |
| loge("Failed to call onFullResult: " + ci); |
| } |
| }); |
| } |
| } |
| |
| private void sendPnoScanFailedToAllAndClear(int reason, String description) { |
| for (RequestInfo<Pair<PnoSettings, ScanSettings>> entry : mActivePnoScans) { |
| ClientInfo ci = entry.clientInfo; |
| ci.reportEvent((listener) -> { |
| try { |
| listener.onFailure(reason, description); |
| } catch (RemoteException e) { |
| loge("Failed to call onFullResult: " + ci); |
| } |
| }); |
| } |
| mActivePnoScans.clear(); |
| } |
| |
| private void addSingleScanRequest(ScanSettings settings) { |
| if (DBG) localLog("Starting single scan"); |
| if (mInternalClientInfo != null) { |
| Message msg = Message.obtain(); |
| msg.what = WifiScanner.CMD_START_SINGLE_SCAN; |
| msg.obj = new ScanParams(mInternalClientInfo.mListener, settings, |
| ClientModeImpl.WIFI_WORK_SOURCE); |
| mSingleScanStateMachine.sendMessage(msg); |
| } |
| mWifiMetrics.getScanMetrics().setWorkSource(ClientModeImpl.WIFI_WORK_SOURCE); |
| } |
| |
| /** |
| * Checks if IE are present in scan data, if no single scan is needed to report event to |
| * client |
| */ |
| private boolean isSingleScanNeeded(ScanResult[] scanResults) { |
| for (ScanResult scanResult : scanResults) { |
| if (scanResult.informationElements != null |
| && scanResult.informationElements.length > 0) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| |
| @FunctionalInterface |
| private interface ListenerCallback { |
| void callListener(IWifiScannerListener listener); |
| } |
| |
| private abstract class ClientInfo { |
| private final int mUid; |
| private final String mPackageName; |
| private final WorkSource mWorkSource; |
| private boolean mScanWorkReported = false; |
| protected final IWifiScannerListener mListener; |
| |
| ClientInfo(int uid, String packageName, IWifiScannerListener listener) { |
| mUid = uid; |
| mPackageName = packageName; |
| mListener = listener; |
| mWorkSource = new WorkSource(uid); |
| } |
| |
| /** |
| * Register this client to main client map. |
| */ |
| public void register() { |
| mClients.put(mListener, this); |
| } |
| |
| /** |
| * Unregister this client from main client map. |
| */ |
| private void unregister() { |
| mClients.remove(mListener); |
| } |
| |
| public void cleanup() { |
| mSingleScanListeners.removeAllForClient(this); |
| mSingleScanStateMachine.removeSingleScanRequests(this); |
| mBackgroundScanStateMachine.removeBackgroundScanSettings(this); |
| unregister(); |
| localLog("Successfully stopped all requests for client " + this); |
| } |
| |
| public int getUid() { |
| return mUid; |
| } |
| |
| // This has to be implemented by subclasses to report events back to clients. |
| public abstract void reportEvent(ListenerCallback cb); |
| |
| // TODO(b/27903217, 71530998): This is dead code. Should this be wired up ? |
| private void reportBatchedScanStart() { |
| if (mUid == 0) |
| return; |
| |
| int csph = getCsph(); |
| |
| mBatteryStats.reportWifiBatchedScanStartedFromSource(mWorkSource, csph); |
| } |
| |
| // TODO(b/27903217, 71530998): This is dead code. Should this be wired up ? |
| private void reportBatchedScanStop() { |
| if (mUid == 0) |
| return; |
| |
| mBatteryStats.reportWifiBatchedScanStoppedFromSource(mWorkSource); |
| } |
| |
| // TODO migrate batterystats to accept scan duration per hour instead of csph |
| private int getCsph() { |
| int totalScanDurationPerHour = 0; |
| Collection<ScanSettings> settingsList = |
| mBackgroundScanStateMachine.getBackgroundScanSettings(this); |
| for (ScanSettings settings : settingsList) { |
| int scanDurationMs = mChannelHelper.estimateScanDuration(settings); |
| int scans_per_Hour = settings.periodInMs == 0 ? 1 : (3600 * 1000) / |
| settings.periodInMs; |
| totalScanDurationPerHour += scanDurationMs * scans_per_Hour; |
| } |
| |
| return totalScanDurationPerHour / ChannelHelper.SCAN_PERIOD_PER_CHANNEL_MS; |
| } |
| |
| // TODO(b/27903217, 71530998): This is dead code. Should this be wired up ? |
| private void reportScanWorkUpdate() { |
| if (mScanWorkReported) { |
| reportBatchedScanStop(); |
| mScanWorkReported = false; |
| } |
| if (mBackgroundScanStateMachine.getBackgroundScanSettings(this).isEmpty()) { |
| reportBatchedScanStart(); |
| mScanWorkReported = true; |
| } |
| } |
| |
| void replySucceeded() { |
| if (mListener != null) { |
| try { |
| mListener.onSuccess(); |
| mLog.trace("onSuccess").flush(); |
| } catch (RemoteException e) { |
| // There's not much we can do if reply can't be sent! |
| } |
| } else { |
| // locally generated message; doesn't need a reply! |
| } |
| } |
| |
| void replyFailed(int reason, String description) { |
| if (mListener != null) { |
| try { |
| mListener.onFailure(reason, description); |
| mLog.trace("onFailure reason=% description=%") |
| .c(reason) |
| .c(description) |
| .flush(); |
| } catch (RemoteException e) { |
| // There's not much we can do if reply can't be sent! |
| } |
| } else { |
| // locally generated message; doesn't need a reply! |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return "ClientInfo[uid=" + mUid + ", package=" + mPackageName + ", " + mListener |
| + "]"; |
| } |
| } |
| |
| /** |
| * This class is used to represent external clients to the WifiScanning Service. |
| */ |
| private class ExternalClientInfo extends ClientInfo { |
| /** |
| * Indicates if the client is still connected |
| * If the client is no longer connected then messages to it will be silently dropped |
| */ |
| private boolean mDisconnected = false; |
| |
| ExternalClientInfo(int uid, String packageName, IWifiScannerListener listener) { |
| super(uid, packageName, listener); |
| if (DBG) localLog("New client, listener: " + listener); |
| try { |
| listener.asBinder().linkToDeath(new DeathRecipient() { |
| @Override |
| public void binderDied() { |
| mWifiThreadRunner.post(() -> { |
| if (DBG) localLog("binder died: client listener: " + listener); |
| cleanup(); |
| }); |
| } |
| }, 0); |
| } catch (RemoteException e) { |
| Log.e(TAG, "can't register death recipient! " + listener); |
| } |
| } |
| |
| public void reportEvent(ListenerCallback cb) { |
| if (!mDisconnected) { |
| cb.callListener(mListener); |
| } |
| } |
| |
| @Override |
| public void cleanup() { |
| mDisconnected = true; |
| mPnoScanStateMachine.removePnoSettings(this); |
| super.cleanup(); |
| } |
| } |
| |
| /** |
| * This class is used to represent internal clients to the WifiScanning Service. This is needed |
| * for communicating between State Machines. |
| * This leaves the onReportEvent method unimplemented, so that the clients have the freedom |
| * to handle the events as they need. |
| */ |
| private class InternalClientInfo extends ClientInfo { |
| /** |
| * The UID here is used to proxy the original external requester UID. |
| */ |
| InternalClientInfo(int requesterUid, String packageName, IWifiScannerListener listener) { |
| super(requesterUid, packageName, listener); |
| } |
| |
| @Override |
| public void reportEvent(ListenerCallback cb) { |
| cb.callListener(mListener); |
| } |
| |
| @Override |
| public String toString() { |
| return "InternalClientInfo[]"; |
| } |
| } |
| |
| private static class InternalListener extends IWifiScannerListener.Stub { |
| InternalListener() { |
| } |
| |
| @Override |
| public void onSuccess() { |
| } |
| |
| @Override |
| public void onFailure(int reason, String description) { |
| } |
| |
| @Override |
| public void onResults(WifiScanner.ScanData[] results) { |
| } |
| |
| @Override |
| public void onFullResult(ScanResult fullScanResult) { |
| } |
| |
| @Override |
| public void onSingleScanCompleted() { |
| } |
| |
| @Override |
| public void onPnoNetworkFound(ScanResult[] results) { |
| } |
| } |
| |
| private static String toString(int uid, ScanSettings settings) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("ScanSettings[uid=").append(uid); |
| sb.append(", period=").append(settings.periodInMs); |
| sb.append(", report=").append(settings.reportEvents); |
| if (settings.reportEvents == WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL |
| && settings.numBssidsPerScan > 0 |
| && settings.maxScansToCache > 1) { |
| sb.append(", batch=").append(settings.maxScansToCache); |
| sb.append(", numAP=").append(settings.numBssidsPerScan); |
| } |
| sb.append(", ").append(ChannelHelper.toString(settings)); |
| sb.append("]"); |
| |
| return sb.toString(); |
| } |
| |
| @Override |
| protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) |
| != PERMISSION_GRANTED) { |
| pw.println("Permission Denial: can't dump WifiScanner from from pid=" |
| + Binder.getCallingPid() |
| + ", uid=" + Binder.getCallingUid() |
| + " without permission " |
| + android.Manifest.permission.DUMP); |
| return; |
| } |
| pw.println("WifiScanningService - Log Begin ----"); |
| mLocalLog.dump(fd, pw, args); |
| pw.println("WifiScanningService - Log End ----"); |
| pw.println(); |
| pw.println("clients:"); |
| for (ClientInfo client : mClients.values()) { |
| pw.println(" " + client); |
| } |
| pw.println("listeners:"); |
| for (ClientInfo client : mClients.values()) { |
| Collection<ScanSettings> settingsList = |
| mBackgroundScanStateMachine.getBackgroundScanSettings(client); |
| for (ScanSettings settings : settingsList) { |
| pw.println(" " + toString(client.mUid, settings)); |
| } |
| } |
| if (mBackgroundScheduler != null) { |
| WifiNative.ScanSettings schedule = mBackgroundScheduler.getSchedule(); |
| if (schedule != null) { |
| pw.println("schedule:"); |
| pw.println(" base period: " + schedule.base_period_ms); |
| pw.println(" max ap per scan: " + schedule.max_ap_per_scan); |
| pw.println(" batched scans: " + schedule.report_threshold_num_scans); |
| pw.println(" buckets:"); |
| for (int b = 0; b < schedule.num_buckets; b++) { |
| WifiNative.BucketSettings bucket = schedule.buckets[b]; |
| pw.println(" bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)[" |
| + bucket.report_events + "]: " |
| + ChannelHelper.toString(bucket)); |
| } |
| } |
| } |
| if (mPnoScanStateMachine != null) { |
| mPnoScanStateMachine.dump(fd, pw, args); |
| } |
| pw.println(); |
| |
| if (mSingleScanStateMachine != null) { |
| mSingleScanStateMachine.dump(fd, pw, args); |
| pw.println(); |
| List<ScanResult> scanResults = mSingleScanStateMachine.getCachedScanResultsAsList(); |
| long nowMs = mClock.getElapsedSinceBootMillis(); |
| Log.d(TAG, "Latest scan results nowMs = " + nowMs); |
| pw.println("Latest scan results:"); |
| ScanResultUtil.dumpScanResults(pw, scanResults, nowMs); |
| pw.println(); |
| } |
| for (WifiScannerImpl impl : mScannerImpls.values()) { |
| impl.dump(fd, pw, args); |
| } |
| } |
| |
| void logScanRequest(String request, ClientInfo ci, WorkSource workSource, |
| ScanSettings settings, PnoSettings pnoSettings) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(request) |
| .append(": ") |
| .append((ci == null) ? "ClientInfo[unknown]" : ci.toString()); |
| if (workSource != null) { |
| sb.append(",").append(workSource); |
| } |
| if (settings != null) { |
| sb.append(", "); |
| describeTo(sb, settings); |
| } |
| if (pnoSettings != null) { |
| sb.append(", "); |
| describeTo(sb, pnoSettings); |
| } |
| localLog(sb.toString()); |
| } |
| |
| void logCallback(String callback, ClientInfo ci, String extra) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(callback) |
| .append(": ") |
| .append((ci == null) ? "ClientInfo[unknown]" : ci.toString()); |
| if (extra != null) { |
| sb.append(",").append(extra); |
| } |
| localLog(sb.toString()); |
| } |
| |
| static String describeForLog(ScanData[] results) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("results="); |
| for (int i = 0; i < results.length; ++i) { |
| if (i > 0) sb.append(";"); |
| sb.append(results[i].getResults().length); |
| } |
| return sb.toString(); |
| } |
| |
| static String describeForLog(ScanResult[] results) { |
| return "results=" + results.length; |
| } |
| |
| static String getScanTypeString(int type) { |
| switch(type) { |
| case WifiScanner.SCAN_TYPE_LOW_LATENCY: |
| return "LOW LATENCY"; |
| case WifiScanner.SCAN_TYPE_LOW_POWER: |
| return "LOW POWER"; |
| case WifiScanner.SCAN_TYPE_HIGH_ACCURACY: |
| return "HIGH ACCURACY"; |
| default: |
| // This should never happen because we've validated the incoming type in |
| // |validateScanType|. |
| throw new IllegalArgumentException("Invalid scan type " + type); |
| } |
| } |
| |
| static String describeTo(StringBuilder sb, ScanSettings scanSettings) { |
| sb.append("ScanSettings { ") |
| .append(" type:").append(getScanTypeString(scanSettings.type)) |
| .append(" band:").append(ChannelHelper.bandToString(scanSettings.band)) |
| .append(" ignoreLocationSettings:").append(scanSettings.ignoreLocationSettings) |
| .append(" period:").append(scanSettings.periodInMs) |
| .append(" reportEvents:").append(scanSettings.reportEvents) |
| .append(" numBssidsPerScan:").append(scanSettings.numBssidsPerScan) |
| .append(" maxScansToCache:").append(scanSettings.maxScansToCache) |
| .append(" rnrSetting:").append( |
| SdkLevel.isAtLeastS() ? scanSettings.getRnrSetting() : "Not supported") |
| .append(" 6GhzPscOnlyEnabled:").append( |
| SdkLevel.isAtLeastS() ? scanSettings.is6GhzPscOnlyEnabled() |
| : "Not supported") |
| .append(" channels:[ "); |
| if (scanSettings.channels != null) { |
| for (int i = 0; i < scanSettings.channels.length; i++) { |
| sb.append(scanSettings.channels[i].frequency).append(" "); |
| } |
| } |
| sb.append(" ] ").append(" } "); |
| return sb.toString(); |
| } |
| |
| static String describeTo(StringBuilder sb, PnoSettings pnoSettings) { |
| sb.append("PnoSettings { ") |
| .append(" min5GhzRssi:").append(pnoSettings.min5GHzRssi) |
| .append(" min24GhzRssi:").append(pnoSettings.min24GHzRssi) |
| .append(" min6GhzRssi:").append(pnoSettings.min6GHzRssi) |
| .append(" isConnected:").append(pnoSettings.isConnected) |
| .append(" networks:[ "); |
| if (pnoSettings.networkList != null) { |
| for (int i = 0; i < pnoSettings.networkList.length; i++) { |
| sb.append(pnoSettings.networkList[i].ssid).append(","); |
| } |
| } |
| sb.append(" ] ") |
| .append(" } "); |
| return sb.toString(); |
| } |
| } |