blob: f67a7f7502015e23cc560fabe7725194b27336f4 [file] [log] [blame]
Hall Liu5b70c1c2016-03-03 18:42:57 -08001/*
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
Shigeru Muraif2373e12016-09-12 10:26:23 +090019import android.annotation.Nullable;
Hall Liu5b70c1c2016-03-03 18:42:57 -080020import android.content.Context;
21import android.graphics.Bitmap;
22import android.graphics.drawable.Drawable;
23import android.net.Uri;
24import android.os.Handler;
25import android.os.Looper;
Brad Ebingera3eccfe2016-10-05 15:45:22 -070026import android.telecom.Log;
27import android.telecom.Logging.Runnable;
28import android.telecom.Logging.Session;
Hall Liu5b70c1c2016-03-03 18:42:57 -080029import android.text.TextUtils;
30
31import com.android.internal.annotations.VisibleForTesting;
32import com.android.internal.telephony.CallerInfo;
33import com.android.internal.telephony.CallerInfoAsyncQuery;
34
35import java.io.InputStream;
36import java.util.HashMap;
37import java.util.LinkedList;
38import java.util.List;
39import java.util.Map;
40
41public class CallerInfoLookupHelper {
42 public interface OnQueryCompleteListener {
43 /**
44 * Called when the query returns with the caller info
45 * @param info
46 * @return true if the value should be cached, false otherwise.
47 */
Shigeru Muraif2373e12016-09-12 10:26:23 +090048 void onCallerInfoQueryComplete(Uri handle, @Nullable CallerInfo info);
Hall Liu5b70c1c2016-03-03 18:42:57 -080049 void onContactPhotoQueryComplete(Uri handle, CallerInfo info);
50 }
51
52 private static class CallerInfoQueryInfo {
53 public CallerInfo callerInfo;
54 public List<OnQueryCompleteListener> listeners;
55 public boolean imageQueryPending = false;
56
57 public CallerInfoQueryInfo() {
58 listeners = new LinkedList<>();
59 }
60 }
Shigeru Muraif2373e12016-09-12 10:26:23 +090061
Hall Liu5b70c1c2016-03-03 18:42:57 -080062 private final Map<Uri, CallerInfoQueryInfo> mQueryEntries = new HashMap<>();
63
64 private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
65 private final ContactsAsyncHelper mContactsAsyncHelper;
66 private final Context mContext;
Hall Liu46598272016-04-05 12:58:51 -070067 private final TelecomSystem.SyncRoot mLock;
Hall Liu5b70c1c2016-03-03 18:42:57 -080068 private final Handler mHandler = new Handler(Looper.getMainLooper());
69
70 public CallerInfoLookupHelper(Context context,
71 CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
Hall Liu46598272016-04-05 12:58:51 -070072 ContactsAsyncHelper contactsAsyncHelper,
73 TelecomSystem.SyncRoot lock) {
Hall Liu5b70c1c2016-03-03 18:42:57 -080074 mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
75 mContactsAsyncHelper = contactsAsyncHelper;
76 mContext = context;
Hall Liu46598272016-04-05 12:58:51 -070077 mLock = lock;
Hall Liu5b70c1c2016-03-03 18:42:57 -080078 }
79
80 public void startLookup(final Uri handle, OnQueryCompleteListener listener) {
81 if (handle == null) {
Shigeru Muraif2373e12016-09-12 10:26:23 +090082 listener.onCallerInfoQueryComplete(handle, null);
Hall Liu5b70c1c2016-03-03 18:42:57 -080083 return;
84 }
85
86 final String number = handle.getSchemeSpecificPart();
87 if (TextUtils.isEmpty(number)) {
Shigeru Muraif2373e12016-09-12 10:26:23 +090088 listener.onCallerInfoQueryComplete(handle, null);
Hall Liu5b70c1c2016-03-03 18:42:57 -080089 return;
90 }
91
92 synchronized (mLock) {
93 if (mQueryEntries.containsKey(handle)) {
94 CallerInfoQueryInfo info = mQueryEntries.get(handle);
95 if (info.callerInfo != null) {
96 Log.i(this, "Caller info already exists for handle %s; using cached value",
97 Log.piiHandle(handle));
98 listener.onCallerInfoQueryComplete(handle, info.callerInfo);
99 if (!info.imageQueryPending && (info.callerInfo.cachedPhoto != null ||
100 info.callerInfo.cachedPhotoIcon != null)) {
101 listener.onContactPhotoQueryComplete(handle, info.callerInfo);
102 } else if (info.imageQueryPending) {
Hall Liu36b4ee22016-10-05 16:52:05 -0700103 Log.i(this, "There is a pending photo query for handle %s. " +
Hall Liu5b70c1c2016-03-03 18:42:57 -0800104 "Adding to listeners for this query.", Log.piiHandle(handle));
105 info.listeners.add(listener);
106 }
107 } else {
108 Log.i(this, "There is a previously incomplete query for handle %s. Adding to " +
109 "listeners for this query.", Log.piiHandle(handle));
110 info.listeners.add(listener);
111 return;
112 }
113 } else {
114 CallerInfoQueryInfo info = new CallerInfoQueryInfo();
115 info.listeners.add(listener);
116 mQueryEntries.put(handle, info);
117 }
118 }
119
Brad Ebingerf5e06662016-08-25 16:16:27 -0700120 mHandler.post(new Runnable("CILH.sL", mLock) {
Hall Liu5b70c1c2016-03-03 18:42:57 -0800121 @Override
122 public void loggedRun() {
123 Session continuedSession = Log.createSubsession();
124 try {
125 CallerInfoAsyncQuery query = mCallerInfoAsyncQueryFactory.startQuery(
126 0, mContext, number,
127 makeCallerInfoQueryListener(handle), continuedSession);
128 if (query == null) {
129 Log.w(this, "Lookup failed for %s.", Log.piiHandle(handle));
130 Log.cancelSubsession(continuedSession);
131 }
132 } catch (Throwable t) {
133 Log.cancelSubsession(continuedSession);
134 throw t;
135 }
136 }
137 }.prepare());
138 }
139
140 private CallerInfoAsyncQuery.OnQueryCompleteListener makeCallerInfoQueryListener(
141 final Uri handle) {
142 return (token, cookie, ci) -> {
143 synchronized (mLock) {
144 Log.continueSession((Session) cookie, "CILH.oQC");
145 try {
146 if (mQueryEntries.containsKey(handle)) {
Hall Liu36b4ee22016-10-05 16:52:05 -0700147 Log.i(CallerInfoLookupHelper.this, "CI query for handle %s has completed;" +
148 " notifying all listeners.", Log.piiHandle(handle));
Hall Liu5b70c1c2016-03-03 18:42:57 -0800149 CallerInfoQueryInfo info = mQueryEntries.get(handle);
150 for (OnQueryCompleteListener l : info.listeners) {
151 l.onCallerInfoQueryComplete(handle, ci);
152 }
153 if (ci.contactDisplayPhotoUri == null) {
Hall Liu36b4ee22016-10-05 16:52:05 -0700154 Log.i(CallerInfoLookupHelper.this, "There is no photo for this " +
155 "contact, skipping photo query");
Hall Liu5b70c1c2016-03-03 18:42:57 -0800156 mQueryEntries.remove(handle);
157 } else {
158 info.callerInfo = ci;
159 info.imageQueryPending = true;
160 startPhotoLookup(handle, ci.contactDisplayPhotoUri);
161 }
162 } else {
163 Log.i(CallerInfoLookupHelper.this, "CI query for handle %s has completed," +
Brad Ebinger13a96ce2016-09-06 15:00:39 -0700164 " but there are no listeners left.", Log.piiHandle(handle));
Hall Liu5b70c1c2016-03-03 18:42:57 -0800165 }
166 } finally {
167 Log.endSession();
168 }
169 }
170 };
171 }
172
173 private void startPhotoLookup(final Uri handle, final Uri contactPhotoUri) {
Brad Ebingerf5e06662016-08-25 16:16:27 -0700174 mHandler.post(new Runnable("CILH.sPL", mLock) {
Hall Liu5b70c1c2016-03-03 18:42:57 -0800175 @Override
176 public void loggedRun() {
177 Session continuedSession = Log.createSubsession();
178 try {
179 mContactsAsyncHelper.startObtainPhotoAsync(
180 0, mContext, contactPhotoUri,
181 makeContactPhotoListener(handle), continuedSession);
182 } catch (Throwable t) {
183 Log.cancelSubsession(continuedSession);
184 throw t;
185 }
186 }
187 }.prepare());
188 }
189
190 private ContactsAsyncHelper.OnImageLoadCompleteListener makeContactPhotoListener(
191 final Uri handle) {
192 return (token, photo, photoIcon, cookie) -> {
193 synchronized (mLock) {
194 Log.continueSession((Session) cookie, "CLIH.oILC");
195 try {
196 if (mQueryEntries.containsKey(handle)) {
197 CallerInfoQueryInfo info = mQueryEntries.get(handle);
198 if (info.callerInfo == null) {
199 Log.w(CallerInfoLookupHelper.this, "Photo query finished, but the " +
200 "CallerInfo object previously looked up was not cached.");
Hall Liu36b4ee22016-10-05 16:52:05 -0700201 mQueryEntries.remove(handle);
Hall Liu5b70c1c2016-03-03 18:42:57 -0800202 return;
203 }
204 info.callerInfo.cachedPhoto = photo;
205 info.callerInfo.cachedPhotoIcon = photoIcon;
206 for (OnQueryCompleteListener l : info.listeners) {
207 l.onContactPhotoQueryComplete(handle, info.callerInfo);
208 }
209 mQueryEntries.remove(handle);
210 } else {
211 Log.i(CallerInfoLookupHelper.this, "Photo query for handle %s has" +
Brad Ebinger13a96ce2016-09-06 15:00:39 -0700212 " completed, but there are no listeners left.",
213 Log.piiHandle(handle));
Hall Liu5b70c1c2016-03-03 18:42:57 -0800214 }
215 } finally {
216 Log.endSession();
217 }
218 }
219 };
220 }
221
222 @VisibleForTesting
223 public Map<Uri, CallerInfoQueryInfo> getCallerInfoEntries() {
224 return mQueryEntries;
225 }
226
227 @VisibleForTesting
228 public Handler getHandler() {
229 return mHandler;
230 }
231}