Android11 热点配置信息保存分析

Android11 热点配置信息保存分析

Android11 热点配置信息保存分析

文章目录

Android11 热点配置信息保存分析一、Android11 wifi和热点 配置信息保存的文件位置1、wifi和热点保存的实际位置2、wifi和热点保存位置的描述

二、热点配置保存文件位置分析1、热点信息保存流程(1)保存数据的具体代码:(2) 后续流程

2、热点信息保存流程(1)热点配置获取代码(2)热点配置获取WifiManager暴露接口(2)热点配置获取Service实现(2)热点配置获取具体存储类WifiApConfigStore

3、热点信息保存文件分析继续追踪 WifiConfigStore.java 逻辑。还是 WifiConfigStore.java 这个文件!继续追踪。继续追文件定义源码Environment.java:继续追File对象源头,ApexEnvironment.java还要往下追系统源目录 Environment.java

4、热点信息保存文件具体内容(1)wifi信息数据:(2)热点信息数据:

三、其他:1、流程总结:2、配置参数总结3、在底层的生成的热点配置文件

本文分析热点信息保存生成的文件位置。

通过本文可以大致了解保存/获取热点信息过程,并且了解热点配置文件生成的具体文件位置。

直接从网上搜索很多都是说这个目录:/data/misc/wifi/softap.conf

但是实际上,我从Android11 上对应的目录搜不到这个文件。所以在Android11 上这个目录肯定是不对的!

一、Android11 wifi和热点 配置信息保存的文件位置

1、wifi和热点保存的实际位置

先公布一下答案,Android11 中wifi 和热点信息保存的文件位置:

wifi信息保存位置:

/data/misc/apexdata/com.android.wifi/WifiConfigStore.xml

热点信息保存位置:

/data/misc/apexdata/com.android.wifi/WifiConfigStoreSoftAp.xml

2、wifi和热点保存位置的描述

通过系统源码全局搜索找到一个softap.conf 的相关描述:

frameworks\base\wifi\java\android\net\wifi\migration_samples\README.txt

Shared files

============

//wifi信息保存的文件声明

1) WifiConfigStore.xml - General storage for shared configurations. Includes

user's saved Wi-Fi networks.

AOSP Path in Android 10: /data/misc/wifi/WifiConfigStore.xml

AOSP Path in Android 11: /data/misc/apexdata/com.android/wifi/WifiConfigStore.xml

Sample File (in this folder): Shared_WifiConfigStore.xml

//热点信息保存的文件声明

2) WifiConfigStoreSoftAp.xml - Storage for user's softap/tethering configuration.

AOSP Path in Android 10: /data/misc/wifi/softap.conf.

Note: Was key/value format in Android 10. Conversion to XML done in SoftApConfToXmlMigrationUtil.java.

AOSP Path in Android 11: /data/misc/apexdata/com.android/wifi/WifiConfigStore.xml

Sample File (in this folder): Shared_WifiConfigStoreSoftAp.xml

从上面的描述可以看到无论是wifi还是热点的信息配置保存位置都是存在的变动。 wifi信息上面说的没问题,但是热点信息说的就比较不清楚了!

看起来是保存在 /data/misc/apexdata/com.android/wifi/WifiConfigStore.xml ,那不是和wifi信息重名了? 后面加了已经实际名称是:Shared_WifiConfigStoreSoftAp.xml,但是实际源码中分析只有 WifiConfigStoreSoftAp.xml。 所以上面热点这里提示是同热点信息同一个文件夹(in this folder)。

所以我们要看Android11 wifi 和 热点信息的保存配置,查看 /data/misc/apexdata/com.android/wifi/ 目录就可以。

这个只是源码的提示,具体的还是要分析源码进行确认。这样有利于我们在不用系统版本同样能分析出具体目录。

二、热点配置保存文件位置分析

1、热点信息保存流程

(1)保存数据的具体代码:

WifiManager mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);

SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();

configBuilder.setSsid(getActivity().getString(R.string.str_androidap));

configBuilder.setBand(WiFiHotspotSetupDialog.BANd_5GHZ);

SoftApConfiguration config = configBuilder.build();

mWifiManager.setSoftApConfiguration(config);

