• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.server.telecom;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.graphics.Bitmap;
22 import android.graphics.drawable.Drawable;
23 import android.net.Uri;
24 import android.os.Handler;
25 import android.os.Looper;
26 import android.text.TextUtils;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.telephony.CallerInfo;
30 import com.android.internal.telephony.CallerInfoAsyncQuery;
31 
32 import java.io.InputStream;
33 import java.util.HashMap;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Map;
37 
38 public 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          */
onCallerInfoQueryComplete(Uri handle, @Nullable CallerInfo info)45         void onCallerInfoQueryComplete(Uri handle, @Nullable CallerInfo info);
onContactPhotoQueryComplete(Uri handle, 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 
CallerInfoQueryInfo()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 
CallerInfoLookupHelper(Context context, CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory, ContactsAsyncHelper contactsAsyncHelper, TelecomSystem.SyncRoot lock)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 
startLookup(final Uri handle, OnQueryCompleteListener listener)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 
makeCallerInfoQueryListener( final Uri handle)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