Android7.0 Phone应用源码分析 phone来电流程分析

Posted 蓝斯老师

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android7.0 Phone应用源码分析 phone来电流程分析相关的知识,希望对你有一定的参考价值。

接上篇博文:Android7.0 Phone应用源码分析(一) phone拨号流程分析

今天我们再来分析下Android7.0 的phone的来电流程

incoming

1.1TelephonyFramework

当有来电通知时,首先接收到消息的是Modem层,然后Medoem再上传给RIL层,RIL进程通过sokcet将消息发送给RILJ(framework层的RIL),同样进入RILJ的processResponse方法,根据上一章节去电流程的分析得知,来电属于UnSolicited消息,事件ID是

RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED,看看RILJ里的处理

com.android.internal.telephony.RIL
processUnsolicited (Parcel p, int type) {
        ………………………………
   switch(response) {
        case RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED: ret =  responseVoid(p); 
        break;
        ………………………………
   }
        ………………………………
  switch(response) {
       case RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED:
         if (RILJ_LOGD) unsljLog(response);
             mCallStateRegistrants .notifyRegistrants(new AsyncResult(null, null, null));
         break;
         ………………………………
    }
         ………………………………
}

mCallStateRegistrants是RegistrantList实例,这里用到了观察者模式,mCallStateRegistrants将call状态的变化通知给了所有感兴趣的注册者,BaseCommands提供了相关注册接口

com.android.internal.telephony. registerForCallStateChanged
 @Override
    public void registerForCallStateChanged(Handler h, int what, Object obj) {
        Registrant r = new Registrant (h, what, obj);

        mCallStateRegistrants.add(r);
    }
}

最后找到GsmCdmaCallTracker在创建的时候注册了该事件

com.android.internal.telephony. GsmCdmaCallTracker
public GsmCdmaCallTracker (GsmCdmaPhone phone) {
        this.mPhone = phone;
        mCi = phone.mCi;
        mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
        mCi.registerForOn(this, EVENT_RADIO_AVAILABLE, null);
        mCi.registerForNotAvailable(this, EVENT_RADIO_NOT_AVAILABLE, null);
        ...... ......

}

收到EVENT_CALL_STATE_CHANGE消息后进入pollCallsWhenSafe方法

protected void pollCallsWhenSafe() {
        mNeedsPoll = true;

        if (checkNoOperationsPending()) {
            mLastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT);
            mCi.getCurrentCalls(mLastRelevantPoll);
        }}
}

这里的处理流程跟之前拨号类似,同样是通过RILJ获取当前call状态,收到回应后进入handlePollCalls方法

protected synchronized void handlePollCalls(AsyncResult ar) {
      ………………………………
     if (newRinging != null) {          // 新来电通知
        mPhone.notifyNewRingingConnection(newRinging);
     }
       ………………………………
    updatePhoneState();                // 更新phone状态
       ………………………………
  if (hasNonHangupStateChanged || newRinging != null || hasAnyCallDisconnected)           {
      mPhone.notifyPreciseCallStateChanged();  // 发出call状态变化通知

   }
}

新来电进入phone的notifyNewRingingConnection的方法

com.android.internal.telephony.Phone 
public void notifyNewRingingConnectionP(Connection cn) {
   if (!mIsVoiceCapable)
         return;
    AsyncResult ar = new AsyncResult(null, cn, null);
   mNewRingingConnectionRegistrants.notifyRegistrants(ar);}
}

又是一个观察者模式,最后找到是注册了该事件的监听对象PstnIncomingCallNotifier

1.2TelephonyService

com.android.services.telephony. PstnIncomingCallNotifier
private void registerForNotifications() {
  if (mPhone != null) {
         Log.i(this, "Registering: %s", mPhone);
         mPhone.registerForNewRingingConnection(mHandler, EVENT_NEW_RINGING_CONNECTION, null);
         mPhone.registerForCallWaiting(mHandler, EVENT_CDMA_CALL_WAITING, null);
         mPhone.registerForUnknownConnection(mHandler, EVENT_UNKNOWN_CONNECTION, null);
        }
}