Android11 不能使用 WifiManager.setWifiApConfiguration(mConfig);

需要使用:WifiManager.setSoftApConfiguration(config);

(2) 后续流程

frameworks\base\wifi\java\android\net\wifi\WifiManager.java

setSoftApConfiguration(SoftApConfiguration softApConfig)

frameworks\opt\net\wifi\service\java\com\android\server\wifi\WifiServiceImpl.java

setSoftApConfiguration(SoftApConfiguration softApConfig, String packageName)

frameworks\opt\net\wifi\service\java\com\android\server\wifi\WifiApConfigStore.java

setApConfiguration(softApConfig)

persistConfigAndTriggerBackupManagerProxy(config)

WifiConfigManager.saveToStore(true);//重点:保存配置信息成本地文件

frameworks\opt\net\wifi\service\java\com\android\server\wifi\WifiConfigManager.java

saveToStore(boolean forceWrite)

mWifiConfigStore.write(forceWrite);

frameworks\opt\net\wifi\service\java\com\android\server\wifi\WifiConfigStore.java //wifi 和 热点最终都在这里处理

write(boolean forceSync)

writeBufferedData();

sharedStoreFile.writeBufferedRawData(); //数据写入

这里找到StoreFile对应的路径就可以找到配置信息具体的文件位置了!

先看完热点获取信息流程,后续再分析具体目录文件。

2、热点信息保存流程

(1)热点配置获取代码

SoftApConfiguration wifiConfig = mWifiManager.getSoftApConfiguration(); //重点

//获取名称

wifiConfig.getSsid()

//获取加密类型

wifiConfig.getSecurityType(); //SECURITY_TYPE_OPEN = 0;SECURITY_TYPE_WPA2_PSK = 1;

//获取密码

wifiConfig.getPassphrase();

//获取band值

wifiConfig.getBand();

//获取channel值

wifiConfig.getChannel();

(2)热点配置获取WifiManager暴露接口

frameworks\base\wifi\java\android\net\wifi\WifiManager.java

public WifiConfiguration getWifiApConfiguration() {

try {

return mService.getWifiApConfiguration();

} catch (RemoteException e) {

throw e.rethrowFromSystemServer();

}

}

(2)热点配置获取Service实现

frameworks\opt\net\wifi\service\java\com\android\server\wifi\WifiServiceImpl.java

public WifiConfiguration getWifiApConfiguration() {

...

return (mWifiThreadRunner.call(mWifiApConfigStore::getApConfiguration,

new SoftApConfiguration.Builder().build())).toWifiConfiguration();

}

(2)热点配置获取具体存储类WifiApConfigStore

frameworks\opt\net\wifi\service\java\com\android\server\wifi\WifiApConfigStore.java

private SoftApConfiguration mPersistentWifiApConfig = null;

public synchronized SoftApConfiguration getApConfiguration() {

if (mPersistentWifiApConfig == null) {

/* Use default configuration. */

Log.d(TAG, "Fallback to use default AP configuration");

persistConfigAndTriggerBackupManagerProxy(getDefaultApConfiguration());

}

SoftApConfiguration sanitizedPersistentconfig =

sanitizePersistentApConfig(mPersistentWifiApConfig);

if (mPersistentWifiApConfig != sanitizedPersistentconfig) {

Log.d(TAG, "persisted config was converted, need to resave it");

persistConfigAndTriggerBackupManagerProxy(sanitizedPersistentconfig);

}

return mPersistentWifiApConfig;

}

//保存配置文件

private void persistConfigAndTriggerBackupManagerProxy(SoftApConfiguration config) {

mPersistentWifiApConfig = config;

mHasNewDataToSerialize = true;

mWifiConfigManager.saveToStore(true);//重点:保存配置信息成本地文件

mBackupManagerProxy.notifyDataChanged();

}

//系统默认配置,如果系统没有会生成保存到本地一次

//名称:AndroidAP_XXXX(XXX为随机数值),

//频段:2.4G Band=2,channel=0,channel后续会随机生成一个

//密码为随机15位字母字符串

