WLAN / WiFi 全流程

模块根:settingsPage/micarConnectionSettings/src/main/java/com/android/car/settings/miauto/wifi/

0. 数据源与库定位(最关键)

  • WifiEntry / WifiPickerTracker 来自仓库内自带 com.android.wifitrackerlibbase/WifiTrackerLib/), AOSP com.android.wifitracker2
    • 依赖链:micarConnectionSettings/build.gradle:82 api project(":base:settingsBaseLib")base/settingsBaseLib/build.gradle:92 api project(":base:WifiTrackerLib")
    • WifiPickerTrackerbase/WifiTrackerLib/src/{xcddif,dcddif}/java/com/android/wifitrackerlib/(两套 flavor)
  • 工厂 createWifiPickerTracker(...) 是 flavor-specific:src/{xcddif,dcddif}/.../wifi/MiCarWifiCollinear.kt:17/:20,调用点 MiWifiTrackerUtils.kt:65
  • 扫描参数:MiCarWifiUtils.DEFAULT_SCAN_INTERVAL_MILLIS = 10_000L(:19),MiWifiTrackerUtils.DEFAULT_MAX_SCAN_AGE_MILLIS = 15_000L(:29)
  • 回调链:WifiPickerTracker.WifiPickerTrackerCallback.onWifiEntriesChanged/onWifiStateChangedMiWifiTrackerUtils.mTrackerListener:44-64 → 各 Controller

1. 架构总览

分层:View(Fragment/Activity) 持有 MiWifiTrackerUtils(数据源) → Controller(业务) 通过 InstanceGetter 拿 tracker → 驱动 Preference(UI)。

classDiagram
    class MiWifiTrackerUtils {
        -WifiPickerTracker mWifiTracker
        +getAllWifiEntries()
        +getSavedWifiEntries()
        +getConnectedWifiWifiEntry()}
    class MiCarBaseWifiEntryController {<<abstract>>
        #CopyOnWriteArrayList mAllWifiEntries
        +fillWifiEntries()*
        +updateState()*}
    class MiCarAvailableWifiListController
    class MiCarSavedWifiListController
    class MiCarProvisionWifiListController
    class MiCarWifiUtils {
        +setWifiEnable() +connect() +startScan()}
    class WifiSwitchController
    class WifiConnectionStateController
    WifiPickerTracker <|.. MiWifiTrackerUtils : wraps
    MiWifiTrackerUtils o-- MiCarBaseWifiEntryController : Listener
    MiCarBaseWifiEntryController <|-- MiCarAvailableWifiListController
    MiCarBaseWifiEntryController <|-- MiCarSavedWifiListController
    MiCarBaseWifiEntryController <|-- MiCarProvisionWifiListController

2. 开关与状态

WLAN 开关(WifiSwitchController)

  • handlePreferenceChanged:101:防双击 isQuickDoubleClick(:107)→ 真变化时 ThreadPoolUtils.ASYNC.execute { MiCarWifiUtils.setWifiEnabled(it) }(:113-115)→ setLoading(true)
  • 底层 MiCarWifiUtils.setWifiEnable:147-149mWifiManager?.setWifiEnabled全仓唯一点
  • 回调 onWifiStateChanged:74:仅 WIFI_STATE_ENABLED/DISABLED 才更新 UI 并 Trace.endSection(),中间态保持 loading
stateDiagram-v2
    [*] --> DISABLED
    DISABLED --> ENABLING : setWifiEnabled(true)
    ENABLING --> ENABLED : onWifiStateChanged(ENABLED)
    ENABLED --> DISABLING : setWifiEnabled(false)
    DISABLING --> DISABLED : onWifiStateChanged(DISABLED)
    note right of ENABLING: toggle 显示 loading
    note right of ENABLED: 列表可见, startScan()

连接主页 WLAN 入口状态(WifiConnectionStateController)

不依赖 WifiPickerTracker,直接注册 BroadcastReceiver(:39-65)监听 WIFI_STATE_CHANGED_ACTION + NETWORK_STATE_CHANGED_ACTIONrefreshWifiEntrySummary:82:WiFi 关→wifi_state_off_summary;开但 SSID 未知→wifi_state_on_summary;已连接→取 WifiInfo.ssidgetRealSsid 引号(:117-123);Passpoint→passpointProviderFriendlyName。异步取数据主线程刷 updateRightText

3. 扫描与列表

数据来源(MiWifiTrackerUtils.getWifiEntries:105)

  1. HotspotUtils.refreshHotspotInfo() 先刷本地热点信息
  2. WiFi 关→返回空
  3. 遍历 mWifiTracker.wifiEntries 过滤:isHotspot(本地热点不算)排除、level==UNREACHABLE 排除、按 isOnlyNeedSaved 筛选