Handler处理消息进入handleNewRingingConnection方法

private void handleNewRingingConnection(AsyncResult asyncResult) {
        Log.d(this, "handleNewRingingConnection");
        Connection connection = (Connection) asyncResult.result;
        if (connection != null) {
            Call call = connection.getCall();

            // Final verification of the ringing state before sending the intent to Telecom.
            if (call != null && call.getState().isRinging()) {
                sendIncomingCallIntent(connection);
            }
        }
 }

获取到call对象以后,最后进入sendIncomingCallIntent

private void sendIncomingCallIntent(Connection connection) {    
       ………………………………
        PhoneAccountHandle handle = findCorrectPhoneAccountHandle();
        if (handle == null) {
            try {
                connection.hangup();
            } catch (CallStateException e) {
                // connection already disconnected. Do nothing
            }
        } else {
           TelecomManager.from(mPhone.getContext()).addNewIncomingCall(handle, extras);
        }
  }

通过aidl接口调用telecomservice的的addNewIncomingCall方法

1.3 TelecomService

TelecomServiceImpl的成员变量mBinderImpl是具体实现类

com.android.server.telecom.TelecomServiceImpl
private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub(){
public void addNewIncomingCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
   ………………………………
   Intent intent = new Intent(TelecomManager.ACTION_INCOMING_CALL);
   intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
   intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, true);
    if (extras != null) { 
           extras.setDefusable(true);
           intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras);
    }
    mCallIntentProcessorAdapter.processIncomingCallIntent(mCallsManager, intent);

}
    ………………………………

}

这里调用到的是CallIntentProcessor 的processIncomingCallIntent方法

com.android.server.telecom. CallIntentProcessor
static void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
       ………………………………
        Log.d(CallIntentProcessor.class,
                "Processing incoming call from connection service [%s]",
                phoneAccountHandle.getComponentName());
        callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras);
}

进入callsmanager的processIncomingCallIntent方法

void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
       ………………………………
        Call call = new Call(
                getNextCallId(),
                mContext,
                this,
                mLock,
                mConnectionServiceRepository,
                mContactsAsyncHelper,
                mCallerInfoAsyncQueryFactory,
                handle,
                null /* gatewayInfo */,
                null /* connectionManagerPhoneAccount */,
                phoneAccountHandle,
                Call.CALL_DIRECTION_INCOMING /* callDirection */,
                false /* forceAttachToExistingConnection */,
                false /* isConference */
        );
        ………………………………
        call.addListener(this);
        call.startCreateConnection(mPhoneAccountRegistrar);
    }

走到这一步,跟之前分析的拨号流程一样,创建了一个call对象,然后调用

startCreateConnection创建连接,根据之前拨号的流程分析最后会进入 ConnectionService的createConnection方法

1.4 TelecommFramework

再把实现代码贴一遍:

private void createConnection(final PhoneAccountHandle callManagerAccount, final String callId, 
             final ConnectionRequest request,  boolean isIncoming,  boolean isUnknown) {
              ………………………………
        Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
          : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
          : onCreateOutgoingConnection(callManagerAccount, request); 
              ………………………………
        mAdapter.handleCreateConnectionComplete(
                callId,
                request,
                new ParcelableConnection(
                        request.getAccountHandle(),
                        connection.getState(),
                        connection.getConnectionCapabilities(),
                        connection.getConnectionProperties(),
                        connection.getAddress(),
                        connection.getAddressPresentation(),
                        connection.getCallerDisplayName(),
                        connection.getCallerDisplayNamePresentation(),
                        connection.getVideoProvider() == null ?
                                null : connection.getVideoProvider().getInterface(),
                        connection.getVideoState(),
                        connection.isRingbackRequested(),
                        connection.getAudioModeIsVoip(),
                        connection.getConnectTimeMillis(),
                        connection.getStatusHints(),
                        connection.getDisconnectCause(),
                        createIdList(connection.getConferenceables()),
                        connection.getExtras(),
                        connection.getUserData()));//MOTO Calling Code - IKPIM-1774 (ftr 33860)
        if (isUnknown) {
            triggerConferenceRecalculate();
        }
}

