WiFi 扫描处理过程

本来源自 WiFi 扫描处理过程

从 WifiServiceImpl.java 中进行上层的服务请求,进行扫描:

/**
 * See {@link android.net.wifi.WifiManager#startScan}
 *
 * @param packageName Package name of the app that requests wifi scan.
 * @param featureId The feature in the package
 */
@Override
public boolean startScan(String packageName, String featureId) {
    if (enforceChangePermission(packageName) != MODE_ALLOWED) {
        return false;
    }

    int callingUid = Binder.getCallingUid();
    long ident = Binder.clearCallingIdentity();
    mLog.info("startScan uid=%").c(callingUid).flush();
    synchronized (this) {
        if (mInIdleMode) {
            // Need to send an immediate scan result broadcast in case the
            // caller is waiting for a result ..

            // TODO: investigate if the logic to cancel scans when idle can move to
            // WifiScanningServiceImpl.  This will 1 - clean up WifiServiceImpl and 2 -
            // avoid plumbing an awkward path to report a cancelled/failed scan.  This will
            // be sent directly until b/31398592 is fixed.
            sendFailedScanBroadcast();
            mScanPending = true;
            return false;
        }
    }
    try {
        mWifiPermissionsUtil.enforceCanAccessScanResults(packageName, featureId, callingUid,
                null);
        Boolean scanSuccess = mWifiThreadRunner.call(() ->
                mScanRequestProxy.startScan(callingUid, packageName), null);
        if (scanSuccess == null) {
            sendFailedScanBroadcast();
            return false;
        }
        if (!scanSuccess) {
            Log.e(TAG, "Failed to start scan");
            return false;
        }
    } catch (SecurityException e) {
        Log.e(TAG, "Permission violation - startScan not allowed for"
                + " uid=" + callingUid + ", packageName=" + packageName + ", reason=" + e);
        return false;
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
    return true;
}

其中实际调用了 ScanRequestProxy.java 中 ScanRequestProxy 类的 startScan 方法:

/*
 * This class manages all scan requests originating from external apps using the
 * {@link WifiManager#startScan()}
 * Note: This class is not thread-safe. It needs to be invoked from the main Wifi thread only.
 */


/**
 * Initiate a wifi scan.
 *
 * @param callingUid The uid initiating the wifi scan. Blame will be given to this uid.
 * @return true if the scan request was placed or a scan is already ongoing, false otherwise.
 */
public boolean startScan(int callingUid, String packageName) {
    if (!mScanningEnabled || !retrieveWifiScannerIfNecessary()) {
        Log.e(TAG, "Failed to retrieve wifiscanner");
        sendScanResultFailureBroadcastToPackage(packageName);
        return false;
    }
    boolean fromSettingsOrSetupWizard =
            mWifiPermissionsUtil.checkNetworkSettingsPermission(callingUid)
                    || mWifiPermissionsUtil.checkNetworkSetupWizardPermission(callingUid);
    // Check and throttle scan request unless,
    // a) App has either NETWORK_SETTINGS or NETWORK_SETUP_WIZARD permission.
    // b) Throttling has been disabled by user.
    if (!fromSettingsOrSetupWizard && mThrottleEnabled
            && shouldScanRequestBeThrottledForApp(callingUid, packageName)) {
        Log.i(TAG, "Scan request from " + packageName + " throttled");
        sendScanResultFailureBroadcastToPackage(packageName);
        return false;
    }
    // Create a worksource using the caller's UID.
    WorkSource workSource = new WorkSource(callingUid, packageName);

    // Create the scan settings.
    WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings();
    // Scan requests from apps with network settings will be of high accuracy type.
    if (fromSettingsOrSetupWizard) {
        settings.type = WifiScanner.SCAN_TYPE_HIGH_ACCURACY;
    }
    // always do full scans
    settings.band = WifiScanner.WIFI_BAND_ALL;
    settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
            | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
    if (mScanningForHiddenNetworksEnabled) {
        settings.hiddenNetworks.clear();
        // retrieve the list of hidden network SSIDs from saved network to scan for, if enabled.
        settings.hiddenNetworks.addAll(mWifiConfigManager.retrieveHiddenNetworkList());
        // retrieve the list of hidden network SSIDs from Network suggestion to scan for.
        settings.hiddenNetworks.addAll(
                mWifiInjector.getWifiNetworkSuggestionsManager().retrieveHiddenNetworkList());
    }
    mWifiScanner.startScan(settings, new HandlerExecutor(mHandler),
            new ScanRequestProxyScanListener(), workSource);
    return true;
}

进而继续调用了 WifiScanner.java 中 WifiScanner 类的 startScan 方法:

/* WifiScanner.java */
/**
 * starts a single scan and reports results asynchronously
 * @param settings specifies various parameters for the scan; for more information look at
 * {@link ScanSettings}
 * @param listener specifies the object to report events to. This object is also treated as a
 *                 key for this scan, and must also be specified to cancel the scan. Multiple
 *                 scans should also not share this object.
 */
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
public void startScan(ScanSettings settings, ScanListener listener) {
    startScan(settings, listener, null);
}

/**
 * starts a single scan and reports results asynchronously
 * @param settings specifies various parameters for the scan; for more information look at
 * {@link ScanSettings}
 * @param listener specifies the object to report events to. This object is also treated as a
 *                 key for this scan, and must also be specified to cancel the scan. Multiple
 *                 scans should also not share this object.
 * @param workSource WorkSource to blame for power usage
 */
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
    startScan(settings, null, listener, workSource);
}

