aosp-wifi八个基础客制化功能,前4个最简单,你掌握了几个?

522 阅读15分钟

为何要收藏这篇干货?——番茄憨憨的自我独白

我是番茄憨憨,我要坦白。

无论是智能穿戴设备,还是智能通讯设备,为满足用户需求,靓仔靓女们都知道,我们会做各种各样的定制化开发,而在wifi模块,通常表现为sta功能定制或者ap功能定制,所以我将从sta和ap两端给大家分享常见的wifi定制功能,本章将介绍sta的定制,话不多说,我们直接发车。

一、功能大纲先上

    1. wifi default switch 、Turn on wifi automatically、Notify for public network
    1. wifi direct name
    1. avoid bad wifi
    1. passpoint

二、具体实现及其修改

1.wifi default switch、Turn on wifi automatically、Notify for public network

对于wifi默认开关,wifi自动打开(检测到高质量可用wifi时)、公共可用wifi(检测到附近有公共可用wifi时下发通知)三项配置,SettingProvider为我们提供了wifi相关的默认配置,如果要求我们在开机时就启动,我们可以对framework层相关数据库默认值进行一个更改,不同的平台有不同的架构,有些平台是s(vendor)+t(system)或者t(vendor)+u(system)这种集合性的架构,这种架构的修改代码需要在system分区路劲下去寻找(不是这种架构的直接找下面路劲),路劲如下:

frameworks/base/packages/SettingsProvider/res/values

    <bool name="def_wifi_on">false</bool>
    <!-- 0 == never, 1 == only when plugged in, 2 == always -->
    <integer name="def_wifi_sleep_policy">2</integer>
    <bool name="def_wifi_wakeup_enabled">true</bool>
    <bool name="def_networks_available_notification_on">true</bool>

可以看到上面的代码中有四个属性,其中def_wifi_on就代表wifi默认功能开关def_wifi_wakeup_enabled代表wifi是否自动打开功能def_networks_available_notification_on代表公共可用wifi通知,三项配置开或关都只需要设置为true或false即可,说到这里,有的童鞋可能就问了,既然是三项配置,为啥多了一项def_wifi_sleep_policy?既然你诚心诚意的问了,我就大发慈悲的告诉你。

我就是粘贴复制多了,懒得删!

zhuai.jpg

其实这个属性值代表着wifi的休眠策略,简单来说,就是设备在灭屏情况下,wifi是否保持连接。

  • 0 (Never) : 当设备进入休眠状态时,Wi-Fi 将被完全关闭。这意味着即使设备处于睡眠状态,也不会通过 Wi-Fi 进行任何数据传输,这有助于节省电池电量。
  • 1 (Only when plugged in) : 只有当设备连接到电源时,Wi-Fi 才会在设备休眠状态下保持开启。如果设备没有连接电源,则 Wi-Fi 会在设备进入休眠后关闭。
  • 2 (Always) : 不论设备是否连接到电源,Wi-Fi 都会一直保持开启状态,即使设备处于休眠模式。这样可以让设备在后台继续进行数据同步、接收邮件和通知等操作。 有0、1、2三个值,设置不同的值有相同的效果,0是即使在灭屏情况下,wifi断开连接,1是对于这个点,往往伴随着低功耗

2、wifi direct name

对于wifi直连名, 不区分平台,我们直接上动态代码修改!

1、导入如下包
import android.net.wifi.p2p.WifiP2pManager;

2、创建对象
private WifiP2pManager mP2pManager;
private WifiP2pManager.Channel mChannel;
private String  deviceName = "";

mP2pManager = (WifiP2pManager) mContext.getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mManager.initialize(mContext, mContext.getMainLooper(), null);

3、具体实现逻辑代码
            if (mP2pManager == null) {
                Log.d(TAG, "Failed to get WifiP2pManager");
                return;
            }
            if (mChannel == null) {
                Log.e(TAG, "Failed to initialize WifiP2pManager channel");
            }

            if (mManager != null && mChannel != null) {
                mManager.setDeviceName(mChannel, deviceName, new WifiP2pManager.ActionListener() {
                    @Override
                    public void onSuccess() {
                        Log.d(TAG, "Device name set to: " + deviceName);
                    }

                    @Override
                    public void onFailure(int reason) {
                        Log.e(TAG, "Failed to set device name: " + reason);
                    }
                });
            }

