17ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee/* 27ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Copyright (C) 2006 The Android Open Source Project 37ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * 47ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Licensed under the Apache License, Version 2.0 (the "License"); 57ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * you may not use this file except in compliance with the License. 67ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * You may obtain a copy of the License at 77ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * 87ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * http://www.apache.org/licenses/LICENSE-2.0 97ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * 107ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Unless required by applicable law or agreed to in writing, software 117ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * distributed under the License is distributed on an "AS IS" BASIS, 127ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * See the License for the specific language governing permissions and 147ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * limitations under the License. 157ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 167ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 177ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Leepackage com.android.incallui; 187ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 197ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Leeimport android.content.AsyncQueryHandler; 207ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Leeimport android.content.Context; 217ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Leeimport android.database.Cursor; 227ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Leeimport android.database.SQLException; 237ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Leeimport android.net.Uri; 247ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Leeimport android.os.Handler; 257ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Leeimport android.os.Looper; 267ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Leeimport android.os.Message; 278e64fce150b131468942087a59b5f6fd3c295562Makoto Onukiimport android.provider.ContactsContract; 287ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Leeimport android.provider.ContactsContract.PhoneLookup; 2904fb50696f53d728d9c9175c786544427c20e381Andrew Leeimport android.telephony.PhoneNumberUtils; 307ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Leeimport android.text.TextUtils; 317ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 3255e998472730cae27a82eed49cf7b020699b9286Christine Chenimport com.android.contacts.common.util.PhoneNumberHelper; 3355e998472730cae27a82eed49cf7b020699b9286Christine Chenimport com.android.contacts.common.util.TelephonyManagerUtils; 3455e998472730cae27a82eed49cf7b020699b9286Christine Chen 358e64fce150b131468942087a59b5f6fd3c295562Makoto Onukiimport java.util.Arrays; 3655e998472730cae27a82eed49cf7b020699b9286Christine Chenimport java.util.Locale; 3755e998472730cae27a82eed49cf7b020699b9286Christine Chen 387ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee/** 397ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Helper class to make it easier to run asynchronous caller-id lookup queries. 407ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * @see CallerInfo 417ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * 427ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 437ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Leepublic class CallerInfoAsyncQuery { 447ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private static final boolean DBG = false; 457ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private static final String LOG_TAG = "CallerInfoAsyncQuery"; 467ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 477ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private static final int EVENT_NEW_QUERY = 1; 487ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private static final int EVENT_ADD_LISTENER = 2; 497ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private static final int EVENT_END_OF_QUEUE = 3; 507ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private static final int EVENT_EMERGENCY_NUMBER = 4; 517ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private static final int EVENT_VOICEMAIL_NUMBER = 5; 527ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 537ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private CallerInfoAsyncQueryHandler mHandler; 547ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 557ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // If the CallerInfo query finds no contacts, should we use the 567ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // PhoneNumberOfflineGeocoder to look up a "geo description"? 577ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // (TODO: This could become a flag in config.xml if it ever needs to be 587ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // configured on a per-product basis.) 597ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private static final boolean ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION = true; 607ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 617ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee /** 627ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Interface for a CallerInfoAsyncQueryHandler result return. 637ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 647ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee public interface OnQueryCompleteListener { 657ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee /** 667ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Called when the query is complete. 677ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 687ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee public void onQueryComplete(int token, Object cookie, CallerInfo ci); 697ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 707ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 717ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 727ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee /** 737ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Wrap the cookie from the WorkerArgs with additional information needed by our 747ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * classes. 757ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 767ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private static final class CookieWrapper { 777ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee public OnQueryCompleteListener listener; 787ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee public Object cookie; 797ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee public int event; 807ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee public String number; 817ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 827ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 837ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 847ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee /** 857ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Simple exception used to communicate problems with the query pool. 867ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 877ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee public static class QueryPoolException extends SQLException { 887ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee public QueryPoolException(String error) { 897ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee super(error); 907ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 917ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 927ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 937ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee /** 947ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Our own implementation of the AsyncQueryHandler. 957ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 967ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private class CallerInfoAsyncQueryHandler extends AsyncQueryHandler { 977ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 988e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki @Override 998e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki public void startQuery(int token, Object cookie, Uri uri, String[] projection, 1008e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki String selection, String[] selectionArgs, String orderBy) { 1018e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki if (DBG) { 1028e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki // Show stack trace with the arguments. 1038e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki android.util.Log.d(LOG_TAG, "InCall: startQuery: url=" + uri + 1048e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki " projection=[" + Arrays.toString(projection) + "]" + 1058e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki " selection=" + selection + " " + 1068e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki " args=[" + Arrays.toString(selectionArgs) + "]", 1078e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki new RuntimeException("STACKTRACE")); 1088e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki } 1098e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki super.startQuery(token, cookie, uri, projection, selection, selectionArgs, orderBy); 1108e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki } 1118e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki 1127ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee /** 1137ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * The information relevant to each CallerInfo query. Each query may have multiple 1147ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * listeners, so each AsyncCursorInfo is associated with 2 or more CookieWrapper 1157ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * objects in the queue (one with a new query event, and one with a end event, with 1167ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * 0 or more additional listeners in between). 1177ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 1187ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private Context mQueryContext; 1197ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private Uri mQueryUri; 1207ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private CallerInfo mCallerInfo; 1217ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 1227ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee /** 1237ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Our own query worker thread. 1247ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * 1257ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * This thread handles the messages enqueued in the looper. The normal sequence 1267ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * of events is that a new query shows up in the looper queue, followed by 0 or 1277ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * more add listener requests, and then an end request. Of course, these requests 1287ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * can be interlaced with requests from other tokens, but is irrelevant to this 1297ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * handler since the handler has no state. 1307ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * 1317ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Note that we depend on the queue to keep things in order; in other words, the 1327ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * looper queue must be FIFO with respect to input from the synchronous startQuery 1337ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * calls and output to this handleMessage call. 1347ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * 1357ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * This use of the queue is required because CallerInfo objects may be accessed 1367ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * multiple times before the query is complete. All accesses (listeners) must be 1377ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * queued up and informed in order when the query is complete. 1387ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 1397ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee protected class CallerInfoWorkerHandler extends WorkerHandler { 1407ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee public CallerInfoWorkerHandler(Looper looper) { 1417ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee super(looper); 1427ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 1437ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 1447ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee @Override 1457ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee public void handleMessage(Message msg) { 1467ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee WorkerArgs args = (WorkerArgs) msg.obj; 1477ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee CookieWrapper cw = (CookieWrapper) args.cookie; 1487ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 1497ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee if (cw == null) { 1507ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // Normally, this should never be the case for calls originating 1517ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // from within this code. 1527ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // However, if there is any code that this Handler calls (such as in 1537ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // super.handleMessage) that DOES place unexpected messages on the 1547ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // queue, then we need pass these messages on. 1551a7f2bcab2d2023f2ee4cfb0bc57bc265b5aab87Chiao Cheng Log.d(this, "Unexpected command (CookieWrapper is null): " + msg.what + 1567ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee " ignored by CallerInfoWorkerHandler, passing onto parent."); 1577ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 1587ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee super.handleMessage(msg); 1597ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } else { 1607ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 1611a7f2bcab2d2023f2ee4cfb0bc57bc265b5aab87Chiao Cheng Log.d(this, "Processing event: " + cw.event + " token (arg1): " + msg.arg1 + 1621a7f2bcab2d2023f2ee4cfb0bc57bc265b5aab87Chiao Cheng " command: " + msg.what + " query URI: " + 1631a7f2bcab2d2023f2ee4cfb0bc57bc265b5aab87Chiao Cheng sanitizeUriToString(args.uri)); 1647ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 1657ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee switch (cw.event) { 1667ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee case EVENT_NEW_QUERY: 1677ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee //start the sql command. 1687ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee super.handleMessage(msg); 1697ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee break; 1707ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 1717ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // shortcuts to avoid query for recognized numbers. 1727ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee case EVENT_EMERGENCY_NUMBER: 1737ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee case EVENT_VOICEMAIL_NUMBER: 1747ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 1757ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee case EVENT_ADD_LISTENER: 1767ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee case EVENT_END_OF_QUEUE: 1777ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // query was already completed, so just send the reply. 1787ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // passing the original token value back to the caller 1797ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // on top of the event values in arg1. 1807ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee Message reply = args.handler.obtainMessage(msg.what); 1817ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee reply.obj = args; 1827ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee reply.arg1 = msg.arg1; 1837ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 1847ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee reply.sendToTarget(); 1857ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 1867ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee break; 1877ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee default: 1887ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 1897ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 1907ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 1917ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 1927ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 1937ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 1947ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee /** 1957ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Asynchronous query handler class for the contact / callerinfo object. 1967ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 1977ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private CallerInfoAsyncQueryHandler(Context context) { 1987ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee super(context.getContentResolver()); 1997ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 2007ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 2017ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee @Override 2027ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee protected Handler createHandler(Looper looper) { 2037ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee return new CallerInfoWorkerHandler(looper); 2047ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 2057ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 2067ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee /** 2077ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Overrides onQueryComplete from AsyncQueryHandler. 2087ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * 2097ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * This method takes into account the state of this class; we construct the CallerInfo 2107ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * object only once for each set of listeners. When the query thread has done its work 2117ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * and calls this method, we inform the remaining listeners in the queue, until we're 2127ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * out of listeners. Once we get the message indicating that we should expect no new 2137ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * listeners for this CallerInfo object, we release the AsyncCursorInfo back into the 2147ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * pool. 2157ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 2167ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee @Override 2177ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 218eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner try { 219eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner Log.d(this, "##### onQueryComplete() ##### query complete for token: " + token); 2207ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 221eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner //get the cookie and notify the listener. 222eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner CookieWrapper cw = (CookieWrapper) cookie; 223eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner if (cw == null) { 224eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // Normally, this should never be the case for calls originating 225eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // from within this code. 226eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // However, if there is any code that calls this method, we should 227eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // check the parameters to make sure they're viable. 228eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner Log.d(this, "Cookie is null, ignoring onQueryComplete() request."); 229eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner return; 230eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner } 2317ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 232eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner if (cw.event == EVENT_END_OF_QUEUE) { 233eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner release(); 234eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner return; 2357ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 2367ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 237eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // check the token and if needed, create the callerinfo object. 238eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner if (mCallerInfo == null) { 239eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner if ((mQueryContext == null) || (mQueryUri == null)) { 240eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner throw new QueryPoolException 241eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner ("Bad context or query uri, or CallerInfoAsyncQuery already released."); 2427ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 2437ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 244eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // adjust the callerInfo data as needed, and only if it was set from the 245eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // initial query request. 246eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // Change the callerInfo number ONLY if it is an emergency number or the 247eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // voicemail number, and adjust other data (including photoResource) 248eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // accordingly. 249eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner if (cw.event == EVENT_EMERGENCY_NUMBER) { 250eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // Note we're setting the phone number here (refer to javadoc 251eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // comments at the top of CallerInfo class). 252eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner mCallerInfo = new CallerInfo().markAsEmergency(mQueryContext); 253eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner } else if (cw.event == EVENT_VOICEMAIL_NUMBER) { 254eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner mCallerInfo = new CallerInfo().markAsVoiceMail(mQueryContext); 255eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner } else { 256eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner mCallerInfo = CallerInfo.getCallerInfo(mQueryContext, mQueryUri, cursor); 257eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner Log.d(this, "==> Got mCallerInfo: " + mCallerInfo); 258eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner 259eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner CallerInfo newCallerInfo = CallerInfo.doSecondaryLookupIfNecessary( 260eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner mQueryContext, cw.number, mCallerInfo); 261eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner if (newCallerInfo != mCallerInfo) { 262eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner mCallerInfo = newCallerInfo; 263eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner Log.d(this, "#####async contact look up with numeric username" 264eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner + mCallerInfo); 265eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner } 266eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner 267eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // Final step: look up the geocoded description. 268eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner if (ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION) { 269eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // Note we do this only if we *don't* have a valid name (i.e. if 270eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // no contacts matched the phone number of the incoming call), 271eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // since that's the only case where the incoming-call UI cares 272eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // about this field. 273eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // 274eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // (TODO: But if we ever want the UI to show the geoDescription 275eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // even when we *do* match a contact, we'll need to either call 276eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // updateGeoDescription() unconditionally here, or possibly add a 277eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // new parameter to CallerInfoAsyncQuery.startQuery() to force 278eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // the geoDescription field to be populated.) 279eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner 280eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner if (TextUtils.isEmpty(mCallerInfo.name)) { 281eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // Actually when no contacts match the incoming phone number, 282eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // the CallerInfo object is totally blank here (i.e. no name 283eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // *or* phoneNumber). So we need to pass in cw.number as 284eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // a fallback number. 285eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner mCallerInfo.updateGeoDescription(mQueryContext, cw.number); 286eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner } 2877ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 2887ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 289eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // Use the number entered by the user for display. 290eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner if (!TextUtils.isEmpty(cw.number)) { 291eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner mCallerInfo.phoneNumber = PhoneNumberHelper.formatNumber(cw.number, 292eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner mCallerInfo.normalizedNumber, 293eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner TelephonyManagerUtils.getCurrentCountryIso(mQueryContext, 294eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner Locale.getDefault())); 295eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner } 2967ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 2977ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 298eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner Log.d(this, "constructing CallerInfo object for token: " + token); 2997ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 300eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner //notify that we can clean up the queue after this. 301eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner CookieWrapper endMarker = new CookieWrapper(); 302eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner endMarker.event = EVENT_END_OF_QUEUE; 303eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner startQuery(token, endMarker, null, null, null, null, null); 304eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner } 3057ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 306eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner //notify the listener that the query is complete. 307eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner if (cw.listener != null) { 308eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner Log.d(this, "notifying listener: " + cw.listener.getClass().toString() + 309eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner " for token: " + token + mCallerInfo); 310eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo); 311eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner } 312eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner } finally { 313eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner // The cursor may have been closed in CallerInfo.getCallerInfo() 314eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner if (cursor != null && !cursor.isClosed()) { 315eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner cursor.close(); 316eb1ee099bd4af3e18e7d42500e2ed318452328b0Jay Shrauner } 3177ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 3187ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 3197ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 3207ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 3217ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee /** 3227ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Private constructor for factory methods. 3237ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 3247ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private CallerInfoAsyncQuery() { 3257ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 3267ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 3277ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee /** 3285ea0dcd39ff1f29d09d7bb146d7eb20f24b32201Nancy Chen * Factory method to start the query based on a CallerInfo object. 3297ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * 3307ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Note: if the number contains an "@" character we treat it 3317ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * as a SIP address, and look it up directly in the Data table 3327ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * rather than using the PhoneLookup table. 3337ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * TODO: But eventually we should expose two separate methods, one for 3347ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * numbers and one for SIP addresses, and then have 3357ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * PhoneUtils.startGetCallerInfo() decide which one to call based on 3367ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * the phone type of the incoming connection. 3377ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 3385ea0dcd39ff1f29d09d7bb146d7eb20f24b32201Nancy Chen public static CallerInfoAsyncQuery startQuery(int token, Context context, CallerInfo info, 3397ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee OnQueryCompleteListener listener, Object cookie) { 3401a7f2bcab2d2023f2ee4cfb0bc57bc265b5aab87Chiao Cheng Log.d(LOG_TAG, "##### CallerInfoAsyncQuery startQuery()... #####"); 3415ea0dcd39ff1f29d09d7bb146d7eb20f24b32201Nancy Chen Log.d(LOG_TAG, "- number: " + info.phoneNumber); 3421a7f2bcab2d2023f2ee4cfb0bc57bc265b5aab87Chiao Cheng Log.d(LOG_TAG, "- cookie: " + cookie); 3437ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 3447ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // Construct the URI object and query params, and start the query. 3457ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 3468e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki final Uri contactRef = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon() 3475ea0dcd39ff1f29d09d7bb146d7eb20f24b32201Nancy Chen .appendPath(info.phoneNumber) 3488e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki .appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, 3495ea0dcd39ff1f29d09d7bb146d7eb20f24b32201Nancy Chen String.valueOf(PhoneNumberHelper.isUriNumber(info.phoneNumber))) 3508e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki .build(); 3517ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 3527ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee if (DBG) { 3531a7f2bcab2d2023f2ee4cfb0bc57bc265b5aab87Chiao Cheng Log.d(LOG_TAG, "==> contactRef: " + sanitizeUriToString(contactRef)); 3547ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 3557ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 3567ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee CallerInfoAsyncQuery c = new CallerInfoAsyncQuery(); 3577ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee c.allocate(context, contactRef); 3587ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 3597ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee //create cookieWrapper, start query 3607ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee CookieWrapper cw = new CookieWrapper(); 3617ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee cw.listener = listener; 3627ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee cw.cookie = cookie; 3635ea0dcd39ff1f29d09d7bb146d7eb20f24b32201Nancy Chen cw.number = info.phoneNumber; 3647ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 3657ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee // check to see if these are recognized numbers, and use shortcuts if we can. 366d8356985d2a01b944ad082fb66ebb3ec5a56f3ccSantos Cordon if (PhoneNumberUtils.isLocalEmergencyNumber(context, info.phoneNumber)) { 3677ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee cw.event = EVENT_EMERGENCY_NUMBER; 36804318a393b5492fb72d6edafca588e6b25ad752cNancy Chen } else if (info.isVoiceMailNumber()) { 3697ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee cw.event = EVENT_VOICEMAIL_NUMBER; 3707ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } else { 3717ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee cw.event = EVENT_NEW_QUERY; 3727ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 3737ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 3747ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee c.mHandler.startQuery(token, 3757ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee cw, // cookie 3767ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee contactRef, // uri 3777ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee null, // projection 3788e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki null, // selection 3798e64fce150b131468942087a59b5f6fd3c295562Makoto Onuki null, // selectionArgs 3807ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee null); // orderBy 3817ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee return c; 3827ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 3837ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 3847ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee /** 3857ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Method to create a new CallerInfoAsyncQueryHandler object, ensuring correct 3867ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * state of context and uri. 3877ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 3887ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private void allocate(Context context, Uri contactRef) { 3897ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee if ((context == null) || (contactRef == null)){ 3907ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee throw new QueryPoolException("Bad context or query uri."); 3917ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 3927ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee mHandler = new CallerInfoAsyncQueryHandler(context); 3937ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee mHandler.mQueryContext = context; 3947ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee mHandler.mQueryUri = contactRef; 3957ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 3967ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 3977ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee /** 3987ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee * Releases the relevant data. 3997ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee */ 4007ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private void release() { 4017ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee mHandler.mQueryContext = null; 4027ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee mHandler.mQueryUri = null; 4037ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee mHandler.mCallerInfo = null; 4047ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee mHandler = null; 4057ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 4067ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee 4077ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee private static String sanitizeUriToString(Uri uri) { 4087ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee if (uri != null) { 4097ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee String uriString = uri.toString(); 4107ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee int indexOfLastSlash = uriString.lastIndexOf('/'); 4117ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee if (indexOfLastSlash > 0) { 4127ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee return uriString.substring(0, indexOfLastSlash) + "/xxxxxxx"; 4137ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } else { 4147ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee return uriString; 4157ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 4167ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } else { 4177ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee return ""; 4187ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 4197ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee } 4207ef707319c0e3563ff92a772ba53cb5dca0e3ae1Yorke Lee} 421