/**
 * starts a single scan and reports results asynchronously
 * @param settings specifies various parameters for the scan; for more information look at
 * {@link ScanSettings}
 * @param executor the Executor on which to run the callback.
 * @param listener specifies the object to report events to. This object is also treated as a
 *                 key for this scan, and must also be specified to cancel the scan. Multiple
 *                 scans should also not share this object.
 * @param workSource WorkSource to blame for power usage
 * @hide
 */
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
public void startScan(ScanSettings settings, @Nullable @CallbackExecutor Executor executor,
        ScanListener listener, WorkSource workSource) {
    Objects.requireNonNull(listener, "listener cannot be null");
    int key = addListener(listener, executor);
    if (key == INVALID_KEY) return;
    validateChannel();
    Bundle scanParams = new Bundle();
    scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
    scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
    scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
    scanParams.putString(REQUEST_FEATURE_ID_KEY, mContext.getAttributionTag());
    mAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, scanParams);
}

通过 AsyncChannel 将 CMD_START_SINGLE_SCAN 消息发送到 WifiScanningServiceImpl.java 进行消息处理,消息处理在 WifiScanningServiceImpl 类内部状态机类 WifiSingleScanStateMachine DriverStartedState 状态下进行,实际调用了 tryToStartNewScan 处理过程

/**
 * 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

/**
 * 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) {
        ClientInfo ci = mClients.get(msg.replyTo);

        switch (msg.what) {
            case WifiScanner.CMD_ENABLE:
                // Ignore if we're already in driver loaded state.
                return HANDLED;
            case WifiScanner.CMD_START_SINGLE_SCAN:
                int handler = msg.arg2;
                Bundle scanParams = (Bundle) msg.obj;
                if (scanParams == null) {
                    logCallback("singleScanInvalidRequest",  ci, handler, "null params");
                    replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null");
                    return HANDLED;
                }
                ScanSettings scanSettings = null;
                WorkSource workSource = null;
                try {
                    scanSettings =
                            scanParams.getParcelable(
                                    WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY);
                    workSource =
                            scanParams.getParcelable(
                                    WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY);
                } catch (BadParcelableException e) {
                    Log.e(TAG, "Failed to get parcelable params", e);
                    logCallback("singleScanInvalidRequest",  ci, handler,
                            "bad parcel params");
                    replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST,
                            "bad parcel params");
                    return HANDLED;
                }
                if (validateScanRequest(ci, handler, scanSettings)) {
                    mWifiMetrics.incrementOneshotScanCount();
                    if ((scanSettings.band & WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY) != 0) {
                        mWifiMetrics.incrementOneshotScanWithDfsCount();
                    }
                    logScanRequest("addSingleScanRequest", ci, handler, workSource,
                            scanSettings, null);
                    replySucceeded(msg);

                    // 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 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.
                    if (getCurrentState() == mScanningState) {
                        if (activeScanSatisfies(scanSettings)) {
                            mActiveScans.addRequest(ci, handler, workSource, scanSettings);
                        } else {
                            mPendingScans.addRequest(ci, handler, workSource, scanSettings);
                        }
                    } else {
                        mPendingScans.addRequest(ci, handler, workSource, scanSettings);
                        tryToStartNewScan();
                    }
                } else {
                    logCallback("singleScanInvalidRequest",  ci, handler, "bad request");
                    replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
                    mWifiMetrics.incrementScanReturnEntry(
                            WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION, 1);
                }
                return HANDLED;
            case WifiScanner.CMD_STOP_SINGLE_SCAN:
                removeSingleScanRequest(ci, msg.arg2);
                return HANDLED;
            default:
                return NOT_HANDLED;
        }
    }
}

tryToStartNewScan 调用了 ScannerImplsTracker 的 startSingleScan 方法进行一次扫描,并将状态转化为 ScanningState:

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();
    List<WifiNative.HiddenNetwork> hiddenNetworkList = new ArrayList<>();
    for (RequestInfo<ScanSettings> entry : mPendingScans) {
        settings.scanType = mergeScanTypes(settings.scanType, entry.settings.type);
        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 (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)) {
        // 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());
        // notify and cancel failed scans
        sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED,
                "Failed to start single scan");
    }
}

ScannerImplsTracker 是 WifiSingleScanStateMachine 的内部类,内部再次调用了 WifiScannerImpl 类的 startSingleScan:

/**
 * 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

/**
 * 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);
            continue;
        }
        mStatusPerImpl.put(ifaceName, STATUS_PENDING);
        anySuccess = true;
    }
    return anySuccess;
}

WifiScannerImpl 是一个抽象类,实际的类实现在 WificondScannerImpl 中:

/* WifiScannerImpl.java */
/**
 * Start a one time scan. This method should only be called when there is no scan going on
 * (after a callback indicating that the previous scan succeeded/failed).
 * @return if the scan paramaters are valid
 * Note this may return true even if the parameters are not accepted by the chip because the
 * scan may be scheduled async.
 */