三个列表差异(关键:可用与已保存互斥)

Controller数据源已连接已保存
MiCarAvailableWifiListController.fillWifiEntries:50getAllWifiEntries仅计 mStartIndex 不显示显式跳过 isSaved(:52-58)
MiCarSavedWifiListController.fillWifiEntries:73getConnected + getSaved头部
MiCarProvisionWifiListController.fillWifiEntries:47getConnected + getAll头部不区分

互斥设计:同一 WiFi 不会同时出现在两栏。available 显式跳过 isSaved,隐含 UX 约定——已保存但未连接的只在”已保存”栏。

刷新触发

  • 自动扫描:WifiPickerTracker 内部 Scanner 按 10s 周期
  • 手动:MiCarAvailableWifiListController.startScan:70MiCarWifiUtils.startScan:134唯一点),触发器=下拉刷新 / WiFi 刚 ENABLED
  • UI 节流:MiCarBaseWifiEntryController.updateState:87-111removeMessages + SINGLE_ASYNC.remove 避免抖屏;扫描动画 10s 后强制停(:73-74)
sequenceDiagram
    participant Tracker as WifiPickerTracker
    participant Utils as MiWifiTrackerUtils
    participant Ctrl as MiCarAvailableWifiListController
    participant UI as Preference列表
    Note over Tracker: 周期10s 或 startScan()
    Tracker->>Utils: onWifiEntriesChanged()
    Utils->>Ctrl: Listener.onWifiEntriesChanged()
    Ctrl->>Ctrl: refreshUi→updateState 调度 SINGLE_ASYNC
    Note over Ctrl: 子线程 fillWifiEntries 过滤
    Ctrl->>UI: mHandler sendMessage removeAll+createPreference

4. 连接流程

点击入口(基类 MiCarBaseWifiEntryController.initWifiEntryPreference:162)

  1. 防双击(:166)
  2. 已连接且 canSignIn(captive portal)→ signIn(null)(:172)
  3. 未连接:tryConnectWifiEntry = wifiEntry(标记重试对象)→ wifiEntry.connect(WifiEntryConnectCallback)(:177-178)

回调与密码框(WifiEntryConnectCallback.onConnectResult:237)

  • CONNECT_STATUS_FAILURE_NO_CONFIG(首次需密码)→ WifiPasswordDialog.show(:247)
  • CONNECT_STATUS_FAILURE_UNKNOWN → 仅日志
  • 其他 → gotoTopPosition()

密码框 → WifiManager.connect

WifiPasswordDialog.show:49:EAP 显示 identity 框;密码默认隐藏;校验 WIFI_PWD_LENGTH_RANGE=8..63(:25)+ identity 非空。点连接→MiCarWifiUtils.connect:52WifiManager.connect 唯一点)→ getWifiConfig:66-131 按 security 分支(NONE/WEP/PSK/SAE/OWE/EAP/EAP_SUITE_B)。

已保存免密直连

已保存网络 wifiEntry.connect 时 wifitrackerlib 直接用已保存 WifiConfigurationconnect(networkId)不触发 NO_CONFIG,不弹密码框

