Home | History | Annotate | Download | only in telecom
      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.telecom.Log;
     27 import android.telecom.Logging.Runnable;
     28 import android.telecom.Logging.Session;
     29 import android.text.TextUtils;
     30 
     31 import com.android.internal.annotations.VisibleForTesting;
     32 import com.android.internal.telephony.CallerInfo;
     33 import com.android.internal.telephony.CallerInfoAsyncQuery;
     34 
     35 import java.io.InputStream;
     36 import java.util.HashMap;
     37 import java.util.LinkedList;
     38 import java.util.List;
     39 import java.util.Map;
     40 
     41 public 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          */
     48         void onCallerInfoQueryComplete(Uri handle, @Nullable CallerInfo info);
     49         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     }
     61 
     62     private final Map<Uri, CallerInfoQueryInfo> mQueryEntries = new HashMap<>();
     63 
     64     private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
     65     private final ContactsAsyncHelper mContactsAsyncHelper;
     66     private final Context mContext;
     67     private final TelecomSystem.SyncRoot mLock;
     68     private final Handler mHandler = new Handler(Looper.getMainLooper());
     69 
     70     public CallerInfoLookupHelper(Context context,
     71             CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
     72             ContactsAsyncHelper contactsAsyncHelper,
     73             TelecomSystem.SyncRoot lock) {
     74         mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
     75         mContactsAsyncHelper = contactsAsyncHelper;
     76         mContext = context;
     77         mLock = lock;
     78     }
     79 
     80     public void startLookup(final Uri handle, OnQueryCompleteListener listener) {
     81         if (handle == null) {
     82             listener.onCallerInfoQueryComplete(handle, null);
     83             return;
     84         }
     85 
     86         final String number = handle.getSchemeSpecificPart();
     87         if (TextUtils.isEmpty(number)) {
     88             listener.onCallerInfoQueryComplete(handle, null);
     89             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) {
    103                         Log.i(this, "There is a pending photo query for handle %s. " +
    104                                 "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 
    120         mHandler.post(new Runnable("CILH.sL", mLock) {
    121             @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)) {
    147                         Log.i(CallerInfoLookupHelper.this, "CI query for handle %s has completed;" +
    148                                 " notifying all listeners.", Log.piiHandle(handle));
    149                         CallerInfoQueryInfo info = mQueryEntries.get(handle);
    150                         for (OnQueryCompleteListener l : info.listeners) {
    151                             l.onCallerInfoQueryComplete(handle, ci);
    152                         }
    153                         if (ci.contactDisplayPhotoUri == null) {
    154                             Log.i(CallerInfoLookupHelper.this, "There is no photo for this " +
    155                                     "contact, skipping photo query");
    156                             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," +
    164                                 " but there are no listeners left.", Log.piiHandle(handle));
    165                     }
    166                 } finally {
    167                     Log.endSession();
    168                 }
    169             }
    170         };
    171     }
    172 
    173     private void startPhotoLookup(final Uri handle, final Uri contactPhotoUri) {
    174         mHandler.post(new Runnable("CILH.sPL", mLock) {
    175             @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.");
    201                             mQueryEntries.remove(handle);
    202                             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" +
    212                                 " completed, but there are no listeners left.",
    213                                 Log.piiHandle(handle));
    214                     }
    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 }
    232