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