• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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