3、avoid bad wifi

bad wifi,见词知意,就是不具备探网(无传输能力的wifi),针对于bad wifi,有些时候我们会做一些特定的需求,每个项目的需求可能不同,所以最关键的,我们应当知晓如何去判断bad wifi,主要是网络性能完成检测,代码如下。

//通过网络性能来判断,需要通过connectivityManager来获取此时网络性能
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);

/**
*  两种情况下wifi被视作bad wifi
*  1、通过了网络检测,但是私有DNS配置出现问题的wifi
*  2、直接不具备传输能力的WiFi
**/
if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
            Log.d("ABWC", "networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)");
            if (networkCapabilities.isPrivateDnsBroken()) {
                Log.d("ABWC", "networkCapabilities.isPrivateDnsBroken() if2");
            } else {
                Log.d("ABWC", "networkCapabilities.isPrivateDnsBroken() else2");
                badConnection = true;
            }
        } else if (!networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
            Log.d("ABWC", "has sim and sim data is opened, !networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI");
            badConnection = true;
        }

4、passpoint

passpoint是属于协议支持,代码配置相对而言比较简单,可能许多朋友对于这个模块比较陌生,所以这里我将简单的对其进行介绍,然后再上代码配置.

4.1、passpoint功能定义

Passpoint(以前称为Hotspot 2.0)是由Wi-Fi联盟认证的一种无线网络技术标准,旨在为用户提供更加简便、安全的Wi-Fi连接体验。它允许设备自动连接到兼容的Wi-Fi热点,无需用户手动输入网络密码或进行其他配置。这种无缝的连接体验类似于蜂窝网络的切换,当用户在不同位置移动时,可以自动连接到最佳的可用网络,其主要目标是提升公共Wi-Fi网络的安全性和易用性,确保无论用户身在何处,只要是在Passpoint认证的热点覆盖范围内,都能享受到一致的高质量服务。对于企业和服务提供商来说,Passpoint有助于减少支持成本,并提供一个更可控的用户体验。

4.2、passpoint认证架构

1、802.1x认证架构

802.1X 为认证对话程序定义了三个元件,如图所示。申请者(supplicant)是寻求访问网络资源的使用者机器。网络访问由认证者(authenticator)所控制;它扮演着传统拨接网络中访问服务器一样的角色。申请者与认证者在规格书中称为连接埠认证实体(PortAuthentication Entities,简称 PAE)。认证者只负责链路层的身份认证交换程序,并不维护任何使用者信息。任何认证要求均会被转送至认证服务器(例如 RADIUS)进行实际的处理。

image.png

2、eap认证交换程序

image.png

EAP 交换程序范例如图所示,EAP 交换程序是一系列的步骤,以认证要求信息开始,以成功或失败信息结束。

在 EAP 认证方式交换过程中,由认证者所发送的封包是以 Request/Method 表示,如果是用户端所答复的回应则以 Response/Method 表示。

1 认证者(authenticator)发出一个 Request/Identity(要求/身份证明)封包以辨识使、用者身份。Request/Identity 有两个目的。除了启动交换程序外,也用来告诉用户端,在身份认证完成之前将会丢弃任何传输数据。

2 用户端要求使用者输入识别码,随后将所搜集到的使用者识别码以 Response/Identify(回复/身份证明)信息送出。

3 一旦认出该使用者,认证者随即会送出认证验查。在本图第三个步骤中,认证者以一个Request/MD-5 Challenge(要求/MD-5 挑战)封包送出 MD-5 Challenge(MD-5 挑战)给使用者。

4 用户端在设置上是以 token card(标记卡)进行身份认证,因此它会送出一个Response/NAK(回复/负面回应)信息,提议以 Generic Token Card(一般标记卡)作为认证机制。

5 认证者会送出一个 Request/Generic Token Card(要求/一般标记卡)的挑战,要求取得卡号(numerical sequence on the card)。

