Home | History | Annotate | Download | only in phone
      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          */
     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 {
    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      */
    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. */
    139         void lastOutgoingCall(String number);
    140     }
    141 
    142     /**
    143      * CallLog.getLastOutgoingCall(...)
    144      */
    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
    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
    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;
    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
    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
    222         protected void onPostExecute(String number) {
    223             assertUiThread();
    224             mCallback.lastOutgoingCall(number);
    225         }
    226     }
    227 
    228     private void assertUiThread() {
    229         if (!Looper.getMainLooper().equals(Looper.myLooper())) {
    230             throw new RuntimeException("Not on the UI thread!");
    231         }
    232     }
    233 }
    234