public abstract boolean startSingleScan(WifiNative.ScanSettings settings,
        WifiNative.ScanEventHandler eventHandler);


/* WificondScannerImpl */
@Override
public boolean startSingleScan(WifiNative.ScanSettings settings,
        WifiNative.ScanEventHandler eventHandler) {
    if (eventHandler == null || settings == null) {
        Log.w(TAG, "Invalid arguments for startSingleScan: settings=" + settings
                + ",eventHandler=" + eventHandler);
        return false;
    }
    synchronized (mSettingsLock) {
        if (mLastScanSettings != null) {
            Log.w(TAG, "A single scan is already running");
            return false;
        }

        ChannelCollection allFreqs = mChannelHelper.createChannelCollection();
        boolean reportFullResults = false;

        for (int i = 0; i < settings.num_buckets; ++i) {
            WifiNative.BucketSettings bucketSettings = settings.buckets[i];
            if ((bucketSettings.report_events
                            & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
                reportFullResults = true;
            }
            allFreqs.addChannels(bucketSettings);
        }

        List<String> hiddenNetworkSSIDSet = new ArrayList<>();
        if (settings.hiddenNetworks != null) {
            int numHiddenNetworks =
                    Math.min(settings.hiddenNetworks.length, MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
            for (int i = 0; i < numHiddenNetworks; i++) {
                hiddenNetworkSSIDSet.add(settings.hiddenNetworks[i].ssid);
            }
        }
        mLastScanSettings = new LastScanSettings(
                    mClock.getElapsedSinceBootMillis(),
                    reportFullResults, allFreqs, eventHandler);

        boolean success = false;
        Set<Integer> freqs;
        if (!allFreqs.isEmpty()) {
            freqs = allFreqs.getScanFreqs();
            success = mWifiNative.scan(
                    getIfaceName(), settings.scanType, freqs, hiddenNetworkSSIDSet);
            if (!success) {
                Log.e(TAG, "Failed to start scan, freqs=" + freqs);
            }
        } else {
            // There is a scan request but no available channels could be scanned for.
            // We regard it as a scan failure in this case.
            Log.e(TAG, "Failed to start scan because there is no available channel to scan");
        }
        if (success) {
            if (DBG) {
                Log.d(TAG, "Starting wifi scan for freqs=" + freqs);
            }

            mScanTimeoutListener = new AlarmManager.OnAlarmListener() {
                @Override public void onAlarm() {
                    handleScanTimeout();
                }
            };

            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                    mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS,
                    TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
        } else {
            // indicate scan failure async
            mEventHandler.post(new Runnable() {
                    @Override public void run() {
                        reportScanFailure();
                    }
                });
        }

        return true;
    }
}

进而实际调用了 WifiNative 中的 scan 接口

涉及到 Native 之后的走读就不进行了,看看就行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值