Android6.0来电号码与电话薄联系人进行匹配

本文将介绍系统接收到来电之后,如何在电话薄中进行匹配联系人的流程。分析将从另外一篇文章(基于Android6.0的RIL框架层模块分析)中提到的与本文内容相关的代码开始。

//packages/service/***/Call.java

public void handleCreateConnectionSuccess(

 CallIdMapper idMapper,

 ParcelableConnection connection) {

 setHandle(connection.getHandle(), connection.getHandlePresentation());//这个函数很重要,会启动一个查询

 setCallerDisplayName(connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());

 setExtras(connection.getExtras());

 if (mIsIncoming) {

 // We do not handle incoming calls immediately when they are verified by the connection

 // service. We allow the caller-info-query code to execute first so that we can read the

 // direct-to-voicemail property before deciding if we want to show the incoming call to

 // the user or if we want to reject the call.

 mDirectToVoicemailQueryPending = true;

 // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before

 // showing the user the incoming call screen.

 mHandler.postDelayed(mDirectToVoicemailRunnable, Timeouts.getDirectToVoicemailMillis(

  mContext.getContentResolver()));

 }

}

这个setHandle函数如下:

//Call.java

public void setHandle(Uri handle, int presentation) {

 startCallerInfoLookup();

}

private void startCallerInfoLookup() {

 final String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();

 mQueryToken++; // Updated so that previous queries can no longer set the information.

 mCallerInfo = null;

 if (!TextUtils.isEmpty(number)) {

 mHandler.post(new Runnable() {

  @Override

  public void run() {

  mCallerInfoAsyncQueryFactory.startQuery(mQueryToken,

   mContext,number,mCallerInfoQueryListener,Call.this);

  }});

 }

}

注意后面post的那个Runnable。这个就是启动查询号码的逻辑了。这个mCallerInfoAsyncQueryFactory的赋值的流程比较曲折。在TelecomService被连接上调用onBind的时候,会调用initializeTelecomSystem函数。那这个TelecomService是在哪里被启动的呢?在TelecomLoaderService.java里面定义了:

private static final ComponentName SERVICE_COMPONENT = new ComponentName(

  "com.android.server.telecom",

  "com.android.server.telecom.components.TelecomService");

private void connectToTelecom() {

 synchronized (mLock) {

 TelecomServiceConnection serviceConnection = new TelecomServiceConnection();

 Intent intent = new Intent(SERVICE_ACTION);

 intent.setComponent(SERVICE_COMPONENT);

 // Bind to Telecom and register the service

 if (mContext.bindServiceAsUser(intent, serviceConnection, flags, UserHandle.OWNER)) {

  mServiceConnection = serviceConnection;

 } }}

public void onBootPhase(int phase) {//这个在系统启动阶段就会触发

 if (phase == PHASE_ACTIVITY_MANAGER_READY) {

 connectToTelecom();

 }}

所以从这里看,在系统启动阶段就会触发TelecomService这个service,且在成功连接到服务之后,将调用ServiceManager.addService(Context.TELECOM_SERVICE, service),将这个服务添加到系统服务中了。这个类的构造函数中,在调用函数initializeTelecomSystem初始化TelecomSystem时,就实例化了一个内部匿名对象,并且在TelecomSystem的构造函数中初始化一个mCallsManager时将该匿名对象传入,而在CallsManager的processIncomingCallIntent中会用这个函数初始化一个Call对象。所以这个mCallerInfoAsyncQueryFactory的实际内容见TelecomService中的initializeTelecomSystem:

//TelecomService.java

TelecomSystem.setInstance(

 new TelecomSystem(

 context,

 new MissedCallNotifierImpl(context.getApplicationContext()),

 new CallerInfoAsyncQueryFactory() {

  @Override

  public CallerInfoAsyncQuery startQuery(int token, Context context,

   String number,CallerInfoAsyncQuery.OnQueryCompleteListener listener,

   Object cookie) {

  return CallerInfoAsyncQuery.startQuery(token, context, number, listener, cookie);

  }},

 new HeadsetMediaButtonFactory() {},

 new ProximitySensorManagerFactory() {},

 new InCallWakeLockControllerFactory() {},

 new ViceNotifier() {}));

可以看到,通过startQuery来查询传入的number的动作。我们来看看CallerInfoAsyncQuery的startQuery函数。

//frameworks/base/telephony/java/com/android/internal/CallerInfoAsyncQuery.java