6.使用者键入卡号,通过 Response/Generic Token Card〈回复/一般标记卡)送回。

7 使用者的回复并不正确,因此认证失败。不过,这个认证者在 EAP 的实现允许多次认证机会,因此会送出第二个 Request/Generic Token Card(要求/一般标记卡)的挑战。

8 使用者再度回复,依然通过 Response/Generic Token Card(回复/一般标记卡)传递。

9 此次的回复正确因此认证者发出一个 Success(成功)信息。

3、eap认证方式

除了流量控制与协商信息,EAP 也会为身份认证的方式指定类型代码。EAP 会把证明使用者身份的过程,授权给一个称为 EAP method 的附属协议,EAP method 乃是一组验证使用者身份的规则。

image.png

MD-5 Challenge(MD-5 挑战)

MD-5 Challenge (MD-5 挑战)相当于 RFC 1994 所规范的 CHAP 协议。身份认证要求中包含了给用户端的挑战。用户端只要能够成功回应挑战,就可以证明它的确握有共享密钥。所有的EAP 实现必须支持 MD-5 Challenge。不过,它在无线网络领域并末得到广泛使用,因为它无法在无线网络上提供动态密钥。

Generic Token Card(一般标记卡)

RSA 公司的 SecurID 以及 Secure Computing 公司的 Safeword,这一类的 Token card(标记卡)在某些机构里特别受到欢迎,因为它们(以「随机」的方式)提供类似抛弃式密码的安全性,但是没有单次密码(one-time password,简称 OTP)的种种麻烦。在「要求」信息中包含了身份认证所需要的 Generic Token Card 信息。要求信息的 Type-Data 字段长度必须大于零。在「答复」信息里,Type-Data 字段用来携带复制自 token card 的信息。在「要求」与「回复」封包里,EAP 封包的 Length 字段则是用来计算 Type-Data 要求信息的长度。EAP-GTC 与 EAP 标准同样规范于 RFC 2284。它允许通过网络交换明文身份认证数据。除了搭配标记卡使用 EAP-GTC 通常会被“使用者名称十密码”进行身份认证的 EAP 认证方式。如果数据库中既有的使用者帐号具有只能进行比较但无法读取的单向加密密码(one-wayencrypted password),EAP-GTC 将能提供用来验证使用者身份的 EAP 认证方式。当然,如果想要以 EAP-GTC 来传输可重复使用的密码,那么你必须使用管道加以保护。

EAP-TLS

Transport Layer Security(简称 TLS)协议原本就是设计来用在易遭窥视的链路层上。TLS的前身是保护网际网络交易安全的协议 Secure Socket Layer(简称 SSL)。从许多方面来看,无线局域网络的使用案例(use case)与网际网络类似。数据必须在完全不可信赖且攻击者立的网络环境中进行传送。TLS 的目的,就是在不可信赖的网络环境中建立一条可信赖的沟通管道。TLS 通过凭证交换来进行相互认证。使用者必须将数位凭证送交认证服务器以进行验证,但是认证服务器也必须提供本身的凭证。通过可信赖的凭证发行机构验证服务器的凭证真伪,用户端就可以确定所连接的网络经过凭证发行机构授权无误。EAP-TLS 是第一个符合无线网络三项要求的身份认证方式。凭证提供牢靠的「使用者对网络」以及「网络对使用者」双向认证。相互认证可以防范所谓的「假」(rogue)基站,让用户端得以判定基站是否由正确的部门,而非为了窃取密码的有心人士所设立。TLS 还会建立一组主密码(master secret),用来衍生出链路层安全防护协议所需要的密钥。EAP-TLS 虽然安全,不过并未被广泛使用。无线网络中任何潜在的用户都必须配备本身的数位凭证。产生与传递凭证以及遵循验证程序都是莫大的挑战。已经采用公开密钥基础建设(publickey infrastructure,简称 PKI )的机构如要使用 EAP-TLS 就相当容易;有些机构并不想组建 PKI,而另外选用别种方式。

EAP-TTLS 与EAP-PEAP