private SoftApConfiguration getDefaultApConfiguration() {

SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();

configBuilder.setBand(SoftApConfiguration.BAND_2GHZ);

configBuilder.setSsid(mContext.getResources().getString(

R.string.wifi_tether_configure_ssid_default) + "_" + getRandomIntForDefaultSsid());

if (ApConfigUtil.isWpa3SaeSupported(mContext)) {

configBuilder.setPassphrase(generatePassword(),

SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION);

} else {

configBuilder.setPassphrase(generatePassword(),

SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);

}

return configBuilder.build();

}

从上面代码看出热点配置信息都是从 mPersistentWifiApConfig 返回的。

这个配置信息是在wifiService 启动的时候,会 WifiApConfigStore 并且从本地文件中读取新给 mPersistentWifiApConfig。

如果开机本地文件没用热点配置文件,那么就会 getDefaultApConfiguration() 生成默认配置,并保存到本地文件。

那么具体文件保存到哪里的,就要接着分析 WifiConfigStore.java 里面的具体逻辑了!

frameworks\opt\net\wifi\service\java\com\android\server\wifi\WifiConfigStore.java //wifi 和 热点最终都在这里处理

3、热点信息保存文件分析

那么重头戏来了!看懂这个,wifi配置信息一样能找到。

接上面的逻辑:

frameworks\opt\net\wifi\service\java\com\android\server\wifi\WifiConfigManager.java

saveToStore(boolean forceWrite)

mWifiConfigStore.write(forceWrite);

frameworks\opt\net\wifi\service\java\com\android\server\wifi\WifiConfigStore.java //wifi 和 热点最终都在这里处理

write(boolean forceSync)

writeBufferedData();

sharedStoreFile.writeBufferedRawData(); //数据写入

继续追踪 WifiConfigStore.java 逻辑。

frameworks\opt\net\wifi\service\java\com\android\server\wifi\WifiConfigStore.java

public void write(boolean forceSync)

throws XmlPullParserException, IOException {

boolean hasAnyNewData = false;

// Serialize the provided data and send it to the respective stores. The actual write will

// be performed later depending on the |forceSync| flag .

for (StoreFile sharedStoreFile : mSharedStores) {

if (hasNewDataToSerialize(sharedStoreFile)) {

byte[] sharedDataBytes = serializeData(sharedStoreFile);

sharedStoreFile.storeRawDataToWrite(sharedDataBytes);

hasAnyNewData = true;

}

}

if (mUserStores != null) {

for (StoreFile userStoreFile : mUserStores) {

if (hasNewDataToSerialize(userStoreFile)) {

byte[] userDataBytes = serializeData(userStoreFile);

userStoreFile.storeRawDataToWrite(userDataBytes);

hasAnyNewData = true;

}

}

}

//上面的是否有newData,可以先不管,重点是:writeBufferedData()

if (hasAnyNewData) {

// Every write provides a new snapshot to be persisted, so |forceSync| flag overrides

// any pending buffer writes.

if (forceSync) {

writeBufferedData();

} else {

startBufferedWriteAlarm();

}

} else if (forceSync && mBufferedWritePending) {

// no new data to write, but there is a pending buffered write. So, |forceSync| should

// flush that out.

writeBufferedData();

}

}

//数据写入,这里看不到File的写入,只看到调用 writeBufferedRawData 方法,只能继续跟踪了!

private void writeBufferedData() throws IOException {

stopBufferedWriteAlarm();

long writeStartTime = mClock.getElapsedSinceBootMillis();

for (StoreFile sharedStoreFile : mSharedStores) {

sharedStoreFile.writeBufferedRawData(); //数据写入

}

if (mUserStores != null) {

for (StoreFile userStoreFile : mUserStores) {

userStoreFile.writeBufferedRawData(); //数据写入

}

}

long writeTime = mClock.getElapsedSinceBootMillis() - writeStartTime;

try {

mWifiMetrics.noteWifiConfigStoreWriteDuration(toIntExact(writeTime));

} catch (ArithmeticException e) {

// Silently ignore on any overflow errors.

}

Log.d(TAG, "Writing to stores completed in " + writeTime + " ms.");

}

//内部类哦

