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