/**

 * Factory method to start the query based on a number.

 *

 * Note: if the number contains an "@" character we treat it

 * as a SIP address, and look it up directly in the Data table

 * rather than using the PhoneLookup table.

 * TODO: But eventually we should expose two separate methods, one for

 * numbers and one for SIP addresses, and then have

 * PhoneUtils.startGetCallerInfo() decide which one to call based on

 * the phone type of the incoming connection.

 */

 public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,

  OnQueryCompleteListener listener, Object cookie) {

 int subId = SubscriptionManager.getDefaultSubId();

 return startQuery(token, context, number, listener, cookie, subId);

 }

/**

 * Factory method to start the query with a Uri query spec.

 */ 

public static CallerInfoAsyncQuery startQuery(int token, Context context, Uri contactRef,

  OnQueryCompleteListener listener, Object cookie) {

c.mHandler.startQuery(token,

    cw, // cookie
    contactRef, // uri,注意这里的查询地址
    null, // projection
    null, // selection
    null, // selectionArgs
    null); // orderBy
 return c;
}

注意看注释,该函数还会对SIP号码(包含@的号码)进行处理,还有紧急号码和语音邮箱号码进行区分。实际上,当对一个号码进行查询的时候,这三个startQuery都用到了。注意,上面的startQuery会根据结果对connection的值进行修改。

其中将号码转换成uri格式的数据,后续会对这个数据进行查询:

//frameworks/base/***/CallerInfoAsyncQuery.java
public static CallerInfoAsyncQuery startQuery(int token, Context context, String number, OnQueryCompleteListener listener, Object cookie, int subId) {
 // Construct the URI object and query params, and start the query.
 final Uri contactRef = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon().appendPath(number)
  .appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, String.valueOf(PhoneNumberUtils.isUriNumber(number)))
  .build();
 CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
 c.allocate(context, contactRef);
 //create cookieWrapper, start query
 CookieWrapper cw = new CookieWrapper();
 cw.listener = listener; cw.cookie = cookie;
 cw.number = number; cw.subId = subId;
 // check to see if these are recognized numbers, and use shortcuts if we can.
 if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
 cw.event = EVENT_EMERGENCY_NUMBER;
 } else if (PhoneNumberUtils.isVoiceMailNumber(subId, number)) {
 cw.event = EVENT_VOICEMAIL_NUMBER;
 } else {
 cw.event = EVENT_NEW_QUERY;
 }

 c.mHandler.startQuery(token,
    cw, // cookie
    contactRef, // uri
    null, // projection
    null, // selection
    null, // selectionArgs
    null); // orderBy
 return c;
}

这个函数里面的contactRef的值应该是“content://com.android.contacts/phone_lookup_enterprise/13678909678/sip?”类似的。

实际上这个query是调用CallerInfoAsyncQueryHandler的startQuery函数,而这个函数是直接调用它的父类AsyncQueryHandler的同名函数。

//AsyncQueryHandler.java
public void startQuery(int token, Object cookie, Uri uri,
 String[] projection, String selection, String[] selectionArgs,
 String orderBy) {
 // Use the token as what so cancelOperations works properly
 Message msg = mWorkerThreadHandler.obtainMessage(token);
 msg.arg1 = EVENT_ARG_QUERY;
 WorkerArgs args = new WorkerArgs();
 args.handler = this;
 args.uri = uri;
 msg.obj = args;
 mWorkerThreadHandler.sendMessage(msg);

}

这个mWorkerThreadHandler是在CallerInfoAsyncQueryHandler函数覆写父类的createHandler函数中赋值,是CallerInfoWorkerHandler类型。所以后续的处理函数是该类的handleMessage函数。

//AsyncQueryHandler.java
public void handleMessage(Message msg) {
 WorkerArgs args = (WorkerArgs) msg.obj;
 CookieWrapper cw = (CookieWrapper) args.cookie;
 if (cw == null) {
 // Normally, this should never be the case for calls originating
 // from within this code.
 // However, if there is any code that this Handler calls (such as in
 // super.handleMessage) that DOES place unexpected messages on the
 // queue, then we need pass these messages on.
 } else {
 switch (cw.event) {
  case EVENT_NEW_QUERY://它的值跟AsyncQueryHandler的EVENT_ARG_QUERY一样,都是1
  //start the sql command.
  super.handleMessage(msg);
  break;
  case EVENT_END_OF_QUEUE:
  // query was already completed, so just send the reply.
  // passing the original token value back to the caller
  // on top of the event values in arg1.
  Message reply = args.handler.obtainMessage(msg.what);
  reply.obj = args;
  reply.arg1 = msg.arg1;
  reply.sendToTarget();
  break;
  default:
 }}}}

这个super就是AsyncQueryHandler的内部类WorkerHandler了。

