1 /*
2  * Copyright (C) 2015 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 package com.android.car.dialer.telecom;
17 
18 import android.content.ContentResolver;
19 import android.graphics.Bitmap;
20 import android.os.AsyncTask;
21 import android.support.annotation.MainThread;
22 import android.support.annotation.Nullable;
23 import android.widget.ImageView;
24 
25 import java.lang.ref.WeakReference;
26 
27 /**
28  * Helper task that retrieves a Contact photo from the local Contacts store. The loading task
29  * is tied to an ImageView that allows a lightweight management of the task upon update of the view.
30  *
31  * TODO(mcrico): Using a View's TAG to store and manage Async task loading is pretty brittle.
32  * Vanagon is not depending on this logic and projected shouldn't either.
33  */
34 public class ContactBitmapWorker extends AsyncTask<Void, Void, Bitmap> {
35     private final WeakReference<ImageView> mImageViewReference;
36     private final WeakReference<ContentResolver> mContentResolverReference;
37     private final String mNumber;
38     private final BitmapWorkerListener mListener;
39 
40     /** Interface to receive updates from this worker */
41     public interface BitmapWorkerListener {
42         /** Called in the main thread upon bitmap load finish */
43         @MainThread
onBitmapLoaded(@ullable Bitmap bitmap)44         void onBitmapLoaded(@Nullable Bitmap bitmap);
45     }
46 
47     /**
48      * @return A worker task if a new one was needed to load the bitmap.
49      */
loadBitmap( ContentResolver contentResolver, ImageView imageView, String number, BitmapWorkerListener listener)50     @Nullable public static ContactBitmapWorker loadBitmap(
51             ContentResolver contentResolver,
52             ImageView imageView,
53             String number,
54             BitmapWorkerListener listener) {
55 
56         // This work may be underway already.
57         if (!cancelPotentialWork(number, imageView)) {
58             return null;
59         }
60 
61         ContactBitmapWorker task =
62                 new ContactBitmapWorker(contentResolver, imageView, number, listener);
63         imageView.setTag(task);
64         imageView.setImageResource(0);
65         task.execute();
66         return task;
67     }
68 
69     /** Use {@link #loadBitmap} instead, as it guarantees de-duplication of work */
ContactBitmapWorker( ContentResolver contentResolver, ImageView imageView, String number, BitmapWorkerListener listener)70     private ContactBitmapWorker(
71             ContentResolver contentResolver,
72             ImageView imageView,
73             String number,
74             BitmapWorkerListener listener) {
75         mImageViewReference = new WeakReference<>(imageView);
76         mContentResolverReference = new WeakReference<>(contentResolver);
77         mNumber = number;
78         mListener = listener;
79     }
80 
81     @Override
doInBackground(Void... voids)82     protected Bitmap doInBackground(Void... voids) {
83         final ContentResolver contentResolver = mContentResolverReference.get();
84         if (contentResolver != null) {
85             return TelecomUtils.getContactPhotoFromNumber(contentResolver, mNumber);
86         }
87         return null;
88     }
89 
90     @Override
onPostExecute(Bitmap bitmap)91     protected void onPostExecute(Bitmap bitmap) {
92         if (isCancelled()) {
93             return;
94         }
95 
96         if (mImageViewReference.get() != null) {
97             mListener.onBitmapLoaded(bitmap);
98         }
99     }
100 
101     /**
102      * @return Whether a new Bitmap loading should continue for this imageView.
103      */
cancelPotentialWork(String number, ImageView imageView)104     private static boolean cancelPotentialWork(String number, ImageView imageView) {
105         final ContactBitmapWorker bitmapWorkerTask = (ContactBitmapWorker) imageView.getTag();
106         if (bitmapWorkerTask != null) {
107             if (bitmapWorkerTask.mNumber != number) {
108                 bitmapWorkerTask.cancel(true);
109                 imageView.setTag(null);
110             } else {
111                 // The same work is already in progress
112                 return false;
113             }
114         }
115 
116         return true;
117     }
118 }
119