详情页(MiCarWifiEntryDetailFragment)

  • 改密码:开放网络禁用(:104)→ WifiPasswordDialog → connect
  • 删除:confirmDeleteApforget(null)(:157,forget 唯一点

连接失败重试广播

ACTION_MICAR_WIFI_CONNECTION_FAILURE = "micar.intent.action.WIFI_CONNECTION_FAILURE"(:20)。本仓不发送,由 framework 发(带 EXTRA_WIFI_CONFIGURATION)。接收方 WifiSettingsActivity.mConnectFailureReceiver:42 + WlanProvisionDialogActivity.mConnectFailureReceiver:50,匹配 SSID 后重弹密码框(isShowPasswordWrong=true)。

sequenceDiagram
    participant U as 用户
    participant Base as MiCarBaseWifiEntryController
    participant Entry as WifiEntry
    participant Dia as WifiPasswordDialog
    participant Utils as MiCarWifiUtils
    participant Mgr as WifiManager
    U->>Base: 点击 WiFi 项
    Base->>Entry: connect(callback)
    alt 有保存配置
        Entry->>Mgr: connect(savedConfig)
    else 无配置(首次)
        Entry-->>Base: onConnectResult(NO_CONFIG)
        Base->>Dia: WifiPasswordDialog.show()
        U->>Dia: 输入密码
        Dia->>Utils: connect(wifiEntry,pwd)
        Utils->>Mgr: connect(config, listener)
        Note over Mgr: 失败时 framework 发 WIFI_CONNECTION_FAILURE
        Mgr->>Base: (广播)重弹密码框
    end

5. NetworkRequest 弹窗体系(独立子模块)

触发:App 通过 ConnectivityManager#requestNetwork 申请网络时,系统发 com.android.settings.wifi.action.NETWORK_REQUEST,命中 NetworkRequestDialogActivity(manifest:143-151,权限限定系统调用)。

与普通列表根本差异:不走 WifiPickerTracker/WifiEntry,直接用 WifiManager.NetworkRequestMatchCallback+ScanResult;用户选择调 NetworkRequestUserSelectionCallback.select(config)/reject(),由系统完成连接。单独 taskAffinity、singleTop、excludeFromRecents。

Fragment职责用户操作
NetworkRequestDialogBaseFragment抽象基类,提供标题/summary 钩子
NetworkRequestDialogFragment多 SSID 列表(默认模式)selectNetworkselect(config)
NetworkRequestSingleSsidDialogFragment单 SSID 确认(EXTRA_IS_SPECIFIED_SSID=true)“连接”→select(config)
NetworkRequestErrorDialogFragment错误/超时(onAbort/30s 无结果)重试/取消→reject()

Activity 要点(NetworkRequestDialogActivity.java):onResume:73 注册 callback + 30s timeout;onMatch:175 按 specifiedSsid 分流;success/failure 都 setResult OK(仅文案不同)。

NetworkRequest 子包完全不引用 MiWifiTrackerUtils/WifiEntry/MiCarWifiUtils,新增功能勿复用普通列表 Preference/Controller。配置构建 NetworkRequestWifiConfigUtils.java:11ScanResult.capabilities 判加密,与 wifitrackerlib 完全解耦。

6. Provision 开机引导

触发WifiSettingsActivity.onCreate:93-110):

val isProvisioned = DevelopmentSettingsUtil.isDeviceProvisioned(applicationContext)
val launchProvisionPage = !isProvisioned && refer != SETTINGS_CONNECTION_WIFI

系统未完成开机引导非从连接主页来startActivity(WlanProvisionDialogActivity)+finish。WlanProvisionDialogActivity manifest action com.android.car.settings.provision.WIFI_SETTINGS(:160,priority=100,singleInstance)。

与正常列表差异

维度正常列表引导页
Fragment 基类DialogSettingsFragmentTopLevelSettingsFragment
密码框非全屏、无取消键全屏 + 带取消键
”继续”按钮仅 hasConnectedAp=true 可用
语音序号显示隐藏
列表填充saved/available 分流connected + 所有(不区分)

切换isDeviceProvisioned 返回 true 后下次进正常 Fragment;MiCarWifiEntryPreference.mIsProvisioned:42 构造时取值,生命周期内不刷新(需列表重建)。

7. 设计要点与坑

  1. 扫描节流/防抖:tracker 10s 自动扫;UI updateState 用 removeMessages 防堆叠;手动扫描动画 10s 强制停防 loading 卡死;WiFi 关时直接 removeAll 不触发异步。
  2. 密码框安全:默认隐藏;8..63 长度(与 AOSP 一致);全屏隐藏 systemBars;isShowing 判重防重复弹。
  3. Provision 切换隐患:用 refer != SETTINGS_CONNECTION_WIFI 区分入口,调用方漏传 refer 则未 provisioned 也进正常列表(可能非期望)。
  4. 单例隐患MiCarWifiUtils.detailWifiEntry/tryConnectWifiEntry(:31,36)是 object 字段跨 Fragment 传递。WifiSettingsActivity(singleTask) 与 WlanProvisionDialogActivity(singleInstance) taskAffinity 不同,并发覆盖会错乱。onDestroy 清 tryConnectWifiEntry,但 detailWifiEntry 永不清
  5. 双平台createWifiPickerTrackerbase/WifiTrackerLib 均分 dcddif/xcddif 两套,改 WiFi 逻辑两套都要改。
  6. 连接失败广播隐性约定ACTION_MICAR_WIFI_CONNECTION_FAILURE 由 framework 发,改时需同步确认 framework 侧(packages/modules/Wifi)仍发该 action、是否带 EXTRA_WIFI_CONFIGURATION,否则重试链路失效。
  7. NetworkRequest 与普通列表解耦:两套完全独立,勿强行复用。

关键调用点速查

操作位置
WifiManager.setWifiEnabledMiCarWifiUtils.kt:148(唯一点)
WifiManager.startScanMiCarWifiUtils.kt:134(唯一点)
WifiManager.connect(config)MiCarWifiUtils.kt:52(唯一点)
WifiEntry.connectMiCarBaseWifiEntryController.kt:178
WifiEntry.forgetMiCarWifiEntryDetailFragment.kt:157(唯一点)
NetworkRequestUserSelectionCallback.select/rejectNetworkRequestDialogActivity.java:227,249