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