Android Q 基站刷新接口源码分析 & 适配双卡手机基站刷新逻辑
Posted Alex_MaHao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Q 基站刷新接口源码分析 & 适配双卡手机基站刷新逻辑相关的知识,希望对你有一定的参考价值。
目录
一、获取基站信息的两个关键方法
// 获取所有的基站信息列表
// 在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);
// 0 对应卡槽0 所对应的系统中的subId,1 对应卡槽1
int[] subIds0 = subscriptionManager.getSubscriptionIds(0);
int[] subIds1 = subscriptionManager.getSubscriptionIds(1);
// 判断是否有效,如果有效,取返回的数组中的第一位,为subId
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;
// 获取系统的TelphoneManager对象 系统的telehonyM
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
// 卡槽0 对应的TelephonyManager
TelephonyManager tm0 = telephonyManager.createForSubscriptionId(subId0);
// 卡槽1 对应的TelephonyManager
TelephonyManager tm1 = telephonyManager.createForSubscriptionId(subId1);
//.... 调用对应的requestCellInfoUpdate即可
根据如上逻辑,即可达到刷新双卡基站信息的逻辑,通过卡槽获取subId,根据subId创建对应的TelephoneManager。
在这里存在一个疑问,通过context获取系统的TelephonyManager和哪个卡槽对应?这个是不一定的,取决于系统的设置。可以通过subscriptionManager.getDefaultSubscriptionId获取当前默认的subId,和卡槽对应的subId比对可知。
三、getAllCellInfo方法源码流程
源码总结:具体细分见下:
应用层
应用层调用getAllCellInfo接口,具体逻辑实现
public List<CellInfo> getAllCellInfo()
try
// Binder机制,会调用到系统进程
ITelephony telephony = getITelephony();
if (telephony == null)
return null;
// 通过跨进程访问系统的Phone相关服务
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<>();
// 判断当前targetSdk版本,如果>=Q,则会获取缓存位置
final int targetSdk = TelephonyPermissions.getTargetSdk(mApp, callingPackage);
if (targetSdk >= android.os.Build.VERSION_CODES.Q)
// 返回缓存的基站信息,最终返回ServiceStateTracker.java的成员变量mLastCellInfoList;
return getCachedCellInfo();
if (DBG_LOC) log("getAllCellInfo: is active user");
// 如果当前targetSdk版本 < Q,则刷新基站信息,返回基站信息
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);
检查权限,如果权限不通过,则返回空
/** Check if location permissions have been granted */
public static LocationPermissionResult checkLocationPermission(
Context context, LocationPermissionQuery query)
// Always allow the phone process, system server, and network stack to access location.
// This avoid breaking legacy code that rely on public-facing APIs to access cell location,
// and it doesn't create an info leak risk because the cell location is stored in the phone
// process anyway, and the system server already has location access.
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;
// Check the system-wide requirements. If the location main switch is off or
// the app's profile isn't in foreground, return a soft denial.
if (!checkSystemLocationAccess(context, query.callingUid, query.callingPid))
return LocationPermissionResult.DENIED_SOFT;
// Do the check for fine, then for coarse.
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;
// At this point, we're out of location checks to do. If the app bypassed all the previous
// ones due to the SDK backwards compatibility schemes, allow it access.
return LocationPermissionResult.ALLOWED;
请求基站刷新,并获取基站结果
private @Nullable Object sendRequest(int command, Object argument, Integer subId, Phone phone,
WorkSource workSource, long timeoutInMs)
// 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)
// Wait for at least timeoutInMs before returning null request result
long now = SystemClock.elapsedRealtime();
long deadline = now + timeoutInMs;
while (request.result == null && now < deadline)
try
request.wait(deadline - now);
catch (InterruptedException e)
// Do nothing, go back and check if request is completed or timeout
finally
now = SystemClock.elapsedRealtime();
else
// Wait for the request to complete
// 等待结果返回
while (request.result == null)
try
request.wait();
catch (InterruptedException e)
// Do nothing, go back and wait until the request is complete
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
public void requestAllCellInfo(WorkSource workSource, Message rspMsg)
// .....
synchronized (mPendingCellInfoRequests)
// 如果当前正在请求刷新基站信息,则保存回调信息,等待刷新结束后通知
if (mIsPendingCellInfoRequest)
if (rspMsg != null) mPendingCellInfoRequests.add(rspMsg);
return;
// 判断是否可以刷新,刷新时间间隔需 > mCellInfoMinIntervalMs
// 该间隔时间:亮屏 & (未连接wifi || 充电中) = 2000ms,其它10000ms
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;
// 请求基站刷新,刷新成功后会回调EVENT_GET_CELL_INFO_LIST
Message msg = obtainMessage(EVENT_GET_CELL_INFO_LIST);
mCi.getCellInfoList(msg, workSource);
// 超时判断,如果超时,则强制返回结果,2000ms
sendMessageDelayed(
obtainMessage(EVENT_GET_CELL_INFO_LIST), CELL_INFO_LIST_QUERY_TIMEOUT);
刷新结束后,基站信息回调:
case EVENT_GET_CELL_INFO_LIST: // fallthrough
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 we receive an empty message, it's probably a timeout; if there is no
// pending request, drop it.
if (!mIsPendingCellInfoRequest) break;
// 判断是否是超时
final long curTime = SystemClock.elapsedRealtime();
if ((curTime - mLastCellInfoReqTime) < CELL_INFO_LIST_QUERY_TIMEOUT)
break;
// We've received a legitimate timeout, so something has gone terribly
// wrong.
loge("Timeout waiting for CellInfo; (everybody panic)!");
// 如果是超时,则将lastCellInfo置为空
mLastCellInfoList = null;
// Since the timeout is applicable, fall through and update all synchronous
// callers with the failure.
synchronized (mPendingCellInfoRequests)
// If we have pending requests, then service them. Note that in case of a
// timeout, we send null responses back to the callers.
if (mIsPendingCellInfoRequest)
// 将最新的基站信息回调给上层业务
mIsPendingCellInfoRequest = false;
for (Message m : mPendingCellInfoRequests)
AsyncResult.forMessage(m, cellInfo, ex);
m.sendToTarget();
mPendingCellInfoRequests.clear()Android Q 基站刷新接口源码分析 & 适配双卡手机基站刷新逻辑
Android Q 基站刷新接口源码分析 & 适配双卡手机基站刷新逻辑
Android Q 基站刷新接口源码分析 & 适配双卡手机基站刷新逻辑