blob: 123ee731937bfb7b467a07a9a05a6d16b20d58d9 [file] [log] [blame]
Jon Miranda16ea1b12017-12-12 14:52:48 -08001/*
2 * Copyright (C) 2017 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 */
16package com.android.wallpaper.asset;
17
18import android.app.Activity;
19import android.content.Context;
20import android.graphics.Bitmap;
21import android.graphics.Point;
22import android.graphics.Rect;
23import android.graphics.drawable.ColorDrawable;
24import android.net.Uri;
25import android.os.AsyncTask;
26import android.support.annotation.Nullable;
Jon Miranda16ea1b12017-12-12 14:52:48 -080027import android.util.Log;
28import android.widget.ImageView;
29
30import com.bumptech.glide.Glide;
31import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
32import com.bumptech.glide.request.RequestOptions;
33
34import java.io.FileNotFoundException;
35import java.io.IOException;
36import java.io.InputStream;
37
38/**
39 * Represents an asset located via an Android content URI.
40 */
41public final class ContentUriAsset extends StreamableAsset {
42 private static final String TAG = "ContentUriAsset";
43 private static final String JPEG_MIME_TYPE = "image/jpeg";
44 private static final String PNG_MIME_TYPE = "image/png";
Jon Miranda16ea1b12017-12-12 14:52:48 -080045
46 private final Context mContext;
47 private final Uri mUri;
48
Santiago Etchebehere8e9a8472018-07-24 14:30:21 -070049 private ExifInterfaceCompat mExifCompat;
Jon Miranda16ea1b12017-12-12 14:52:48 -080050 private int mExifOrientation;
51
52 /**
53 * @param context The application's context.
54 * @param uri Content URI locating the asset.
55 */
56 public ContentUriAsset(Context context, Uri uri) {
Santiago Etchebehere8e9a8472018-07-24 14:30:21 -070057 mExifOrientation = ExifInterfaceCompat.EXIF_ORIENTATION_UNKNOWN;
Jon Miranda16ea1b12017-12-12 14:52:48 -080058 mContext = context.getApplicationContext();
59 mUri = uri;
60 }
61
62 @Override
63 public void decodeBitmapRegion(final Rect rect, int targetWidth, int targetHeight,
64 final BitmapReceiver receiver) {
Santiago Etchebehere8e9a8472018-07-24 14:30:21 -070065 // BitmapRegionDecoder only supports images encoded in either JPEG or PNG, so if the content
66 // URI asset is encoded with another format (for example, GIF), then fall back to cropping a
67 // bitmap region from the full-sized bitmap.
Jon Miranda16ea1b12017-12-12 14:52:48 -080068 if (isJpeg() || isPng()) {
69 super.decodeBitmapRegion(rect, targetWidth, targetHeight, receiver);
70 return;
71 }
72
73 decodeRawDimensions(null /* activity */, new DimensionsReceiver() {
74 @Override
75 public void onDimensionsDecoded(@Nullable Point dimensions) {
76 if (dimensions == null) {
Santiago Etchebehere8e9a8472018-07-24 14:30:21 -070077 Log.e(TAG, "There was an error decoding the asset's raw dimensions with " +
78 "content URI: " + mUri);
Jon Miranda16ea1b12017-12-12 14:52:48 -080079 receiver.onBitmapDecoded(null);
80 return;
81 }
82
83 decodeBitmap(dimensions.x, dimensions.y, new BitmapReceiver() {
84 @Override
85 public void onBitmapDecoded(@Nullable Bitmap fullBitmap) {
86 if (fullBitmap == null) {
Santiago Etchebehere8e9a8472018-07-24 14:30:21 -070087 Log.e(TAG, "There was an error decoding the asset's full bitmap with " +
88 "content URI: " + mUri);
Jon Miranda16ea1b12017-12-12 14:52:48 -080089 receiver.onBitmapDecoded(null);
90 return;
91 }
92
93 BitmapCropTask task = new BitmapCropTask(fullBitmap, rect, receiver);
94 task.execute();
95 }
96 });
97 }
98 });
99 }
100
101 /**
102 * Returns whether this image is encoded in the JPEG file format.
103 */
104 public boolean isJpeg() {
105 String mimeType = mContext.getContentResolver().getType(mUri);
106 return mimeType != null && mimeType.equals(JPEG_MIME_TYPE);
107 }
108
109 /**
110 * Returns whether this image is encoded in the PNG file format.
111 */
112 public boolean isPng() {
113 String mimeType = mContext.getContentResolver().getType(mUri);
114 return mimeType != null && mimeType.equals(PNG_MIME_TYPE);
115 }
116
117 /**
118 * Reads the EXIF tag on the asset. Automatically trims leading and trailing whitespace.
119 *
120 * @return String attribute value for this tag ID, or null if ExifInterface failed to read tags
121 * for this asset, if this tag was not found in the image's metadata, or if this tag was
122 * empty (i.e., only whitespace).
123 */
124 public String readExifTag(String tagId) {
Santiago Etchebehere8e9a8472018-07-24 14:30:21 -0700125 ensureExifInterface();
126 if (mExifCompat == null) {
127 Log.w(TAG, "Unable to read EXIF tags for content URI asset");
128 return null;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800129 }
130
Santiago Etchebehere8e9a8472018-07-24 14:30:21 -0700131
132 String attribute = mExifCompat.getAttribute(tagId);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800133 if (attribute == null || attribute.trim().isEmpty()) {
134 return null;
135 }
136
137 return attribute.trim();
138 }
139
Santiago Etchebehere8e9a8472018-07-24 14:30:21 -0700140 private void ensureExifInterface() {
141 if (mExifCompat == null) {
142 try (InputStream inputStream = openInputStream()) {
143 if (inputStream != null) {
144 mExifCompat = new ExifInterfaceCompat(inputStream);
145 }
146 } catch (IOException e) {
147 Log.w(TAG, "Couldn't read stream for " + mUri, e);
148 }
149 }
150
151 }
152
Jon Miranda16ea1b12017-12-12 14:52:48 -0800153 @Override
154 protected InputStream openInputStream() {
155 try {
156 return mContext.getContentResolver().openInputStream(mUri);
157 } catch (FileNotFoundException e) {
158 Log.w(TAG, "Image file not found", e);
159 return null;
160 }
161 }
162
163 @Override
164 protected int getExifOrientation() {
Santiago Etchebehere8e9a8472018-07-24 14:30:21 -0700165 if (mExifOrientation != ExifInterfaceCompat.EXIF_ORIENTATION_UNKNOWN) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800166 return mExifOrientation;
167 }
168
169 mExifOrientation = readExifOrientation();
170 return mExifOrientation;
171 }
172
173 /**
Santiago Etchebehere8e9a8472018-07-24 14:30:21 -0700174 * Returns the EXIF rotation for the content URI asset. This method should only be called off
175 * the main UI thread.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800176 */
177 private int readExifOrientation() {
Santiago Etchebehere8e9a8472018-07-24 14:30:21 -0700178 ensureExifInterface();
179 if (mExifCompat == null) {
180 Log.w(TAG, "Unable to read EXIF rotation for content URI asset with content URI: "
181 + mUri);
182 return ExifInterfaceCompat.EXIF_ORIENTATION_NORMAL;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800183 }
184
Santiago Etchebehere8e9a8472018-07-24 14:30:21 -0700185 return mExifCompat.getAttributeInt(ExifInterfaceCompat.TAG_ORIENTATION,
186 ExifInterfaceCompat.EXIF_ORIENTATION_NORMAL);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800187 }
188
189 @Override
190 public void loadDrawable(Activity activity, ImageView imageView,
191 int placeholderColor) {
192 Glide.with(activity)
193 .asDrawable()
194 .load(mUri)
195 .apply(RequestOptions.centerCropTransform()
196 .placeholder(new ColorDrawable(placeholderColor)))
197 .transition(DrawableTransitionOptions.withCrossFade())
198 .into(imageView);
199 }
200
201 /**
202 * Custom AsyncTask which crops a bitmap region from a larger bitmap.
203 */
204 private static class BitmapCropTask extends AsyncTask<Void, Void, Bitmap> {
205
206 private Bitmap mFromBitmap;
207 private Rect mCropRect;
208 private BitmapReceiver mReceiver;
209
210 public BitmapCropTask(Bitmap fromBitmap, Rect cropRect, BitmapReceiver receiver) {
211 mFromBitmap = fromBitmap;
212 mCropRect = cropRect;
213 mReceiver = receiver;
214 }
215
216 @Override
217 protected Bitmap doInBackground(Void... unused) {
218 if (mFromBitmap == null) {
219 return null;
220 }
221
222 return Bitmap.createBitmap(
Santiago Etchebehere8e9a8472018-07-24 14:30:21 -0700223 mFromBitmap, mCropRect.left, mCropRect.top, mCropRect.width(),
224 mCropRect.height());
Jon Miranda16ea1b12017-12-12 14:52:48 -0800225 }
226
227 @Override
228 protected void onPostExecute(Bitmap bitmapRegion) {
229 mReceiver.onBitmapDecoded(bitmapRegion);
230 }
231 }
232
233}