此文非教程,作常用工具類記錄,因此主要是代碼---
--前言
公司項目有這麼一個需求,獲取用戶的通話記錄,如果存在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<CallLogEntity> 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<CallLogEntity> 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裏面的控件通知欄是不會收起來的,通知也不會取消,都需要手動操作
收起通知需要聲明此權限:
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
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<CallLogEntity> 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<Context> mContext = new WeakReference<>(context);
Observable.create(new ObservableOnSubscribe<CallLogEntity>() {
@Override
public void subscribe(ObservableEmitter<CallLogEntity> emitter) throws Exception {
if (hasPermission(mContext.get())) {
List<CallLogEntity> 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<CallLogEntity>() {
@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<CallLogEntity> 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<CallLogEntity> 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);
}
}