/*
* Copyright (c) 2016 yvolk (Yuri Volkov), http://yurivolkov.com
*
* 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 org.andstatus.app.graphics;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.Display;
import android.view.WindowManager;
import org.andstatus.app.context.MyContextHolder;
import org.andstatus.app.context.MyPreferences;
import org.andstatus.app.context.MyTheme;
import org.andstatus.app.data.AvatarFile;
import org.andstatus.app.util.I18n;
import org.andstatus.app.util.MyLog;
import org.andstatus.app.util.SharedPreferencesUtil;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author yvolk@yurivolkov.com
*/
public class MyImageCache {
private static final float ATTACHED_IMAGES_CACHE_PART_OF_TOTAL_APP_MEMORY = 0.20f;
public static final int ATTACHED_IMAGES_CACHE_SIZE_MIN = 10;
public static final int ATTACHED_IMAGES_CACHE_SIZE_MAX = 20;
private static final float AVATARS_CACHE_PART_OF_TOTAL_APP_MEMORY = 0.05f;
public static final int AVATARS_CACHE_SIZE_MIN = 200;
public static final int AVATARS_CACHE_SIZE_MAX = 700;
static volatile MyDrawableCache attachedImagesCache;
private static volatile MyDrawableCache avatarsCache;
private MyImageCache() {
// Empty
}
public static synchronized void initialize(Context context) {
styledDrawables.clear();
if (attachedImagesCache != null) {
return;
}
initializeAttachedImagesCache(context);
initializeAvatarsCache(context);
MyLog.i(MyImageCache.class.getSimpleName(), "Cache initialized. " + getCacheInfo());
}
private static void initializeAttachedImagesCache(Context context) {
// We assume that current display orientation is preferred, so we use "y" size only
int imageSize = (int) Math.round(AttachedImageView.MAX_ATTACHED_IMAGE_PART *
getDisplaySize(context).y);
int cacheSize = 0;
for (int i = 0 ; i < 5; i++) {
cacheSize = calcCacheSize(context, imageSize,
ATTACHED_IMAGES_CACHE_PART_OF_TOTAL_APP_MEMORY);
if (cacheSize >= ATTACHED_IMAGES_CACHE_SIZE_MIN || imageSize < 2 ) {
break;
}
imageSize = (imageSize * 2) / 3;
}
if (cacheSize > ATTACHED_IMAGES_CACHE_SIZE_MAX) {
cacheSize = ATTACHED_IMAGES_CACHE_SIZE_MAX;
}
attachedImagesCache = new MyDrawableCache(context, "Attached images", imageSize,
cacheSize);
}
private static void initializeAvatarsCache(Context context) {
float displayDensity = context.getResources().getDisplayMetrics().density;
int imageSize = Math.round(AvatarFile.AVATAR_SIZE_DIP * displayDensity);
int cacheSize = 0;
for (int i = 0 ; i < 5; i++) {
cacheSize = calcCacheSize(context, imageSize, AVATARS_CACHE_PART_OF_TOTAL_APP_MEMORY);
if (cacheSize >= AVATARS_CACHE_SIZE_MIN || imageSize < 48 ) {
break;
}
imageSize = (imageSize * 2) / 3;
}
if (cacheSize > AVATARS_CACHE_SIZE_MAX) {
cacheSize = AVATARS_CACHE_SIZE_MAX;
}
avatarsCache = new MyDrawableCache(context, "Avatars", imageSize, cacheSize);
setAvatarsRounded();
}
public static void setAvatarsRounded() {
avatarsCache.evictAll();
avatarsCache.rounded = SharedPreferencesUtil.getBoolean(MyPreferences.KEY_ROUNDED_AVATARS, true);
}
private static int calcCacheSize(Context context, int imageSize, float partOfAvailableMemory) {
return Math.round(partOfAvailableMemory * getTotalAppMemory(context)
/ imageSize / imageSize / MyDrawableCache.BYTES_PER_PIXEL);
}
@NonNull
private static long getTotalAppMemory(Context context) {
int memoryClass = 16;
if (context != null) {
ActivityManager actManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
memoryClass = actManager.getMemoryClass();
}
return memoryClass * 1024L * 1024L;
}
@NonNull
private static ActivityManager.MemoryInfo getMemoryInfo(Context context) {
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
if (context != null) {
ActivityManager actManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
actManager.getMemoryInfo(memInfo);
}
return memInfo;
}
@NonNull
public static Point getAttachedImageSize(String path) {
return attachedImagesCache.getImageSize(path);
}
@Nullable
public static Drawable getAvatarDrawable(Object objTag, String path) {
return avatarsCache.getDrawable(objTag, path);
}
public static int getAvatarWidthPixels() {
return avatarsCache.getMaxBitmapWidth();
}
public static Drawable getCachedAttachedImageDrawable(Object objTag, String path) {
return attachedImagesCache.getCachedDrawable(objTag, path);
}
public static Drawable getAttachedImageDrawable(Object objTag, String path) {
return attachedImagesCache.getDrawable(objTag, path);
}
public static String getCacheInfo() {
StringBuilder builder = new StringBuilder("ImageCaches: ");
if (avatarsCache == null || attachedImagesCache == null) {
builder.append("not initialized\n");
} else {
builder.append(avatarsCache.getInfo() + "\n");
builder.append(attachedImagesCache.getInfo() + "\n");
builder.append("Styled drawables: " + styledDrawables.size() + "\n");
}
Context context = MyContextHolder.get().context();
if (context != null) {
builder.append("Memory. App total: "
+ I18n.formatBytes(getTotalAppMemory(context)));
ActivityManager.MemoryInfo memInfo = getMemoryInfo(context);
builder.append("; Device: available " + I18n.formatBytes(memInfo.availMem) + " of "
+ I18n.formatBytes(memInfo.totalMem) + "\n");
}
return builder.toString();
}
/**
* See http://stackoverflow.com/questions/1016896/how-to-get-screen-dimensions
*/
public static Point getDisplaySize(Context context) {
Point size = new Point();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (windowManager != null) {
Display display = windowManager.getDefaultDisplay();
if (display != null) {
display.getSize(size);
}
}
if (size.x < 480 || size.y < 480) {
// This is needed for preview in Layout editor
size.x = 1280;
size.y = 768;
}
return size;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static Drawable getDrawableCompat(Context context, int drawableId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return context.getTheme().getDrawable(drawableId);
} else {
return context.getResources().getDrawable(drawableId);
}
}
private static final Map<Integer, Drawable[]> styledDrawables = new ConcurrentHashMap<>();
public static Drawable getStyledDrawable(int resourceIdLight, int resourceId) {
Drawable[] styledDrawable = styledDrawables.get(resourceId);
if (styledDrawable == null) {
Context context = MyContextHolder.get().context();
if (context != null) {
Drawable drawable = getDrawableCompat(context, resourceId);
Drawable drawableLight = getDrawableCompat(context, resourceIdLight);
styledDrawable = new Drawable[]{drawable, drawableLight};
styledDrawables.put(resourceId, styledDrawable);
} else {
return new BitmapDrawable();
}
}
return styledDrawable[MyTheme.isThemeLight() ? 1 : 0];
}
}