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

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;
            
            // 判断是否可以刷新,刷新时间间隔需 >  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 基站刷新接口源码分析 & 适配双卡手机基站刷新逻辑

基于基站网络定位的源码分析

Android10Q/11R/12S Service bindService系统源码分析1-千里马framework

Android10Q/11R/12S Service bindService系统源码分析1-千里马framework