1 /* 2 * Copyright (C) 2010 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 17 package com.android.phone; 18 import android.content.Context; 19 import android.net.Uri; 20 import android.os.AsyncTask; 21 import android.os.Looper; 22 import android.provider.CallLog.Calls; 23 import android.util.Log; 24 import com.android.internal.telephony.CallerInfo; 25 26 /** 27 * Class to access the call logs database asynchronously since 28 * database ops can take a long time depending on the system's load. 29 * It uses AsyncTask which has its own thread pool. 30 * 31 * <pre class="prettyprint"> 32 * Typical usage: 33 * ============== 34 * 35 * // From an activity... 36 * String mLastNumber = ""; 37 * 38 * CallLogAsync log = new CallLogAsync(); 39 * 40 * CallLogAsync.AddCallArgs addCallArgs = new CallLogAsync.AddCallArgs( 41 * this, ci, number, presentation, type, timestamp, duration); 42 * 43 * log.addCall(addCallArgs); 44 * 45 * CallLogAsync.GetLastOutgoingCallArgs lastCallArgs = new CallLogAsync.GetLastOutgoingCallArgs( 46 * this, new CallLogAsync.OnLastOutgoingCallComplete() { 47 * public void lastOutgoingCall(String number) { mLastNumber = number; } 48 * }); 49 * log.getLastOutgoingCall(lastCallArgs); 50 * </pre> 51 * 52 */ 53 54 public class CallLogAsync { 55 private static final String TAG = "CallLogAsync"; 56 57 /** 58 * Parameter object to hold the args to add a call in the call log DB. 59 */ 60 public static class AddCallArgs { 61 /** 62 * @param ci CallerInfo. 63 * @param number To be logged. 64 * @param presentation Of the number. 65 * @param callType The type of call (e.g INCOMING_TYPE). @see 66 * android.provider.CallLog for the list of values. 67 * @param timestamp Of the call (millisecond since epoch). 68 * @param durationInMillis Of the call (millisecond). 69 */ AddCallArgs(Context context, CallerInfo ci, String number, int presentation, int callType, long timestamp, long durationInMillis)70 public AddCallArgs(Context context, 71 CallerInfo ci, 72 String number, 73 int presentation, 74 int callType, 75 long timestamp, 76 long durationInMillis) { 77 // Note that the context is passed each time. We could 78 // have stored it in a member but we've run into a bunch 79 // of memory leaks in the past that resulted from storing 80 // references to contexts in places that were long lived 81 // when the contexts were expected to be short lived. For 82 // example, if you initialize this class with an Activity 83 // instead of an Application the Activity can't be GCed 84 // until this class can, and Activities tend to hold 85 // references to large amounts of RAM for things like the 86 // bitmaps in their views. 87 // 88 // Having hit more than a few of those bugs in the past 89 // we've grown cautious of storing references to Contexts 90 // when it's not very clear that the thing holding the 91 // references is tightly tied to the Context, for example 92 // Views the Activity is displaying. 93 94 this.context = context; 95 this.ci = ci; 96 this.number = number; 97 this.presentation = presentation; 98 this.callType = callType; 99 this.timestamp = timestamp; 100 this.durationInSec = (int)(durationInMillis / 1000); 101 } 102 // Since the members are accessed directly, we don't use the 103 // mXxxx notation. 104 public final Context context; 105 public final CallerInfo ci; 106 public final String number; 107 public final int presentation; 108 public final int callType; 109 public final long timestamp; 110 public final int durationInSec; 111 } 112 113 /** 114 * Parameter object to hold the args to get the last outgoing call 115 * from the call log DB. 116 */ 117 public static class GetLastOutgoingCallArgs { GetLastOutgoingCallArgs(Context context, OnLastOutgoingCallComplete callback)118 public GetLastOutgoingCallArgs(Context context, 119 OnLastOutgoingCallComplete callback) { 120 this.context = context; 121 this.callback = callback; 122 } 123 public final Context context; 124 public final OnLastOutgoingCallComplete callback; 125 } 126 127 /** 128 * Non blocking version of CallLog.addCall(...) 129 */ addCall(AddCallArgs args)130 public AsyncTask addCall(AddCallArgs args) { 131 assertUiThread(); 132 return new AddCallTask().execute(args); 133 } 134 135 /** Interface to retrieve the last dialed number asynchronously. */ 136 public interface OnLastOutgoingCallComplete { 137 /** @param number The last dialed number or an empty string if 138 * none exists yet. */ lastOutgoingCall(String number)139 void lastOutgoingCall(String number); 140 } 141 142 /** 143 * CallLog.getLastOutgoingCall(...) 144 */ getLastOutgoingCall(GetLastOutgoingCallArgs args)145 public AsyncTask getLastOutgoingCall(GetLastOutgoingCallArgs args) { 146 assertUiThread(); 147 return new GetLastOutgoingCallTask(args.callback).execute(args); 148 } 149 150 /** 151 * AsyncTask to save calls in the DB. 152 */ 153 private class AddCallTask extends AsyncTask<AddCallArgs, Void, Uri[]> { 154 @Override doInBackground(AddCallArgs... callList)155 protected Uri[] doInBackground(AddCallArgs... callList) { 156 int count = callList.length; 157 Uri[] result = new Uri[count]; 158 for (int i = 0; i < count; i++) { 159 AddCallArgs c = callList[i]; 160 161 try { 162 // May block. 163 result[i] = Calls.addCall( 164 c.ci, c.context, c.number, c.presentation, 165 c.callType, c.timestamp, c.durationInSec); 166 } catch (Exception e) { 167 // This must be very rare but may happen in legitimate cases. 168 // e.g. If the phone is encrypted and thus write request fails, it may 169 // cause some kind of Exception (right now it is IllegalArgumentException, but 170 // might change). 171 // 172 // We don't want to crash the whole process just because of that. 173 // Let's just ignore it and leave logs instead. 174 Log.e(TAG, "Exception raised during adding CallLog entry: " + e); 175 result[i] = null; 176 } 177 } 178 return result; 179 } 180 181 // Perform a simple sanity check to make sure the call was 182 // written in the database. Typically there is only one result 183 // per call so it is easy to identify which one failed. 184 @Override onPostExecute(Uri[] result)185 protected void onPostExecute(Uri[] result) { 186 for (Uri uri : result) { 187 if (uri == null) { 188 Log.e(TAG, "Failed to write call to the log."); 189 } 190 } 191 } 192 } 193 194 /** 195 * AsyncTask to get the last outgoing call from the DB. 196 */ 197 private class GetLastOutgoingCallTask extends AsyncTask<GetLastOutgoingCallArgs, Void, String> { 198 private final OnLastOutgoingCallComplete mCallback; 199 private String mNumber; GetLastOutgoingCallTask(OnLastOutgoingCallComplete callback)200 public GetLastOutgoingCallTask(OnLastOutgoingCallComplete callback) { 201 mCallback = callback; 202 } 203 204 // Happens on a background thread. We cannot run the callback 205 // here because only the UI thread can modify the view 206 // hierarchy (e.g enable/disable the dial button). The 207 // callback is ran rom the post execute method. 208 @Override doInBackground(GetLastOutgoingCallArgs... list)209 protected String doInBackground(GetLastOutgoingCallArgs... list) { 210 int count = list.length; 211 String number = ""; 212 for (GetLastOutgoingCallArgs args : list) { 213 // May block. Select only the last one. 214 number = Calls.getLastOutgoingCall(args.context); 215 } 216 return number; // passed to the onPostExecute method. 217 } 218 219 // Happens on the UI thread, it is safe to run the callback 220 // that may do some work on the views. 221 @Override onPostExecute(String number)222 protected void onPostExecute(String number) { 223 assertUiThread(); 224 mCallback.lastOutgoingCall(number); 225 } 226 } 227 assertUiThread()228 private void assertUiThread() { 229 if (!Looper.getMainLooper().equals(Looper.myLooper())) { 230 throw new RuntimeException("Not on the UI thread!"); 231 } 232 } 233 } 234