CallerInfoLookupHelper.java revision 465982730ff480ff3037cbc22c9dcae005346971
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.content.Context;
20import android.graphics.Bitmap;
21import android.graphics.drawable.Drawable;
22import android.net.Uri;
23import android.os.Handler;
24import android.os.Looper;
25import android.text.TextUtils;
26
27import com.android.internal.annotations.VisibleForTesting;
28import com.android.internal.telephony.CallerInfo;
29import com.android.internal.telephony.CallerInfoAsyncQuery;
30
31import java.io.InputStream;
32import java.util.HashMap;
33import java.util.LinkedList;
34import java.util.List;
35import java.util.Map;
36
37public 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") {
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") {
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