blob: 08486a1216ad5e51cff0087c44dae3b09cc860b9 [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;
Ching-Sung Lie4525482019-12-05 14:03:50 +080020import android.content.res.AssetFileDescriptor;
Jon Miranda16ea1b12017-12-12 14:52:48 -080021import android.graphics.Bitmap;
Ching-Sung Lie4525482019-12-05 14:03:50 +080022import android.graphics.BitmapFactory;
Santiago Etchebeherea5736dc2021-06-14 14:32:32 -070023import android.graphics.Canvas;
Austin Wange5005ee2023-03-31 18:19:55 +080024import android.graphics.Point;
Jon Miranda16ea1b12017-12-12 14:52:48 -080025import android.graphics.Rect;
26import android.graphics.drawable.BitmapDrawable;
27import android.graphics.drawable.ColorDrawable;
28import android.graphics.drawable.Drawable;
Austin Wange5005ee2023-03-31 18:19:55 +080029import android.graphics.drawable.LayerDrawable;
Ching-Sung Lie4525482019-12-05 14:03:50 +080030import android.net.Uri;
Chris Poultney999c15e2023-04-10 19:10:33 +000031import android.os.Handler;
32import android.os.Looper;
Ching-Sung Lie4525482019-12-05 14:03:50 +080033import android.util.Log;
Jon Miranda16ea1b12017-12-12 14:52:48 -080034import android.widget.ImageView;
35
Santiago Etchebeherea5736dc2021-06-14 14:32:32 -070036import androidx.annotation.WorkerThread;
37
Austin Wange5005ee2023-03-31 18:19:55 +080038import com.android.wallpaper.module.DrawableLayerResolver;
39import com.android.wallpaper.module.InjectorProvider;
40
Jon Miranda16ea1b12017-12-12 14:52:48 -080041import com.bumptech.glide.Glide;
42import com.bumptech.glide.load.Key;
Ching-Sung Li3e0674d2021-05-21 00:52:12 +080043import com.bumptech.glide.load.MultiTransformation;
Santiago Etchebeherea5736dc2021-06-14 14:32:32 -070044import com.bumptech.glide.load.Transformation;
Ching-Sung Lie4525482019-12-05 14:03:50 +080045import com.bumptech.glide.load.engine.DiskCacheStrategy;
Ching-Sung Li3e0674d2021-05-21 00:52:12 +080046import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
47import com.bumptech.glide.load.resource.bitmap.FitCenter;
Jon Miranda16ea1b12017-12-12 14:52:48 -080048import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
49import com.bumptech.glide.request.RequestOptions;
50
Ching-Sung Lie4525482019-12-05 14:03:50 +080051import java.io.IOException;
Jon Miranda16ea1b12017-12-12 14:52:48 -080052import java.security.MessageDigest;
Santiago Etchebeherea5736dc2021-06-14 14:32:32 -070053import java.util.concurrent.ExecutionException;
John Pan48dd75e2022-01-13 18:44:40 +080054import java.util.concurrent.ExecutorService;
55import java.util.concurrent.Executors;
Santiago Etchebeherea5736dc2021-06-14 14:32:32 -070056import java.util.concurrent.TimeUnit;
57import java.util.concurrent.TimeoutException;
Jon Miranda16ea1b12017-12-12 14:52:48 -080058
59/**
60 * Asset wrapping a drawable for a live wallpaper thumbnail.
61 */
Santiago Etchebeheredec38a22019-07-02 13:24:32 -070062public class LiveWallpaperThumbAsset extends Asset {
Ching-Sung Lie4525482019-12-05 14:03:50 +080063 private static final String TAG = "LiveWallpaperThumbAsset";
John Pan48dd75e2022-01-13 18:44:40 +080064 private static final ExecutorService sExecutorService = Executors.newCachedThreadPool();
Santiago Etchebeherea5736dc2021-06-14 14:32:32 -070065 private static final int LOW_RES_THUMB_TIMEOUT_SECONDS = 2;
Ching-Sung Lie4525482019-12-05 14:03:50 +080066
Santiago Etchebeheredec38a22019-07-02 13:24:32 -070067 protected final Context mContext;
68 protected final android.app.WallpaperInfo mInfo;
Austin Wange5005ee2023-03-31 18:19:55 +080069 protected final DrawableLayerResolver mLayerResolver;
Ching-Sung Lie4525482019-12-05 14:03:50 +080070 // The content Uri of thumbnail
71 protected Uri mUri;
Ching-Sung Li30aa6002022-01-24 12:36:33 +000072 private Drawable mThumbnailDrawable;
Jon Miranda16ea1b12017-12-12 14:52:48 -080073
74 public LiveWallpaperThumbAsset(Context context, android.app.WallpaperInfo info) {
75 mContext = context.getApplicationContext();
76 mInfo = info;
Austin Wange5005ee2023-03-31 18:19:55 +080077 mLayerResolver = InjectorProvider.getInjector().getDrawableLayerResolver();
Jon Miranda16ea1b12017-12-12 14:52:48 -080078 }
79
Ching-Sung Lie4525482019-12-05 14:03:50 +080080 public LiveWallpaperThumbAsset(Context context, android.app.WallpaperInfo info, Uri uri) {
81 this(context, info);
82 mUri = uri;
83 }
84
Jon Miranda16ea1b12017-12-12 14:52:48 -080085 @Override
86 public void decodeBitmap(int targetWidth, int targetHeight,
87 BitmapReceiver receiver) {
John Pan48dd75e2022-01-13 18:44:40 +080088 sExecutorService.execute(() -> {
Bob Yang785d6022022-05-17 15:31:26 +080089 Drawable thumb = getThumbnailDrawable();
John Pan48dd75e2022-01-13 18:44:40 +080090
91 // Live wallpaper components may or may not specify a thumbnail drawable.
92 if (thumb instanceof BitmapDrawable) {
93 decodeBitmapCompleted(receiver,
94 Bitmap.createScaledBitmap(((BitmapDrawable) thumb).getBitmap(), targetWidth,
95 targetHeight, true));
96 return;
97 } else if (thumb != null) {
98 Bitmap bitmap;
99 if (thumb.getIntrinsicWidth() > 0 && thumb.getIntrinsicHeight() > 0) {
100 bitmap = Bitmap.createBitmap(thumb.getIntrinsicWidth(),
101 thumb.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
102 } else {
103 decodeBitmapCompleted(receiver, null);
104 return;
105 }
106
107 Canvas canvas = new Canvas(bitmap);
108 thumb.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
109 thumb.draw(canvas);
110 decodeBitmapCompleted(receiver,
111 Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, true));
112 return;
113 }
114 decodeBitmapCompleted(receiver, null);
115 });
Jon Miranda16ea1b12017-12-12 14:52:48 -0800116 }
117
118 @Override
119 public void decodeBitmapRegion(Rect rect, int targetWidth, int targetHeight,
Santiago Etchebehere16cf0602020-12-11 17:04:18 -0800120 boolean shouldAdjustForRtl, BitmapReceiver receiver) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800121 receiver.onBitmapDecoded(null);
122 }
123
124 @Override
125 public void decodeRawDimensions(Activity unused, DimensionsReceiver receiver) {
Austin Wange5005ee2023-03-31 18:19:55 +0800126 // TODO(b/277166654): Reuse the logic for all thumb asset decoding
127 sExecutorService.execute(() -> {
128 Bitmap result = null;
129 Drawable thumb = mInfo.loadThumbnail(mContext.getPackageManager());
130 if (thumb instanceof BitmapDrawable) {
131 result = ((BitmapDrawable) thumb).getBitmap();
132 } else if (thumb instanceof LayerDrawable) {
133 Drawable layer = mLayerResolver.resolveLayer((LayerDrawable) thumb);
134 if (layer instanceof BitmapDrawable) {
135 result = ((BitmapDrawable) layer).getBitmap();
136 }
137 }
Chris Poultney999c15e2023-04-10 19:10:33 +0000138 final Bitmap lr = result;
139 new Handler(Looper.getMainLooper()).post(
140 () ->
141 receiver.onDimensionsDecoded(
142 lr == null ? null : new Point(lr.getWidth(), lr.getHeight()))
143 );
Austin Wange5005ee2023-03-31 18:19:55 +0800144 });
Jon Miranda16ea1b12017-12-12 14:52:48 -0800145 }
146
147 @Override
148 public boolean supportsTiling() {
149 return false;
150 }
151
152 @Override
Santiago Etchebehere646ff1f2019-02-04 17:40:12 -0800153 public void loadDrawable(Context context, ImageView imageView,
Jon Miranda16ea1b12017-12-12 14:52:48 -0800154 int placeholderColor) {
Ching-Sung Lie4525482019-12-05 14:03:50 +0800155 RequestOptions reqOptions;
156 if (mUri != null) {
157 reqOptions = RequestOptions.centerCropTransform().apply(RequestOptions
158 .diskCacheStrategyOf(DiskCacheStrategy.NONE)
159 .skipMemoryCache(true))
160 .placeholder(new ColorDrawable(placeholderColor));
161 } else {
162 reqOptions = RequestOptions.centerCropTransform()
163 .placeholder(new ColorDrawable(placeholderColor));
164 }
zonghuayangdf9c9632021-12-09 17:54:53 +0800165 imageView.setBackgroundColor(placeholderColor);
Santiago Etchebehere646ff1f2019-02-04 17:40:12 -0800166 Glide.with(context)
Jon Miranda16ea1b12017-12-12 14:52:48 -0800167 .asDrawable()
168 .load(LiveWallpaperThumbAsset.this)
Ching-Sung Lie4525482019-12-05 14:03:50 +0800169 .apply(reqOptions)
Jon Miranda16ea1b12017-12-12 14:52:48 -0800170 .transition(DrawableTransitionOptions.withCrossFade())
171 .into(imageView);
172 }
173
Ching-Sung Li3e0674d2021-05-21 00:52:12 +0800174 @Override
175 public void loadLowResDrawable(Activity activity, ImageView imageView, int placeholderColor,
176 BitmapTransformation transformation) {
Santiago Etchebeherea5736dc2021-06-14 14:32:32 -0700177 Transformation<Bitmap> finalTransformation = (transformation == null)
178 ? new FitCenter()
179 : new MultiTransformation<>(new FitCenter(), transformation);
Ching-Sung Li3e0674d2021-05-21 00:52:12 +0800180 Glide.with(activity)
181 .asDrawable()
182 .load(LiveWallpaperThumbAsset.this)
Santiago Etchebeherea5736dc2021-06-14 14:32:32 -0700183 .apply(RequestOptions.bitmapTransform(finalTransformation)
Ching-Sung Li3e0674d2021-05-21 00:52:12 +0800184 .placeholder(new ColorDrawable(placeholderColor)))
185 .into(imageView);
186 }
187
Santiago Etchebeherea5736dc2021-06-14 14:32:32 -0700188 @Override
189 @WorkerThread
190 public Bitmap getLowResBitmap(Context context) {
191 try {
192 Drawable drawable = Glide.with(context)
193 .asDrawable()
194 .load(this)
195 .submit()
196 .get(LOW_RES_THUMB_TIMEOUT_SECONDS, TimeUnit.SECONDS);
197
198 if (drawable instanceof BitmapDrawable) {
199 BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
200 Bitmap bitmap = bitmapDrawable.getBitmap();
201 if (bitmap != null) {
202 return bitmap;
203 }
204 }
205 Bitmap bitmap;
206 // If not a bitmap, draw the drawable into a bitmap
207 if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
208 return null;
209 } else {
210 bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
211 drawable.getIntrinsicHeight(), Bitmap.Config.RGB_565);
212 }
213
214 Canvas canvas = new Canvas(bitmap);
215 drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
216 drawable.draw(canvas);
217 return bitmap;
218 } catch (InterruptedException | ExecutionException | TimeoutException e) {
219 Log.w(TAG, "Couldn't obtain low res bitmap", e);
220 }
221 return null;
222 }
223
Jon Miranda16ea1b12017-12-12 14:52:48 -0800224 /**
225 * Returns a Glide cache key.
226 */
227 Key getKey() {
228 return new LiveWallpaperThumbKey(mInfo);
229 }
230
231 /**
232 * Returns the thumbnail drawable for the live wallpaper synchronously. Should not be called on
233 * the main UI thread.
234 */
Santiago Etchebeheredec38a22019-07-02 13:24:32 -0700235 protected Drawable getThumbnailDrawable() {
Santiago Etchebeherea5736dc2021-06-14 14:32:32 -0700236 if (mThumbnailDrawable != null) {
237 return mThumbnailDrawable;
238 }
Ching-Sung Lie4525482019-12-05 14:03:50 +0800239 if (mUri != null) {
Santiago Etchebehere08fd09c2021-05-06 16:09:52 -0700240 try (AssetFileDescriptor assetFileDescriptor =
241 mContext.getContentResolver().openAssetFileDescriptor(mUri, "r")) {
Ching-Sung Lie4525482019-12-05 14:03:50 +0800242 if (assetFileDescriptor != null) {
243 mThumbnailDrawable = new BitmapDrawable(mContext.getResources(),
244 BitmapFactory.decodeStream(assetFileDescriptor.createInputStream()));
245 return mThumbnailDrawable;
246 }
247 } catch (IOException e) {
248 Log.w(TAG, "Not found thumbnail from URI.");
249 }
250 }
Santiago Etchebeherea5736dc2021-06-14 14:32:32 -0700251 mThumbnailDrawable = mInfo.loadThumbnail(mContext.getPackageManager());
252 return mThumbnailDrawable;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800253 }
254
255 /**
256 * Glide caching key for resources from any arbitrary package.
257 */
258 private static final class LiveWallpaperThumbKey implements Key {
259 private android.app.WallpaperInfo mInfo;
260
261 public LiveWallpaperThumbKey(android.app.WallpaperInfo info) {
262 mInfo = info;
263 }
264
265 @Override
266 public String toString() {
267 return getCacheKey();
268 }
269
270 @Override
271 public int hashCode() {
272 return getCacheKey().hashCode();
273 }
274
275 @Override
276 public boolean equals(Object object) {
277 if (!(object instanceof LiveWallpaperThumbKey)) {
278 return false;
279 }
280
281 LiveWallpaperThumbKey otherKey = (LiveWallpaperThumbKey) object;
282 return getCacheKey().equals(otherKey.getCacheKey());
283 }
284
285 @Override
286 public void updateDiskCacheKey(MessageDigest messageDigest) {
287 messageDigest.update(getCacheKey().getBytes(CHARSET));
288 }
289
290 /**
291 * Returns an inexpensively calculated {@link String} suitable for use as a disk cache key,
292 * based on the live wallpaper's package name and service name, which is enough to uniquely
293 * identify a live wallpaper.
294 */
295 private String getCacheKey() {
296 return "LiveWallpaperThumbKey{"
297 + "packageName=" + mInfo.getPackageName() + ","
298 + "serviceName=" + mInfo.getServiceName()
299 + '}';
300 }
301 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800302}