赞
踩
本文分析热点信息保存生成的文件位置。
通过本文可以大致了解保存/获取热点信息过程,并且了解热点配置文件生成的具体文件位置。
直接从网上搜索很多都是说这个目录:/data/misc/wifi/softap.conf
但是实际上,我从Android11 上对应的目录搜不到这个文件。所以在Android11 上这个目录肯定是不对的!
先公布一下答案,Android11 中wifi 和热点信息保存的文件位置:
wifi信息保存位置:
/data/misc/apexdata/com.android.wifi/WifiConfigStore.xml
热点信息保存位置:
/data/misc/apexdata/com.android.wifi/WifiConfigStoreSoftAp.xml
通过系统源码全局搜索找到一个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/ 目录就可以。
这个只是源码的提示,具体的还是要分析源码进行确认。这样有利于我们在不用系统版本同样能分析出具体目录。
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);
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对应的路径就可以找到配置信息具体的文件位置了!
先看完热点获取信息流程,后续再分析具体目录文件。
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();
frameworks\base\wifi\java\android\net\wifi\WifiManager.java
public WifiConfiguration getWifiApConfiguration() {
try {
return mService.getWifiApConfiguration();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
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();
}
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 和 热点最终都在这里处理
那么重头戏来了!看懂这个,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(); //数据写入
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 对象即可。
//(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<StoreFile> createFiles(File storeDir, List<Integer> storeFileIds, UserHandle userHandle, boolean shouldEncryptCredentials) { List<StoreFile> 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<StoreFile> createSharedFiles(boolean shouldEncryptCredentials) { return createFiles( Environment.getWifiSharedDirectory(), //重点:路径文件夹 Arrays.asList(STORE_FILE_SHARED_GENERAL, STORE_FILE_SHARED_SOFTAP), //重点:文件名称 UserHandle.ALL, shouldEncryptCredentials); } private static final SparseArray<String> STORE_ID_TO_FILE_NAME = new SparseArray<String>() {{ //就是前面两个是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” 方式找到热点配置信息文件
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();
}
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,具体还有啥,要继续分析。
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信息保存文件和一个热点信息保存文件!
再看看里面文件的数据:
WifiConfigStore.xml 部分内容如下:
console:/data/misc/apexdata/com.android.wifi # cat WifiConfigStore.xml <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <WifiConfigStoreData> <int name="Version" value="3" /> <NetworkList> <Network> <WifiConfiguration> <string name="ConfigKey">"VPN_5G"WPA_PSK</string> //wifi名称 + 密码类型 <string name="SSID">"VPN_5G"</string> //wifi名称 <string name="PreSharedKey">"12345678"</string> wifi密码 。。。 </WifiConfiguration> <IpConfiguration>//静态ip和代理信息 <string name="IpAssignment">DHCP</string> <string name="ProxySettings">NONE</string> </IpConfiguration> </Network> </NetworkList> 。。。 </WifiConfigStoreData> console:/data/misc/apexdata/com.android.wifi #
WifiConfigStoreSoftAp.xml 部分内容如下:
console:/data/misc/apexdata/com.android.wifi # console:/data/misc/apexdata/com.android.wifi # cat WifiConfigStoreSoftAp.xml <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <WifiConfigStoreData> <int name="Version" value="3" /> <SoftAp> <string name="SSID">AndroidAP_6564</string> //热点名称 <int name="ApBand" value="2" /> //热点频段 band <int name="Channel" value="36" /> //热点信道 channel <boolean name="HiddenSSID" value="false" /> <int name="SecurityType" value="1" /> //密码类型 <string name="Passphrase">gutir33r</string> //热点密码 <int name="MaxNumberOfClients" value="0" /> <boolean name="ClientControlByUser" value="false" /> <boolean name="AutoShutdownEnabled" value="true" /> //是否未使用自动关闭 <long name="ShutdownTimeoutMillis" value="0" /> //设置多久未使用自动关闭热点,未设置就是5分钟 <BlockedClientList /> <AllowedClientList /> </SoftAp> </WifiConfigStoreData> console:/data/misc/apexdata/com.android.wifi #
(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
热点的配置在SoftApManager.startSoftAp() 会有一定的修改,
比如channel ==0 的情况是会在ApConfigUtil.java中,对应的band范围内随机生成一个channel值。
frameworks\opt\net\wifi\service\java\com\android\server\wifi\util\ApConfigUtil.java
所以热点配置有变化,需要分析的生活,可以在SoftApManager 中多添加日志即可。
热点信息在底层逻辑也是会保存一个配置文件,这个配置文件是底层生成的,具体逻辑不清楚,没怎么开发过驱动逻辑。
/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()和相关代码 里面。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。