Android6.0读取通话记录
来源:互联网 发布:mac版战网客户端 编辑:程序博客网 时间:2024/05/17 03:16
需求:读取通话记录,然后列表显示,每条记录的数据包括姓名、号码、类型(来电、去电、未接,字体颜色分别为绿、蓝、红),然后长按条目弹出一个列表弹窗,显示【复制号码到拨号盘】、【发短信】、【打电话】。
先做读取通话记录并列表显示。工程文件及分包如下:
采用MVC模式:CallInfo数据模型,CallInfoService负责获取数据,MainActivity负责显示。
CallInfo数据模型:包含字段姓名、号码、类型。
public class CallInfo { public String number; // 号码 public long date; // 日期 public int type; // 类型:来电、去电、未接 public CallInfo(String number, long date, int type) { this.number = number; this.date = date; this.type = type; } @Override public String toString() { return "CallInfo{" + "number='" + number + '\'' + ", date=" + date + ", type=" + type + '}'; }}
CallInfoService类获取通话记录数据。
public class CallInfoService { /** * 获取通话记录 * @param context 上下文。通话记录需要从系统的【通话应用】中的内容提供者中获取,内容提供者需要上下文。通话记录保存在联系人数据库中:data/data/com.android.provider.contacts/databases/contacts2.db库中的calls表。 * @return 包含所有通话记录的一个集合 */ public static List<CallInfo> getCallInfos(Context context) { List<CallInfo> infos = new ArrayList<CallInfo>(); ContentResolver resolver = context.getContentResolver(); // uri的写法需要查看源码JB\packages\providers\ContactsProvider\AndroidManifest.xml中内容提供者的授权 // 从清单文件可知该提供者是CallLogProvider,且通话记录相关操作被封装到了Calls类中 Uri uri = CallLog.Calls.CONTENT_URI; String[] projection = new String[]{ CallLog.Calls.NUMBER, // 号码 CallLog.Calls.DATE, // 日期 CallLog.Calls.TYPE // 类型:来电、去电、未接 }; Cursor cursor = resolver.query(uri, projection, null, null, null); while (cursor.moveToNext()){ String number = cursor.getString(0); long date = cursor.getLong(1); int type = cursor.getInt(2); infos.add(new CallInfo(number, date, type)); } cursor.close(); return infos; }}
MainActivity负责显示:
public class MainActivity extends AppCompatActivity { private MyAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 条目显示 ListView lv = (ListView) findViewById(R.id.lv); List<CallInfo> infos = CallInfoService.getCallInfos(this); adapter = new MyAdapter(infos); lv.setAdapter(adapter); } private class MyAdapter extends BaseAdapter{ private List<CallInfo> infos; private LayoutInflater mInflater; public MyAdapter(List<CallInfo> infos) { super(); this.infos = infos; mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public int getCount() { return infos.size(); } @Override public Object getItem(int position) { return infos.get(position); } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { // 加载布局 View view = mInflater.inflate(R.layout.calllog_item, null); // 获取控件 TextView tv_number = (TextView) view.findViewById(R.id.tv_number); TextView tv_date = (TextView) view.findViewById(R.id.tv_date); TextView tv_type = (TextView) view.findViewById(R.id.tv_type); // 设置控件内容 CallInfo info = infos.get(position); // 号码 tv_number.setText(info.number); // 日期 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); String dateString = format.format(info.date); tv_date.setText(dateString); // 类型 String type = null; int textColor = 0; switch (info.type){ case CallLog.Calls.INCOMING_TYPE: // 来电,字体蓝色 type = "来电"; textColor = Color.BLUE; break; case CallLog.Calls.OUTGOING_TYPE: // 去电,字体绿色 type = "去电"; textColor = Color.GREEN; break; case CallLog.Calls.MISSED_TYPE: // 未接,字体红色 type = "未接"; textColor = Color.RED; break; } tv_type.setText(type); tv_type.setTextColor(textColor); return view; } }}
AndroidManifest.xml清单文件需要添加读(写)通话记录的权限:
<!-- 读写通话记录 --> <uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.READ_CONTACTS" /><!-- 低版本使用该权限 --> <uses-permission android:name="android.permission.WRITE_CALL_LOG" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" /><!-- 为了兼容低版本 -->
写完以上部分进行测试,在Android4.4.3真机上可正常运行。启动时系统会弹窗提示该应用正在尝试读取通话记录,是否允许。在Android6.0模拟器上测试报错安全异常:
java.lang.SecurityException: Permission Denial...requires android.permission.READ_CALL_LOG or android.permission.WRITE_CALL_LOG
权限被拒绝,需要READ_CALL_LOG或WRITE_CALL_LOG权限。是的,即使在清单文件中已经声明了该权限,依然报错没有该权限,推断这个问题依然是Android6.0的运行时权限问题。
搜索一下看到如下资料:
http://www.javahelps.com/2015/10/android-60-runtime-permission-model.html
上文的step8提到了这一点:
解决方法还是参考官方文档:
https://developer.android.com/training/permissions/requesting.html#perm-check
所以要将MainActivity和CallInfoService的代码修改,加上长按条目弹出列表弹窗的功能后,代码如下:
MainActivity:
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private MyAdapter adapter; private ListView lv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 获取条目显示的控件 lv = (ListView) findViewById(R.id.lv); // 尝试获所有需要的授权 CallInfoService.getPermissions(this); } @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case CallInfoService.MY_PERMISSIONS_REQUESTS: // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 授权成功,开始获取通话记录 Log.i(TAG, "所需权限授权成功!"); List<CallInfo> infos = CallInfoService.getCallInfo(this); // 显示条目 adapter = new MyAdapter(infos); lv.setAdapter(adapter); // 条目设置长按事件,弹出一个列表对话框 lv.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { // 获取条目对应的号码 CallInfo info = (CallInfo) adapter.getItem(position); final String number = info.number; String[] items = new String[]{ "复制号码到拨号盘", "拨号", "发送短信" }; new AlertDialog.Builder(MainActivity.this) .setTitle("操作") .setItems(items, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case 0: // 复制号码到拨号盘 startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + number))); break; case 1: // 拨号 if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { Log.i(TAG, "没有授权拨号!"); return; } startActivity(new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + number))); break; case 2: // 发送短信 if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) { Log.i(TAG, "没有授权发短信!"); return; } startActivity(new Intent(Intent.ACTION_SENDTO, Uri.parse("sms:" + number))); break; } } }).show(); return false; } }); } else { Log.i(TAG, "所需权限授权失败!"); } break; default: break; } } private class MyAdapter extends BaseAdapter{ private List<CallInfo> infos; private LayoutInflater mInflater; public MyAdapter(List<CallInfo> infos) { super(); this.infos = infos; mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public int getCount() { return infos.size(); } @Override public Object getItem(int position) { return infos.get(position); } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { // 加载布局 View view = mInflater.inflate(R.layout.calllog_item, null); // 获取控件 TextView tv_number = (TextView) view.findViewById(R.id.tv_number); TextView tv_date = (TextView) view.findViewById(R.id.tv_date); TextView tv_type = (TextView) view.findViewById(R.id.tv_type); // 设置控件内容 CallInfo info = infos.get(position); // 号码 tv_number.setText(info.number); // 日期 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); String dateString = format.format(info.date); tv_date.setText(dateString); // 类型 String type = null; int textColor = 0; switch (info.type){ case CallLog.Calls.INCOMING_TYPE: // 来电,字体蓝色 type = "来电"; textColor = Color.BLUE; break; case CallLog.Calls.OUTGOING_TYPE: // 去电,字体绿色 type = "去电"; textColor = Color.GREEN; break; case CallLog.Calls.MISSED_TYPE: // 未接,字体红色 type = "未接"; textColor = Color.RED; break; } tv_type.setText(type); tv_type.setTextColor(textColor); return view; } }}
CallInfoService:
public class CallInfoService { private static final String TAG = "CallInfoService"; private static String[] permissionList = new String[]{ Manifest.permission.READ_CALL_LOG, Manifest.permission.CALL_PHONE, Manifest.permission.SEND_SMS }; public static final int MY_PERMISSIONS_REQUESTS = 0; // 批量申请多个权限:读取通话记录、打电话、发短信 /** * 获取读取通话记录、打电话、发短信的权限 * @param activity 用于弹窗申请权限的Activity */ public static void getPermissions(Activity activity) { ArrayList<String> list = new ArrayList<String>(); // 循环判断所需权限中有哪个尚未被授权 for (int i = 0; i < permissionList.length; i++){ if (ActivityCompat.checkSelfPermission(activity, permissionList[i]) != PackageManager.PERMISSION_GRANTED) list.add(permissionList[i]); } ActivityCompat.requestPermissions(activity, list.toArray(new String[list.size()]), MY_PERMISSIONS_REQUESTS); } /** * 请求获取通话记录 * @param context 上下文。通话记录需要从系统的【通话应用】中的内容提供者中获取,内容提供者需要上下文。 * 通话记录保存在联系人数据库中:data/data/com.android.provider.contacts/databases/contacts2.db库中的calls表。 * @return 一个包含所有通话记录的集合。 */ public static List<CallInfo> getCallInfo(Context context) { List<CallInfo> infos = new ArrayList<CallInfo>(); ContentResolver resolver = context.getContentResolver(); // uri的写法需要查看源码JB\packages\providers\ContactsProvider\AndroidManifest.xml中内容提供者的授权 // 从清单文件可知该提供者是CallLogProvider,且通话记录相关操作被封装到了Calls类中 Uri uri = CallLog.Calls.CONTENT_URI; String[] projection = new String[]{ CallLog.Calls.NUMBER, // 号码 CallLog.Calls.DATE, // 日期 CallLog.Calls.TYPE // 类型:来电、去电、未接 }; if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED) { Log.i(TAG, "授权失败,无法获取通话记录!"); return null; } Cursor cursor = resolver.query(uri, projection, null, null, null); while (cursor.moveToNext()){ String number = cursor.getString(0); long date = cursor.getLong(1); int type = cursor.getInt(2); infos.add(new CallInfo(number, date, type)); } cursor.close(); return infos; }}
这里是一次批量申请多个权限,写法比较蛋疼,涉及List集合和Stirng[]相互转换,不懂哪位大佬有更好的批量申请写法,这里暂时就这么写了。
最后在Android6.0模拟器上测试成功,效果如下图:
然而!!!!!!
坑爹的情况又出现了,以上代码采用Android6.0的运行时权限的模式编写,可以在Android6.0的模拟器上正常运行,但是在Android5.1.1的真机上测试出了些问题:
通过打Log发现是SEND_SMS权限没有获得授权,然后在调用ActivityCompat.requestPermissions()去申请权限时并没有任何弹窗,直接就返回申请失败了。
好吧,再次搜索资料,找到了重要参考:
http://www.jianshu.com/p/e1ab1a179fbb
http://blog.csdn.net/hudashi/article/details/50775180
综上,解决方案大致有以下几种:
- 直接简单粗暴地判断是否Build.VERSION.SDK_INT >= 23,然后分高低版本两种逻辑处理。
- 或者用兼容库使代码兼容旧版,如v4兼容库。
- 或者使用第三方库简化代码,如Github上的开源项目 PermissionHelper和hotchemi’s PermissionsDispatcher
好吧,看来还需要再花些时间整理一下,未完待续。。。
小结:
- 如果已经在清单文件中声明了权限,依然报错安全异常权限被拒绝,多半是因为这是个运行时权限。
- 现在Android Studio创建项目时默认是targetSdkVersion 24,如果暂不打算兼容高版本,需要在build.gradle修改targetSdkVersion至23以下。
- Android6.0读取通话记录
- Android6.0的通话记录获取
- iPhone上读取通话记录
- 读取通话记录的权限
- 16读取通话记录
- android 读取手机通话记录
- Android端通话记录读取
- ContentProvider读取通讯录和通话记录
- android6.0 读取storage文件夹需要手动检测读取权限
- android6.0 读取storage文件夹需要手动检测读取权限
- android6.0 读取storage文件夹需要手动检测读取权限
- 通话记录
- 通话记录
- android中读取联系人和通话记录
- android中读取联系人和通话记录
- android中根据电话号码读取通话记录表
- android读取联系人,短信,通话记录,IMSI
- 使用ContentResolver读取通话记录与短信记录
- composer autoload原理浅析
- 基础知识
- java网络编程之TCP
- 高数常考考点总结
- Kaldi中的plda的训练以及computer-socre
- Android6.0读取通话记录
- 【自考】数据库系统原理--范式
- iOS_数字变化(UICountingLabel与CADisplayLink)
- 安装5.7.11版本mysql所遇问题
- ruby基本概念(学习笔记二)
- Spring MVC 使用ajax异步交互返回数据
- 网络——host绑定
- Oracle 11g学习笔记--model子句
- listen、connect、accept流程及原理