这里由于是来电,所以调用onCreateIncomingConnection方法,该方法同样返回null,所以具体是由其子类实现的,也就是TelephonyConnectionService

public Connection onCreateIncomingConnection(
            PhoneAccountHandle connectionManagerPhoneAccount,
            ConnectionRequest request) {
       
    
        Connection connection =  createConnectionFor(phone, originalConnection, false /* isOutgoing */,
                        request.getAccountHandle(), request.getTelecomCallId(),
                        request.getAddress());
        if (connection == null) {
            return Connection.createCanceledConnection();
        } else {
            return connection;
        }
  }

最后根据GMS或是CDMA返回对应Connection对象,最后进入ConnectionServiceAdapter处理

android.telecom. ConnectionServiceAdapter
  void handleCreateConnectionComplete(
            String id,
            ConnectionRequest request,
            ParcelableConnection connection) {
        for (IConnectionServiceAdapter adapter : mAdapters) {
            try {
                adapter.handleCreateConnectionComplete(id, request, connection);
            } catch (RemoteException e) {
            }
        }
}

这里的adapter实际上就是ConnectionServiceWrapper的内部类Adapter,需要注意的是之前拨号的时候创建完connection并呼出之后,后续也会走到这个流程里

public void handleCreateConnectionComplete(String callId, ConnectionRequest request,
                ParcelableConnection connection) {
            Log.startSession("CSW.hCCC");
            long token = Binder.clearCallingIdentity();
            try {
                synchronized (mLock) {
                    logIncoming("handleCreateConnectionComplete %s", callId);
                    ConnectionServiceWrapper.this
                       handleCreateConnectionComplete(callId, request, connection);
                }
            } finally {
                Binder.restoreCallingIdentity(token);
                Log.endSession();
            }
}

最后进入handleCreateConnectionComplete方法

private void handleCreateConnectionComplete(
            String callId,
            ConnectionRequest request,
            ParcelableConnection connection) {
        // TODO: Note we are not using parameter "request", which is a side effect of our tacit
        // assumption that we have at most one outgoing connection attempt per ConnectionService.
        // This may not continue to be the case.
        if (connection.getState() == Connection.STATE_DISCONNECTED) {
            // A connection that begins in the DISCONNECTED state is an indication of
            // failure to connect; we handle all failures uniformly
            removeCall(callId, connection.getDisconnectCause());
        } else {
            // Successful connection
            if (mPendingResponses.containsKey(callId)) {
                mPendingResponses.remove(callId).handleCreateConnectionSuccess(mCallIdMapper, connection);
            }
        }
}

这里的mPendingResponses是map容器

private final Map<String, CreateConnectionResponse> mPendingResponses = new HashMap<>();

ConnectionServiceWrapper在调用createConnection的时候会往该容器里添加对象,也就是CreateConnectionProcessor对象

public void handleCreateConnectionSuccess(
            CallIdMapper idMapper,
            ParcelableConnection connection) {
        if (mCallResponse == null) {
            // Nobody is listening for this connection attempt any longer; ask the responsible
            // ConnectionService to tear down any resources associated with the call
            mService.abort(mCall);
        } else {
            // Success -- share the good news and remember that we are no longer interested
            // in hearing about any more attempts
            mCallResponse.handleCreateConnectionSuccess(idMapper, connection);
            mCallResponse = null;
            // If there\'s a timeout running then don\'t clear it. The timeout can be triggered
            // after the call has successfully been created but before it has become active.
        }
}

这个mCallResponse是CreateConnectionProcessor创建的时候引入的,也就是call对象

