一、获取基站信息的两个关键方法
// 获取所有的基站信息列表
// 在targetSDK < Q时,主动请求基站信息刷新并返回,
// >Q 需要调用下面的方法请求刷新
public List<CellInfo> getAllCellInfo()
// Android Q 新增,请求基站刷新
public void requestCellInfoUpdate(
@NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback)
getAllCellInfo调用流程总结
1.通过TelephoneyManager.getAllCellInfo方法获取所有基站信息。
2.该方法会调用到TelephonyManager在系统层的对应接口类PhoneInterfaceManager的getAllCellInfo。
3.PhoneInterfaceManager.getAllCellInfo中首先判断是否有位置权限,如果没有权限则根据情况抛出异常或者返回空。
4.PhoneInterfaceManager.getAllCellInfo继续判断如果当前targetSdk > ,循环Phone列表,调用每一个Phone.getAllCellInfo(),最终调用ServiceStateTracker的成员变量mLastCellInfoList,返回缓存基站信息。否则,循环当前的Phone列表,分别调用Phone.requestCellInfoUpdate方法
5.Phone.requestCellInfoUpdate方法最终的响应类为ServiceStateTracker.requestAllCellInfo
6.ServiceStateTracker.requestAllCellInfo中首先判断当前刷新时间的间隔,如果刷新时间间隔小于一定阈值,则直接返回缓存结果,不再刷新。刷新时间间隔为(亮屏 & (未连接wifi || 充电中) = 2000ms,其它10000ms)。
7.如果符合间隔,则调用和硬件对接的接口,刷新基站信息,同时通过handler等待结果回调,并设置超时时间2000ms。
8.如果超时或者刷新接口回调刷新结果,则回调基站信息,如果超时或者刷新异常,则返回空基站信息。
9.上4中,最终所有的Phone刷新结束,组装所有Phone的基站信息结果并返回。
具体源码分析见下第三部分
requestCellInfoUpdate 流程总结
1.通过TelephoneyManager.requestCellInfoUpdate方法请求刷新基站信息并等待回调结果,TelephoneyManager实现中,调用系统层的requestCellInfoUpdate会传入当前TelephonyManager对应的subId,可以理解为对应的手机卡槽中的某一张卡。
2.该方法会调用到TelephonyManager在系统层的对应接口类PhoneInterfaceManager的requestCellInfoUpdate,并最终调用到requestCellInfoUpdateInternal,该方法首先判断是否有位置权限,如果没有权限则根据情况抛出异常或者返回空。其次,通过subId获取到Phone对象,并调用Phone.requestCellInfoUpdate。
3.其余步骤同上getAllCellInfo中5~7。
4.最终根据刷新基站信息结果,如果异常,则回调onError,成功回调onCellInfo。
具体源码分析见下第四部分
问题
根据如上分析,当targetSdk > Q时,系统不会再主动刷新基站信息,需要我们调用requestCellInfoUpdate主动刷新。
但根据源码分析,requestCellInfoUpdate刷新时和 getAllCellInfo & targetSdk < Q时,刷新逻辑存在差异。requestCellInfoUpdate刷新时,因传入了subId,只会刷新subId对应的Phone对象,而getAllCellInfo刷新时,会调用所有Phone的刷新基站信息接口。
按照该源码表现,requestCellInfoUpdate仅刷新了一个卡的基站信息,对于双卡手机,差异如下:
1)requestCellInfoUpdate回调的onCellInfo方法中,基站列表信息少于getAllCellInfo所获取基站信息。
2)requestCellInfoUpdate刷新时,仅刷新了单卡的基站信息,如果调用requestCellInfoUpdate刷新基站后,立即通过getAllCellInfo获取,会发现部分基站信息的刷新为缓存时间。
二、双卡手机适配 Android Q requestCellInfoUpdate接口
根据以上分析,核心问题是如何找到第二张卡以及对应的TelephonyManager对象,具体逻辑如下:
SubscriptionManager subscriptionManager = (SubscriptionManager) mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
int[] subIds0 = subscriptionManager.getSubscriptionIds(0);
int[] subIds1 = subscriptionManager.getSubscriptionIds(1);
int subId0 = subIds0 != null && subIds0.length > 0 ? subIds0[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
int subId1 = subIds1 != null && subIds1.length > 0 ? subIds1[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
TelephonyManager tm0 = telephonyManager.createForSubscriptionId(subId0);
TelephonyManager tm1 = telephonyManager.createForSubscriptionId(subId1);
根据如上逻辑,即可达到刷新双卡基站信息的逻辑,通过卡槽获取subId,根据subId创建对应的TelephoneManager。
在这里存在一个疑问,通过context获取系统的TelephonyManager和哪个卡槽对应?这个是不一定的,取决于系统的设置。可以通过subscriptionManager.getDefaultSubscriptionId获取当前默认的subId,和卡槽对应的subId比对可知。
三、getAllCellInfo方法源码流程
源码总结:具体细分见下:
应用层
应用层调用getAllCellInfo接口,具体逻辑实现
public List<CellInfo> getAllCellInfo() {
try {
ITelephony telephony = getITelephony();
if (telephony == null)
return null;
return telephony.getAllCellInfo(getOpPackageName(), getAttributionTag());
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
}
return null;
}
获取系统的基站信息,需要和系统的进程通信,那么根据跨进程相关的aidl逻辑,系统进程肯定存在一个响应的接口,该接口为packages/services/Telephony/src/com/android/phone/PhoneInterfaceManager.java
系统层
PhoneInterfaceManager.java
@Override
public List<CellInfo> getAllCellInfo(String callingPackage, String callingFeatureId) {
mApp.getSystemService(AppOpsManager.class)
.checkPackage(Binder.getCallingUid(), callingPackage);
LocationAccessPolicy.LocationPermissionResult locationResult =
LocationAccessPolicy.checkLocationPermission(mApp,
new LocationAccessPolicy.LocationPermissionQuery.Builder()
.setCallingPackage(callingPackage)
.setCallingFeatureId(callingFeatureId)
.setCallingPid(Binder.getCallingPid())
.setCallingUid(Binder.getCallingUid())
.setMethod("getAllCellInfo")
.setMinSdkVersionForCoarse(Build.VERSION_CODES.BASE)
.setMinSdkVersionForFine(Build.VERSION_CODES.Q)
.build());
switch (locationResult) {
case DENIED_HARD:
throw new SecurityException("Not allowed to access cell info");
case DENIED_SOFT:
return new ArrayList<>();
}
final int targetSdk = TelephonyPermissions.getTargetSdk(mApp, callingPackage);
if (targetSdk >= android.os.Build.VERSION_CODES.Q) {
return getCachedCellInfo();
}
if (DBG_LOC) log("getAllCellInfo: is active user");
WorkSource workSource = getWorkSource(Binder.getCallingUid());
final long identity = Binder.clearCallingIdentity();
try {
List<CellInfo> cellInfos = new ArrayList<CellInfo>();
for (Phone phone : PhoneFactory.getPhones()) {
final List<CellInfo> info = (List<CellInfo>) sendRequest(
CMD_GET_ALL_CELL_INFO, null, phone, workSource);
if (info != null) cellInfos.addAll(info);
}
return cellInfos;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
检查权限,如果权限不通过,则返回空
public static LocationPermissionResult checkLocationPermission(
Context context, LocationPermissionQuery query) {
if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID
|| query.callingUid == Process.NETWORK_STACK_UID
|| query.callingUid == Process.ROOT_UID) {
return LocationPermissionResult.ALLOWED;
}
if (!checkSystemLocationAccess(context, query.callingUid, query.callingPid)) {
return LocationPermissionResult.DENIED_SOFT;
}
if (query.minSdkVersionForFine < Integer.MAX_VALUE) {
LocationPermissionResult resultForFine = checkAppLocationPermissionHelper(
context, query, Manifest.permission.ACCESS_FINE_LOCATION);
if (resultForFine != null) {
return resultForFine;
}
}
if (query.minSdkVersionForCoarse < Integer.MAX_VALUE) {
LocationPermissionResult resultForCoarse = checkAppLocationPermissionHelper(
context, query, Manifest.permission.ACCESS_COARSE_LOCATION);
if (resultForCoarse != null) {
return resultForCoarse;
}
}
return LocationPermissionResult.ALLOWED;
}
请求基站刷新,并获取基站结果
private @Nullable Object sendRequest(int command, Object argument, Integer subId, Phone phone,
WorkSource workSource, long timeoutInMs) {
if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
throw new RuntimeException("This method will deadlock if called from the main thread.");
}
MainThreadRequest request = null;
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID && phone != null) {
throw new IllegalArgumentException("subId and phone cannot both be specified!");
} else if (phone != null) {
request = new MainThreadRequest(argument, phone, workSource);
} else {
request = new MainThreadRequest(argument, subId, workSource);
}
Message msg = mMainThreadHandler.obtainMessage(command, request);
msg.sendToTarget();
synchronized (request) {
if (timeoutInMs >= 0) {
long now = SystemClock.elapsedRealtime();
long deadline = now + timeoutInMs;
while (request.result == null && now < deadline) {
try {
request.wait(deadline - now);
} catch (InterruptedException e) {
} finally {
now = SystemClock.elapsedRealtime();
}
}
} else {
while (request.result == null) {
try {
request.wait();
} catch (InterruptedException e) {
}
}
}
}
if (request.result == null) {
Log.wtf(LOG_TAG,
"sendRequest: Blocking command timed out. Something has gone terribly wrong.");
}
return request.result;
}
通过handler响应事件处理处理
case CMD_GET_ALL_CELL_INFO:
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_GET_ALL_CELL_INFO_DONE, request);
request.phone.requestCellInfoUpdate(request.workSource, onCompleted);
break;
case EVENT_GET_ALL_CELL_INFO_DONE:
ar = (AsyncResult) msg.obj;
request = (MainThreadRequest) ar.userObj;
request.result = (ar.exception == null && ar.result != null)
? ar.result : new ArrayList<CellInfo>();
synchronized (request) {
request.notifyAll();
}
break;
最终响应的类:ServiceStateTracker.java
https://cs.android.com/android/platform/superproject/+/master:frameworks/opt/telephony/src/java/com/android/internal/telephony/ServiceStateTracker.java;l=1271;drc=master?hl=zh-cn
public void requestAllCellInfo(WorkSource workSource, Message rspMsg) {
synchronized (mPendingCellInfoRequests) {
if (mIsPendingCellInfoRequest) {
if (rspMsg != null) mPendingCellInfoRequests.add(rspMsg);
return;
}
final long curTime = SystemClock.elapsedRealtime();
if ((curTime - mLastCellInfoReqTime) < mCellInfoMinIntervalMs) {
if (rspMsg != null) {
AsyncResult.forMessage(rspMsg, mLastCellInfoList, null);
rspMsg.sendToTarget();
}
return;
}
if (rspMsg != null) mPendingCellInfoRequests.add(rspMsg);
mLastCellInfoReqTime = curTime;
mIsPendingCellInfoRequest = true;
Message msg = obtainMessage(EVENT_GET_CELL_INFO_LIST);
mCi.getCellInfoList(msg, workSource);
sendMessageDelayed(
obtainMessage(EVENT_GET_CELL_INFO_LIST), CELL_INFO_LIST_QUERY_TIMEOUT);
}
}
刷新结束后,基站信息回调:
case EVENT_GET_CELL_INFO_LIST:
case EVENT_UNSOL_CELL_INFO_LIST: {
List<CellInfo> cellInfo = null;
Throwable ex = null;
if (msg.obj != null) {
ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
log("EVENT_GET_CELL_INFO_LIST: error ret null, e=" + ar.exception);
ex = ar.exception;
} else if (ar.result == null) {
loge("Invalid CellInfo result");
} else {
cellInfo = (List<CellInfo>) ar.result;
updateOperatorNameForCellInfo(cellInfo);
mLastCellInfoList = cellInfo;
mPhone.notifyCellInfo(cellInfo);
if (VDBG) {
log("CELL_INFO_LIST: size=" + cellInfo.size() + " list=" + cellInfo);
}
}
} else {
synchronized (mPendingCellInfoRequests) {
if (!mIsPendingCellInfoRequest) break;
final long curTime = SystemClock.elapsedRealtime();
if ((curTime - mLastCellInfoReqTime) < CELL_INFO_LIST_QUERY_TIMEOUT) {
break;
}
loge("Timeout waiting for CellInfo; (everybody panic)!");
mLastCellInfoList = null;
}
}
synchronized (mPendingCellInfoRequests) {
if (mIsPendingCellInfoRequest) {
mIsPendingCellInfoRequest = false;
for (Message m : mPendingCellInfoRequests) {
AsyncResult.forMessage(m, cellInfo, ex);
m.sendToTarget();
}
mPendingCellInfoRequests.clear();
}
}
break;
}
四、requestCellInfoUpdate源码流程分析
应用层:
应用层调用requestCellInfoUpdate接口,具体逻辑实现
public void requestCellInfoUpdate(
@NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) {
try {
ITelephony telephony = getITelephony();
if (telephony == null) return;
telephony.requestCellInfoUpdate(
getSubId(),
new ICellInfoCallback.Stub() {
@Override
public void onCellInfo(List<CellInfo> cellInfo) {
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(() -> callback.onCellInfo(cellInfo));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void onError(int errorCode, String exceptionName, String message) {
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(() -> callback.onError(
errorCode,
createThrowableByClassName(exceptionName, message)));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}, getOpPackageName()
, getAttributionTag());
} catch (RemoteException ex) {
}
}
系统层
PhoneInterfaceManager.java
private void requestCellInfoUpdateInternal(int subId, ICellInfoCallback cb,
String callingPackage, String callingFeatureId, WorkSource workSource) {
mApp.getSystemService(AppOpsManager.class)
.checkPackage(Binder.getCallingUid(), callingPackage);
LocationAccessPolicy.LocationPermissionResult locationResult =
LocationAccessPolicy.checkLocationPermission(mApp,
new LocationAccessPolicy.LocationPermissionQuery.Builder()
.setCallingPackage(callingPackage)
.setCallingFeatureId(callingFeatureId)
.setCallingPid(Binder.getCallingPid())
.setCallingUid(Binder.getCallingUid())
.setMethod("requestCellInfoUpdate")
.setMinSdkVersionForCoarse(Build.VERSION_CODES.BASE)
.setMinSdkVersionForFine(Build.VERSION_CODES.BASE)
.build());
switch (locationResult) {
case DENIED_HARD:
if (TelephonyPermissions
.getTargetSdk(mApp, callingPackage) < Build.VERSION_CODES.Q) {
EventLog.writeEvent(0x534e4554, "154934934", Binder.getCallingUid());
}
throw new SecurityException("Not allowed to access cell info");
case DENIED_SOFT:
if (TelephonyPermissions
.getTargetSdk(mApp, callingPackage) < Build.VERSION_CODES.Q) {
EventLog.writeEvent(0x534e4554, "154934934", Binder.getCallingUid());
}
try {
cb.onCellInfo(new ArrayList<CellInfo>());
} catch (RemoteException re) {
}
return;
}
final Phone phone = getPhoneFromSubId(subId);
if (phone == null) throw new IllegalArgumentException("Invalid Subscription Id: " + subId);
sendRequestAsync(CMD_REQUEST_CELL_INFO_UPDATE, cb, phone, workSource);
}
Handler处理逻辑为
case CMD_REQUEST_CELL_INFO_UPDATE:
request = (MainThreadRequest) msg.obj;
request.phone.requestCellInfoUpdate(request.workSource,
obtainMessage(EVENT_REQUEST_CELL_INFO_UPDATE_DONE, request));
break;
case EVENT_REQUEST_CELL_INFO_UPDATE_DONE:
ar = (AsyncResult) msg.obj;
request = (MainThreadRequest) ar.userObj;
ICellInfoCallback cb = (ICellInfoCallback) request.argument;
try {
if (ar.exception != null) {
Log.e(LOG_TAG, "Exception retrieving CellInfo=" + ar.exception);
cb.onError(
TelephonyManager.CellInfoCallback.ERROR_MODEM_ERROR,
ar.exception.getClass().getName(),
ar.exception.toString());
} else if (ar.result == null) {
Log.w(LOG_TAG, "Timeout Waiting for CellInfo!");
cb.onError(TelephonyManager.CellInfoCallback.ERROR_TIMEOUT, null, null);
} else {
cb.onCellInfo((List<CellInfo>) ar.result);
}
} catch (RemoteException re) {
Log.w(LOG_TAG, "Discarded CellInfo due to Callback RemoteException");
}
break;
刷新基站信息逻辑与第三部分getAllCellInfo逻辑一致,不再分析。
|