1ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian/* 2ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Copyright (C) 2008 The Android Open Source Project 3ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * 4ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Licensed under the Apache License, Version 2.0 (the "License"); 5ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * you may not use this file except in compliance with the License. 6ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * You may obtain a copy of the License at 7ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * 8ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * http://www.apache.org/licenses/LICENSE-2.0 9ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * 10ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Unless required by applicable law or agreed to in writing, software 11ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * distributed under the License is distributed on an "AS IS" BASIS, 12ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * See the License for the specific language governing permissions and 14ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * limitations under the License. 15ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian */ 16ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 17ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianpackage com.android.incallui; 18ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 19ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.app.Notification; 20ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.content.Context; 21ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.graphics.Bitmap; 22ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.graphics.drawable.BitmapDrawable; 23ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.graphics.drawable.Drawable; 24ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.net.Uri; 25ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.annotation.MainThread; 26fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanianimport android.support.annotation.Nullable; 27ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.annotation.WorkerThread; 28fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanianimport com.android.dialer.common.LogUtil; 29fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanianimport com.android.dialer.common.concurrent.DialerExecutor; 300cd36a6e3bfda1c8b78c0375225929f50ee408a5zachhimport com.android.dialer.common.concurrent.DialerExecutorComponent; 31ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport java.io.IOException; 32ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport java.io.InputStream; 33ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 34ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian/** Helper class for loading contacts photo asynchronously. */ 35ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianpublic class ContactsAsyncHelper { 36ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 37ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian /** Interface for a WorkerHandler result return. */ 38fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian interface OnImageLoadCompleteListener { 39ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 40ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian /** 41ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Called when the image load is complete. Must be called in main thread. 42ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * 43ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * @param token Integer passed in {@link ContactsAsyncHelper#startObtainPhotoAsync(int, Context, 44ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Uri, OnImageLoadCompleteListener, Object)}. 45ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * @param photo Drawable object obtained by the async load. 46ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * @param photoIcon Bitmap object obtained by the async load. 47ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * @param cookie Object passed in {@link ContactsAsyncHelper#startObtainPhotoAsync(int, Context, 48ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Uri, OnImageLoadCompleteListener, Object)}. Can be null iff. the original cookie is null. 49ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian */ 50ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @MainThread 51ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, Object cookie); 52ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 53ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian /** Called when image is loaded to udpate data. Must be called in worker thread. */ 54ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @WorkerThread 55ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian void onImageLoaded(int token, Drawable photo, Bitmap photoIcon, Object cookie); 56ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 57ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 58ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian /** 59ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Starts an asynchronous image load. After finishing the load, {@link 60ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable, Bitmap, Object)} will be called. 61ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * 62ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * @param token Arbitrary integer which will be returned as the first argument of {@link 63ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable, Bitmap, Object)} 64ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * @param context Context object used to do the time-consuming operation. 65ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * @param displayPhotoUri Uri to be used to fetch the photo 66ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * @param listener Callback object which will be used when the asynchronous load is done. Can be 67ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * null, which means only the asynchronous load is done while there's no way to obtain the 68ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * loaded photos. 69ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * @param cookie Arbitrary object the caller wants to remember, which will become the fourth 70ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * argument of {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable, Bitmap, 71ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Object)}. Can be null, at which the callback will also has null for the argument. 72ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian */ 73fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian static void startObtainPhotoAsync( 74ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian int token, 75ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian Context context, 76ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian Uri displayPhotoUri, 77ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian OnImageLoadCompleteListener listener, 78ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian Object cookie) { 79ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // in case the source caller info is null, the URI will be null as well. 80ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // just update using the placeholder image in this case. 81ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (displayPhotoUri == null) { 82fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian LogUtil.e("ContactsAsyncHelper.startObjectPhotoAsync", "uri is missing"); 83ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return; 84ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 85ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 86ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // Added additional Cookie field in the callee to handle arguments 87ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // sent to the callback function. 88ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 89ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // setup arguments 90ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian WorkerArgs args = new WorkerArgs(); 91fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian args.token = token; 92ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian args.cookie = cookie; 93ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian args.context = context; 94ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian args.displayPhotoUri = displayPhotoUri; 95ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian args.listener = listener; 96ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 970cd36a6e3bfda1c8b78c0375225929f50ee408a5zachh DialerExecutorComponent.get(context) 980cd36a6e3bfda1c8b78c0375225929f50ee408a5zachh .dialerExecutorFactory() 990cd36a6e3bfda1c8b78c0375225929f50ee408a5zachh .createNonUiTaskBuilder(new Worker()) 100fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian .onSuccess( 101fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian output -> { 102fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian if (args.listener != null) { 103fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian LogUtil.d( 104fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian "ContactsAsyncHelper.startObtainPhotoAsync", 105fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian "notifying listener: " 106fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian + args.listener 107fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian + " image: " 108fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian + args.displayPhotoUri 109fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian + " completed"); 110fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian args.listener.onImageLoadComplete( 111fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian args.token, args.photo, args.photoIcon, args.cookie); 112fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 113fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian }) 114fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian .build() 115fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian .executeParallel(args); 116ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 117ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 118ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private static final class WorkerArgs { 119ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 120fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian public int token; 121ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public Context context; 122ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public Uri displayPhotoUri; 123ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public Drawable photo; 124ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public Bitmap photoIcon; 125ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public Object cookie; 126ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public OnImageLoadCompleteListener listener; 127ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 128ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 129fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian private static class Worker implements DialerExecutor.Worker<WorkerArgs, Void> { 130ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 131fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian @Nullable 132ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Override 133fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian public Void doInBackground(WorkerArgs args) throws Throwable { 134fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian InputStream inputStream = null; 135fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian try { 136fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian try { 137fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian inputStream = args.context.getContentResolver().openInputStream(args.displayPhotoUri); 138fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } catch (Exception e) { 139fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian LogUtil.e( 140fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian "ContactsAsyncHelper.Worker.doInBackground", "error opening photo input stream", e); 141fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 142ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 143fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian if (inputStream != null) { 144fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian args.photo = Drawable.createFromStream(inputStream, args.displayPhotoUri.toString()); 145fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 146fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // This assumes Drawable coming from contact database is usually 147fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // BitmapDrawable and thus we can have (down)scaled version of it. 148fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian args.photoIcon = getPhotoIconWhenAppropriate(args.context, args.photo); 149fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 150fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian LogUtil.d( 151fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian "ContactsAsyncHelper.Worker.doInBackground", 152fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian "loading image, URI: %s", 153fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian args.displayPhotoUri); 154fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } else { 155fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian args.photo = null; 156fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian args.photoIcon = null; 157fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian LogUtil.d( 158fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian "ContactsAsyncHelper.Worker.doInBackground", 159fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian "problem with image, URI: %s, using default image.", 160fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian args.displayPhotoUri); 161fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 162fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian if (args.listener != null) { 163fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian args.listener.onImageLoaded(args.token, args.photo, args.photoIcon, args.cookie); 164fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 165fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } finally { 166fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian if (inputStream != null) { 167ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian try { 168fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian inputStream.close(); 169fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } catch (IOException e) { 170fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian LogUtil.e( 171fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian "ContactsAsyncHelper.Worker.doInBackground", "Unable to close input stream.", e); 172ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 173fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 174ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 175fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian return null; 176ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 177ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 178ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian /** 179ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Returns a Bitmap object suitable for {@link Notification}'s large icon. This might return 180ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * null when the given Drawable isn't BitmapDrawable, or if the system fails to create a scaled 181ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Bitmap for the Drawable. 182ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian */ 183ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private Bitmap getPhotoIconWhenAppropriate(Context context, Drawable photo) { 184ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (!(photo instanceof BitmapDrawable)) { 185ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return null; 186ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 187ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian int iconSize = context.getResources().getDimensionPixelSize(R.dimen.notification_icon_size); 188ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian Bitmap orgBitmap = ((BitmapDrawable) photo).getBitmap(); 189ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian int orgWidth = orgBitmap.getWidth(); 190ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian int orgHeight = orgBitmap.getHeight(); 191ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian int longerEdge = orgWidth > orgHeight ? orgWidth : orgHeight; 192ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // We want downscaled one only when the original icon is too big. 193ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (longerEdge > iconSize) { 194ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float ratio = ((float) longerEdge) / iconSize; 195ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian int newWidth = (int) (orgWidth / ratio); 196ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian int newHeight = (int) (orgHeight / ratio); 197ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // If the longer edge is much longer than the shorter edge, the latter may 198ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // become 0 which will cause a crash. 199ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (newWidth <= 0 || newHeight <= 0) { 200fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian LogUtil.w( 201fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian "ContactsAsyncHelper.Worker.getPhotoIconWhenAppropriate", 202fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian "Photo icon's width or height become 0."); 203ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return null; 204ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 205ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 206ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // It is sure ratio >= 1.0f in any case and thus the newly created Bitmap 207ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // should be smaller than the original. 208ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return Bitmap.createScaledBitmap(orgBitmap, newWidth, newHeight, true); 209ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } else { 210ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return orgBitmap; 211ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 212ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 213ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 214ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian} 215