com.android.server.telecom.Call
   public void handleCreateConnectionSuccess(
            CallIdMapper idMapper,
            ParcelableConnection connection) {
     
        switch (mCallDirection) {
            case CALL_DIRECTION_INCOMING:
      
                for (Listener l : mListeners) {
                    l.onSuccessfulIncomingCall(this);
                }
                break;
            case CALL_DIRECTION_OUTGOING:
                for (Listener l : mListeners) {
                    l.onSuccessfulOutgoingCall(this,
                            getStateFromConnectionState(connection.getState()));
                }
                break;
            case CALL_DIRECTION_UNKNOWN:
                for (Listener l : mListeners) {
                      l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection .getState()));
                }
                break;
        }
    }

这里根据是来电还是去电类型,执行相应回调,监听者会收到通知,来电事件则触发onSuccessfulIncomingCall的回调

1.5 TelecommService

前面提到CallsManager在执行processIncomingCallIntent方法时候会创建call,之后就会给call添加监听,所以最后会回调到CallsManager类

public void onSuccessfulIncomingCall(Call incomingCall) {
        Log.d(this, "onSuccessfulIncomingCall");
        List<IncomingCallFilter.CallFilter> filters = new ArrayList<>();
        filters.add(new DirectToVoicemailCallFilter(mCallerInfoLookupHelper));
        filters.add(new 
AsyncBlockCheckFilter
(mContext, new BlockCheckerAdapter()));
        filters.add(new CallScreeningServiceFilter(mContext, this, mPhoneAccountRegistrar,
                mDefaultDialerManagerAdapter,
                new ParcelableCallUtils.Converter(), mLock));
        new 
IncomingCallFilter
(mContext, this, incomingCall, mLock,
                mTimeoutsAdapter, filters).performFiltering();
}

这里用到了一个迭代器模式,一个来电操作触发这三个对象的处理:

DirectToVoicemailCallFilter,AsyncBlockCheckFilter,CallScreeningServiceFilter

生成一个IncomingCallFilter对象,调用performFiltering方法

com.android.server.telecom.callfiltering. IncomingCallFilter
 public void performFiltering() {
        Log.event(mCall, Log.Events.FILTERING_INITIATED);
        for (CallFilter filter : mFilters) {
            filter.startFilterLookup(mCall, this);
        }
        mHandler.postDelayed(new Runnable("ICF.pFTO") { // performFiltering time-out
            @Override
            public void loggedRun() {
                // synchronized to prevent a race on mResult and to enter into Telecom.
                synchronized (mTelecomLock) {
                    if (mIsPending) {
                        Log.i(IncomingCallFilter.this, "Call filtering has timed out.");
                        Log.event(mCall, Log.Events.FILTERING_TIMED_OUT);
                        mListener.onCallFilteringComplete(mCall, mResult);
                        mIsPending = false;
                    }
                }
            }
   }.prepare(), mTimeoutsAdapter.getCallScreeningTimeoutMillis(mContext.getContentResolver()));
}

他们依次执行startFilterLookup异步查询方法,通过回调方法并将结果CallFilteringResult传回onCallFilteringComplete将CallFilteringResult对象传递回来

public class CallFilteringResult {

    public boolean shouldAllowCall;          // 是否允许通话
    public boolean shouldReject;            // 是否拒接
    public boolean shouldAddToCallLog;     // 是否添加至通话记
    public boolean shouldShowNotification; // 是否显示通知栏消息
   ………………………………
   ………………………………
}

public void onCallFilteringComplete(Call call, CallFilteringResult result) {
        synchronized (mTelecomLock) { // synchronizing to prevent race on mResult
            mNumPendingFilters--;
            mResult = result.combine(mResult);
            if (mNumPendingFilters == 0) {
                mHandler.post(new Runnable("ICF.oCFC") {
                    @Override
                    public void loggedRun() {
                        // synchronized to enter into Telecom.
                        synchronized (mTelecomLock) {
                            if (mIsPending) {
                                mListener.onCallFilteringComplete(mCall, mResult);
                                mIsPending = false;
                            }
                        }
                    }
                }.prepare());
            }
        }
    }