public static class StoreFile {

/**

* The store file to be written to.

*/

private final AtomicFile mAtomicFile; //文件路径

/**

* This is an intermediate buffer to store the data to be written.

*/

private byte[] mWriteData; //写入的数据

/**

* Store the file name for setting the file permissions/logging purposes.

*/

private final String mFileName; //保存的文件名称

。。。

public StoreFile(File file, @StoreFileId int fileId,

@NonNull UserHandle userHandle,

@Nullable WifiConfigStoreEncryptionUtil encryptionUtil) {

mAtomicFile = new AtomicFile(file);

mFileName = file.getAbsolutePath();

mFileId = fileId;

mUserHandle = userHandle;

mEncryptionUtil = encryptionUtil;

}

public String getName() {

return mAtomicFile.getBaseFile().getName();

}

public byte[] readRawData() throws IOException {

byte[] bytes = null;

try {

bytes = mAtomicFile.readFully();

} catch (FileNotFoundException e) {

return null;

}

return bytes;

}

public void storeRawDataToWrite(byte[] data) {

mWriteData = data;

}

//重点:这里看到FileOutputStream,继续追踪对应的File,肯定能找到对应的保存文件路径了!

public void writeBufferedRawData() throws IOException {

if (mWriteData == null) return; // No data to write for this file.

// Write the data to the atomic file.

FileOutputStream out = null;

try {

out = mAtomicFile.startWrite();

FileUtils.chmod(mFileName, FILE_MODE);

out.write(mWriteData); //数据写入

mAtomicFile.finishWrite(out);

} catch (IOException e) {

if (out != null) {

mAtomicFile.failWrite(out);

}

throw e;

}

// Reset the pending write data after write.

mWriteData = null;

}

}

从上面代码可以看出找到FileOutputStream 对应的路径,就可以找到保存配置文件的路径了。

这里不用研究AtomicFile的实现,只要研究的new StoreFile 对象传入的 File 对象即可。

还是 WifiConfigStore.java 这个文件!继续追踪。

//(1)查看创建StoreFile对象

private static @Nullable StoreFile createFile(@NonNull File storeDir,

@StoreFileId int fileId, UserHandle userHandle, boolean shouldEncryptCredentials) {

//判断是否存在该文件夹,如果没有就创建,创建不了就返回null

if (!storeDir.exists()) {

if (!storeDir.mkdir()) {

Log.w(TAG, "Could not create store directory " + storeDir);

return null;

}

}

File file = new File(storeDir, STORE_ID_TO_FILE_NAME.get(fileId)); //**重点;路径+文件名**

WifiConfigStoreEncryptionUtil encryptionUtil = null;

if (shouldEncryptCredentials) {

encryptionUtil = new WifiConfigStoreEncryptionUtil(file.getName());

}

return new StoreFile(file, fileId, userHandle, encryptionUtil); // **构造StoreFile

}

//(2)查看获取所有的StoreFile对象

private static @Nullable List createFiles(File storeDir, List storeFileIds,

UserHandle userHandle, boolean shouldEncryptCredentials) {

List storeFiles = new ArrayList<>();

for (int fileId : storeFileIds) { //**所以重点是看storeFileIds里面有几个int值

StoreFile storeFile =

createFile(storeDir, fileId, userHandle, shouldEncryptCredentials);// 执行第一步的创建

if (storeFile == null) {

return null;

}

storeFiles.add(storeFile);

}

return storeFiles;

}

//(3)获取StoreFile对象上一步,这个是暴露的,

//但是发现这里没传入路径,所以文件路径+文件名都是在 WifiConfigStore.java 定义的

public static @NonNull List createSharedFiles(boolean shouldEncryptCredentials) {

return createFiles(

Environment.getWifiSharedDirectory(), //重点:路径文件夹

Arrays.asList(STORE_FILE_SHARED_GENERAL, STORE_FILE_SHARED_SOFTAP), //重点:文件名称

UserHandle.ALL,

shouldEncryptCredentials);

}

private static final SparseArray STORE_ID_TO_FILE_NAME =

