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
17package com.android.phone;
18import android.content.Context;
19import android.net.Uri;
20import android.os.AsyncTask;
21import android.os.Looper;
22import android.provider.CallLog.Calls;
23import android.util.Log;
24import 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
54public 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