1 /* 2 * Copyright (c) 2016, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.car.stream.telecom; 17 18 import android.content.Context; 19 import android.content.CursorLoader; 20 import android.content.Loader; 21 import android.content.pm.PackageManager; 22 import android.database.Cursor; 23 import android.net.Uri; 24 import android.provider.CallLog; 25 import android.text.TextUtils; 26 import android.text.format.DateUtils; 27 import android.util.Log; 28 import com.android.car.stream.StreamCard; 29 import com.android.car.stream.StreamProducer; 30 31 import java.util.ArrayList; 32 import java.util.List; 33 34 /** 35 * Loads recent calls from the call log and produces a {@link StreamCard} for each entry. 36 */ 37 public class RecentCallStreamProducer extends StreamProducer 38 implements Loader.OnLoadCompleteListener<Cursor> { 39 private static final String TAG = "RecentCallProducer"; 40 private static final long RECENT_CALL_TIME_RANGE = 6 * DateUtils.HOUR_IN_MILLIS; 41 42 /** Number of call log items to query for */ 43 private static final int CALL_LOG_QUERY_LIMIT = 1; 44 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 45 46 private CursorLoader mCursorLoader; 47 private StreamCard mCurrentStreamCard; 48 private long mCurrentNumber; 49 private RecentCallConverter mConverter = new RecentCallConverter(); 50 RecentCallStreamProducer(Context context)51 public RecentCallStreamProducer(Context context) { 52 super(context); 53 mCursorLoader = createCallLogLoader(); 54 } 55 56 @Override start()57 public void start() { 58 super.start(); 59 if (!hasReadCallLogPermission()) { 60 if (Log.isLoggable(TAG, Log.DEBUG)) { 61 Log.d(TAG, "Could not onStart RecentCallStreamProducer, permissions not granted"); 62 } 63 return; 64 } 65 66 if (!mCursorLoader.isStarted()) { 67 mCursorLoader.startLoading(); 68 } 69 } 70 71 @Override stop()72 public void stop() { 73 if (mCursorLoader.isStarted()) { 74 mCursorLoader.stopLoading(); 75 removeCard(mCurrentStreamCard); 76 mCurrentStreamCard = null; 77 mCurrentNumber = 0; 78 } 79 super.stop(); 80 } 81 82 @Override onLoadComplete(Loader<Cursor> loader, Cursor cursor)83 public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) { 84 if (cursor == null || !cursor.moveToFirst()) { 85 return; 86 } 87 88 int column = cursor.getColumnIndex(CallLog.Calls.NUMBER); 89 String number = cursor.getString(column); 90 column = cursor.getColumnIndex(CallLog.Calls.DATE); 91 long callTimeMs = cursor.getLong(column); 92 // Display if we have a phone number, and the call was within 6hours. 93 number = number.replaceAll("[^0-9]", ""); 94 long timestamp = System.currentTimeMillis(); 95 long digits = Long.parseLong(number); 96 97 if (!TextUtils.isEmpty(number) && 98 (timestamp - callTimeMs) < RECENT_CALL_TIME_RANGE) { 99 if (mCurrentStreamCard == null || mCurrentNumber != digits) { 100 removeCard(mCurrentStreamCard); 101 mCurrentStreamCard = mConverter.createStreamCard(mContext, number, timestamp); 102 mCurrentNumber = digits; 103 postCard(mCurrentStreamCard); 104 } 105 } 106 } 107 hasReadCallLogPermission()108 private boolean hasReadCallLogPermission() { 109 return mContext.checkSelfPermission(android.Manifest.permission.READ_CALL_LOG) 110 == PackageManager.PERMISSION_GRANTED; 111 } 112 113 /** 114 * Creates a CursorLoader for Call data. 115 * Note: NOT to be used with LoaderManagers. 116 */ createCallLogLoader()117 private CursorLoader createCallLogLoader() { 118 // We need to check for NULL explicitly otherwise entries with where READ is NULL 119 // may not match either the query or its negation. 120 // We consider the calls that are not yet consumed (i.e. IS_READ = 0) as "new". 121 StringBuilder where = new StringBuilder(); 122 List<String> selectionArgs = new ArrayList<String>(); 123 124 String selection = where.length() > 0 ? where.toString() : null; 125 Uri uri = CallLog.Calls.CONTENT_URI.buildUpon() 126 .appendQueryParameter(CallLog.Calls.LIMIT_PARAM_KEY, 127 Integer.toString(CALL_LOG_QUERY_LIMIT)) 128 .build(); 129 CursorLoader loader = new CursorLoader(mContext, uri, null, selection, 130 selectionArgs.toArray(EMPTY_STRING_ARRAY), CallLog.Calls.DEFAULT_SORT_ORDER); 131 loader.registerListener(0, this /* OnLoadCompleteListener */); 132 return loader; 133 } 134 135 } 136