实际上,需要使用 PKI 是无线局域网络上推行坚固的身份认证时主要的障碍。PKI 不论在技术或者程序上都是一项艰巨的任务。大多数的组织宁可利用现有的身份认证系统,例如 Windows omain 或 Active Directory,LDAP directory,或者 Kerberos realm。使用现成的帐号比起重新建立一套平行的身份认证系统来得简单。有两种 EAP method 能够搭配所谓的“旧式身份认证方式”一起使用,分别是管道式 TLS(Tunneled TLS,简称 TTLS)与防护型 EAP(Protected EAP,简称 PEAP)。

TTLS 与 PEAP 的运作方式类似。首先,协议会使用类似 EAP-TLS 的方式建立起一个 TLS 管道。进行下一个步骤之前,会先使用认证服务器的数位恋证来验证此网络是否可受信赖。第二个步骤是使用 TLS 管道为旧式的身份认证协议加密,然后以之验证使用者身份。有时候第一个步骤也称为「外层」(outer)身份认证,因为它是用来保护第二个或者「内层」(inner)身份认证的管道。

【注】有些以 EAP-Cisco Wireless 来称呼 LEAP。

认证还是免不了,但只有外层身份认证需要用到。TTLS 与 PEAP 将凭证数目从千百张缩减为屈指可数,只有认证服务器需要使用凭证。这就是为什么 TTLS 与 PEAP 远比 EAP-TLS 受到欢迎。一般机构可以自行设立小型的凭证管理中心,不必依赖外部凭证机构所签发的昂贵凭证。TTLS 与 PEAP 之间有些微的差异,在于内层身份认证的处理方式。TTLS 使用加密管道来交换attribute-value pair(「属性-值」对,简称 AVP), PEAP 则是在管道内进行第二次 EAP 交换程序。采用 AVP 使得 TTLS 较具弹性,因为 AVP 可用来执行 EAP method 未提供的身份认证方式。使用 TTLS 与 PEAP 的好处是,内层与外层身份认证可以使用不同的使用者名称。这两种协议在进行外层身份认证时可以匿名进行,只有在加密管道中才会显示使用者的真实身份,如此一来就不会在末经加密的帧中暴露使用者名称。不过并非所有用户端软件均可隐藏使用者的身份。

EAP-SIM与EAP-AKA

还有两种值得注意的 EAP 认证方式:LAP-SI_M 与 EAP-AKA,它们已经通过标准制定程序,主要是通过移动电话数据库进行身份认证。EAP-SIM 为 GSM 电话网络上的 SIM 卡数据库提供了一个界面。EAP-AKA 则是基于第三代移动电话网络上所使用的身份认证系统,称为 Authenticationand Key Agreement(简称 AKA)。EAP-SIM 与 EAP-AKA 对想要以移动电话帐号整合计费功能的电信公司而言特别有用。他们可以使用现成的智慧型芯片以及使用者帐号来脸证使用者的身份,使用者不必另外指定新的密码即可访问数据网络。

看到这里,想必大家对比passpoint的功能已经了解,但可能不清楚,为啥在这里我要介绍一堆认证,因为passpoint网络的连接就是需要通过eap认证的,这样一说大家就明白了,而它的功能实现,代码中实现了EAP-AKA的方式,业务基本流程图和代码如下:

image.png

业务流程图

//导包
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.SystemProperties;
import android.net.wifi.ScanResult;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSp;
import java.lang.reflect.Method;
import com.tinno.feature.TinnoFeature;

//变量加载
    ConnectivityManager cm;
    private static final int EAP_TYPE = 23;
    private PasspointConfiguration mPasspointConfiguration;
    private static final String ATT_PASSPOINT_1 = "attwifi - Passpoint";
	private static final String ATT_PASSPOINT_2 = "Guest-Passpoint"
	private static final String ATT_PASSPOINT_3 = "RTC - Passpoint"
    private static final String BELL_PASSPOINT_1 = "BELL_WIFI";
    private static final String BELL_PASSPOINT_2 = "5099251212";

