IMEI是设备唯一性的一个重要指标,这篇文章对IMEI获取做一些分析,以达到以下两个目的:
1、梳理Android源码中获取IMEI流程
2、理解获取IMEI时,源码中权限调用流程
备注:以下源码分析,针对的是Android 6.0.1源码
在Android代码中,我们需要获取设备的IMEI,只需调用下面方法
TelephonyManager telephonyMgr = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
imei = telephonyMgr.getDeviceId();
接下来就看看源码中是怎样获取IMEI的。
/**
* Returns the unique device ID, for example, the IMEI for GSM and the MEID
* or ESN for CDMA phones. Return null if device ID is not available.
*
* Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*/
public String getDeviceId() {
try {
ITelephony telephony = getITelephony();
if (telephony == null)
return null;
return telephony.getDeviceId(mContext.getOpPackageName());
} catch (RemoteException ex) {
......
}
}
在TelephonyManager中,可以看到是先获取ITelephony对象,然后在通过该Interface的getDeviceid()方法来获取IMEI的。ITelephony是一个aidl的接口,所以接下来我们需要找到实现这个接口的对应实现类
/**
* Implementation of the ITelephony interface.
*/
public class PhoneInterfaceManager extends ITelephony.Stub {
......
/**
* Returns the unique device ID of phone, for example, the IMEI for
* GSM and the MEID for CDMA phones. Return null if device ID is not available.
*
* Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*/
@Override
public String getDeviceId(String callingPackage) {
if (!canReadPhoneState(callingPackage, "getDeviceId")) {
return null;
}
final Phone phone = PhoneFactory.getPhone(0);
if (phone != null) {
return phone.getDeviceId();
} else {
return null;
}
}
......
}
首先,通过上面类的声明可以看到,PhoneInterfaceManager这个类实现了ITelephony.stub接口,因此就是这个接口的对应的实现类。接下来我们看看对应实现的getDeviceId()方法。
这个方法首先通过canReadPhoneState()方法来判断权限是否通过,如果没有权限直接就返回null了。权限通过之后才会去获取deviceId。
我们这次分析的目的,一个是权限流程分析,一个是IMEI获取流程分析,这里我们通过canReadPhoneState()方法看看权限相关的判断流程,然后在接着看IMEI的获取流程。
1、权限判断流程
通过上面canReadPhoneState()函数入口,我们看看具体的实现。
private boolean canReadPhoneState(String callingPackage, String message) {
try {
mApp.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, message);
// SKIP checking for run-time permission since caller or self has PRIVILEDGED permission
return true;
} catch (SecurityException e) {
mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE,
message);
}
if (mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
callingPackage) != AppOpsManager.MODE_ALLOWED) {
return false;
}
return true;
}
该函数中,首先在try代码块中尝试获取READ_PRIVILEGED_PHONE_STATE权限,如果有这个权限就直接默认权限通过了。一般这个私有权限是系统的应用有,第三方应用都是没有的,所以会抛出安全异常,走到catch代码块中。
在catch代码块,就是真正的去检查应用是否有对应需要的READ_PHONE_STATE权限了。接下来我们需要看看该方法的具体实现
class ContextImpl extends Context {
......
@Override
public void enforceCallingOrSelfPermission(
String permission, String message) {
enforce(permission,
checkCallingOrSelfPermission(permission),
true,
Binder.getCallingUid(),
message);
}
@Override
public int checkCallingOrSelfPermission(String permission) {
if (permission == null) {
throw new IllegalArgumentException("permission is null");
}
return checkPermission(permission, Binder.getCallingPid(),
Binder.getCallingUid());
}
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
throw new IllegalArgumentException("permission is null");
}
try {
return ActivityManagerNative.getDefault().checkPermission(
permission, pid, uid);
} catch (RemoteException e) {
return PackageManager.PERMISSION_DENIED;
}
}
}
public abstract class ActivityManagerNative extends Binder implements IActivityManager {
......
public int checkPermission(String permission, int pid, int uid)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(permission);
data.writeInt(pid);
data.writeInt(uid);
mRemote.transact(CHECK_PERMISSION_TRANSACTION, data, reply, 0);
reply.readException();
int res = reply.readInt();
data.recycle();
reply.recycle();
return res;
}
......
}
从上面调用可以看到,首先是在ContextImpl中开始经过层层调用,在进程中最终调用到ActivityManagerNative中,通过Parcel传输相关数据,完成一次IPC操作来获取权限是否通过的结果。
上述IPC操作,最终会调用到ActivityManagerService中完成,下面我们看看ActivityMangerService中的执行情况
public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
......
/**
* As the only public entry point for permissions checking, this method
* can enforce the semantic that requesting a check on a null global
* permission is automatically denied. (Internally a null permission
* string is used when calling {@link #checkComponentPermission} in cases
* when only uid-based security is needed.)
*
* This can be called with or without the global lock held.
*/
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
return PackageManager.PERMISSION_DENIED;
}
return checkComponentPermission(permission, pid, uid, -1, true);
}
/**
* This can be called with or without the global lock held.
*/
int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
return ActivityManager.checkComponentPermission(permission, uid,
owningUid, exported);
}
}
public class ActivityManager {
......
/** @hide */
public static int checkComponentPermission(String permission, int uid,
int owningUid, boolean exported) {
// Root, system server get to do everything.
final int appId = UserHandle.getAppId(uid);
if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
return PackageManager.PERMISSION_GRANTED;
}
// Isolated processes don't get any permissions.
if (UserHandle.isIsolated(uid)) {
return PackageManager.PERMISSION_DENIED;
}
// If there is a uid that owns whatever is being accessed, it has
// blanket access to it regardless of the permissions it requires.
if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
return PackageManager.PERMISSION_GRANTED;
}
// If the target is not exported, then nobody else can get to it.
if (!exported) {
/*
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
here);
*/
return PackageManager.PERMISSION_DENIED;
}
if (permission == null) {
return PackageManager.PERMISSION_GRANTED;
}
try {
return AppGlobals.getPackageManager()
.checkUidPermission(permission, uid);
} catch (RemoteException e) {
// Should never happen, but if it does... deny!
Slog.e(TAG, "PackageManager is dead?!?", e);
}
return PackageManager.PERMISSION_DENIED;
}
}
从上面调用可以看到,从ActivityManagerService的方法checkPermission()开始,最终会调用到ActivityManager的checkCompomentPermission()方法,最终完成权限的判断。
从ActivityManager的checkCompomentPermission()方法中一堆的if语句,其实也不难发现,权限的判断,主要还是和appId,uid,pid等参数相关,一般第三方app都是通过PackageManagerService来判断的。
下面是PMS中具体的判断逻辑,这里就不再进一步展开分析了,有兴趣的同学可以继续看看。
public class PackageManagerService extends IPackageManager.Stub {
......
@Override
public int checkUidPermission(String permName, int uid) {
final int userId = UserHandle.getUserId(uid);
if (!sUserManager.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
}
synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj != null) {
final SettingBase ps = (SettingBase) obj;
final PermissionsState permissionsState = ps.getPermissionsState();
if (permissionsState.hasPermission(permName, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
// Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
ArraySet perms = mSystemPermissions.get(uid);
if (perms != null) {
if (perms.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
.contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
return PackageManager.PERMISSION_GRANTED;
}
}
}
}
return PackageManager.PERMISSION_DENIED;
}
}
2、IMEI获取流程
下面我们回到前面PhoneInterfaceManager中的getDeviceId()方法,继续分析获取IMEI的流程。
/**
* Implementation of the ITelephony interface.
*/
public class PhoneInterfaceManager extends ITelephony.Stub {
......
/**
* Returns the unique device ID of phone, for example, the IMEI for
* GSM and the MEID for CDMA phones. Return null if device ID is not available.
*
* Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*/
@Override
public String getDeviceId(String callingPackage) {
if (!canReadPhoneState(callingPackage, "getDeviceId")) {
return null;
}
final Phone phone = PhoneFactory.getPhone(0);
if (phone != null) {
return phone.getDeviceId();
} else {
return null;
}
}
......
从上面可以看到,首先是通过PhoneFactory拿到一个Phone对象,Phone是一个Interface,初步的实现类是PhoneBase。
public abstract class PhoneBase extends Handler implements Phone {
}
看到对应类的声明,其实也是一个虚类,在这个类中也没有实现getDeviceId()方法,因此需要继续看看PhoneBase的实现类,发现有这么几个,分别是
CDMAPhone
GSMPhone
ImsPhoneBase
SipPhoneBase
public class CDMAPhone extends PhoneBase {
......
//returns MEID or ESN in CDMA
@Override
public String getDeviceId() {
String id = getMeid();
if ((id == null) || id.matches("^0*$")) {
Rlog.d(LOG_TAG, "getDeviceId(): MEID is not initialized use ESN");
id = getEsn();
}
return id;
}
}
public class GSMPhone extends PhoneBase {
......
@Override
public String getDeviceId() {
return mImei;
}
}
abstract class ImsPhoneBase extends PhoneBase {
......
@Override
public String getDeviceId() {
return null;
}
}
abstract class SipPhoneBase extends PhoneBase {
......
@Override
public String getDeviceId() {
return null;
}
}
可以看出,GSMPhone应该是提供IMEI的类。下面我们具体看看在GSMPhone中,mImei这个全局变量是怎样赋值的。
public class GSMPhone extends PhoneBase {
......
@Override
public void handleMessage (Message msg) {
......
switch (msg.what) {
case EVENT_RADIO_AVAILABLE: {
mCi.getBasebandVersion(
obtainMessage(EVENT_GET_BASEBAND_VERSION_DONE));
mCi.getIMEI(obtainMessage(EVENT_GET_IMEI_DONE));
mCi.getIMEISV(obtainMessage(EVENT_GET_IMEISV_DONE));
mCi.getRadioCapability(obtainMessage(EVENT_GET_RADIO_CAPABILITY));
startLceAfterRadioIsAvailable();
}
break;
......
case EVENT_GET_IMEI_DONE:
ar = (AsyncResult)msg.obj;
if (ar.exception != null) {
break;
}
mImei = (String)ar.result;
break;
......
}
}
通过上面代码可以看到,在handleMessage()方法中,收到EVENT_GET_IMEI_DONE消息后,就得到了IMEI。进一步发现,上面有一行代码
mCi.getIMEI(obtainMessage(EVENT_GET_IMEI_DONE));
发送了这个消息出去,于是我们继续看看前面的调用mCi是什么。
mCi是GSMPhone基类PhoneBase中的一个全局变量,其类型为CommandsInterface,为一个Interface,接下来就看看这个Interface的实现,这里就举出所有实现类型了,实现这个Interface的是RIL这个类
public final class RIL extends BaseCommands implements CommandsInterface {
......
@Override
public void
getIMEI(Message result) {
RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_IMEI, result);
if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
send(rr);
}
private void
send(RILRequest rr) {
Message msg;
if (mSocket == null) {
rr.onError(RADIO_NOT_AVAILABLE, null);
rr.release();
return;
}
msg = mSender.obtainMessage(EVENT_SEND, rr);
acquireWakeLock();
msg.sendToTarget();
}
}
看到上面代码,在RIL类中,将获取IMEI的消息进行一些包装,然后在send()方法中通过mSender对象发送出去了。mSender是什么了?其实也在RIL这个类中,是个内部类,叫RILSender
class RILSender extends Handler implements Runnable {
......
@Override public void
handleMessage(Message msg) {
......
switch (msg.what) {
case EVENT_SEND:
try {
LocalSocket s;
s = mSocket;
if (s == null) {
rr.onError(RADIO_NOT_AVAILABLE, null);
rr.release();
decrementWakeLock();
return;
}
synchronized (mRequestList) {
mRequestList.append(rr.mSerial, rr);
}
byte[] data;
data = rr.mParcel.marshall();
rr.mParcel.recycle();
rr.mParcel = null;
if (data.length > RIL_MAX_COMMAND_BYTES) {
throw new RuntimeException(
"Parcel larger than max bytes allowed! "
+ data.length);
}
// parcel length in big endian
dataLength[0] = dataLength[1] = 0;
dataLength[2] = (byte)((data.length >> 8) & 0xff);
dataLength[3] = (byte)((data.length) & 0xff);
//Rlog.v(RILJ_LOG_TAG, "writing packet: " + data.length + " bytes");
s.getOutputStream().write(dataLength);
s.getOutputStream().write(data);
} catch {
......
}
}
}
熟悉电话框架这块的同学应该比较了解,电话这块,其实也是一个RILSender和一个RILReceiver,分别用于发送和接收。两个分别运行在不同线程,全双工工通信,处理RILRequest和RILResponse。
这里不展开讲Android的电话框架,直接进入处理部分,看看上面发送的消息,是怎样被处理的。
/**
* Call from RIL to us to make a RIL_REQUEST
*
* Must be completed with a call to RIL_onRequestComplete()
*
* RIL_onRequestComplete() may be called from any thread, before or after
* this function returns.
*
* Will always be called from the same thread, so returning here implies
* that the radio is ready to process another command (whether or not
* the previous command has completed).
*/
static void
onRequest (int request, void *data, size_t datalen, RIL_Token t) {
......
case RIL_REQUEST_GET_IMEI:
p_response = NULL;
err = at_send_command_numeric("AT+CGSN", &p_response);
if (err < 0 || p_response->success == 0) {
RIL_onRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
} else {
RIL_onRequestComplete(t, RIL_E_SUCCESS,
p_response->p_intermediates->line, sizeof(char *));
}
at_response_free(p_response);
break;
......
}
-------------------------------------------------------------------------------
int at_send_command_numeric (const char *command,
ATResponse **pp_outResponse)
{
int err;
err = at_send_command_full (command, NUMERIC, NULL,
NULL, 0, pp_outResponse);
......
return err;
}
-------------------------------------------------------------------------------
static int at_send_command_full (const char *command, ATCommandType type,
const char *responsePrefix, const char *smspdu,
long long timeoutMsec, ATResponse **pp_outResponse)
{
int err;
......
err = at_send_command_full_nolock(command, type,
responsePrefix, smspdu,
timeoutMsec, pp_outResponse);
......
return err;
}
-------------------------------------------------------------------------------
/**
* Internal send_command implementation
* Doesn't lock or call the timeout callback
*
* timeoutMsec == 0 means infinite timeout
*/
static int at_send_command_full_nolock (const char *command, ATCommandType type,
const char *responsePrefix, const char *smspdu,
long long timeoutMsec, ATResponse **pp_outResponse)
{
int err = 0;
......
err = writeline (command);
s_type = type;
s_responsePrefix = responsePrefix;
s_smsPDU = smspdu;
sp_response = at_response_new();
while (sp_response->finalResponse == NULL && s_readerClosed == 0) {
if (timeoutMsec != 0) {
......
} else {
err = pthread_cond_wait(&s_commandcond, &s_commandmutex);
}
......
}
......
err = 0;
error:
clearPendingCommand();
return err;
}
上面代码比较多,主要是与硬件层打交道,将获取IMEI的命令写到硬件层,然后等待结果。这样子,通过写命令到硬件层,然后等待对应的IMEI数据被写到对应申请的内存地址后,就可以返回了。
接着底层通过相应的IPC通道将数据返回给调用进程,调用进程读取对应的数据,就拿到了IMEI了。