此文非教程,作常用工具类记录,因此主要是代码---
--前言
公司项目有这么一个需求,获取用户的通话记录,如果存在24小时内的未接来电则发送状态栏通知,通知上除了可以直接回拨还可以及引导用户打开app的指定模块/页面
因此涉及到知识点:
1.获取通话记录,并判断是否未接
2.发送自定义的通知栏信息,并处理响应事件
3.实践发现点击了自定义通知的按钮,通知栏不会自动收起来因此需要手动收起通知栏
----1. 获取通话记录,需要动态权限授权,这里不做额外解释
//获取通话记录肯定是通过ContentResolver查询了,先定义查询语句
private static final Uri callUri = CallLog.Calls.CONTENT_URI;
private static final String[] columns = {
CallLog.Calls.CACHED_NAME// 通话记录的联系人
, CallLog.Calls.NUMBER// 通话记录的电话号码
, CallLog.Calls.DATE// 通话记录的日期
, CallLog.Calls.DURATION// 通话时长
, CallLog.Calls.TYPE// 通话类型
, CallLog.Calls._ID// 通话ID
};
/**
* 读取数据
*
* @return 读取到的数据
*/
private static List getDataList(Context context) {
// 1.获得ContentResolver
ContentResolver resolver = context.getContentResolver();
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED) {
}
// 2.利用ContentResolver的query方法查询通话记录数据库
/**
* @param uri 需要查询的URI,(这个URI是ContentProvider提供的)
* @param projection 需要查询的字段
* @param selection sql语句where之后的语句
* @param selectionArgs ?占位符代表的数据
* @param sortOrder 排序方式
*/
Cursor cursor = resolver.query(callUri, // 查询通话记录的URI
columns
, null, null, CallLog.Calls.DEFAULT_SORT_ORDER// 按照时间逆序排列,最近打的最先显示
);
List list = new ArrayList<>();
if (cursor == null) return list;
// 3.通过Cursor获得数据
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex(CallLog.Calls.CACHED_NAME));
String number = cursor.getString(cursor.getColumnIndex(CallLog.Calls.NUMBER));
long dateLong = cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DATE));
int duration = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.DURATION));
int type = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.TYPE));
int id = cursor.getInt(cursor.getColumnIndex(CallLog.Calls._ID));
if (isMobileNO(number)) {
long day_lead = getTimeDistance(new Date(dateLong), new Date());
if (day_lead < 2) {//只显示48小时以内通话记录,防止通话记录数据过多影响加载速度
CallLogEntity callLog = new CallLogEntity();
callLog.setCallId(id);
callLog.setName(name == null ? "未备注联系人" : name);
callLog.setNumber(number);
callLog.setDate(dateLong);
callLog.setTime(duration);
callLog.setType(type);
list.add(callLog);
} else {
cursor.close();
return list;
}
}
}
cursor.close();
return list;
}
因为要根据时间过滤,所以写了一个计算时间距离的方法,略显复杂,其实简化可以直接用
(System.currentTimeMillis() - callLog.getDate())/DAY
private static long DAY = 1000 * 60 * 60 * 24;
/**
* 获得两个日期间距多少天
*
* @param beginDate
* @param endDate
* @return
*/
public static long getTimeDistance(Date beginDate, Date endDate) {
Calendar fromCalendar = Calendar.getInstance();
fromCalendar.setTime(beginDate);
fromCalendar.set(Calendar.HOUR_OF_DAY, fromCalendar.getMinimum(Calendar.HOUR_OF_DAY));
fromCalendar.set(Calendar.MINUTE, fromCalendar.getMinimum(Calendar.MINUTE));
fromCalendar.set(Calendar.SECOND, fromCalendar.getMinimum(Calendar.SECOND));
fromCalendar.set(Calendar.MILLISECOND, fromCalendar.getMinimum(Calendar.MILLISECOND));
Calendar toCalendar = Calendar.getInstance();
toCalendar.setTime(endDate);
toCalendar.set(Calendar.HOUR_OF_DAY, fromCalendar.getMinimum(Calendar.HOUR_OF_DAY));
toCalendar.set(Calendar.MINUTE, fromCalendar.getMinimum(Calendar.MINUTE));
toCalendar.set(Calendar.SECOND, fromCalendar.getMinimum(Calendar.SECOND));
toCalendar.set(Calendar.MILLISECOND, fromCalendar.getMinimum(Calendar.MILLISECOND));
long dayDistance = (toCalendar.getTime().getTime() - fromCalendar.getTime().getTime()) / DAY;
dayDistance = Math.abs(dayDistance);
return dayDistance;
}
----2.发送自定义通知--兼容所有版本的通知发送
NotificationManager notificationManager = (NotificationManager) mContext.get().getSystemService(Context.NOTIFICATION_SERVICE);
try {
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String channelId = BuildConfig.APPLICATION_ID.concat("_call");
NotificationChannel notificationChannel = new NotificationChannel(channelId, "未接来电", NotificationManager.IMPORTANCE_DEFAULT);
notificationChannel.enableVibration(false);
notificationChannel.enableLights(false);
notificationChannel.setVibrationPattern(new long[]{0});
notificationChannel.setSound(null, null);
notificationManager.createNotificationChannel(notificationChannel);
builder = new Notification.Builder(context1, channelId);
} else {
builder = new Notification.Builder(context1);
}
RemoteViews contentView = new RemoteViews(context1.getPackageName(),R.layout.view_miss_call_remote_view);
//重拨
Intent intent_tel = new Intent(context1, CallReceiver.class);
intent_tel.setAction("com.evan.intent.action.MISS_CALL_RECALL");
intent_tel.putExtra("phone",callLogEntity.getNumber());
PendingIntent pending_intent_tel = PendingIntent.getBroadcast(context1, 2, intent_tel, PendingIntent.FLAG_UPDATE_CURRENT);
contentView.setOnClickPendingIntent(R.id.tel,pending_intent_tel);
//打开app的某个页面
Intent intent_open = new Intent(context1, StartActivity.class);
intent_open.putExtra("phone",callLogEntity.getNumber());
PendingIntent pending_open = PendingIntent.getActivity(context1, 2, intent_open, PendingIntent.FLAG_UPDATE_CURRENT);
contentView.setOnClickPendingIntent(R.id.open,pending_open);
//标题
contentView.setTextViewText(R.id.title,callLogEntity.getNumber());
//副标题
contentView.setTextViewText(R.id.sub_title,"未接来电 - "+new SimpleDateFormat("MM-dd HH:mm").format(callLogEntity.getDate()));
//构建通知
builder.setTicker("未接来电")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pending_open)
.setAutoCancel(true)
.setOnlyAlertOnce(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setCustomContentView(contentView);
}else {
builder.setContent(contentView);
}
Notification notification = builder.build();
notificationManager.notify(MISS_CALL_NOTIFY_ID, notification);
} catch (Exception e) {
e.printStackTrace();
}
注意remoteViews的使用,如果是发送广播必须指定接受类,否则可能收不到广播
--- 3.点击remoteViews里面的控件通知栏是不会收起来的,通知也不会取消,都需要手动操作
收起通知需要声明此权限:
public static void cancelNotify(){
NotificationManager notificationManager = (NotificationManager) CallShowApplication.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(MISS_CALL_NOTIFY_ID);
try {
Object statusBarManager = CallShowApplication.getContext().getSystemService("statusbar");
Method collapse;
if (Build.VERSION.SDK_INT <= 16) {
collapse = statusBarManager.getClass().getMethod("collapse");
} else {
collapse = statusBarManager.getClass().getMethod("collapsePanels");
}
collapse.invoke(statusBarManager);
} catch (Exception localException) {
localException.printStackTrace();
}
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
此类完整代码:
public class CallLogUtil {
private static final int MISS_CALL_NOTIFY_ID = 10294;
private static long DAY = 1000 * 60 * 60 * 24;
private static Box sCallLogBox;
private static final Uri callUri = CallLog.Calls.CONTENT_URI;
private static final String[] columns = {
CallLog.Calls.CACHED_NAME// 通话记录的联系人
, CallLog.Calls.NUMBER// 通话记录的电话号码
, CallLog.Calls.DATE// 通话记录的日期
, CallLog.Calls.DURATION// 通话时长
, CallLog.Calls.TYPE// 通话类型
, CallLog.Calls._ID// 通话ID
};
/**
* 这里为了避免剩下文引用泄漏 使用了弱引用
* 此方法用于查找48小时内未接听的电话,并且没有通知过
* 查询过程可能耗时,因此使用了Rxjava处理
*/
public static void refreshCallLog(Context context) {
final WeakReference mContext = new WeakReference<>(context);
Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter emitter) throws Exception {
if (hasPermission(mContext.get())) {
List logs = getDataList(mContext.get());
for (CallLogEntity log : logs) {
if (log.getType() == CallLog.Calls.MISSED_TYPE && !callIsHandle(log.getCallId())) {
emitter.onNext(log);
break;
}
}
}
emitter.onComplete();
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer() {
@Override
public void accept(CallLogEntity callLogEntity) throws Exception {
if (callLogEntity == null || mContext.get() == null) return;
createNotify(callLogEntity, mContext.get());
}
});
}
private static void createNotify(CallLogEntity callLogEntity, Context context) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
try {
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String channelId = BuildConfig.APPLICATION_ID.concat("_call");
NotificationChannel notificationChannel = new NotificationChannel(channelId, "未接来电", NotificationManager.IMPORTANCE_DEFAULT);
notificationChannel.enableVibration(false);
notificationChannel.enableLights(false);
notificationChannel.setVibrationPattern(new long[]{0});
notificationChannel.setSound(null, null);
notificationManager.createNotificationChannel(notificationChannel);
builder = new Notification.Builder(context, channelId);
} else {
builder = new Notification.Builder(context);
}
RemoteViews contentView = new RemoteViews(context.getPackageName(),R.layout.view_miss_call_remote_view);
//重拨 此处必须指定接收器类,不能隐式发送
Intent intent_tel = new Intent(context, CallReceiver.class);
intent_tel.setAction("com.evan.intent.action.MISS_CALL_RECALL");
intent_tel.putExtra("phone",callLogEntity.getNumber());
PendingIntent pending_intent_tel = PendingIntent.getBroadcast(context, 2, intent_tel, PendingIntent.FLAG_UPDATE_CURRENT);
contentView.setOnClickPendingIntent(R.id.tel,pending_intent_tel);
//跳转app任意界面
Intent intent_open = new Intent(context, StartActivity.class);
intent_open.putExtra("phone",callLogEntity.getNumber());
PendingIntent pending_open = PendingIntent.getActivity(context, 2, intent_open, PendingIntent.FLAG_UPDATE_CURRENT);
contentView.setOnClickPendingIntent(R.id.open,pending_open);
//标题
contentView.setTextViewText(R.id.title,callLogEntity.getNumber());
//副标题
contentView.setTextViewText(R.id.sub_title,"未接来电 - "+new SimpleDateFormat("MM-dd HH:mm").format(callLogEntity.getDate()));
//构建通知
builder.setTicker("未接来电")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pending_open)
.setAutoCancel(true)
.setOnlyAlertOnce(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setCustomContentView(contentView);
}else {
builder.setContent(contentView);
}
Notification notification = builder.build();
notificationManager.notify(MISS_CALL_NOTIFY_ID, notification);
} catch (Exception e) {
e.printStackTrace();
}
}
//判断是否用读取通话记录权限
private static boolean hasPermission(Context context) {
if (context == null) return false;
if (Build.VERSION.SDK_INT >= 23) {
//1. 检测是否添加权限 PERMISSION_GRANTED 表示已经授权并可以使用
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED) {
return false;
} else {//手机为Android6.0的版本,权限已授权可以使用
return true;
}
} else {//手机为Android6.0以前的版本,可以使用
return true;
}
}
/**
* 读取数据
*
* @return 读取到的数据
*/
private static List getDataList(Context context) {
// 1.获得ContentResolver
ContentResolver resolver = context.getContentResolver();
// 2.利用ContentResolver的query方法查询通话记录数据库
/**
* @param uri 需要查询的URI,(这个URI是ContentProvider提供的)
* @param projection 需要查询的字段
* @param selection sql语句where之后的语句
* @param selectionArgs ?占位符代表的数据
* @param sortOrder 排序方式
*/
Cursor cursor = resolver.query(callUri, // 查询通话记录的URI
columns
, null, null, CallLog.Calls.DEFAULT_SORT_ORDER// 按照时间逆序排列,最近打的最先显示
);
List list = new ArrayList<>();
if (cursor == null) return list;
// 3.通过Cursor获得数据
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex(CallLog.Calls.CACHED_NAME));
String number = cursor.getString(cursor.getColumnIndex(CallLog.Calls.NUMBER));
long dateLong = cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DATE));
int duration = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.DURATION));
int type = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.TYPE));
//type : CallLog.Calls.MISSED_TYPE 未接 CallLog.Calls.INCOMING_TYPE 打入 CallLog.Calls.OUTGOING_TYPE 拨出
int id = cursor.getInt(cursor.getColumnIndex(CallLog.Calls._ID));
if (isMobileNO(number)) {
long day_lead = getTimeDistance(new Date(dateLong), new Date());
if (day_lead < 2) {//只显示48小时以内通话记录,防止通话记录数据过多影响加载速度
CallLogEntity callLog = new CallLogEntity();
callLog.setCallId(id);
callLog.setName(name == null ? "未备注联系人" : name);
callLog.setNumber(number);
callLog.setDate(dateLong);
callLog.setTime(duration);
callLog.setType(type);
list.add(callLog);
} else {
cursor.close();
return list;
}
}
}
cursor.close();
return list;
}
//验证手机号是否正确ֻ
public static boolean isMobileNO(String s) {
Pattern p = Pattern.compile("^(13[0-9]|14[57]|15[0-35-9]|17[6-8]|18[0-9])[0-9]{8}$");
Matcher m = p.matcher(s);
return m.matches();
}
/**
* 获得两个日期间距多少天
*
* @param beginDate
* @param endDate
* @return
*/
public static long getTimeDistance(Date beginDate, Date endDate) {
Calendar fromCalendar = Calendar.getInstance();
fromCalendar.setTime(beginDate);
fromCalendar.set(Calendar.HOUR_OF_DAY, fromCalendar.getMinimum(Calendar.HOUR_OF_DAY));
fromCalendar.set(Calendar.MINUTE, fromCalendar.getMinimum(Calendar.MINUTE));
fromCalendar.set(Calendar.SECOND, fromCalendar.getMinimum(Calendar.SECOND));
fromCalendar.set(Calendar.MILLISECOND, fromCalendar.getMinimum(Calendar.MILLISECOND));
Calendar toCalendar = Calendar.getInstance();
toCalendar.setTime(endDate);
toCalendar.set(Calendar.HOUR_OF_DAY, fromCalendar.getMinimum(Calendar.HOUR_OF_DAY));
toCalendar.set(Calendar.MINUTE, fromCalendar.getMinimum(Calendar.MINUTE));
toCalendar.set(Calendar.SECOND, fromCalendar.getMinimum(Calendar.SECOND));
toCalendar.set(Calendar.MILLISECOND, fromCalendar.getMinimum(Calendar.MILLISECOND));
long dayDistance = (toCalendar.getTime().getTime() - fromCalendar.getTime().getTime()) / DAY;
dayDistance = Math.abs(dayDistance);
return dayDistance;
}
/*取消通知并收起通知栏*/
public static void cancelNotify(){
NotificationManager notificationManager = (NotificationManager) CallShowApplication.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(MISS_CALL_NOTIFY_ID);
try {
Object statusBarManager = CallShowApplication.getContext().getSystemService("statusbar");
Method collapse;
if (Build.VERSION.SDK_INT <= 16) {
collapse = statusBarManager.getClass().getMethod("collapse");
} else {
collapse = statusBarManager.getClass().getMethod("collapsePanels");
}
collapse.invoke(statusBarManager);
} catch (Exception localException) {
localException.printStackTrace();
}
}
/*此方法不必关注,用于初始化数据存储*/
public static void initBox() {
sCallLogBox = ObjectBox.get().boxFor(CallLogEntity.class);
}
/*判断本次通话是否已经发送过通知*/
public static boolean callIsHandle(long callId) {
return sCallLogBox != null &&
sCallLogBox.query()
.equal(CallLogEntity_.callId, callId)
.build().count() > 0;
}
/*记录本次未接通话已经通知过用户,避免重复提示*/
public static void putCallHandle(CallLogEntity callLogEntity) {
if (sCallLogBox == null) initBox();
sCallLogBox.put(callLogEntity);
}
}