new SparseArray() {{

//就是前面两个是wifi和热点的

put(STORE_FILE_SHARED_GENERAL, STORE_FILE_NAME_SHARED_GENERAL); //wifi配置信息

put(STORE_FILE_SHARED_SOFTAP, STORE_FILE_NAME_SHARED_SOFTAP); //热点配置信息

//后面两个不清楚作用

put(STORE_FILE_USER_GENERAL, STORE_FILE_NAME_USER_GENERAL);

put(STORE_FILE_USER_NETWORK_SUGGESTIONS, STORE_FILE_NAME_USER_NETWORK_SUGGESTIONS);

}};

/**

/**

* Config store file for general shared store file.

*/

public static final int STORE_FILE_SHARED_GENERAL = 0;

/**

* Config store file for softap shared store file.

*/

public static final int STORE_FILE_SHARED_SOFTAP = 1;

/**

* Config store file for general user store file.

*/

public static final int STORE_FILE_USER_GENERAL = 2;

/**

* Config store file for network suggestions user store file.

*/

public static final int STORE_FILE_USER_NETWORK_SUGGESTIONS = 3;

/**

* Config store file name for general shared store file.

*/

private static final String STORE_FILE_NAME_SHARED_GENERAL = "WifiConfigStore.xml"; //wifi配置名称

/**

* Config store file name for SoftAp shared store file.

*/

private static final String STORE_FILE_NAME_SHARED_SOFTAP = "WifiConfigStoreSoftAp.xml"; //热点配置名称

/**

* Config store file name for general user store file.

*/

private static final String STORE_FILE_NAME_USER_GENERAL = "WifiConfigStore.xml";

/**

* Config store file name for network suggestions user store file.

*/

private static final String STORE_FILE_NAME_USER_NETWORK_SUGGESTIONS =

"WifiConfigStoreNetworkSuggestions.xml";

上面已经知道了wifi和热点文件名称, 可以使用find . -name “WifiConfigStoreSoftAp.xml” 方式找到热点配置信息文件

继续追文件定义源码Environment.java:

frameworks\opt\net\wifi\service\java\com\android\server\wifi\util\Environment.java

/**

* Wifi apex name.

*/

private static final String WIFI_APEX_NAME = "com.android.wifi";

/**

* Wifi shared folder.//这里写的就是wifi文件夹的意思

*/

public static File getWifiSharedDirectory() {

return ApexEnvironment.getApexEnvironment(WIFI_APEX_NAME).getDeviceProtectedDataDir();

}

继续追File对象源头,ApexEnvironment.java

frameworks\base\core\java\android\content\ApexEnvironment.java

private static final String APEX_DATA = "apexdata";

private final String mApexModuleName;

private ApexEnvironment(String apexModuleName) {

mApexModuleName = apexModuleName;

}

public static ApexEnvironment getApexEnvironment(@NonNull String apexModuleName) {

Objects.requireNonNull(apexModuleName, "apexModuleName cannot be null");

//TODO(b/141148175): Check that apexModuleName is an actual APEX name

return new ApexEnvironment(apexModuleName);

}

@NonNull

public File getDeviceProtectedDataDir() {

return Environment.buildPath(

Environment.getDataMiscDirectory(), APEX_DATA, mApexModuleName);

}

从上面的代码可以文件夹路径有:apexdata + com.android.wifi,具体还有啥,要继续分析。

还要往下追系统源目录 Environment.java

frameworks\base\core\java\android\os\Environment.java

private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");

private static final File DIR_ANDROID_DATA = getDirectory(ENV_ANDROID_DATA, "/data"); //就是data目录

/** {@hide} */

public static File getDataMiscDirectory() {

return new File(getDataDirectory(), "misc");

}

/**

* Return the user data directory.

*/

public static File getDataDirectory() {

return DIR_ANDROID_DATA;

}

//文件对象,在base文件夹后面不断加后面参数添加文件夹层级名称

public static File buildPath(File base, String... segments) {

File cur = base;

for (String segment : segments) {

if (cur == null) {

cur = new File(segment);

} else {

cur = new File(cur, segment);

}

}

return cur;

}

所以最终的路径是: data/misc/apexdata/com.android.wifi

adb shell 使用命令看确认是在这里的!

console:/data/misc/apexdata/com.android.wifi # ls

