Merge branch 'UIL-update' into 'master'

Universal Image Loader update to use the cache better

See merge request fdroid/fdroidclient!661
This commit is contained in:
Hans-Christoph Steiner 2018-03-29 18:24:23 +00:00
commit a5acdc3db8
16 changed files with 145 additions and 220 deletions

View File

@ -173,7 +173,7 @@ public class AppDetails2 extends AppCompatActivity
// Load the feature graphic, if present // Load the feature graphic, if present
final FeatureImage featureImage = (FeatureImage) findViewById(R.id.feature_graphic); final FeatureImage featureImage = (FeatureImage) findViewById(R.id.feature_graphic);
DisplayImageOptions displayImageOptions = Utils.getImageLoadingOptions().build(); DisplayImageOptions displayImageOptions = Utils.getRepoAppDisplayImageOptions();
String featureGraphicUrl = app.getFeatureGraphicUrl(this); String featureGraphicUrl = app.getFeatureGraphicUrl(this);
featureImage.loadImageAndDisplay(ImageLoader.getInstance(), displayImageOptions, featureImage.loadImageAndDisplay(ImageLoader.getInstance(), displayImageOptions,
featureGraphicUrl, app.iconUrl); featureGraphicUrl, app.iconUrl);

View File

@ -32,17 +32,13 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.os.StrictMode; import android.os.StrictMode;
import android.support.annotation.NonNull;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.Toast; import android.widget.Toast;
import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiskCache;
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import info.guardianproject.netcipher.NetCipher; import info.guardianproject.netcipher.NetCipher;
@ -59,7 +55,6 @@ import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.InstalledAppProviderService; import org.fdroid.fdroid.data.InstalledAppProviderService;
import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.SanitizedFile;
import org.fdroid.fdroid.installer.ApkFileProvider; import org.fdroid.fdroid.installer.ApkFileProvider;
import org.fdroid.fdroid.installer.InstallHistoryService; import org.fdroid.fdroid.installer.InstallHistoryService;
import org.fdroid.fdroid.net.ImageLoaderForUIL; import org.fdroid.fdroid.net.ImageLoaderForUIL;
@ -302,6 +297,20 @@ public class FDroidApp extends Application {
atStartTime.edit().putString(lastLocaleKey, currentLocale).apply(); atStartTime.edit().putString(lastLocaleKey, currentLocale).apply();
} }
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level >= TRIM_MEMORY_BACKGROUND) {
ImageLoader.getInstance().clearMemoryCache();
}
}
@Override
public void onLowMemory() {
super.onLowMemory();
ImageLoader.getInstance().clearMemoryCache();
}
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
@ -387,34 +396,8 @@ public class FDroidApp extends Application {
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext()) ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
.imageDownloader(new ImageLoaderForUIL(getApplicationContext())) .imageDownloader(new ImageLoaderForUIL(getApplicationContext()))
.diskCache(new LimitedAgeDiskCache( .defaultDisplayImageOptions(Utils.getDefaultDisplayImageOptionsBuilder().build())
Utils.getImageCacheDir(this), .threadPoolSize(getThreadPoolSize())
null,
new FileNameGenerator() {
@NonNull
@Override
public String generate(String imageUri) {
if (TextUtils.isEmpty(imageUri)) {
return "null";
}
String fileNameToSanitize;
Uri uri = Uri.parse(imageUri);
if (TextUtils.isEmpty(uri.getPath())) {
// files with URL like "drawable://213083835209" used by the category backgrounds
fileNameToSanitize = imageUri.replaceAll("[:/]", "");
} else {
fileNameToSanitize = uri.getPath().replace("/", "-");
}
return SanitizedFile.sanitizeFileName(fileNameToSanitize);
}
},
// 30 days in secs: 30*24*60*60 = 2592000
2592000)
)
.threadPoolSize(4)
.threadPriority(Thread.NORM_PRIORITY - 2) // Default is NORM_PRIORITY - 1
.build(); .build();
ImageLoader.getInstance().init(config); ImageLoader.getInstance().init(config);
@ -487,6 +470,24 @@ public class FDroidApp extends Application {
return false; return false;
} }
/**
* Return the number of threads Universal Image Loader should use, based on
* the total RAM in the device. Devices with lots of RAM can do lots of
* parallel operations for fast icon loading.
*/
@TargetApi(16)
private int getThreadPoolSize() {
if (Build.VERSION.SDK_INT >= 16) {
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
if (activityManager != null) {
activityManager.getMemoryInfo(memInfo);
return (int) Math.max(1, Math.min(16, memInfo.totalMem / 256 / 1024 / 1024));
}
}
return 2;
}
@TargetApi(18) @TargetApi(18)
private BluetoothAdapter getBluetoothAdapter() { private BluetoothAdapter getBluetoothAdapter() {
// to use the new, recommended way of getting the adapter // to use the new, recommended way of getting the adapter

View File

@ -21,10 +21,8 @@ import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.view.View; import android.view.View;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.assist.ImageSize; import com.nostra13.universalimageloader.core.assist.ImageSize;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import com.nostra13.universalimageloader.utils.DiskCacheUtils; import com.nostra13.universalimageloader.utils.DiskCacheUtils;
@ -54,7 +52,6 @@ class NotificationHelper {
private final Context context; private final Context context;
private final NotificationManagerCompat notificationManager; private final NotificationManagerCompat notificationManager;
private final AppUpdateStatusManager appUpdateStatusManager; private final AppUpdateStatusManager appUpdateStatusManager;
private final DisplayImageOptions displayImageOptions;
private final ArrayList<AppUpdateStatusManager.AppUpdateStatus> updates = new ArrayList<>(); private final ArrayList<AppUpdateStatusManager.AppUpdateStatus> updates = new ArrayList<>();
private final ArrayList<AppUpdateStatusManager.AppUpdateStatus> installed = new ArrayList<>(); private final ArrayList<AppUpdateStatusManager.AppUpdateStatus> installed = new ArrayList<>();
@ -62,12 +59,6 @@ class NotificationHelper {
this.context = context; this.context = context;
appUpdateStatusManager = AppUpdateStatusManager.getInstance(context); appUpdateStatusManager = AppUpdateStatusManager.getInstance(context);
notificationManager = NotificationManagerCompat.from(context); notificationManager = NotificationManagerCompat.from(context);
displayImageOptions = new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(true)
.imageScaleType(ImageScaleType.NONE)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_LIST_CHANGED); filter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_LIST_CHANGED);
@ -506,7 +497,8 @@ class NotificationHelper {
Bitmap iconLarge = null; Bitmap iconLarge = null;
if (TextUtils.isEmpty(entry.app.iconUrl)) { if (TextUtils.isEmpty(entry.app.iconUrl)) {
return null; return null;
} else if (entry.status == AppUpdateStatusManager.Status.Downloading || entry.status == AppUpdateStatusManager.Status.Installing) { } else if (entry.status == AppUpdateStatusManager.Status.Downloading
|| entry.status == AppUpdateStatusManager.Status.Installing) {
Bitmap bitmap = Bitmap.createBitmap(largeIconSize.x, largeIconSize.y, Bitmap.Config.ARGB_8888); Bitmap bitmap = Bitmap.createBitmap(largeIconSize.x, largeIconSize.y, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap); Canvas canvas = new Canvas(bitmap);
Drawable downloadIcon = ContextCompat.getDrawable(context, R.drawable.ic_notification_download); Drawable downloadIcon = ContextCompat.getDrawable(context, R.drawable.ic_notification_download);
@ -516,10 +508,11 @@ class NotificationHelper {
} }
return bitmap; return bitmap;
} else if (DiskCacheUtils.findInCache(entry.app.iconUrl, ImageLoader.getInstance().getDiskCache()) != null) { } else if (DiskCacheUtils.findInCache(entry.app.iconUrl, ImageLoader.getInstance().getDiskCache()) != null) {
iconLarge = ImageLoader.getInstance().loadImageSync(entry.app.iconUrl, new ImageSize(largeIconSize.x, largeIconSize.y), displayImageOptions); iconLarge = ImageLoader.getInstance().loadImageSync(
entry.app.iconUrl, new ImageSize(largeIconSize.x, largeIconSize.y));
} else { } else {
// Load it for later! // Load it for later!
ImageLoader.getInstance().loadImage(entry.app.iconUrl, new ImageSize(largeIconSize.x, largeIconSize.y), displayImageOptions, new ImageLoadingListener() { ImageLoader.getInstance().loadImage(entry.app.iconUrl, new ImageSize(largeIconSize.x, largeIconSize.y), new ImageLoadingListener() {
AppUpdateStatusManager.AppUpdateStatus entry; AppUpdateStatusManager.AppUpdateStatus entry;

View File

@ -92,6 +92,9 @@ public final class Utils {
"%.0f B", "%.0f KiB", "%.1f MiB", "%.2f GiB", "%.0f B", "%.0f KiB", "%.1f MiB", "%.2f GiB",
}; };
private static DisplayImageOptions.Builder defaultDisplayImageOptionsBuilder;
private static DisplayImageOptions repoAppDisplayImageOptions;
public static final String FALLBACK_ICONS_DIR = "/icons/"; public static final String FALLBACK_ICONS_DIR = "/icons/";
/* /*
@ -369,15 +372,33 @@ public final class Utils {
return new Locale(languageTag); return new Locale(languageTag);
} }
public static DisplayImageOptions.Builder getImageLoadingOptions() { public static DisplayImageOptions.Builder getDefaultDisplayImageOptionsBuilder() {
return new DisplayImageOptions.Builder() if (defaultDisplayImageOptionsBuilder == null) {
defaultDisplayImageOptionsBuilder = new DisplayImageOptions.Builder()
.cacheInMemory(true) .cacheInMemory(true)
.cacheOnDisk(true) .cacheOnDisk(true)
.imageScaleType(ImageScaleType.NONE) .considerExifParams(false)
.bitmapConfig(Bitmap.Config.RGB_565)
.imageScaleType(ImageScaleType.NONE);
}
return defaultDisplayImageOptionsBuilder;
}
/**
* Gets the {@link DisplayImageOptions} instance used to configure
* {@link com.nostra13.universalimageloader.core.ImageLoader} instances
* used to display app icons. It lazy loads a reusable static instance.
*/
public static DisplayImageOptions getRepoAppDisplayImageOptions() {
if (repoAppDisplayImageOptions == null) {
repoAppDisplayImageOptions = getDefaultDisplayImageOptionsBuilder()
.showImageOnLoading(R.drawable.ic_repo_app_default) .showImageOnLoading(R.drawable.ic_repo_app_default)
.showImageForEmptyUri(R.drawable.ic_repo_app_default) .showImageForEmptyUri(R.drawable.ic_repo_app_default)
.showImageOnFail(R.drawable.ic_repo_app_default)
.displayer(new FadeInBitmapDisplayer(200, true, true, false)) .displayer(new FadeInBitmapDisplayer(200, true, true, false))
.bitmapConfig(Bitmap.Config.RGB_565); .build();
}
return repoAppDisplayImageOptions;
} }
// this is all new stuff being added // this is all new stuff being added
@ -554,8 +575,10 @@ public final class Utils {
} }
} }
// Need this to add the unimplemented support for ordered and unordered /**
// lists to Html.fromHtml(). * Need this to add the unimplemented support for ordered and unordered
* lists to Html.fromHtml().
*/
public static class HtmlTagHandler implements Html.TagHandler { public static class HtmlTagHandler implements Html.TagHandler {
int listNum; int listNum;

View File

@ -3,7 +3,6 @@ package org.fdroid.fdroid.net;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.Schema; import org.fdroid.fdroid.data.Schema;
@ -41,11 +40,9 @@ public class DownloaderFactory {
localBroadcastManager = LocalBroadcastManager.getInstance(context); localBroadcastManager = LocalBroadcastManager.getInstance(context);
} }
if (isBluetoothAddress(url)) { if ("bluetooth".equalsIgnoreCase(url.getProtocol())) {
String macAddress = url.getHost().replace("-", ":"); String macAddress = url.getHost().replace("-", ":");
downloader = new BluetoothDownloader(macAddress, url, destFile); downloader = new BluetoothDownloader(macAddress, url, destFile);
} else if (isLocalFile(url)) {
downloader = new LocalFileDownloader(url, destFile);
} else { } else {
final String[] projection = {Schema.RepoTable.Cols.USERNAME, Schema.RepoTable.Cols.PASSWORD}; final String[] projection = {Schema.RepoTable.Cols.USERNAME, Schema.RepoTable.Cols.PASSWORD};
Repo repo = RepoProvider.Helper.findByUrl(context, Uri.parse(url.toString()), projection); Repo repo = RepoProvider.Helper.findByUrl(context, Uri.parse(url.toString()), projection);
@ -57,12 +54,4 @@ public class DownloaderFactory {
} }
return downloader; return downloader;
} }
private static boolean isBluetoothAddress(URL url) {
return "bluetooth".equalsIgnoreCase(url.getProtocol());
}
private static boolean isLocalFile(URL url) {
return "file".equalsIgnoreCase(url.getProtocol());
}
} }

View File

@ -1,15 +1,18 @@
package org.fdroid.fdroid.net; package org.fdroid.fdroid.net;
import android.content.Context; import android.content.Context;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader; import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
/** /**
* Class used by the Universal Image Loader library (UIL) to fetch images for displaying in F-Droid. * Class used by the Universal Image Loader library (UIL) to fetch images for
* See {@link org.fdroid.fdroid.FDroidApp} for where this gets configured. * displaying in F-Droid. A custom subclass is needed since F-Droid's
* {@link HttpDownloader} provides support for Tor, proxying, and automatic
* mirror failover.
*
* @see org.fdroid.fdroid.FDroidApp#onCreate() for where this is setup
*/ */
public class ImageLoaderForUIL implements com.nostra13.universalimageloader.core.download.ImageDownloader { public class ImageLoaderForUIL implements com.nostra13.universalimageloader.core.download.ImageDownloader {
@ -22,15 +25,10 @@ public class ImageLoaderForUIL implements com.nostra13.universalimageloader.core
@Override @Override
public InputStream getStream(String imageUri, Object extra) throws IOException { public InputStream getStream(String imageUri, Object extra) throws IOException {
switch (Scheme.ofUri(imageUri)) { switch (Scheme.ofUri(imageUri)) {
case ASSETS: case HTTP:
return context.getAssets().open(Scheme.ASSETS.crop(imageUri)); case HTTPS:
case DRAWABLE:
return new BaseImageDownloader(context).getStream(imageUri, extra);
default:
return DownloaderFactory.create(context, imageUri).getInputStream(); return DownloaderFactory.create(context, imageUri).getInputStream();
} }
return new BaseImageDownloader(context).getStream(imageUri, extra);
} }
} }

View File

@ -1,52 +0,0 @@
package org.fdroid.fdroid.net;
import org.fdroid.fdroid.Utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
public class LocalFileDownloader extends Downloader {
private InputStream inputStream;
LocalFileDownloader(URL url, File destFile) throws FileNotFoundException, MalformedURLException {
super(url, destFile);
}
@Override
protected InputStream getDownloadersInputStream() throws IOException {
inputStream = new FileInputStream(new File(sourceUrl.getPath()));
return inputStream;
}
@Override
protected void close() {
if (inputStream != null) {
Utils.closeQuietly(inputStream);
}
}
@Override
public boolean hasChanged() {
return false;
}
@Override
public int totalDownloadSize() {
return 0;
}
@Override
public void download() throws IOException, InterruptedException {
if (new File(sourceUrl.getPath()).exists()) {
downloadFromStream(1024 * 50, false);
} else {
notFound = true;
}
}
}

View File

@ -23,7 +23,6 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnCancelListener;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
@ -36,11 +35,10 @@ import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TabHost; import android.widget.TabHost;
import android.widget.TextView; import android.widget.TextView;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.App;
@ -71,15 +69,6 @@ public class InstallConfirmActivity extends FragmentActivity implements OnCancel
private App app; private App app;
private final DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(true)
.imageScaleType(ImageScaleType.NONE)
.showImageOnLoading(R.drawable.ic_repo_app_default)
.showImageForEmptyUri(R.drawable.ic_repo_app_default)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
private void startInstallConfirm() { private void startInstallConfirm() {
View appSnippet = findViewById(R.id.app_snippet); View appSnippet = findViewById(R.id.app_snippet);
TextView appName = (TextView) appSnippet.findViewById(R.id.app_name); TextView appName = (TextView) appSnippet.findViewById(R.id.app_name);
@ -87,7 +76,8 @@ public class InstallConfirmActivity extends FragmentActivity implements OnCancel
TabHost tabHost = (TabHost) findViewById(android.R.id.tabhost); TabHost tabHost = (TabHost) findViewById(android.R.id.tabhost);
appName.setText(app.name); appName.setText(app.name);
ImageLoader.getInstance().displayImage(app.iconUrl, appIcon, displayImageOptions); ImageLoader.getInstance().displayImage(app.iconUrl, appIcon,
Utils.getRepoAppDisplayImageOptions());
tabHost.setup(); tabHost.setup();
ViewPager viewPager = (ViewPager) findViewById(R.id.pager); ViewPager viewPager = (ViewPager) findViewById(R.id.pager);

View File

@ -6,7 +6,6 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.DrawableRes; import android.support.annotation.DrawableRes;
import android.support.annotation.LayoutRes; import android.support.annotation.LayoutRes;
@ -34,11 +33,7 @@ import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
@ -317,7 +312,6 @@ public class AppDetailsRecyclerViewAdapter
final TextView progressLabel; final TextView progressLabel;
final TextView progressPercent; final TextView progressPercent;
final View progressCancel; final View progressCancel;
final DisplayImageOptions displayImageOptions;
boolean descriptionIsExpanded; boolean descriptionIsExpanded;
HeaderViewHolder(View view) { HeaderViewHolder(View view) {
@ -340,14 +334,6 @@ public class AppDetailsRecyclerViewAdapter
progressLabel = (TextView) view.findViewById(R.id.progress_label); progressLabel = (TextView) view.findViewById(R.id.progress_label);
progressPercent = (TextView) view.findViewById(R.id.progress_percent); progressPercent = (TextView) view.findViewById(R.id.progress_percent);
progressCancel = view.findViewById(R.id.progress_cancel); progressCancel = view.findViewById(R.id.progress_cancel);
displayImageOptions = new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(true)
.imageScaleType(ImageScaleType.NONE)
.showImageOnLoading(R.drawable.ic_repo_app_default)
.showImageForEmptyUri(R.drawable.ic_repo_app_default)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
descriptionView.setMaxLines(MAX_LINES); descriptionView.setMaxLines(MAX_LINES);
descriptionView.setEllipsize(TextUtils.TruncateAt.MARQUEE); descriptionView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
descriptionMoreView.setOnClickListener(new View.OnClickListener() { descriptionMoreView.setOnClickListener(new View.OnClickListener() {
@ -408,7 +394,7 @@ public class AppDetailsRecyclerViewAdapter
} }
public void bindModel() { public void bindModel() {
ImageLoader.getInstance().displayImage(app.iconUrl, iconView, displayImageOptions); ImageLoader.getInstance().displayImage(app.iconUrl, iconView, Utils.getRepoAppDisplayImageOptions());
titleView.setText(app.name); titleView.setText(app.name);
if (!TextUtils.isEmpty(app.authorName)) { if (!TextUtils.isEmpty(app.authorName)) {
authorView.setText(context.getString(R.string.by_author_format, app.authorName)); authorView.setText(context.getString(R.string.by_author_format, app.authorName));

View File

@ -3,7 +3,6 @@ package org.fdroid.fdroid.views;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -16,18 +15,21 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.AppProvider;
/** /**
* Full screen view of an apps screenshots to swipe through. * Full screen view of an apps screenshots to swipe through. This will always
* download the image, even if the user has said not to use "unmetered" networks,
* e.g. WiFi. That is because the user has to click on the thumbnail in
* {@link org.fdroid.fdroid.AppDetails2} in order to bring up this activity.
* That makes it a specific request for that image, rather than regular
* background loading.
*/ */
public class ScreenShotsActivity extends AppCompatActivity { public class ScreenShotsActivity extends AppCompatActivity {
@ -114,13 +116,9 @@ public class ScreenShotsActivity extends AppCompatActivity {
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder() DisplayImageOptions displayImageOptions = Utils.getDefaultDisplayImageOptionsBuilder()
.cacheInMemory(true)
.cacheOnDisk(true)
.imageScaleType(ImageScaleType.NONE)
.showImageOnLoading(R.drawable.screenshot_placeholder) .showImageOnLoading(R.drawable.screenshot_placeholder)
.showImageForEmptyUri(R.drawable.screenshot_placeholder) .showImageForEmptyUri(R.drawable.screenshot_placeholder)
.bitmapConfig(Bitmap.Config.RGB_565)
.build(); .build();
View rootView = inflater.inflate(R.layout.activity_screenshots_page, container, false); View rootView = inflater.inflate(R.layout.activity_screenshots_page, container, false);

View File

@ -1,20 +1,20 @@
package org.fdroid.fdroid.views; package org.fdroid.fdroid.views;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.App;
/**
* Loads and displays the small screenshots that are inline in {@link org.fdroid.fdroid.AppDetails2}
*/
class ScreenShotsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { class ScreenShotsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final String[] screenshots; private final String[] screenshots;
private final DisplayImageOptions displayImageOptions; private final DisplayImageOptions displayImageOptions;
@ -25,13 +25,9 @@ class ScreenShotsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.V
this.listener = listener; this.listener = listener;
screenshots = app.getAllScreenshots(context); screenshots = app.getAllScreenshots(context);
displayImageOptions = new DisplayImageOptions.Builder() displayImageOptions = Utils.getDefaultDisplayImageOptionsBuilder()
.cacheInMemory(true)
.cacheOnDisk(true)
.imageScaleType(ImageScaleType.NONE)
.showImageOnLoading(R.drawable.screenshot_placeholder) .showImageOnLoading(R.drawable.screenshot_placeholder)
.showImageForEmptyUri(R.drawable.screenshot_placeholder) .showImageForEmptyUri(R.drawable.screenshot_placeholder)
.bitmapConfig(Bitmap.Config.RGB_565)
.build(); .build();
} }

View File

@ -18,6 +18,7 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.AppProvider;
@ -124,6 +125,22 @@ public class AppListActivity extends AppCompatActivity implements LoaderManager.
appView.setHasFixedSize(true); appView.setHasFixedSize(true);
appView.setLayoutManager(new LinearLayoutManager(this)); appView.setLayoutManager(new LinearLayoutManager(this));
appView.setAdapter(appAdapter); appView.setAdapter(appAdapter);
appView.addOnScrollListener(new RecyclerView.OnScrollListener() {
private final ImageLoader imageLoader = ImageLoader.getInstance();
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
switch (newState) {
case RecyclerView.SCROLL_STATE_DRAGGING:
imageLoader.pause();
break;
case RecyclerView.SCROLL_STATE_IDLE:
imageLoader.resume();
break;
}
super.onScrollStateChanged(recyclerView, newState);
}
});
parseIntentForSearchQuery(); parseIntentForSearchQuery();
} }

View File

@ -26,10 +26,7 @@ import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import org.fdroid.fdroid.AppDetails2; import org.fdroid.fdroid.AppDetails2;
import org.fdroid.fdroid.AppUpdateStatusManager; import org.fdroid.fdroid.AppUpdateStatusManager;
import org.fdroid.fdroid.AppUpdateStatusManager.AppUpdateStatus; import org.fdroid.fdroid.AppUpdateStatusManager.AppUpdateStatus;
@ -57,7 +54,7 @@ import java.util.Iterator;
* {@link org.fdroid.fdroid.views.updates.items.UpdateableAppListItemController}</li> * {@link org.fdroid.fdroid.views.updates.items.UpdateableAppListItemController}</li>
* <li>installed_app_list_item (see {@link StandardAppListItemController}</li> * <li>installed_app_list_item (see {@link StandardAppListItemController}</li>
* </ul> * </ul>
* * <p>
* The state of the UI is defined in a dumb {@link AppListItemState} class, then applied to the UI * The state of the UI is defined in a dumb {@link AppListItemState} class, then applied to the UI
* in the {@link #refreshView(App, AppUpdateStatus)} method. * in the {@link #refreshView(App, AppUpdateStatus)} method.
*/ */
@ -98,8 +95,6 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
@Nullable @Nullable
private final Button secondaryButton; private final Button secondaryButton;
private final DisplayImageOptions displayImageOptions;
@Nullable @Nullable
private App currentApp; private App currentApp;
@ -156,8 +151,6 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
cancelButton.setOnClickListener(onCancelDownload); cancelButton.setOnClickListener(onCancelDownload);
} }
displayImageOptions = Utils.getImageLoadingOptions().build();
itemView.setOnClickListener(onAppClicked); itemView.setOnClickListener(onAppClicked);
} }
@ -169,7 +162,7 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
public void bindModel(@NonNull App app) { public void bindModel(@NonNull App app) {
currentApp = app; currentApp = app;
ImageLoader.getInstance().displayImage(app.iconUrl, icon, displayImageOptions); ImageLoader.getInstance().displayImage(app.iconUrl, icon, Utils.getRepoAppDisplayImageOptions());
// Figures out the current install/update/download/etc status for the app we are viewing. // Figures out the current install/update/download/etc status for the app we are viewing.
// Then, asks the view to update itself to reflect this status. // Then, asks the view to update itself to reflect this status.
@ -193,7 +186,9 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
broadcastManager.registerReceiver(onStatusChanged, intentFilter); broadcastManager.registerReceiver(onStatusChanged, intentFilter);
} }
/** To be overridden if required */ /**
* To be overridden if required
*/
public boolean canDismiss() { public boolean canDismiss() {
return false; return false;
} }
@ -214,6 +209,7 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
/** /**
* Override to respond to the user swiping an app to dismiss it from the list. * Override to respond to the user swiping an app to dismiss it from the list.
*
* @return Optionally return a description of what you did if it is not obvious to the user. It will be shown as * @return Optionally return a description of what you did if it is not obvious to the user. It will be shown as
* a {@link android.widget.Toast} for a {@link android.widget.Toast#LENGTH_SHORT} time. * a {@link android.widget.Toast} for a {@link android.widget.Toast#LENGTH_SHORT} time.
* @see #canDismiss() This must also be overriden and should return true. * @see #canDismiss() This must also be overriden and should return true.
@ -235,7 +231,7 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
/** /**
* Queries the current state via {@link #getCurrentViewState(App, AppUpdateStatus)} * Queries the current state via {@link #getCurrentViewState(App, AppUpdateStatus)}
* and then updates the relevant widgets depending on that state. * and then updates the relevant widgets depending on that state.
* * <p>
* Should contain little to no business logic, this all belongs to * Should contain little to no business logic, this all belongs to
* {@link #getCurrentViewState(App, AppUpdateStatus)}. * {@link #getCurrentViewState(App, AppUpdateStatus)}.
* *
@ -489,7 +485,8 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI); intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
try { try {
pendingIntent.send(); pendingIntent.send();
} catch (PendingIntent.CanceledException ignored) { } } catch (PendingIntent.CanceledException ignored) {
}
} }
} }
}; };
@ -503,8 +500,11 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
} }
} }
/** To be overridden by subclasses if desired */ /**
protected void onSecondaryButtonPressed(@NonNull App app) { } * To be overridden by subclasses if desired
*/
protected void onSecondaryButtonPressed(@NonNull App app) {
}
@SuppressWarnings("FieldCanBeLocal") @SuppressWarnings("FieldCanBeLocal")
private final View.OnClickListener onCancelDownload = new View.OnClickListener() { private final View.OnClickListener onCancelDownload = new View.OnClickListener() {

View File

@ -18,7 +18,6 @@ import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
@ -70,7 +69,6 @@ public class AppCardController extends RecyclerView.ViewHolder
private App currentApp; private App currentApp;
private final Activity activity; private final Activity activity;
private final DisplayImageOptions displayImageOptions;
public AppCardController(Activity activity, View itemView) { public AppCardController(Activity activity, View itemView) {
super(itemView); super(itemView);
@ -83,8 +81,6 @@ public class AppCardController extends RecyclerView.ViewHolder
featuredImage = (FeatureImage) itemView.findViewById(R.id.featured_image); featuredImage = (FeatureImage) itemView.findViewById(R.id.featured_image);
newTag = (TextView) itemView.findViewById(R.id.new_tag); newTag = (TextView) itemView.findViewById(R.id.new_tag);
displayImageOptions = Utils.getImageLoadingOptions().build();
itemView.setOnClickListener(this); itemView.setOnClickListener(this);
} }
@ -117,7 +113,7 @@ public class AppCardController extends RecyclerView.ViewHolder
} }
} }
ImageLoader.getInstance().displayImage(app.iconUrl, icon, displayImageOptions, this); ImageLoader.getInstance().displayImage(app.iconUrl, icon, Utils.getRepoAppDisplayImageOptions(), this);
if (featuredImage != null) { if (featuredImage != null) {
featuredImage.setColour(ContextCompat.getColor(activity, R.color.fdroid_blue)); featuredImage.setColour(ContextCompat.getColor(activity, R.color.fdroid_blue));
@ -133,7 +129,7 @@ public class AppCardController extends RecyclerView.ViewHolder
// feature image to be loaded). // feature image to be loaded).
if (!TextUtils.isEmpty(app.featureGraphic)) { if (!TextUtils.isEmpty(app.featureGraphic)) {
featuredImage.loadImageAndDisplay(ImageLoader.getInstance(), featuredImage.loadImageAndDisplay(ImageLoader.getInstance(),
displayImageOptions, app.getFeatureGraphicUrl(activity)); Utils.getRepoAppDisplayImageOptions(), app.getFeatureGraphicUrl(activity));
} }
} }
} }
@ -162,7 +158,8 @@ public class AppCardController extends RecyclerView.ViewHolder
Pair<View, String> iconTransitionPair = Pair.create((View) icon, Pair<View, String> iconTransitionPair = Pair.create((View) icon,
activity.getString(R.string.transition_app_item_icon)); activity.getString(R.string.transition_app_item_icon));
@SuppressWarnings("unchecked") // the right type is passed as 2nd varargs arg: Pair<View, String> // unchecked since the right type is passed as 2nd varargs arg: Pair<View, String>
@SuppressWarnings("unchecked")
Bundle b = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, iconTransitionPair).toBundle(); Bundle b = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, iconTransitionPair).toBundle();
activity.startActivity(intent, b); activity.startActivity(intent, b);
} else { } else {

View File

@ -5,7 +5,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Bundle; import android.os.Bundle;
@ -23,9 +22,9 @@ import android.widget.FrameLayout;
import android.widget.TextView; import android.widget.TextView;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.Schema; import org.fdroid.fdroid.data.Schema;
import org.fdroid.fdroid.views.apps.AppListActivity; import org.fdroid.fdroid.views.apps.AppListActivity;
@ -67,11 +66,8 @@ public class CategoryController extends RecyclerView.ViewHolder implements Loade
appCards.setAdapter(appCardsAdapter); appCards.setAdapter(appCardsAdapter);
appCards.addItemDecoration(new ItemDecorator(activity)); appCards.addItemDecoration(new ItemDecorator(activity));
displayImageOptions = new DisplayImageOptions.Builder() displayImageOptions = Utils.getDefaultDisplayImageOptionsBuilder()
.cacheInMemory(true)
.imageScaleType(ImageScaleType.NONE)
.displayer(new FadeInBitmapDisplayer(100, true, true, false)) .displayer(new FadeInBitmapDisplayer(100, true, true, false))
.bitmapConfig(Bitmap.Config.RGB_565)
.build(); .build();
} }

View File

@ -36,10 +36,7 @@ import android.widget.ListView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.UpdateService; import org.fdroid.fdroid.UpdateService;
@ -63,8 +60,6 @@ public class SwapAppsView extends ListView implements
LoaderManager.LoaderCallbacks<Cursor>, LoaderManager.LoaderCallbacks<Cursor>,
SearchView.OnQueryTextListener { SearchView.OnQueryTextListener {
private DisplayImageOptions displayImageOptions;
public SwapAppsView(Context context) { public SwapAppsView(Context context) {
super(context); super(context);
} }
@ -113,8 +108,6 @@ public class SwapAppsView extends ListView implements
// either reconnect with an existing loader or start a new one // either reconnect with an existing loader or start a new one
getActivity().getSupportLoaderManager().initLoader(LOADER_SWAPABLE_APPS, null, this); getActivity().getSupportLoaderManager().initLoader(LOADER_SWAPABLE_APPS, null, this);
displayImageOptions = Utils.getImageLoadingOptions().build();
LocalBroadcastManager.getInstance(getActivity()).registerReceiver( LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
pollForUpdatesReceiver, new IntentFilter(UpdateService.LOCAL_ACTION_STATUS)); pollForUpdatesReceiver, new IntentFilter(UpdateService.LOCAL_ACTION_STATUS));
@ -354,7 +347,7 @@ public class SwapAppsView extends ListView implements
nameView.setText(app.name); nameView.setText(app.name);
} }
ImageLoader.getInstance().displayImage(app.iconUrl, iconView, displayImageOptions); ImageLoader.getInstance().displayImage(app.iconUrl, iconView, Utils.getRepoAppDisplayImageOptions());
if (app.hasUpdates()) { if (app.hasUpdates()) {
btnInstall.setText(R.string.menu_upgrade); btnInstall.setText(R.string.menu_upgrade);