赞
踩
不说废话,直接上代码:
public String getWifiMac() {
String wifiMac = "";
try {
WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
wifiMac = wifi.getConnectionInfo().getMacAddress();
Log.d(TAG, "wifiMac: " + wifiMac);
} catch (Exception e) {
e.printStackTrace();
}
return wifiMac;
}
那么问题来了,这份代码在Android5.1上跑的好好的,在Android8.1上,拿到的却是一个固定地址02:00:00:00:00:00。
估计是Android新特性,不让第三方应用拿MAC地址了。我们阅读源码,证实一下。
frameworks/base/wifi/java/android/net/wifi/WifiManager.java
public WifiInfo getConnectionInfo() {
try {
//这里没做啥事,只是封装了一下,调到服务端去了。
return mService.getConnectionInfo(getContext().getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiServiceImpl.java
@Override public WifiInfo getConnectionInfo(String callingPackage) { enforceAccessPermission();//权限检查 mLog.info("getConnectionInfo uid=%").c(Binder.getCallingUid()).flush(); /* * Make sure we have the latest information, by sending * a status request to the supplicant. */ //继续往下,调WifiStateMachine return mWifiStateMachine.syncRequestConnectionInfo(callingPackage); } //要声明android.permission.ACCESS_WIFI_STATE权限 private void enforceAccessPermission() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, "WifiService"); }
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java
public WifiInfo syncRequestConnectionInfo(String callingPackage) { int uid = Binder.getCallingUid(); WifiInfo result = new WifiInfo(mWifiInfo);// new了一个WifiInfo实例 if (uid == Process.myUid()) return result; boolean hideBssidAndSsid = true; //这里把MacAddress设成了一个默认值,这个值的内容,就是02:00:00:00:00:00 result.setMacAddress(WifiInfo.DEFAULT_MAC_ADDRESS); IPackageManager packageManager = AppGlobals.getPackageManager(); try { if (packageManager.checkUidPermission(Manifest.permission.LOCAL_MAC_ADDRESS, uid) == PackageManager.PERMISSION_GRANTED) { //这里又设置了MacAddress,原因就在这里, //第三方应用过不了这个权限检查,代码走不到这里,所以拿到的是02:00:00:00:00:00 result.setMacAddress(mWifiInfo.getMacAddress()); } final WifiConfiguration currentWifiConfiguration = getCurrentWifiConfiguration(); if (mWifiPermissionsUtil.canAccessFullConnectionInfo( currentWifiConfiguration, callingPackage, uid, Build.VERSION_CODES.O)) { hideBssidAndSsid = false; } } catch (RemoteException e) { Log.e(TAG, "Error checking receiver permission", e); } catch (SecurityException e) { Log.e(TAG, "Security exception checking receiver permission", e); } if (hideBssidAndSsid) { result.setBSSID(WifiInfo.DEFAULT_MAC_ADDRESS); result.setSSID(WifiSsid.createFromHex(null)); } return result; }
看下这个WifiInfo.DEFAULT_MAC_ADDRESS是什么:
frameworks/base/wifi/java/android/net/wifi/WifiInfo.java
/**
* Default MAC address reported to a client that does not have the
* android.permission.LOCAL_MAC_ADDRESS permission.
*
* @hide
*/
public static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00";
源码的注释非常清晰,你没有声明"android.permission.LOCAL_MAC_ADDRESS"这个权限,不按规矩来,那就给个DEFAULT_MAC_ADDRESS 忽悠一下吧!
既然是没权限,那简单,加上就行了。
然而,我在测试应用的AndroidManifest.xml加了权限,再测试,还是不行,这就尴尬了。
继续分析,看下这个权限是个啥东西,为啥加了还不行?
frameworks/base/core/res/AndroidManifest.xml
<!-- @SystemApi Allows applications to read the local WiFi and Bluetooth MAC address.
@hide -->
<permission android:name="android.permission.LOCAL_MAC_ADDRESS"
android:protectionLevel="signature|privileged" />
这个权限的protectionLevel是"signature|privileged",这个目的就很明确了,就是不让第三方应用拿MAC地址,除非你用系统签名。
如果客户说,我同样的代码,在A平台可以,为啥B平台就不行了,都是你们的产品啊!
那就改吧,把权限检查去掉,就OK了。
--- a/code/frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java +++ b/code/frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java @@ -1897,10 +1897,10 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss IPackageManager packageManager = AppGlobals.getPackageManager(); try { - if (packageManager.checkUidPermission(Manifest.permission.LOCAL_MAC_ADDRESS, - uid) == PackageManager.PERMISSION_GRANTED) { +// if (packageManager.checkUidPermission(Manifest.permission.LOCAL_MAC_ADDRESS, +// uid) == PackageManager.PERMISSION_GRANTED) { result.setMacAddress(mWifiInfo.getMacAddress()); - } +// } final WifiConfiguration currentWifiConfiguration = getCurrentWifiConfiguration(); if (mWifiPermissionsUtil.canAccessFullConnectionInfo( currentWifiConfiguration,
再看看Android5.1为啥没问题。调用流程都是一样的,不多说,直接看关键代码。
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java
/**
* Get status information for the current connection, if any.
* @return a {@link WifiInfo} object containing information about the current connection
*
*/
public WifiInfo syncRequestConnectionInfo() {
return mWifiInfo;
}
没有权限检查,直接把mWifiInfo丢回去,你再getMacAddress就OK了。
需要注意的是,如果WIFI没有打开过,可能拿不到MAC地址(取决于wifi模组),就是说要Supplicant跑起来,MAC才会传上来,这里就不深究了。
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java
@Override
public boolean processMessage(Message message) {
logStateAndMessage(message, getClass().getSimpleName());
switch(message.what) {
case WifiMonitor.SUP_CONNECTION_EVENT:
if (DBG) log("Supplicant connection established");
setWifiState(WIFI_STATE_ENABLED);
//... ...省略部分代码
mWifiInfo.setMacAddress(mWifiNative.getMacAddress());
应用代码:
public String getBlueToothMac() {
String btMac = "";
try {
BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
btMac = manager.getAdapter().getAddress();
Log.d(TAG, "btMac: " + btMac);
} catch (Exception e) {
e.printStackTrace();
}
return btMac;
}
同Wifi一样,这个代码在Android8.1上运行,也是拿到固定地址02:00:00:00:00:00。
下面分析源码。
frameworks/base/core/java/android/bluetooth/BluetoothManager.java
/**
* Get the default BLUETOOTH Adapter for this device.
*
* @return the default BLUETOOTH Adapter
*/
public BluetoothAdapter getAdapter() {
return mAdapter;//返回BluetoothAdapter 实例
}
frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java
/**
* Returns the hardware address of the local Bluetooth adapter.
* <p>For example, "00:11:22:AA:BB:CC".
*
* @return Bluetooth hardware address as string
*/
@RequiresPermission(Manifest.permission.BLUETOOTH) //需要“android.permission.BLUETOOTH”权限
public String getAddress() {
try {
return mManagerService.getAddress(); //调服务端的方法
} catch (RemoteException e) {Log.e(TAG, "", e);}
return null;
}
frameworks/base/services/core/java/com/android/server/BluetoothManagerService.java
public String getAddress() { //定义:private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) { Slog.w(TAG,"getAddress(): not allowed for non-active and non system user"); return null; } //这个权限检查跟wifi一样,如果没有“android.permission.LOCAL_MAC_ADDRESS”,就返回默认地址 if (mContext.checkCallingOrSelfPermission(Manifest.permission.LOCAL_MAC_ADDRESS) != PackageManager.PERMISSION_GRANTED) { return BluetoothAdapter.DEFAULT_MAC_ADDRESS; } try { mBluetoothLock.readLock().lock(); if (mBluetooth != null) return mBluetooth.getAddress(); } catch (RemoteException e) { Slog.e(TAG, "getAddress(): Unable to retrieve address remotely. Returning cached address", e); } finally { mBluetoothLock.readLock().unlock(); } // mAddress is accessed from outside. // It is alright without a lock. Here, bluetooth is off, no other thread is // changing mAddress return mAddress; }
跟wifi一个套路,跟着代码流程走一遍就行了,
Android没有提供获取以太网MAC地址的API,但有一个API可以间接实现。
public String getEthernetMac() {
String ethMac = "";
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getNetworkInfo(ConnectivityManager.TYPE_ETHERNET);
if (info != null) {
ethMac = info.getExtraInfo();//这个ExtraInfo就是以太网的mac地址
Log.d(TAG, "ethernet mac = " + ethMac);
} else {
Log.e(TAG, "info is null !");
}
return ethMac;
}
为什么“getExtraInfo”就能拿到以太网的MAC地址?我们继续往下看。
frameworks/base/core/java/android/net/NetworkInfo.java
/** * Report the extra information about the network state, if any was * provided by the lower networking layers. * @return the extra information, or null if not available */ public String getExtraInfo() { synchronized (this) { return mExtraInfo; } } /** * Set the extraInfo field. * @param extraInfo an optional {@code String} providing addditional network state * information passed up from the lower networking layers. * @hide */ public void setExtraInfo(String extraInfo) { synchronized (this) { this.mExtraInfo = extraInfo; } }
这个“getExtraInfo”的本意是“Report the extra information about the network state”,具体是什么信息,取决于底层。
setExtraInfo添加了哪些附加信息?
如上图,
Ethernet是mHwAddr,
Wifi是mWifiInfo.getSSID(),
telephony是mApnSetting.apn
所以,对于以太网而言,getExtraInfo就能取出MAC地址。
如果不改Android源码,而是由应用端来解决这个问题,有没有办法?
直接读节点,也是可行的。
wifi的节点:/sys/class/net/wlan0/address
ethernet的节点:/sys/class/net/eth0/address
Bt没有找到节点。
写一下获取wifi mac地址的代码,ethernet类似。
public String getWifiMacFromNode() { String wifiMac = ""; RandomAccessFile f = null; try { f = new RandomAccessFile("/sys/class/net/wlan0/address", "r"); f.seek(0); wifiMac = f.readLine().trim(); f.close(); Log.d(TAG, "getWifiMacFromNode "+wifiMac); return wifiMac; } catch (FileNotFoundException e) { e.printStackTrace(); return wifiMac; } catch (IOException e) { e.printStackTrace(); return wifiMac; } finally { if (f != null) { try { f.close(); f = null; } catch (IOException e) { e.printStackTrace(); } } } }
其他方法,反射调用Android的API,同时绕过权限检查,应该也是可行的。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。