//服务对象内存回收、扫描广播注册
        cm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        IntentFilter intentFilter =  new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
        getContext().registerReceiver(mScanResultsReceiver,intentFilter);

//服务对象内存回收、扫描广播注销
		if (cm != null){
			cm = null;
		}
        if (mScanResultsReceiver != null) {
            getContext().unregisterReceiver(mScanResultsReceiver);
        }

//核心扫描代码
    private BroadcastReceiver mScanResultsReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            List<ScanResult> results = mWifiManager.getScanResults();
            NetworkInfo info = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
            boolean flag = false;
            for(ScanResult result : results){
                Log.i(TAG,"NetworkInfo.State.CONNECTED ==== " +  info.getState());
                if (isNoSimcard() || "".equals(getIMSI()) || (info.getState() == NetworkInfo.State.CONNECTED)) {
                    return;
                }
                List<PasspointConfiguration> passpointConfigurations = mWifiManager.getPasspointConfigurations();
                if(passpointConfigurations.size() > 0) {
                    for (PasspointConfiguration pc : passpointConfigurations) {
                        String imsi = pc.getCredential().getSimCredential().getImsi();
                        Log.i(TAG,"passpointConfigurations.size() > 0 " + "flag ===" + flag + "pc.getHomeSp().getFriendlyName() ===" + pc.getHomeSp().getFriendlyName() + "result.SSID == " + result.SSID);
                        if (TinnoFeature.isATTVersion()) {
                            if ((!getIMSI().equals(imsi) || !pc.getHomeSp().getFriendlyName().contains("attwifi - Passpoint")) && result.SSID.equals("attwifi - Passpoint") && !flag) {
                                Log.i(TAG, "add  attwifi - Passpoint ");
                                flag = true;
                                mWifiManager.addOrUpdatePasspointConfiguration(createPasspointConfiguration(true, "attwifi - Passpoint"));
                            } else if ((!getIMSI().equals(imsi) || !pc.getHomeSp().getFriendlyName().contains("Guest-Passpoint")) && result.SSID.equals("Guest-Passpoint") && !flag) {
                                flag = true;
                                mWifiManager.addOrUpdatePasspointConfiguration(createPasspointConfiguration(true, "Guest-Passpoint"));
                                Log.i(TAG, "add  Guest-Passpoint ");
                            } else if ((!getIMSI().equals(imsi) || !pc.getHomeSp().getFriendlyName().contains("RTC - Passpoint")) && result.SSID.equals("RTC - Passpoint") && !flag) {
                                Log.i(TAG, "add  RTC - Passpoint ");
                                flag = true;
                                mWifiManager.addOrUpdatePasspointConfiguration(createPasspointConfiguration(true, "RTC - Passpoint"));
                            }
                        } else { // Bell veirsion passpoint , it's belong to 209TM_CA
                            if ((!getIMSI().equals(imsi) || !pc.getHomeSp().getFriendlyName().contains(BELL_PASSPOINT1)) && result.SSID.equals(BELL_PASSPOINT1) && !flag) {
                                flag = true;
                                mWifiManager.addOrUpdatePasspointConfiguration(createPasspointConfiguration(true, BELL_PASSPOINT1));
                                Log.i(TAG, "add  BELL_WIFI ");
                            } else if((!getIMSI().equals(imsi) || !pc.getHomeSp().getFriendlyName().contains(BELL_PASSPOINT2)) && result.SSID.equals(BELL_PASSPOINT2) && !flag) {
                                flag = true;
                                mWifiManager.addOrUpdatePasspointConfiguration(createPasspointConfiguration(true, BELL_PASSPOINT2));
                                Log.i(TAG, "add  5099251212 ");
                            }
                        }
                    }
                }else {
                    Log.i(TAG,"passpointConfigurations.size() = 0 ");
                    if (TinnoFeature.isATTVersion()) {
                        if (result.SSID.equals("attwifi - Passpoint") && !flag) {
                            flag = true;
                            mWifiManager.addOrUpdatePasspointConfiguration(createPasspointConfiguration(true, "attwifi - Passpoint"));
                        } else if (result.SSID.equals("Guest-Passpoint") && !flag) {
                            flag = true;
                            mWifiManager.addOrUpdatePasspointConfiguration(createPasspointConfiguration(true, "Guest-Passpoint"));
                        } else if (result.SSID.equals("RTC - Passpoint") && !flag) {
                            flag = true;
                            mWifiManager.addOrUpdatePasspointConfiguration(createPasspointConfiguration(true, "RTC - Passpoint"));
                        }
                    } else {
                        if (result.SSID.equals(BELL_PASSPOINT1) && !flag) {
                            flag = true;
                            mWifiManager.addOrUpdatePasspointConfiguration(createPasspointConfiguration(true, BELL_PASSPOINT1));
                        } else if (result.SSID.equals(BELL_PASSPOINT2) && !flag) {
                            flag = true;
                            mWifiManager.addOrUpdatePasspointConfiguration(createPasspointConfiguration(true, BELL_PASSPOINT2));
                        }
                    }
                }
            }
        }
    };

    private PasspointConfiguration createPasspointConfiguration(boolean isEnabled,String SSID) {
        try {
            PasspointConfiguration config = new PasspointConfiguration();
            String imsi = getIMSI();
            //Set AutojoinEnabled via reflection way
            Method setAutojoinEnabled = config.getClass().getDeclaredMethod("setAutojoinEnabled", boolean.class);
            setAutojoinEnabled.setAccessible(true);
            setAutojoinEnabled.invoke(config, isEnabled);

            HomeSp homeSp = new HomeSp();
            homeSp.setFqdn(getFQDN());
            homeSp.setFriendlyName(SSID);
            config.setHomeSp(homeSp);

            Credential.SimCredential simCred = new Credential.SimCredential();
            simCred.setImsi(imsi);
            simCred.setEapType(EAP_TYPE /* EAP_AKA */);

            Credential cred = new Credential();
            cred.setRealm(getFQDN());
            cred.setSimCredential(simCred);
            config.setCredential(cred);
            Log.d(TAG, "createPasspointConfiguration getIMSI : " + getIMSI());
            return config;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getIMSI() {
        try {
            TelephonyManager telephonyManager = (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
            String imsi = telephonyManager.getSubscriberId();
            if (null == imsi) {
                imsi = "";
            }
            return imsi;
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    private String getFQDN(){
        TelephonyManager telephonyManager = (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
        String mccmnc = telephonyManager.getSimOperatorNumeric();
        Log.d(TAG, "getFQDN: mccmnc = telephonyManager.getSimOperatorNumeric() == " + mccmnc);
        if (TextUtils.isEmpty(mccmnc)) {
            if (!TextUtils.isEmpty(SystemProperties.get("gsm.sim.operator.numeric"))){
                mccmnc = SystemProperties.get("gsm.sim.operator.numeric");
                Log.d(TAG, "gsm.sim.operator.numeric ! = null ,mccmnc == " + mccmnc);
            } else {
                Log.d(TAG, "gsm.sim.operator.numeric == null");
            }
        } else {
            Log.d(TAG, "telephonyManager.getSimOperatorNumeric() != null , default mccmnc == " + mccmnc);
        }
        String fqdn = String.format("wlan.mnc%s.mcc%s.3gppnetwork.org",
                mccmnc.substring(3), mccmnc.substring(0, 3));
        Log.d(TAG, "getFQDN: "+fqdn);
        return fqdn;
    }

    private boolean isNoSimcard(){
        TelephonyManager telMgr = (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
        int simState = telMgr.getSimState();
        switch (simState) {
            case TelephonyManager.SIM_STATE_ABSENT:
            case TelephonyManager.SIM_STATE_UNKNOWN:
            case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
                return true;
        }
        return false;
	}

至此,aosp-wifi(sta上)—— 4大基础客制化功能你掌握了几个? 结束,这些属于比较基础的客制化功能,所以憨憨的宗旨是好记性不如烂笔头,记下了随取随用,对于各位来说,关注,收藏即可,当然,觉得不错或者需要更正的地方也可评论,转发,下期将为大家带来另外的4个wifi基础客制化功能,我是番茄憨憨,同志们,我们下期再见*