Home | History | Annotate | Download | only in telephony
      1 /*
      2  * Copyright (C) 2006 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.internal.telephony;
     18 
     19 import android.content.AsyncQueryHandler;
     20 import android.content.Context;
     21 import android.database.Cursor;
     22 import android.database.SQLException;
     23 import android.net.Uri;
     24 import android.os.Handler;
     25 import android.os.Looper;
     26 import android.os.Message;
     27 import android.provider.ContactsContract.PhoneLookup;
     28 import android.telephony.PhoneNumberUtils;
     29 import android.telephony.TelephonyManager;
     30 import android.text.TextUtils;
     31 import android.util.Log;
     32 
     33 /**
     34  * ASYNCHRONOUS QUERY API
     35  */
     36 
     37 public class CallerInfoAsyncQuery {
     38 
     39     private static final boolean DBG = false;
     40     private static final String LOG_TAG = "CallerInfoAsyncQuery";
     41 
     42     private static final int EVENT_NEW_QUERY = 1;
     43     private static final int EVENT_ADD_LISTENER = 2;
     44     private static final int EVENT_END_OF_QUEUE = 3;
     45     private static final int EVENT_EMERGENCY_NUMBER = 4;
     46     private static final int EVENT_VOICEMAIL_NUMBER = 5;
     47 
     48     private CallerInfoAsyncQueryHandler mHandler;
     49 
     50     /**
     51      * Interface for a CallerInfoAsyncQueryHandler result return.
     52      */
     53     public interface OnQueryCompleteListener {
     54         /**
     55          * Called when the query is complete.
     56          */
     57         public void onQueryComplete(int token, Object cookie, CallerInfo ci);
     58     }
     59 
     60 
     61     /**
     62      * Wrap the cookie from the WorkerArgs with additional information needed by our
     63      * classes.
     64      */
     65     private static final class CookieWrapper {
     66         public OnQueryCompleteListener listener;
     67         public Object cookie;
     68         public int event;
     69         public String number;
     70     }
     71 
     72 
     73     /**
     74      * Simple exception used to communicate problems with the query pool.
     75      */
     76     public static class QueryPoolException extends SQLException {
     77         public QueryPoolException(String error) {
     78             super(error);
     79         }
     80     }
     81 
     82     /**
     83      * Our own implementation of the AsyncQueryHandler.
     84      */
     85     private class CallerInfoAsyncQueryHandler extends AsyncQueryHandler {
     86 
     87         /**
     88          * The information relevant to each CallerInfo query.  Each query may have multiple
     89          * listeners, so each AsyncCursorInfo is associated with 2 or more CookieWrapper
     90          * objects in the queue (one with a new query event, and one with a end event, with
     91          * 0 or more additional listeners in between).
     92          */
     93         private Context mQueryContext;
     94         private Uri mQueryUri;
     95         private CallerInfo mCallerInfo;
     96 
     97         /**
     98          * Our own query worker thread.
     99          *
    100          * This thread handles the messages enqueued in the looper.  The normal sequence
    101          * of events is that a new query shows up in the looper queue, followed by 0 or
    102          * more add listener requests, and then an end request.  Of course, these requests
    103          * can be interlaced with requests from other tokens, but is irrelevant to this
    104          * handler since the handler has no state.
    105          *
    106          * Note that we depend on the queue to keep things in order; in other words, the
    107          * looper queue must be FIFO with respect to input from the synchronous startQuery
    108          * calls and output to this handleMessage call.
    109          *
    110          * This use of the queue is required because CallerInfo objects may be accessed
    111          * multiple times before the query is complete.  All accesses (listeners) must be
    112          * queued up and informed in order when the query is complete.
    113          */
    114         protected class CallerInfoWorkerHandler extends WorkerHandler {
    115             public CallerInfoWorkerHandler(Looper looper) {
    116                 super(looper);
    117             }
    118 
    119             @Override
    120             public void handleMessage(Message msg) {
    121                 WorkerArgs args = (WorkerArgs) msg.obj;
    122                 CookieWrapper cw = (CookieWrapper) args.cookie;
    123 
    124                 if (cw == null) {
    125                     // Normally, this should never be the case for calls originating
    126                     // from within this code.
    127                     // However, if there is any code that this Handler calls (such as in
    128                     // super.handleMessage) that DOES place unexpected messages on the
    129                     // queue, then we need pass these messages on.
    130                     if (DBG) log("Unexpected command (CookieWrapper is null): " + msg.what +
    131                             " ignored by CallerInfoWorkerHandler, passing onto parent.");
    132 
    133                     super.handleMessage(msg);
    134                 } else {
    135 
    136                     if (DBG) log("Processing event: " + cw.event + " token (arg1): " + msg.arg1 +
    137                             " command: " + msg.what + " query URI: " + args.uri);
    138 
    139                     switch (cw.event) {
    140                         case EVENT_NEW_QUERY:
    141                             //start the sql command.
    142                             super.handleMessage(msg);
    143                             break;
    144 
    145                         // shortcuts to avoid query for recognized numbers.
    146                         case EVENT_EMERGENCY_NUMBER:
    147                         case EVENT_VOICEMAIL_NUMBER:
    148 
    149                         case EVENT_ADD_LISTENER:
    150                         case EVENT_END_OF_QUEUE:
    151                             // query was already completed, so just send the reply.
    152                             // passing the original token value back to the caller
    153                             // on top of the event values in arg1.
    154                             Message reply = args.handler.obtainMessage(msg.what);
    155                             reply.obj = args;
    156                             reply.arg1 = msg.arg1;
    157 
    158                             reply.sendToTarget();
    159 
    160                             break;
    161                         default:
    162                     }
    163                 }
    164             }
    165         }
    166 
    167 
    168         /**
    169          * Asynchronous query handler class for the contact / callerinfo object.
    170          */
    171         private CallerInfoAsyncQueryHandler(Context context) {
    172             super(context.getContentResolver());
    173         }
    174 
    175         @Override
    176         protected Handler createHandler(Looper looper) {
    177             return new CallerInfoWorkerHandler(looper);
    178         }
    179 
    180         /**
    181          * Overrides onQueryComplete from AsyncQueryHandler.
    182          *
    183          * This method takes into account the state of this class; we construct the CallerInfo
    184          * object only once for each set of listeners. When the query thread has done its work
    185          * and calls this method, we inform the remaining listeners in the queue, until we're
    186          * out of listeners.  Once we get the message indicating that we should expect no new
    187          * listeners for this CallerInfo object, we release the AsyncCursorInfo back into the
    188          * pool.
    189          */
    190         @Override
    191         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    192             if (DBG) log("query complete for token: " + token);
    193 
    194             //get the cookie and notify the listener.
    195             CookieWrapper cw = (CookieWrapper) cookie;
    196             if (cw == null) {
    197                 // Normally, this should never be the case for calls originating
    198                 // from within this code.
    199                 // However, if there is any code that calls this method, we should
    200                 // check the parameters to make sure they're viable.
    201                 if (DBG) log("Cookie is null, ignoring onQueryComplete() request.");
    202                 return;
    203             }
    204 
    205             if (cw.event == EVENT_END_OF_QUEUE) {
    206                 release();
    207                 return;
    208             }
    209 
    210             // check the token and if needed, create the callerinfo object.
    211             if (mCallerInfo == null) {
    212                 if ((mQueryContext == null) || (mQueryUri == null)) {
    213                     throw new QueryPoolException
    214                             ("Bad context or query uri, or CallerInfoAsyncQuery already released.");
    215                 }
    216 
    217                 // adjust the callerInfo data as needed, and only if it was set from the
    218                 // initial query request.
    219                 // Change the callerInfo number ONLY if it is an emergency number or the
    220                 // voicemail number, and adjust other data (including photoResource)
    221                 // accordingly.
    222                 if (cw.event == EVENT_EMERGENCY_NUMBER) {
    223                     // Note we're setting the phone number here (refer to javadoc
    224                     // comments at the top of CallerInfo class).
    225                     mCallerInfo = new CallerInfo().markAsEmergency(mQueryContext);
    226                 } else if (cw.event == EVENT_VOICEMAIL_NUMBER) {
    227                     mCallerInfo = new CallerInfo().markAsVoiceMail();
    228                 } else {
    229                     mCallerInfo = CallerInfo.getCallerInfo(mQueryContext, mQueryUri, cursor);
    230                     // Use the number entered by the user for display.
    231                     if (!TextUtils.isEmpty(cw.number)) {
    232                         mCallerInfo.phoneNumber = PhoneNumberUtils.formatNumber(cw.number);
    233                     }
    234                 }
    235 
    236                 if (DBG) log("constructing CallerInfo object for token: " + token);
    237 
    238                 //notify that we can clean up the queue after this.
    239                 CookieWrapper endMarker = new CookieWrapper();
    240                 endMarker.event = EVENT_END_OF_QUEUE;
    241                 startQuery (token, endMarker, null, null, null, null, null);
    242             }
    243 
    244             //notify the listener that the query is complete.
    245             if (cw.listener != null) {
    246                 if (DBG) log("notifying listener: " + cw.listener.getClass().toString() +
    247                              " for token: " + token + mCallerInfo);
    248                 cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo);
    249             }
    250         }
    251     }
    252 
    253     /**
    254      * Private constructor for factory methods.
    255      */
    256     private CallerInfoAsyncQuery() {
    257     }
    258 
    259 
    260     /**
    261      * Factory method to start query with a Uri query spec
    262      */
    263     public static CallerInfoAsyncQuery startQuery(int token, Context context, Uri contactRef,
    264             OnQueryCompleteListener listener, Object cookie) {
    265 
    266         CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
    267         c.allocate(context, contactRef);
    268 
    269         if (DBG) log("starting query for URI: " + contactRef + " handler: " + c.toString());
    270 
    271         //create cookieWrapper, start query
    272         CookieWrapper cw = new CookieWrapper();
    273         cw.listener = listener;
    274         cw.cookie = cookie;
    275         cw.event = EVENT_NEW_QUERY;
    276 
    277         c.mHandler.startQuery (token, cw, contactRef, null, null, null, null);
    278 
    279         return c;
    280     }
    281 
    282     /**
    283      * Factory method to start query with a number
    284      */
    285     public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,
    286             OnQueryCompleteListener listener, Object cookie) {
    287         //contruct the URI object and start Query.
    288         Uri contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
    289 
    290         CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
    291         c.allocate(context, contactRef);
    292 
    293         if (DBG) log("starting query for number: " + number + " handler: " + c.toString());
    294 
    295         //create cookieWrapper, start query
    296         CookieWrapper cw = new CookieWrapper();
    297         cw.listener = listener;
    298         cw.cookie = cookie;
    299         cw.number = number;
    300 
    301         // check to see if these are recognized numbers, and use shortcuts if we can.
    302         if (PhoneNumberUtils.isEmergencyNumber(number)) {
    303             cw.event = EVENT_EMERGENCY_NUMBER;
    304         } else if (PhoneNumberUtils.isVoiceMailNumber(number)) {
    305             cw.event = EVENT_VOICEMAIL_NUMBER;
    306         } else {
    307             cw.event = EVENT_NEW_QUERY;
    308         }
    309 
    310         c.mHandler.startQuery (token, cw, contactRef, null, null, null, null);
    311 
    312         return c;
    313    }
    314 
    315     /**
    316      * Method to add listeners to a currently running query
    317      */
    318     public void addQueryListener(int token, OnQueryCompleteListener listener, Object cookie) {
    319 
    320         if (DBG) log("adding listener to query: " + mHandler.mQueryUri + " handler: " +
    321                 mHandler.toString());
    322 
    323         //create cookieWrapper, add query request to end of queue.
    324         CookieWrapper cw = new CookieWrapper();
    325         cw.listener = listener;
    326         cw.cookie = cookie;
    327         cw.event = EVENT_ADD_LISTENER;
    328 
    329         mHandler.startQuery (token, cw, null, null, null, null, null);
    330     }
    331 
    332     /**
    333      * Method to create a new CallerInfoAsyncQueryHandler object, ensuring correct
    334      * state of context and uri.
    335      */
    336     private void allocate (Context context, Uri contactRef) {
    337         if ((context == null) || (contactRef == null)){
    338             throw new QueryPoolException("Bad context or query uri.");
    339         }
    340         mHandler = new CallerInfoAsyncQueryHandler(context);
    341         mHandler.mQueryContext = context;
    342         mHandler.mQueryUri = contactRef;
    343     }
    344 
    345     /**
    346      * Releases the relevant data.
    347      */
    348     private void release () {
    349         mHandler.mQueryContext = null;
    350         mHandler.mQueryUri = null;
    351         mHandler.mCallerInfo = null;
    352         mHandler = null;
    353     }
    354 
    355     /**
    356      * static logging method
    357      */
    358     private static void log(String msg) {
    359         Log.d(LOG_TAG, msg);
    360     }
    361 }
    362