1 /* 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 */ 16 package com.android.wallpaper.asset; 17 18 import android.app.Activity; 19 import android.content.Context; 20 import android.content.pm.PackageManager; 21 import android.content.res.AssetFileDescriptor; 22 import android.graphics.Bitmap; 23 import android.graphics.BitmapFactory; 24 import android.graphics.Canvas; 25 import android.graphics.Rect; 26 import android.graphics.drawable.BitmapDrawable; 27 import android.graphics.drawable.ColorDrawable; 28 import android.graphics.drawable.Drawable; 29 import android.net.Uri; 30 import android.os.AsyncTask; 31 import android.util.Log; 32 import android.widget.ImageView; 33 34 import androidx.annotation.WorkerThread; 35 36 import com.bumptech.glide.Glide; 37 import com.bumptech.glide.load.Key; 38 import com.bumptech.glide.load.MultiTransformation; 39 import com.bumptech.glide.load.Transformation; 40 import com.bumptech.glide.load.engine.DiskCacheStrategy; 41 import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; 42 import com.bumptech.glide.load.resource.bitmap.FitCenter; 43 import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; 44 import com.bumptech.glide.request.RequestOptions; 45 46 import java.io.IOException; 47 import java.security.MessageDigest; 48 import java.util.concurrent.ExecutionException; 49 import java.util.concurrent.TimeUnit; 50 import java.util.concurrent.TimeoutException; 51 52 /** 53 * Asset wrapping a drawable for a live wallpaper thumbnail. 54 */ 55 public class LiveWallpaperThumbAsset extends Asset { 56 private static final String TAG = "LiveWallpaperThumbAsset"; 57 private static final int LOW_RES_THUMB_TIMEOUT_SECONDS = 2; 58 59 protected final Context mContext; 60 protected final android.app.WallpaperInfo mInfo; 61 // The content Uri of thumbnail 62 protected Uri mUri; 63 private Drawable mThumbnailDrawable; 64 LiveWallpaperThumbAsset(Context context, android.app.WallpaperInfo info)65 public LiveWallpaperThumbAsset(Context context, android.app.WallpaperInfo info) { 66 mContext = context.getApplicationContext(); 67 mInfo = info; 68 } 69 LiveWallpaperThumbAsset(Context context, android.app.WallpaperInfo info, Uri uri)70 public LiveWallpaperThumbAsset(Context context, android.app.WallpaperInfo info, Uri uri) { 71 this(context, info); 72 mUri = uri; 73 } 74 75 @Override decodeBitmap(int targetWidth, int targetHeight, BitmapReceiver receiver)76 public void decodeBitmap(int targetWidth, int targetHeight, 77 BitmapReceiver receiver) { 78 // No scaling is needed, as the thumbnail is already a thumbnail. 79 LoadThumbnailTask task = new LoadThumbnailTask(mContext, mInfo, receiver); 80 task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 81 } 82 83 @Override decodeBitmapRegion(Rect rect, int targetWidth, int targetHeight, boolean shouldAdjustForRtl, BitmapReceiver receiver)84 public void decodeBitmapRegion(Rect rect, int targetWidth, int targetHeight, 85 boolean shouldAdjustForRtl, BitmapReceiver receiver) { 86 receiver.onBitmapDecoded(null); 87 } 88 89 @Override decodeRawDimensions(Activity unused, DimensionsReceiver receiver)90 public void decodeRawDimensions(Activity unused, DimensionsReceiver receiver) { 91 receiver.onDimensionsDecoded(null); 92 } 93 94 @Override supportsTiling()95 public boolean supportsTiling() { 96 return false; 97 } 98 99 @Override loadDrawable(Context context, ImageView imageView, int placeholderColor)100 public void loadDrawable(Context context, ImageView imageView, 101 int placeholderColor) { 102 RequestOptions reqOptions; 103 if (mUri != null) { 104 reqOptions = RequestOptions.centerCropTransform().apply(RequestOptions 105 .diskCacheStrategyOf(DiskCacheStrategy.NONE) 106 .skipMemoryCache(true)) 107 .placeholder(new ColorDrawable(placeholderColor)); 108 } else { 109 reqOptions = RequestOptions.centerCropTransform() 110 .placeholder(new ColorDrawable(placeholderColor)); 111 } 112 Glide.with(context) 113 .asDrawable() 114 .load(LiveWallpaperThumbAsset.this) 115 .apply(reqOptions) 116 .transition(DrawableTransitionOptions.withCrossFade()) 117 .into(imageView); 118 } 119 120 @Override loadLowResDrawable(Activity activity, ImageView imageView, int placeholderColor, BitmapTransformation transformation)121 public void loadLowResDrawable(Activity activity, ImageView imageView, int placeholderColor, 122 BitmapTransformation transformation) { 123 Transformation<Bitmap> finalTransformation = (transformation == null) 124 ? new FitCenter() 125 : new MultiTransformation<>(new FitCenter(), transformation); 126 Glide.with(activity) 127 .asDrawable() 128 .load(LiveWallpaperThumbAsset.this) 129 .apply(RequestOptions.bitmapTransform(finalTransformation) 130 .placeholder(new ColorDrawable(placeholderColor))) 131 .into(imageView); 132 } 133 134 @Override 135 @WorkerThread getLowResBitmap(Context context)136 public Bitmap getLowResBitmap(Context context) { 137 try { 138 Drawable drawable = Glide.with(context) 139 .asDrawable() 140 .load(this) 141 .submit() 142 .get(LOW_RES_THUMB_TIMEOUT_SECONDS, TimeUnit.SECONDS); 143 144 if (drawable instanceof BitmapDrawable) { 145 BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; 146 Bitmap bitmap = bitmapDrawable.getBitmap(); 147 if (bitmap != null) { 148 return bitmap; 149 } 150 } 151 Bitmap bitmap; 152 // If not a bitmap, draw the drawable into a bitmap 153 if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { 154 return null; 155 } else { 156 bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), 157 drawable.getIntrinsicHeight(), Bitmap.Config.RGB_565); 158 } 159 160 Canvas canvas = new Canvas(bitmap); 161 drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 162 drawable.draw(canvas); 163 return bitmap; 164 } catch (InterruptedException | ExecutionException | TimeoutException e) { 165 Log.w(TAG, "Couldn't obtain low res bitmap", e); 166 } 167 return null; 168 } 169 170 /** 171 * Returns a Glide cache key. 172 */ getKey()173 Key getKey() { 174 return new LiveWallpaperThumbKey(mInfo); 175 } 176 177 /** 178 * Returns the thumbnail drawable for the live wallpaper synchronously. Should not be called on 179 * the main UI thread. 180 */ getThumbnailDrawable()181 protected Drawable getThumbnailDrawable() { 182 if (mThumbnailDrawable != null) { 183 return mThumbnailDrawable; 184 } 185 if (mUri != null) { 186 try (AssetFileDescriptor assetFileDescriptor = 187 mContext.getContentResolver().openAssetFileDescriptor(mUri, "r")) { 188 if (assetFileDescriptor != null) { 189 mThumbnailDrawable = new BitmapDrawable(mContext.getResources(), 190 BitmapFactory.decodeStream(assetFileDescriptor.createInputStream())); 191 return mThumbnailDrawable; 192 } 193 } catch (IOException e) { 194 Log.w(TAG, "Not found thumbnail from URI."); 195 } 196 } 197 mThumbnailDrawable = mInfo.loadThumbnail(mContext.getPackageManager()); 198 return mThumbnailDrawable; 199 } 200 201 /** 202 * Glide caching key for resources from any arbitrary package. 203 */ 204 private static final class LiveWallpaperThumbKey implements Key { 205 private android.app.WallpaperInfo mInfo; 206 LiveWallpaperThumbKey(android.app.WallpaperInfo info)207 public LiveWallpaperThumbKey(android.app.WallpaperInfo info) { 208 mInfo = info; 209 } 210 211 @Override toString()212 public String toString() { 213 return getCacheKey(); 214 } 215 216 @Override hashCode()217 public int hashCode() { 218 return getCacheKey().hashCode(); 219 } 220 221 @Override equals(Object object)222 public boolean equals(Object object) { 223 if (!(object instanceof LiveWallpaperThumbKey)) { 224 return false; 225 } 226 227 LiveWallpaperThumbKey otherKey = (LiveWallpaperThumbKey) object; 228 return getCacheKey().equals(otherKey.getCacheKey()); 229 } 230 231 @Override updateDiskCacheKey(MessageDigest messageDigest)232 public void updateDiskCacheKey(MessageDigest messageDigest) { 233 messageDigest.update(getCacheKey().getBytes(CHARSET)); 234 } 235 236 /** 237 * Returns an inexpensively calculated {@link String} suitable for use as a disk cache key, 238 * based on the live wallpaper's package name and service name, which is enough to uniquely 239 * identify a live wallpaper. 240 */ getCacheKey()241 private String getCacheKey() { 242 return "LiveWallpaperThumbKey{" 243 + "packageName=" + mInfo.getPackageName() + "," 244 + "serviceName=" + mInfo.getServiceName() 245 + '}'; 246 } 247 } 248 249 /** 250 * AsyncTask subclass which loads the live wallpaper's thumbnail bitmap off the main UI thread. 251 * Resolves with null if live wallpaper thumbnail is not a bitmap. 252 */ 253 private static class LoadThumbnailTask extends AsyncTask<Void, Void, Bitmap> { 254 private final PackageManager mPackageManager; 255 private android.app.WallpaperInfo mInfo; 256 private BitmapReceiver mReceiver; 257 LoadThumbnailTask(Context context, android.app.WallpaperInfo info, BitmapReceiver receiver)258 public LoadThumbnailTask(Context context, android.app.WallpaperInfo info, 259 BitmapReceiver receiver) { 260 mInfo = info; 261 mReceiver = receiver; 262 mPackageManager = context.getPackageManager(); 263 } 264 265 @Override doInBackground(Void... unused)266 protected Bitmap doInBackground(Void... unused) { 267 Drawable thumb = mInfo.loadThumbnail(mPackageManager); 268 269 // Live wallpaper components may or may not specify a thumbnail drawable. 270 if (thumb instanceof BitmapDrawable) { 271 return ((BitmapDrawable) thumb).getBitmap(); 272 } else if (thumb != null) { 273 Bitmap bitmap; 274 if (thumb.getIntrinsicWidth() > 0 && thumb.getIntrinsicHeight() > 0) { 275 bitmap = Bitmap.createBitmap(thumb.getIntrinsicWidth(), 276 thumb.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 277 } else { 278 return null; 279 } 280 281 Canvas canvas = new Canvas(bitmap); 282 thumb.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 283 thumb.draw(canvas); 284 return bitmap; 285 } 286 287 // If no thumbnail was specified, return a null bitmap. 288 return null; 289 } 290 291 @Override onPostExecute(Bitmap bitmap)292 protected void onPostExecute(Bitmap bitmap) { 293 mReceiver.onBitmapDecoded(bitmap); 294 } 295 } 296 } 297