/*
* Copyright 2014 Yaroslav Mytkalyk
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.docd.purefm.utils;
import java.io.File;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import android.annotation.SuppressLint;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.ThumbnailUtils;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
final class PFMThumbnailUtils extends ThumbnailUtils {
private PFMThumbnailUtils() {}
private static final Set<SoftReference<Bitmap>> sReusableBitmaps =
Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
public static void addToReusableCache(final Bitmap value) {
sReusableBitmaps.add(new SoftReference<>(value));
}
public static Bitmap createPictureThumbnail(final File target, final int w) {
return decodeSampledBitmap(target, w);
}
@Nullable
public static Bitmap extractApkIcon(@NonNull final PackageManager pm, @NonNull final File file) {
final String filePath = file.getPath();
final PackageInfo packageInfo = pm.getPackageArchiveInfo(filePath, PackageManager.GET_ACTIVITIES);
if(packageInfo != null) {
final ApplicationInfo appInfo = packageInfo.applicationInfo;
if (appInfo != null) {
appInfo.sourceDir = filePath;
appInfo.publicSourceDir = filePath;
final Drawable icon = appInfo.loadIcon(pm);
if (icon != null) {
return ((BitmapDrawable) icon).getBitmap();
}
}
}
return null;
}
@NonNull
private static Bitmap decodeSampledBitmap(@NonNull final File file, final int reqWidth) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
options.inJustDecodeBounds = false;
options.inMutable = true;
if (options.outWidth != -1 && options.outHeight != -1) {
final int originalSize = (options.outHeight > options.outWidth) ? options.outWidth
: options.outHeight;
options.inSampleSize = originalSize / reqWidth;
}
final Bitmap inBitmap = getBitmapFromReusableSet(options);
if (inBitmap != null) {
options.inBitmap = inBitmap;
}
return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
}
/**
* Returns reusable bitmap, if exists
*
* @param options Options to look against
* @return reusable bitmap, if exists
*/
@Nullable
private static Bitmap getBitmapFromReusableSet(@NonNull final BitmapFactory.Options options) {
Bitmap bitmap = null;
if (sReusableBitmaps != null && sReusableBitmaps.isEmpty()) {
synchronized (sReusableBitmaps) {
final Iterator<SoftReference<Bitmap>> iterator
= sReusableBitmaps.iterator();
Bitmap item;
while (iterator.hasNext()) {
item = iterator.next().get();
if (item != null && item.isMutable()) {
// Check to see it the item can be used for inBitmap.
if (canUseForInBitmap(item, options)) {
bitmap = item;
// Remove from reusable set so it can't be used again.
iterator.remove();
break;
}
} else {
// Remove from the set if the reference has been cleared.
iterator.remove();
}
}
}
}
return bitmap;
}
@SuppressLint("NewApi")
private static boolean canUseForInBitmap(@NonNull final Bitmap candidate,
@NonNull final BitmapFactory.Options targetOptions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// From Android 4.4 (KitKat) onward we can re-use if the byte size of
// the new bitmap is smaller than the reusable bitmap candidate
// allocation byte count.
final int width = targetOptions.outWidth / targetOptions.inSampleSize;
final int height = targetOptions.outHeight / targetOptions.inSampleSize;
final int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
return byteCount <= candidate.getAllocationByteCount();
}
// On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
return candidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1;
}
/**
* Returns the byte usage per pixel of a bitmap based on its configuration.
*
* @return byte usage per pixel
*/
private static int getBytesPerPixel(@NonNull final Bitmap.Config config) {
switch (config) {
case ARGB_8888:
return 4;
case RGB_565:
case ARGB_4444:
return 2;
default:
return 1;
}
}
}