WifiConfigStore.xml WifiConfigStoreSoftAp.xml

这里看到一个是wifi信息保存文件和一个热点信息保存文件!

4、热点信息保存文件具体内容

再看看里面文件的数据:

(1)wifi信息数据:

WifiConfigStore.xml 部分内容如下:

console:/data/misc/apexdata/com.android.wifi # cat WifiConfigStore.xml

"VPN_5G"WPA_PSK //wifi名称 + 密码类型

"VPN_5G" //wifi名称

"12345678" wifi密码

。。。

//静态ip和代理信息

DHCP

NONE

。。。

console:/data/misc/apexdata/com.android.wifi #

(2)热点信息数据:

WifiConfigStoreSoftAp.xml 部分内容如下:

console:/data/misc/apexdata/com.android.wifi #

console:/data/misc/apexdata/com.android.wifi # cat WifiConfigStoreSoftAp.xml

AndroidAP_6564 //热点名称

//热点频段 band

//热点信道 channel

//密码类型

gutir33r //热点密码

//是否未使用自动关闭

//设置多久未使用自动关闭热点,未设置就是5分钟

console:/data/misc/apexdata/com.android.wifi #

三、其他:

1、流程总结:

(1)ConnectivityManager.startTethering

(2)TetheringManager.startTethering(request, executor, tetheringCallback)

(3)TetheringService.TetheringConnector.startTethering

(4)Tethering.startTethering(request, listener);

//方法名变化,使用null 对象开启热点

(5)WifiManager.startTetheredHotspot(null /* use existing softap config */)

(6)WifiServiceImpl.startTetheredHotspot(@Nullable SoftApConfiguration softApConfig)

//方法名再变化

(7)ActiveModeWarden.startSoftAp(apModeConfig);

(8)ActiveModeManager.start();

ActiveModeManager manager = mWifiInjector.makeSoftApManager(listener, callback, softApConfig);

listener.setActiveModeManager(manager);

manager.start();

ActiveModeManager是接口类,会调用到SoftApManager.start()

(9)SoftApManager.startSoftAp()

(10)WifiNative.startSoftAp(mApInterfaceName, localConfigBuilder.build(), mSoftApListener)

(11)HostapdHal.addAccessPoint(ifaceName, config, listener::onFailure)

(12)根据硬件版本调用不同的接口实现:addAccessPoint_X_X

2、配置参数总结

热点的配置在SoftApManager.startSoftAp() 会有一定的修改, 比如channel ==0 的情况是会在ApConfigUtil.java中,对应的band范围内随机生成一个channel值。 frameworks\opt\net\wifi\service\java\com\android\server\wifi\util\ApConfigUtil.java

所以热点配置有变化,需要分析的生活,可以在SoftApManager 中多添加日志即可。

3、在底层的生成的热点配置文件

热点信息在底层逻辑也是会保存一个配置文件,这个配置文件是底层生成的,具体逻辑不清楚,没怎么开发过驱动逻辑。

/data/vendor/wifi/hostapd/hostapd_ap0.conf

console:/data/vendor/wifi/hostapd # ls

hostapd_ap0.conf sockets

console:/data/vendor/wifi/hostapd # cat hostapd_ap0.conf

interface=ap0

driver=nl80211

ctrl_interface=/data/vendor/wifi/hostapd/ctrl

ssid2=416e64726f696441505f36363636

channel=36

op_class=128

ieee80211n=1

ieee80211ac=1

hw_mode=a

ht_capab=[SHORT-GI-20][SHORT-GI-40][HT40+]

vht_oper_chwidth=1

vht_oper_centr_freq_seg0_idx=42

ignore_broadcast_ssid=0

wowlan_triggers=any

wpa=2

rsn_pairwise=CCMP

wpa_passphrase=123456789

console:/data/vendor/wifi/hostapd #

注意这里底层保存的配置也是在上层生成的,比如上层channel=0,会直接保存到WifiConfigStoreSoftAp.xml, 但是传给底层前,会随机生成一个合适的channel值,传递给底层。底层开启成功就会保存。

channel 变化的具体的逻辑都在 SoftApManager.startSoftAp()和相关代码 里面。

相关阅读