CallerInfoLookupHelper.java revision 5b70c1c2ef08f06bacd4156b266e8ce5b3ccdc07
1/* 2 * Copyright (C) 2016 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.server.telecom; 18 19import android.content.Context; 20import android.graphics.Bitmap; 21import android.graphics.drawable.Drawable; 22import android.net.Uri; 23import android.os.Handler; 24import android.os.Looper; 25import android.text.TextUtils; 26 27import com.android.internal.annotations.VisibleForTesting; 28import com.android.internal.telephony.CallerInfo; 29import com.android.internal.telephony.CallerInfoAsyncQuery; 30 31import java.io.InputStream; 32import java.util.HashMap; 33import java.util.LinkedList; 34import java.util.List; 35import java.util.Map; 36 37public class CallerInfoLookupHelper { 38 public interface OnQueryCompleteListener { 39 /** 40 * Called when the query returns with the caller info 41 * @param info 42 * @return true if the value should be cached, false otherwise. 43 */ 44 void onCallerInfoQueryComplete(Uri handle, CallerInfo info); 45 void onContactPhotoQueryComplete(Uri handle, CallerInfo info); 46 } 47 48 private static class CallerInfoQueryInfo { 49 public CallerInfo callerInfo; 50 public List<OnQueryCompleteListener> listeners; 51 public boolean imageQueryPending = false; 52 53 public CallerInfoQueryInfo() { 54 listeners = new LinkedList<>(); 55 } 56 } 57 private final Map<Uri, CallerInfoQueryInfo> mQueryEntries = new HashMap<>(); 58 59 private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory; 60 private final ContactsAsyncHelper mContactsAsyncHelper; 61 private final Context mContext; 62 private final Object mLock = new Object(); 63 private final Handler mHandler = new Handler(Looper.getMainLooper()); 64 65 public CallerInfoLookupHelper(Context context, 66 CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory, 67 ContactsAsyncHelper contactsAsyncHelper) { 68 mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory; 69 mContactsAsyncHelper = contactsAsyncHelper; 70 mContext = context; 71 } 72 73 public void startLookup(final Uri handle, OnQueryCompleteListener listener) { 74 if (handle == null) { 75 return; 76 } 77 78 final String number = handle.getSchemeSpecificPart(); 79 if (TextUtils.isEmpty(number)) { 80 return; 81 } 82 83 synchronized (mLock) { 84 if (mQueryEntries.containsKey(handle)) { 85 CallerInfoQueryInfo info = mQueryEntries.get(handle); 86 if (info.callerInfo != null) { 87 Log.i(this, "Caller info already exists for handle %s; using cached value", 88 Log.piiHandle(handle)); 89 listener.onCallerInfoQueryComplete(handle, info.callerInfo); 90 if (!info.imageQueryPending && (info.callerInfo.cachedPhoto != null || 91 info.callerInfo.cachedPhotoIcon != null)) { 92 listener.onContactPhotoQueryComplete(handle, info.callerInfo); 93 } else if (info.imageQueryPending) { 94 Log.i(this, "There is a previously incomplete query for handle %s. " + 95 "Adding to listeners for this query.", Log.piiHandle(handle)); 96 info.listeners.add(listener); 97 } 98 } else { 99 Log.i(this, "There is a previously incomplete query for handle %s. Adding to " + 100 "listeners for this query.", Log.piiHandle(handle)); 101 info.listeners.add(listener); 102 return; 103 } 104 } else { 105 CallerInfoQueryInfo info = new CallerInfoQueryInfo(); 106 info.listeners.add(listener); 107 mQueryEntries.put(handle, info); 108 } 109 } 110 111 mHandler.post(new Runnable("CILH.sL") { 112 @Override 113 public void loggedRun() { 114 Session continuedSession = Log.createSubsession(); 115 try { 116 CallerInfoAsyncQuery query = mCallerInfoAsyncQueryFactory.startQuery( 117 0, mContext, number, 118 makeCallerInfoQueryListener(handle), continuedSession); 119 if (query == null) { 120 Log.w(this, "Lookup failed for %s.", Log.piiHandle(handle)); 121 Log.cancelSubsession(continuedSession); 122 } 123 } catch (Throwable t) { 124 Log.cancelSubsession(continuedSession); 125 throw t; 126 } 127 } 128 }.prepare()); 129 } 130 131 private CallerInfoAsyncQuery.OnQueryCompleteListener makeCallerInfoQueryListener( 132 final Uri handle) { 133 return (token, cookie, ci) -> { 134 synchronized (mLock) { 135 Log.continueSession((Session) cookie, "CILH.oQC"); 136 try { 137 if (mQueryEntries.containsKey(handle)) { 138 CallerInfoQueryInfo info = mQueryEntries.get(handle); 139 for (OnQueryCompleteListener l : info.listeners) { 140 l.onCallerInfoQueryComplete(handle, ci); 141 } 142 if (ci.contactDisplayPhotoUri == null) { 143 mQueryEntries.remove(handle); 144 } else { 145 info.callerInfo = ci; 146 info.imageQueryPending = true; 147 startPhotoLookup(handle, ci.contactDisplayPhotoUri); 148 } 149 } else { 150 Log.i(CallerInfoLookupHelper.this, "CI query for handle %s has completed," + 151 " but there are no listeners left.", handle); 152 } 153 } finally { 154 Log.endSession(); 155 } 156 } 157 }; 158 } 159 160 private void startPhotoLookup(final Uri handle, final Uri contactPhotoUri) { 161 mHandler.post(new Runnable("CILH.sPL") { 162 @Override 163 public void loggedRun() { 164 Session continuedSession = Log.createSubsession(); 165 try { 166 mContactsAsyncHelper.startObtainPhotoAsync( 167 0, mContext, contactPhotoUri, 168 makeContactPhotoListener(handle), continuedSession); 169 } catch (Throwable t) { 170 Log.cancelSubsession(continuedSession); 171 throw t; 172 } 173 } 174 }.prepare()); 175 } 176 177 private ContactsAsyncHelper.OnImageLoadCompleteListener makeContactPhotoListener( 178 final Uri handle) { 179 return (token, photo, photoIcon, cookie) -> { 180 synchronized (mLock) { 181 Log.continueSession((Session) cookie, "CLIH.oILC"); 182 try { 183 if (mQueryEntries.containsKey(handle)) { 184 CallerInfoQueryInfo info = mQueryEntries.get(handle); 185 if (info.callerInfo == null) { 186 Log.w(CallerInfoLookupHelper.this, "Photo query finished, but the " + 187 "CallerInfo object previously looked up was not cached."); 188 return; 189 } 190 info.callerInfo.cachedPhoto = photo; 191 info.callerInfo.cachedPhotoIcon = photoIcon; 192 for (OnQueryCompleteListener l : info.listeners) { 193 l.onContactPhotoQueryComplete(handle, info.callerInfo); 194 } 195 mQueryEntries.remove(handle); 196 } else { 197 Log.i(CallerInfoLookupHelper.this, "Photo query for handle %s has" + 198 " completed, but there are no listeners left.", handle); 199 } 200 } finally { 201 Log.endSession(); 202 } 203 } 204 }; 205 } 206 207 @VisibleForTesting 208 public Map<Uri, CallerInfoQueryInfo> getCallerInfoEntries() { 209 return mQueryEntries; 210 } 211 212 @VisibleForTesting 213 public Handler getHandler() { 214 return mHandler; 215 } 216} 217