//AsyncQueryHandler.java
protected class WorkerHandler extends Handler {
 @Override
 public void handleMessage(Message msg) {
 final ContentResolver resolver = mResolver.get();
 WorkerArgs args = (WorkerArgs) msg.obj;
 int token = msg.what;
 int event = msg.arg1;
 switch (event) {
  case EVENT_ARG_QUERY:
  Cursor cursor;
  try {
   cursor = resolver.query(args.uri, args.projection,
    args.selection, args.selectionArgs,
    args.orderBy);
   // Calling getCount() causes the cursor window to be filled,
   // which will make the first access on the main thread a lot faster.
   if (cursor != null) {
   cursor.getCount();
   }}
  args.result = cursor;
  break;
 }
 // passing the original token value back to the caller
 // on top of the event values in arg1.
 Message reply = args.handler.obtainMessage(token);

 reply.obj = args;
 reply.arg1 = msg.arg1;
 reply.sendToTarget();
 }}

可以看到流程就是简单的用resolver.query来查询指定的query URI,然后将返回值通过消息机制发送到AsyncQueryHandler的handleMessage里面处理,而在这里会调用CallerInfoAsyncQuery的onQueryComplete函数。注意这个ContentResolver是在uri上查询结果,而这个uri是由某个ContentProvider来提供的。注意这个地址里面的authorities里面的值为”com.android.contacts”,同样看看ContactsProvider的androidmanifest.xml文件:

<provider android:name="ContactsProvider2"
  android:authorities="contacts;com.android.contacts"
  android:readPermission="android.permission.READ_CONTACTS"
  android:writePermission="android.permission.WRITE_CONTACTS">
  <path-permission android:pathPrefix="/search_suggest_query"
   android:readPermission="android.permission.GLOBAL_SEARCH" />
  <path-permission android:pathPattern="/contacts/.*/photo"   android:readPermission="android.permission.GLOBAL_SEARCH" />
  <grant-uri-permission android:pathPattern=".*" />
 </provider>

所以最后这个查询是由ContactsProvider来执行的。

我们来看看查询完成之后,调用CallerInfoAsyncQuery的onQueryComplete函数的具体流程:

protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
 // check the token and if needed, create the callerinfo object.
 if (mCallerInfo == null) {
  if (cw.event == EVENT_EMERGENCY_NUMBER) {
  } else if (cw.event == EVENT_VOICEMAIL_NUMBER) {
  } else {
  mCallerInfo = CallerInfo.getCallerInfo(mContext, mQueryUri, cursor);
  }
  }
 }
 //notify the listener that the query is complete.
 if (cw.listener != null) {
  cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo);
 }
 }
}

注意,上面代码里面的CallerInfo.getCallerInfo非常重要。在这里面会使用查询处理的cursor结果,并将合适的结果填充到mCallerInfo,将其传递到cw.listener.onQueryComplete函数中,作为最终结果进行进一步处理。

//CallerInfo.java
public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) {
 CallerInfo info = new CallerInfo();
 if (cursor != null) {
 if (cursor.moveToFirst()) {
  columnIndex = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY);
  if (columnIndex != -1) {
  info.lookupKey = cursor.getString(columnIndex);

  }
  info.contactExists = true;
 }
 cursor.close();
 cursor = null;
 }
 info.needUpdate = false;
 info.name = normalize(info.name);
 info.contactRefUri = contactRef;
 return info;
}

系统原生的逻辑是取搜索结果的第一个记录,并用来实例化。当客户需求改变,需要匹配不同号码的时候,就需要修改这个地方的了。最优先是遍历整个cursor集合,并且根据客户需求选出适合的结果,赋值给CallerInfo实例。

下面是整个号码匹配的流程图:

Call.java会将查询后的结果设置到Call实例里面,并将其传送到CallsManager里面进行后续处理。而这个CallsManager会将这个Call显示给客户。

