From 3b90d48e81b34337a4a30f6a25970df9ec13b397 Mon Sep 17 00:00:00 2001 From: Ceci Wu Date: Thu, 25 Aug 2016 14:48:19 +0800 Subject: [PATCH] Query geo description in worker thread to prevent ANR Currently geo description of a caller is queried in the main thread, for both InCallUI and framework CallerInfo. Since it is a potentially time-consuming operation especially when the system is under heavy loading, the main thread might be blocked for a long time. As a result, the ANR happens. The solution to to move the query to worker thread to free the main thread and enhance the performance. Besides, to ensure listeners are notified with complete CallInfo, we need to delay callbacks until EVENT_END_OF_QUEUE. Change-Id: Ibe444fc0f98d07e7ebbe266987e60ede334a3216 --- .../internal/telephony/CallerInfo.java | 2 +- .../telephony/CallerInfoAsyncQuery.java | 91 ++++++++++++++----- 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java index 5f3f773b062cc..9280456ab66f4 100644 --- a/telephony/java/com/android/internal/telephony/CallerInfo.java +++ b/telephony/java/com/android/internal/telephony/CallerInfo.java @@ -576,7 +576,7 @@ public class CallerInfo { * @return a geographical description string for the specified number. * @see com.android.i18n.phonenumbers.PhoneNumberOfflineGeocoder */ - private static String getGeoDescription(Context context, String number) { + public static String getGeoDescription(Context context, String number) { if (VDBG) Rlog.v(TAG, "getGeoDescription('" + number + "')..."); if (TextUtils.isEmpty(number)) { diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java index d4104bd34db38..9e815312fcb21 100644 --- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java +++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java @@ -27,6 +27,7 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.provider.ContactsContract.PhoneLookup; @@ -35,6 +36,9 @@ import android.text.TextUtils; import android.telephony.Rlog; import android.telephony.SubscriptionManager; +import java.util.ArrayList; +import java.util.List; + /** * Helper class to make it easier to run asynchronous caller-id lookup queries. * @see CallerInfo @@ -50,6 +54,7 @@ public class CallerInfoAsyncQuery { private static final int EVENT_END_OF_QUEUE = 3; private static final int EVENT_EMERGENCY_NUMBER = 4; private static final int EVENT_VOICEMAIL_NUMBER = 5; + private static final int EVENT_GET_GEO_DESCRIPTION = 6; private CallerInfoAsyncQueryHandler mHandler; @@ -79,6 +84,7 @@ public class CallerInfoAsyncQuery { public Object cookie; public int event; public String number; + public String geoDescription; public int subId; } @@ -142,6 +148,7 @@ public class CallerInfoAsyncQuery { private Context mContext; private Uri mQueryUri; private CallerInfo mCallerInfo; + private List mPendingListenerCallbacks = new ArrayList<>(); /** * Our own query worker thread. @@ -206,11 +213,32 @@ public class CallerInfoAsyncQuery { reply.sendToTarget(); + break; + case EVENT_GET_GEO_DESCRIPTION: + handleGeoDescription(msg); break; default: } } } + + private void handleGeoDescription(Message msg) { + WorkerArgs args = (WorkerArgs) msg.obj; + CookieWrapper cw = (CookieWrapper) args.cookie; + if (!TextUtils.isEmpty(cw.number) && cw.cookie != null && mContext != null) { + final long startTimeMillis = SystemClock.elapsedRealtime(); + cw.geoDescription = CallerInfo.getGeoDescription(mContext, cw.number); + final long duration = SystemClock.elapsedRealtime() - startTimeMillis; + if (duration > 500) { + if (DBG) Rlog.d(LOG_TAG, "[handleGeoDescription]" + + "Spends long time to retrieve Geo description: " + duration); + } + } + Message reply = args.handler.obtainMessage(msg.what); + reply.obj = args; + reply.arg1 = msg.arg1; + reply.sendToTarget(); + } } @@ -256,6 +284,11 @@ public class CallerInfoAsyncQuery { } if (cw.event == EVENT_END_OF_QUEUE) { + for (Runnable r : mPendingListenerCallbacks) { + r.run(); + } + mPendingListenerCallbacks.clear(); + release(); if (cursor != null) { cursor.close(); @@ -263,6 +296,18 @@ public class CallerInfoAsyncQuery { return; } + // If the cw.event == EVENT_GET_GEO_DESCRIPTION, means it would not be the 1st + // time entering the onQueryComplete(), mCallerInfo should not be null. + if (cw.event == EVENT_GET_GEO_DESCRIPTION) { + if (mCallerInfo != null) { + mCallerInfo.geoDescription = cw.geoDescription; + } + // notify that we can clean up the queue after this. + CookieWrapper endMarker = new CookieWrapper(); + endMarker.event = EVENT_END_OF_QUEUE; + startQuery(token, endMarker, null, null, null, null, null); + } + // check the token and if needed, create the callerinfo object. if (mCallerInfo == null) { if ((mContext == null) || (mQueryUri == null)) { @@ -293,34 +338,24 @@ public class CallerInfoAsyncQuery { + mCallerInfo); } - // Final step: look up the geocoded description. - if (ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION) { - // Note we do this only if we *don't* have a valid name (i.e. if - // no contacts matched the phone number of the incoming call), - // since that's the only case where the incoming-call UI cares - // about this field. - // - // (TODO: But if we ever want the UI to show the geoDescription - // even when we *do* match a contact, we'll need to either call - // updateGeoDescription() unconditionally here, or possibly add a - // new parameter to CallerInfoAsyncQuery.startQuery() to force - // the geoDescription field to be populated.) - - if (TextUtils.isEmpty(mCallerInfo.name)) { - // Actually when no contacts match the incoming phone number, - // the CallerInfo object is totally blank here (i.e. no name - // *or* phoneNumber). So we need to pass in cw.number as - // a fallback number. - mCallerInfo.updateGeoDescription(mContext, cw.number); - } - } - // Use the number entered by the user for display. if (!TextUtils.isEmpty(cw.number)) { mCallerInfo.phoneNumber = PhoneNumberUtils.formatNumber(cw.number, mCallerInfo.normalizedNumber, CallerInfo.getCurrentCountryIso(mContext)); } + + // This condition refer to the google default code for geo. + // If the number exists in Contacts, the CallCard would never show + // the geo description, so it would be unnecessary to query it. + if (ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION) { + if (TextUtils.isEmpty(mCallerInfo.name)) { + if (DBG) Rlog.d(LOG_TAG, "start querying geo description"); + cw.event = EVENT_GET_GEO_DESCRIPTION; + startQuery(token, cw, null, null, null, null, null); + return; + } + } } if (DBG) Rlog.d(LOG_TAG, "constructing CallerInfo object for token: " + token); @@ -333,9 +368,15 @@ public class CallerInfoAsyncQuery { //notify the listener that the query is complete. if (cw.listener != null) { - Rlog.d(LOG_TAG, "notifying listener: " + cw.listener.getClass().toString() + - " for token: " + token + mCallerInfo); - cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo); + mPendingListenerCallbacks.add(new Runnable() { + @Override + public void run() { + if (DBG) Rlog.d(LOG_TAG, "notifying listener: " + + cw.listener.getClass().toString() + " for token: " + token + + mCallerInfo); + cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo); + } + }); } else { Rlog.w(LOG_TAG, "There is no listener to notify for this query."); }