AOSP编译保姆级教程(9.0)(二)
上次我们记录了一下源码同步和编译以及增加部分内容的方式,本次我们再来看下设备信息的获取。
Android提供了很多API用于获取设备信息,目前个人信息合规方面此类行为也是一个难点和重点,本文就针对可以获取设备信息的安卓API进行分析,不一定特别全面,欢迎大家一起探讨。
由于谷歌在Android 10.0之后对不可重置的标识符(包括 IMEI 和序列号)添加了限制,所以在考虑App获取设备信息行为方面需要考虑更低一些的版本,所以以下源码皆基于Android 9.0。
ANDROID_ID
android.provider.Settings
中有多个内部类,其中$System
、$Global
、$Secure
它们中的getSring方法可以用来获取Android_id
getString(resolver,"android_id")
源码中的方法如下:
public static String getString(ContentResolver resolver, String name) {
return getStringForUser(resolver, name, resolver.getUserId());
}
getString
在所有的类中都相同,不过各自调用的getStringForUser会有所不同,以下为$Secure
中的getStringForUser
:
/** @hide */
public static String getStringForUser(ContentResolver resolver, String name,
int userHandle) {
if (MOVED_TO_GLOBAL.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure"
+ " to android.provider.Settings.Global.");
return Global.getStringForUser(resolver, name, userHandle);
}
if (MOVED_TO_LOCK_SETTINGS.contains(name)) {
synchronized (Secure.class) {
if (sLockSettings == null) {
sLockSettings = ILockSettings.Stub.asInterface(
(IBinder) ServiceManager.getService("lock_settings"));
sIsSystemProcess = Process.myUid() == Process.SYSTEM_UID;
}
}
if (sLockSettings != null && !sIsSystemProcess) {
// No context; use the ActivityThread's context as an approximation for
// determining the target API level.
Application application = ActivityThread.currentApplication();
boolean isPreMnc = application != null
&& application.getApplicationInfo() != null
&& application.getApplicationInfo().targetSdkVersion
<= VERSION_CODES.LOLLIPOP_MR1;
if (isPreMnc) {
try {
return sLockSettings.getString(name, "0", userHandle);
} catch (RemoteException re) {
// Fall through
}
} else {
throw new SecurityException("Settings.Secure." + name
+ " is deprecated and no longer accessible."
+ " See API documentation for potential replacements.");
}
}
}
return sNameValueCache.getStringForUser(resolver, name, userHandle);
}
以下为$Global
中的getStrin
和getStringForUser
:
public static String getString(ContentResolver resolver, String name) {
Log.cfcalog("Settings$Global.getString##param:"+name);
return getStringForUser(resolver, name, resolver.getUserId());
}
/** @hide */
public static String getStringForUser(ContentResolver resolver, String name,
int userHandle) {
if (MOVED_TO_SECURE.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global"
+ " to android.provider.Settings.Secure, returning read-only value.");
return Secure.getStringForUser(resolver, name, userHandle);
}
return sNameValueCache.getStringForUser(resolver, name, userHandle);
}
可以看到getStringForUser存在一个判断,对于在MOVED_TO_SECURE
中的name
打印一个warning,提示就是已经移动到了Settings.Secure
,而且从源码中可以看到MOVED_TO_SECURE
中包含了ANDROID_ID
:
private static final HashSet<String> MOVED_TO_SECURE;
static {
MOVED_TO_SECURE = new HashSet<>(30);
MOVED_TO_SECURE.add(Secure.ANDROID_ID);
// ....
}
另外需要注意的是Android_id与Apk的签名是有关的:在Android 8.0(API级别26)和更高版本的平台上,Android_id是一个64位数字(表示为十六进制字符串),对于应用签名密钥、用户和设备的每个组合都是唯一的。ANDROID_ID的值由签名密钥和用户确定范围。如果在设备上执行重置、重装系统、刷机或APK签名密钥更改,则该值可能会更改。在低于Android 8.0(API级别26)的平台版本中,Android_id也是一个64位数字(表示为十六进制字符串),在用户首次设置设备时随机生成,在用户设备的整个生命周期内应保持不变。
下图即为同一个App不同签名时获取的Android_id,一个为46xx..一个为0fxxx...
MAC地址
Mac地址目前有如下几种获取方式
DevicePolicyManager
android.app.admin.DevicePolicyManager
的``getWifiMacAddress` 设备所有者可以获取mac地址,其他用户不可以,详见设备管理概览 | Android 开发者 | Android Developers (google.cn)此方法为Android 7.0引入。
源码如下:
public @Nullable String getWifiMacAddress(@NonNull ComponentName admin) {
throwIfParentInstance("getWifiMacAddress");
try {
return mService.getWifiMacAddress(admin);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
NetworkInterface
java.net.NetworkInterface
的getHardwareAddress
源码如下:
public byte[] getHardwareAddress() throws SocketException {
// BEGIN Android-changed: Fix upstream not returning link-down interfaces. http://b/26238832
/*
for (InetAddress addr : addrs) {
if (addr instanceof Inet4Address) {
return getMacAddr0(((Inet4Address)addr).getAddress(), name, index);
}
}
return getMacAddr0(null, name, index);
*/
NetworkInterface ni = getByName(name);
if (ni == null) {
throw new SocketException("NetworkInterface doesn't exist anymore");
}
return ni.hardwareAddr;
// END Android-changed: Fix upstream not returning link-down interfaces. http://b/26238832
}
WifiInfo
android.net.wifi.WifiInfo
的getMacAddress
,此方法在6.0之前可用,6.0之后应该都返回”02:00:00:00:00:00“,详见Android 6.0 变更 | Android 开发者 | Android Developers (google.cn)
源码如下:
public String getMacAddress() {
return mMacAddress;
}
其他
另外还可以通过其他方式获取Mac地址,如分析NetworkInterface.getNetworkInterfaces()
和读取/sys/class/net/wlan0/address
下图为以上方法获取到的mac地址:
Android10
Android10以后默认随机分配Mac地址,以下为官网描述:
默认情况下,在搭载 Android 10 或更高版本的设备上,系统会传输随机分配的 MAC 地址。
如果您的应用处理企业使用场景,平台会提供 API,用于执行与 MAC 地址相关的几个操作。
-
获取随机分配的 MAC 地址:设备所有者应用和资料所有者应用可以通过调用
getRandomizedMacAddress()
检索分配给特定网络的随机分配 MAC 地址。 -
获取实际的出厂 MAC 地址:设备所有者应用可以通过调用
getWifiMacAddress())
检索设备的实际硬件 MAC 地址。此方法对于跟踪设备队列非常有用。
对 /proc/net 文件系统的访问权限实施了限制
在搭载 Android 10 或更高版本的设备上,应用无法访问 /proc/net
,其中包含与设备的网络状态相关的信息。需要访问这些信息的应用(如 VPN)应使用 [NetworkStatsManager
或 ConnectivityManager
类。
IMEI
android.telephony.TelephonyManager
有两个方法,getDeviceId
和getIMEI
源码如下:
public String getDeviceId() {
try {
ITelephony telephony = getITelephony();
if (telephony == null)
return null;
return telephony.getDeviceId(mContext.getOpPackageName());
} catch (RemoteException ex) {
return null;
} catch (NullPointerException ex) {
return null;
}
}
public String getImei(int slotIndex) {
ITelephony telephony = getITelephony();
if (telephony == null) return null;
try {
return telephony.getImeiForSlot(slotIndex, getOpPackageName());
} catch (RemoteException ex) {
return null;
} catch (NullPointerException ex) {
return null;
}
}
以下为通过以上方法获取的IMEI:
Android10(API 级别 29)以下(不包含10)获取IMEI需要android.permission.READ_PHONE_STATE
权限。
Android 10(API 级别 29)对不可重置的标识符(包括 IMEI 和序列号)添加了限制。您的应用必须是设备或个人资料所有者应用,具有特殊运营商权限或具有 READ_PRIVILEGED_PHONE_STATE
特许权限,才能访问这些标识符。
这也就意味着,第三方应用在安卓10以后一般是无法获取IMEI的,如果需要监听某些App的实际行为的话,需要关注这一点,目前的App和SDK都已做过适配,不会在Android10以上的设备上尝试访问以上方法。
硬件序列号(serial number)
android.os.Build
中的getSerial
可以获取设备的硬件序列号信息
源码如下:
@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
public static String getSerial() {
Log.cfcalog("Build.getSerial");
IDeviceIdentifiersPolicyService service = IDeviceIdentifiersPolicyService.Stub
.asInterface(ServiceManager.getService(Context.DEVICE_IDENTIFIERS_SERVICE));
try {
return service.getSerial();
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
return UNKNOWN;
}
以下为通过以上方法获取的SN:
此处需要注意的和IMEI相同,此方法已在安卓10被限制使用,第三方应用无法正常调用,详见唯一标识符最佳做法 | Android 开发者 | Android Developers