当网络端来电时,frame层会接收到,并且连接成功之后会触发Call.java里面的handleCreateConnectionSuccess。这个函数逻辑是从数据库中查询复合要求的联系人,并且只取结果集的第一条记录,用来初始化这个Call里面的变量。而后将这个Call传到CallsManager进行处理,显示给用户。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android编程中号码匹配位数修改的方法

    本文实例讲述了Android编程中号码匹配位数修改的方法.分享给大家供大家参考,具体如下: Android2.1上默认的号码匹配位数是7位,也就是说从右向左算起,如果两个号码有7位是匹配的,那么就认为是相同的号码,如+86 1234567和01234567是相同的号码:所以判断两个号码是否匹配,简单的用"=="是不行的. 安卓在Sqlite层新增了函数phone_numbers_equal用于号码匹配的判断,这个函数是数据库级的,对应的SQL语句是PHONE_NUMBERS_EQUAL

  • Android中的Intent Filter匹配规则简介

    本文主要介绍了隐式Intent匹配目标组件的规则,若有叙述不清晰或是不准确的地方希望大家指出,谢谢大家:  ) 1. Intent简介 Intent用于在一个组件(Component,如Activity.Service.Broadcast Receiver)中打开另一个组件. Intent可分为隐式(implicitly)和显式(explicitly)两种: Explicitly Intent:在知道要打开哪个具体的Component时使用,通过指定调用者和被调用者即可打开目标Component

  • Android实现动态自动匹配输入内容功能

    什么是动态自动匹配输入内容呢?举个例子,当我们在百度等搜索引擎的输入框中输入想要搜索的关键词,输入框下面会提示很多相关联的热门搜索项,效果图如下 那在安卓中如何实现这种效果呢?在这里给大家推荐两个Android的控件: AutoCompleteTextView MultiAutoCompleteTextView 一.AutoCompleteTextView 独特属性:android:completionThreshold="2"-–设置输入多少字符时自动匹配 首先,我们先在res文件夹

  • 从Android源码剖析Intent查询匹配的实现

    前言     这篇文章主要是介绍一下Android Intent,并且从Android源码的角度对Intent查询匹配过程进行分析. Intent介绍     Intent的中文是"意图"的意思,而意图是一个非常抽象的概念,那么在Android的编码设计中,如何实例化意图呢?因此Android系统明确指定一个Intent可由两方面属性来衡量. 主要属性:包括Action和Data.其中Action用于表示该Intent所表达的动作意图,Data用于表示该Action所操作的数据.   

  • Android 搜索结果匹配关键字且高亮显示功能

    1. 单关键字 匹配 如果只是单关键字的话,那么我们先计算出他的下标,它的长度,然后就可以标记下标到下标+长度的这一段为特殊颜色即可,代码如下: if (name != null && name.contains(keyWord)) { int index = name.indexOf(keyWord); int len = keyWord.length(); Spanned temp = Html.fromHtml(name.substring(0, index) + "<

  • Android实现自动匹配关键字并且标红功能

    本文实例为大家分享了Android匹配关键字标红的具体代码,供大家参考,具体内容如下 1. 单关键字匹配 若只需匹配 搜索内容  可以写的简单一些,代码如下: if (name != null && name.contains(mKeyWord)) { int index = name.indexOf(mKeyWord); int len = mKeyWord.length(); Spanned temp = Html.fromHtml(name.substring(0, index) +

  • 详解Android中Intent对象与Intent Filter过滤匹配过程

    如果对Intent不是特别了解,可以参见博文<详解Android中Intent的使用方法>,该文对本文要使用的action.category以及data都进行了详细介绍.如果想了解在开发中常见Intent的使用,可以参见<Android中Intent习惯用法>. 本文内容有点长,希望大家可以耐心读完. 本文在描述组件在manifest中注册的Intent Filter过滤器时,统一用intent-filter表示. 一.概述 我们知道,Intent是分两种的:显式Intent和隐式

  • Android Java实现余弦匹配算法示例代码

     Java实现余弦匹配算法 最近在做一个通讯交友的项目,项目中有一个这样的需求,通过用户的兴趣爱好,为用户寻找推荐兴趣相近的好友.其实思路好简单,把用户的兴趣爱好和其他用户的兴趣爱好进行一个匹配,当他们的爱好相似度比较高的时候就给双方进行推荐.那么如何进行比较是一个问题,其实我们可以通过余弦匹配算法来对用户的兴趣爱好进行比较,根据计算出来的值来得到一个兴趣爱好相近好友列表,并进行排序. 因为我做的项目是Android端的,所以算法是通过Java实现的,废话不过多说了,下面是算法的实现: pack

  • Android6.0来电号码与电话薄联系人进行匹配

    本文将介绍系统接收到来电之后,如何在电话薄中进行匹配联系人的流程.分析将从另外一篇文章(基于Android6.0的RIL框架层模块分析)中提到的与本文内容相关的代码开始. //packages/service/***/Call.java public void handleCreateConnectionSuccess( CallIdMapper idMapper, ParcelableConnection connection) { setHandle(connection.getHandle

  • Android监听手机电话状态与发送邮件通知来电号码的方法(基于PhoneStateListene实现)

    本文实例讲述了Android监听手机电话状态与发送邮件通知来电号码的方法.分享给大家供大家参考,具体如下: 在android中可以用PhoneStateListener来聆听手机电话状态(比如待机.通话中.响铃等).本例是通过它来监听手机电话状态,当手机来电时,通过邮件将来电号码发送到用户邮箱的例子.具体程序如下: import android.app.Activity; import android.content.Intent; import android.os.Bundle; impor

  • Android6.0编程实现双向通话自动录音功能的方法详解

    本文实例讲述了Android6.0编程实现双向通话自动录音功能的方法.分享给大家供大家参考,具体如下: 项目中需要实现基于Android 6.0 的双向通话自动录音功能,在查阅相关android电话状态监听文章以及Git上的开源录音项目后,整理出此文 首先,介绍一下android 电话状态的监听(来电和去电): http://www.jb51.net/article/32433.htm 实现手机电话状态的监听,主要依靠两个类: TelephoneManger和PhoneStateListener

  • 谈谈Android6.0运行时的权限处理

    运行时权限介绍 Android 6.0在我们原有的AndroidManifest.xml声明权限的基础上, 又新增了运行时权限动态检测,以下权限都需要在运行时判断: 1.身体传感器 2.日历    3.摄像头 4.通讯录 5.地理位置 6.麦克风 7.电话 8.短信 9.存储空间 在 Android 6.0 中,app 如果想要获得某些权限,会在应用中弹出一个对话框,让用户确认是否授予该权限. 具体的截图如下: 这要做的好处就是运行一个 app 时可以拒绝其中的某些权限,防止 app 触及到你的

  • 详解Android6.0运行时权限管理

    自从Android6.0发布以来,在权限上做出了很大的变动,不再是之前的只要在manifest设置就可以任意获取权限,而是更加的注重用户的隐私和体验,不会再强迫用户因拒绝不该拥有的权限而导致的无法安装的事情,也不会再不征求用户授权的情况下,就可以任意的访问用户隐私,而且即使在授权之后也可以及时的更改权限.这就是6.0版本做出的更拥护和注重用户的一大体现. 一.认知 今天我们就来学习下Android6.0的权限管理. Android6.0系统把权限分为两个级别: 一个是Normal Permiss

  • 用c语言实现一个电话薄(附完整代码)

    先看一下这个小程序的效果 这里我为了演示方便,把人数固定为3个: 人数都是可以自定义的: 下面是这个简单的代码: #include<stdio.h> typedef struct con { int num; char name[11]; char tel[10]; }contact; int main() { contact con[3]; int i; for(i=0;i<3;i++) { printf("请输入编号:"); scanf("%d"

  • 用c语言实现一个电话薄(附完整代码)

    先看一下这个小程序的效果 这里我为了演示方便,把人数固定为3个: 人数都是可以自定义的: 下面是这个简单的代码: #include<stdio.h> typedef struct con { int num; char name[11]; char tel[10]; }contact; int main() { contact con[3]; int i; for(i=0;i<3;i++) { printf("请输入编号:"); scanf("%d"

  • Android6.0蓝牙出现无法扫描设备或闪退问题解决办法

    Android6.0蓝牙出现无法扫描设备或闪退问题解决办法 前言: 目前待的这家公司是做智能家居的,最近客户那边有反馈说为什么我的手机蓝牙也打开了,设备的蓝牙也打开了,为啥总是扫描不到呢,但是我们公司的测试人员几经排查,并未发现客户的所描述的扫描不到设备,但客户所说的问题确实又存在,几经周折,找到了原因,原来是现在市场上出来的android6.0手机需要添加两个权限,Android官网也已经说明了, 直接上图 具体权限官网说的很清楚了 解决办法 Android6.0设备通过蓝牙和Wi-Fi扫描访

  • Android6.0指纹识别开发实例详解

    Android6.0指纹识别开发实例详解 最近在做android指纹相关的功能,谷歌在android6.0及以上版本对指纹识别进行了官方支持.当时在FingerprintManager和FingerprintManagerCompat这两个之间纠结,其中使用FingerprintManager要引入com.android.support:appcompat-v7包,考虑到包的大小,决定使用v4兼容包FingerprintManagerCompat来实现. 主要实现的工具类FingerprintU

  • Android6.0 屏幕固定功能详解

    可能大家看到这个标题不知道是什么东西,我先说明下,android6.0在设置->安全->屏幕固定开启后,然后再长按home键出现最近的几个Activity可以选择一个图钉按钮就开启了屏幕固定功能. 屏幕固定开启后,屏幕只能固定在设定的Task上的Activity切换. 一.设置固定屏幕 我们先来看SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java的代码,这段代码就是长按home键出现几个Activity,然后按

随机推荐