1 /* 2 * Copyright (C) 2018 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.internal.widget; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.graphics.ImageDecoder; 22 import android.graphics.drawable.Drawable; 23 import android.graphics.drawable.Icon; 24 import android.net.Uri; 25 import android.util.Size; 26 27 import java.io.IOException; 28 29 /** A class to extract Drawables from a MessagingStyle/ConversationStyle message. */ 30 public class LocalImageResolver { 31 private static final String TAG = LocalImageResolver.class.getSimpleName(); 32 33 private static final int MAX_SAFE_ICON_SIZE_PX = 480; 34 35 /** 36 * Resolve an image from the given Uri using {@link ImageDecoder} 37 */ resolveImage(Uri uri, Context context)38 public static Drawable resolveImage(Uri uri, Context context) throws IOException { 39 final ImageDecoder.Source source = 40 ImageDecoder.createSource(context.getContentResolver(), uri); 41 final Drawable drawable = 42 ImageDecoder.decodeDrawable(source, LocalImageResolver::onHeaderDecoded); 43 return drawable; 44 } 45 46 /** 47 * Get the drawable from Icon using {@link ImageDecoder} if it contains a Uri, or 48 * using {@link Icon#loadDrawable(Context)} otherwise. This will correctly apply the Icon's, 49 * tint, if present, to the drawable. 50 */ resolveImage(Icon icon, Context context)51 public static Drawable resolveImage(Icon icon, Context context) throws IOException { 52 Uri uri = getResolvableUri(icon); 53 if (uri != null) { 54 Drawable result = resolveImage(uri, context); 55 if (icon.hasTint()) { 56 result.mutate(); 57 result.setTintList(icon.getTintList()); 58 result.setTintBlendMode(icon.getTintBlendMode()); 59 } 60 return result; 61 } 62 return icon.loadDrawable(context); 63 } 64 resolveImage(Uri uri, Context context, int maxWidth, int maxHeight)65 public static Drawable resolveImage(Uri uri, Context context, int maxWidth, int maxHeight) 66 throws IOException { 67 final ImageDecoder.Source source = 68 ImageDecoder.createSource(context.getContentResolver(), uri); 69 return ImageDecoder.decodeDrawable(source, (decoder, info, unused) -> { 70 final Size size = info.getSize(); 71 if (size.getWidth() > size.getHeight()) { 72 if (size.getWidth() > maxWidth) { 73 final int targetHeight = size.getHeight() * maxWidth / size.getWidth(); 74 decoder.setTargetSize(maxWidth, targetHeight); 75 } 76 } else { 77 if (size.getHeight() > maxHeight) { 78 final int targetWidth = size.getWidth() * maxHeight / size.getHeight(); 79 decoder.setTargetSize(targetWidth, maxHeight); 80 } 81 } 82 }); 83 } 84 getPowerOfTwoForSampleRatio(double ratio)85 private static int getPowerOfTwoForSampleRatio(double ratio) { 86 final int k = Integer.highestOneBit((int) Math.floor(ratio)); 87 return Math.max(1, k); 88 } 89 onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info, ImageDecoder.Source source)90 private static void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info, 91 ImageDecoder.Source source) { 92 final Size size = info.getSize(); 93 final int originalSize = Math.max(size.getHeight(), size.getWidth()); 94 final double ratio = (originalSize > MAX_SAFE_ICON_SIZE_PX) 95 ? originalSize * 1f / MAX_SAFE_ICON_SIZE_PX 96 : 1.0; 97 decoder.setTargetSampleSize(getPowerOfTwoForSampleRatio(ratio)); 98 } 99 100 /** 101 * Gets the Uri for this icon, assuming the icon can be treated as a pure Uri. Null otherwise. 102 */ 103 @Nullable getResolvableUri(@ullable Icon icon)104 public static Uri getResolvableUri(@Nullable Icon icon) { 105 if (icon == null || (icon.getType() != Icon.TYPE_URI 106 && icon.getType() != Icon.TYPE_URI_ADAPTIVE_BITMAP)) { 107 return null; 108 } 109 return icon.getUri(); 110 } 111 } 112