先看看DirectToVoicemailCallFilter对象,它处理的是voicemail相关信息,

实际上是由CallerInfoLookupHelper完成查询的,内部调用CallerInfoAsyncQueryFactory的startQuery方法,而CallerInfoAsyncQueryFactory是个接口类,在CallsManager创建的时候由外部传参进来,最后找到是TelecomService的initializeTelecomSystem里创建的

com.android.server.telecom.components. TelecomService
static void initializeTelecomSystem(Context context) {
   new CallerInfoAsyncQueryFactory() {
            @Override
           public CallerInfoAsyncQuery startQuery(
                 int token, Context context, String number, 
                 CallerInfoAsyncQuery.OnQueryCompleteListener listener,
                 Object cookie) {
                     Log.i(TelecomSystem.getInstance(),
                     "CallerInfoAsyncQuery.startQuery number=%s cookie=%s",
                     Log.pii(number), cookie);
                     return CallerInfoAsyncQuery.startQuery(
                     token, context, number, listener, cookie);
           }
 }

进入CallerInfoAsyncQuery的startQuery方法

com.android.internal.telephony.CallerInfoAsyncQuery
public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,
            OnQueryCompleteListener listener, Object cookie, int subId) {

 
 final Uri contactRef = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon()
          .appendPath(number)
          .appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS,
          String.valueOf(PhoneNumberUtils.isUriNumber(number)))
          .build();
}

查询的uri是PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI

返回cursor结果集后转化成CallerInfo,其中shouldSendToVoicemail变量查询的

是PhoneLookup.SEND_TO_VOICEMAIL字段

public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) {
        ...... ......
        ...... ......
        columnIndex = cursor.getColumnIndex(PhoneLookup.SEND_TO_VOICEMAIL);
                info.shouldSendToVoicemail = (columnIndex != -1) &&
                        ((cursor.getInt(columnIndex)) == 1);
                info.contactExists = true;
        ...... ......
}

最后回到DirectToVoicemailCallFilter的查询回调,shouldSendToVoicemail为true时表示允许通话,否则是拒接

if (info.shouldSendToVoicemail) {
    result = new CallFilteringResult(
                       false, // shouldAllowCall
                        true, // shouldReject
                        true, // shouldAddToCallLog
                        true // shouldShowNotification
                         );
    } else {
            result = new CallFilteringResult(
                         true, // shouldAllowCall
                           false, // shouldReject
                          true, // shouldAddToCallLog
                          true // shouldShowNotification
                          );
     }
}

再看看AsyncBlockCheckFilter,它处理的是黑名单事件

判断一个电话号码是否在黑名单里调用到了BlockChecker的isBlocked方法

com.android.internal.telephony. BlockChecker
public static boolean isBlocked(Context context, String phoneNumber) {
        boolean isBlocked = false;
        long startTimeNano = System.nanoTime();

        try {
            if (BlockedNumberContract.SystemContract.shouldSystemBlockNumber(
                    context, phoneNumber)) {
                Rlog.d(TAG, phoneNumber + " is blocked.");
                isBlocked = true;
            }
        } catch (Exception e) {
            Rlog.e(TAG, "Exception checking for blocked number: " + e);
        }

        int durationMillis = (int) ((System.nanoTime() - startTimeNano) / 1000000);
        if (durationMillis > 500 || VDBG) {
            Rlog.d(TAG, "Blocked number lookup took: " + durationMillis + " ms.");
        }
        return isBlocked;
    }

BlockedNumberContract.SystemContract是framework里的一个黑名单协议类

public static final String AUTHORITY = "com.android.blockednumber";
public static final String METHOD_SHOULD_SYSTEM_BLOCK_NUMBER = 
"should_system_block_number";

public static boolean shouldSystemBlockNumber(Context context, String phoneNumber) {
            final Bundle res = context.getContentResolver().call(
                  AUTHORITY_URI, METHOD_SHOULD_SYSTEM_BLOCK_NUMBER, phoneNumber, null);
            return res != null && res.getBoolean(RES_NUMBER_IS_BLOCKED, false);
}

黑名单是BlockedNumberProvider数据库, 调用call方法

com.android.providers.blockednumber. BlockedNumberProvider
@Override
public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
        final Bundle res = new Bundle();
        switch (method) {
                case  SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER:
                    enforceSystemReadPermissionAndPrimaryUser();
                    res.putBoolean(
                    BlockedNumberContract.RES_NUMBER_IS_BLOCKED, shouldSystemBlockNumber(arg));
                break;
                ............
                ............
        }    
        
}

private boolean shouldSystemBlockNumber(String phoneNumber) {
    if (getBlockSuppressionStatus().isSuppressed) {
         return false;
    }
    if (isEmergencyNumber(phoneNumber)) {
        return false;
    }
    
    return isBlocked(phoneNumber);
}

最后调用isBlocked方法查询blocked表中是否存在该number

查询得到结果后返回

CallFilteringResult result;
        if (isBlocked) {
            result = new CallFilteringResult(
                    false, // shouldAllowCall
                    true, //shouldReject
                    false, //shouldAddToCallLog
                    false // shouldShowNotification
            );
        } else {
            result = new CallFilteringResult(
                    true, // shouldAllowCall
                    false, // shouldReject
                    true, // shouldAddToCallLog
                    true // shouldShowNotification
            );
        }

如果号码在黑名单里则拦截

最后是CallScreeningServiceFilter不知道是处理什么,内部绑定一个抽象服务

CallScreeningService但是却找不到哪个子类继承了它,这里先忽略它

回到前面IncomingCallFilter的onCallFilteringCompletev方法,结果集会做个逻辑运算

mResult = result.combine(mResult);看看它的实现

com.android.server.telecom.callfilteringCallFilteringResult
public CallFilteringResult combine(CallFilteringResult other) {
        if (other == null) {
            return this;
        }

        return new CallFilteringResult(
                shouldAllowCall && other.shouldAllowCall,
                shouldReject || other.shouldReject,
                shouldAddToCallLog && other.shouldAddToCallLog,
                shouldShowNotification && other.shouldShowNotification);
    }

只有三个过滤结果都是允许通话才允许通话,添加至通话记录以及是否显示到通知栏同理

当然这里的查询操作也有超时限制,时间是5秒,超过5秒后忽略还未查询到的过滤器则被忽略mTimeoutsAdapter.getCallScreeningTimeoutMillis(mContext.getContentResolver()));

最后过滤结果被回调到CallsManager的onCallFilteringComplete

public void onCallFilteringComplete(Call incomingCall, CallFilteringResult result) {
   ………………………………
   if (incomingCall.getState() != CallState.DISCONNECTED &&
     incomingCall.getState() != CallState.DISCONNECTING) {
            setCallState(incomingCall, CallState.RINGING,
              result.shouldAllowCall 
? "successful incoming call" : "blocking call"
);
    } else {
       Log.i(this, "onCallFilteringCompleted: call already disconnected.");
    }

        if (result.shouldAllowCall) {
            if (hasMaximumRingingCalls()) {
                rejectCallAndLog(incomingCall);
            } else if (hasMaximumDialingCalls()) {
                rejectCallAndLog(incomingCall);
            } else {
                addCall(incomingCall);
            }
        } else {
            if (result.shouldReject) {
                incomingCall.reject(false, null);
            }
            if (result.shouldAddToCallLog) {
     
                mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE,
                        result.shouldShowNotification);
            } else 以上是关于Android7.0 Phone应用源码分析 phone来电流程分析的主要内容,如果未能解决你的问题,请参考以下文章

Android7.0 Phone应用源码分析 phone拨号流程分析

Android7.0 拨号盘应用源码分析 界面浅析

Android7.0 MediaRecorder源码分析

android6.0 Phone源码分析之Phone适配过程

WmS详解之如何理解Window和窗口的关系?基于Android7.0源码

Android7.0手机程序保活(附源码下载)