mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 13:55:43 +00:00
Bug 1012462 - Part 11: Support image loading for distribution files (r=rnewman)
This commit is contained in:
parent
92588f6998
commit
0b216e16ae
154
mobile/android/base/home/ImageLoader.java
Normal file
154
mobile/android/base/home/ImageLoader.java
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.gecko.home;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.squareup.picasso.Picasso;
|
||||||
|
import com.squareup.picasso.Downloader.Response;
|
||||||
|
import com.squareup.picasso.UrlConnectionDownloader;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.distribution.Distribution;
|
||||||
|
|
||||||
|
public class ImageLoader {
|
||||||
|
private static final String LOGTAG = "GeckoImageLoader";
|
||||||
|
|
||||||
|
private static final String DISTRIBUTION_SCHEME = "gecko.distribution";
|
||||||
|
private static final String SUGGESTED_SITES_AUTHORITY = "suggestedsites";
|
||||||
|
|
||||||
|
// The order of density factors to try when looking for an image resource
|
||||||
|
// in the distribution directory. It looks for an exact match first (1.0) then
|
||||||
|
// tries to find images with higher density (2.0 and 1.5). If no image is found,
|
||||||
|
// try a lower density (0.5). See loadDistributionImage().
|
||||||
|
private static final float[] densityFactors = new float[] { 1.0f, 2.0f, 1.5f, 0.5f };
|
||||||
|
|
||||||
|
private static enum Density {
|
||||||
|
MDPI,
|
||||||
|
HDPI,
|
||||||
|
XHDPI,
|
||||||
|
XXHDPI;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString().toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Picasso instance;
|
||||||
|
|
||||||
|
public static synchronized Picasso with(Context context) {
|
||||||
|
if (instance == null) {
|
||||||
|
Picasso.Builder builder = new Picasso.Builder(context);
|
||||||
|
|
||||||
|
final Distribution distribution = Distribution.getInstance(context);
|
||||||
|
builder.downloader(new ImageDownloader(context, distribution));
|
||||||
|
instance = builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Downloader built on top of Picasso's UrlConnectionDownloader
|
||||||
|
* that supports loading images from custom URIs.
|
||||||
|
*/
|
||||||
|
public static class ImageDownloader extends UrlConnectionDownloader {
|
||||||
|
private final Context context;
|
||||||
|
private final Distribution distribution;
|
||||||
|
|
||||||
|
public ImageDownloader(Context context, Distribution distribution) {
|
||||||
|
super(context);
|
||||||
|
this.context = context;
|
||||||
|
this.distribution = distribution;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Density getDensity(float factor) {
|
||||||
|
final DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||||
|
final float densityDpi = dm.densityDpi * factor;
|
||||||
|
|
||||||
|
if (densityDpi >= DisplayMetrics.DENSITY_XXHIGH) {
|
||||||
|
return Density.XXHDPI;
|
||||||
|
} else if (densityDpi >= DisplayMetrics.DENSITY_XHIGH) {
|
||||||
|
return Density.XHDPI;
|
||||||
|
} else if (densityDpi >= DisplayMetrics.DENSITY_HIGH) {
|
||||||
|
return Density.HDPI;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to mdpi, no need to handle ldpi.
|
||||||
|
return Density.MDPI;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response load(Uri uri, boolean localCacheOnly) throws IOException {
|
||||||
|
final String scheme = uri.getScheme();
|
||||||
|
if (DISTRIBUTION_SCHEME.equals(scheme)) {
|
||||||
|
return loadDistributionImage(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.load(uri, localCacheOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getPathForDensity(String basePath, Density density,
|
||||||
|
String filename) {
|
||||||
|
final File dir = new File(basePath, density.toString());
|
||||||
|
return String.format("%s/%s.png", dir.toString(), filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle distribution URIs in Picasso. The expected format is:
|
||||||
|
*
|
||||||
|
* gecko.distribution://<basepath>/<imagename>
|
||||||
|
*
|
||||||
|
* Which will look for the following file in the distribution:
|
||||||
|
*
|
||||||
|
* <distribution-root-dir>/<basepath>/<device-density>/<imagename>.png
|
||||||
|
*/
|
||||||
|
private Response loadDistributionImage(Uri uri) throws IOException {
|
||||||
|
// Eliminate the leading '//'
|
||||||
|
final String ssp = uri.getSchemeSpecificPart().substring(2);
|
||||||
|
|
||||||
|
final String filename;
|
||||||
|
final String basePath;
|
||||||
|
|
||||||
|
final int slashIndex = ssp.lastIndexOf('/');
|
||||||
|
if (slashIndex == -1) {
|
||||||
|
filename = ssp;
|
||||||
|
basePath = "";
|
||||||
|
} else {
|
||||||
|
filename = ssp.substring(slashIndex + 1);
|
||||||
|
basePath = ssp.substring(0, slashIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Density> triedDensities = EnumSet.noneOf(Density.class);
|
||||||
|
|
||||||
|
for (int i = 0; i < densityFactors.length; i++) {
|
||||||
|
final Density density = getDensity(densityFactors[i]);
|
||||||
|
if (!triedDensities.add(density)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String path = getPathForDensity(basePath, density, filename);
|
||||||
|
Log.d(LOGTAG, "Trying to load image from distribution " + path);
|
||||||
|
|
||||||
|
final File f = distribution.getDistributionFile(path);
|
||||||
|
if (f != null) {
|
||||||
|
return new Response(new FileInputStream(f), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ResponseException("Couldn't find suggested site image in distribution");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -56,7 +56,7 @@ class PanelAuthLayout extends LinearLayout {
|
|||||||
// Use a default image if an image URL isn't specified.
|
// Use a default image if an image URL isn't specified.
|
||||||
imageView.setImageResource(R.drawable.icon_home_empty_firefox);
|
imageView.setImageResource(R.drawable.icon_home_empty_firefox);
|
||||||
} else {
|
} else {
|
||||||
Picasso.with(getContext())
|
ImageLoader.with(getContext())
|
||||||
.load(imageUrl)
|
.load(imageUrl)
|
||||||
.into(imageView);
|
.into(imageView);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ class PanelBackItemView extends LinearLayout {
|
|||||||
if (TextUtils.isEmpty(backImageUrl)) {
|
if (TextUtils.isEmpty(backImageUrl)) {
|
||||||
image.setImageResource(R.drawable.folder_up);
|
image.setImageResource(R.drawable.folder_up);
|
||||||
} else {
|
} else {
|
||||||
Picasso.with(getContext())
|
ImageLoader.with(getContext())
|
||||||
.load(backImageUrl)
|
.load(backImageUrl)
|
||||||
.placeholder(R.drawable.folder_up)
|
.placeholder(R.drawable.folder_up)
|
||||||
.into(image);
|
.into(image);
|
||||||
|
@ -68,7 +68,7 @@ class PanelItemView extends LinearLayout {
|
|||||||
image.setVisibility(hasImageUrl ? View.VISIBLE : View.GONE);
|
image.setVisibility(hasImageUrl ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
if (hasImageUrl) {
|
if (hasImageUrl) {
|
||||||
Picasso.with(getContext())
|
ImageLoader.with(getContext())
|
||||||
.load(imageUrl)
|
.load(imageUrl)
|
||||||
.into(image);
|
.into(image);
|
||||||
}
|
}
|
||||||
|
@ -460,7 +460,7 @@ abstract class PanelLayout extends FrameLayout {
|
|||||||
if (TextUtils.isEmpty(imageUrl)) {
|
if (TextUtils.isEmpty(imageUrl)) {
|
||||||
imageView.setImageResource(R.drawable.icon_home_empty_firefox);
|
imageView.setImageResource(R.drawable.icon_home_empty_firefox);
|
||||||
} else {
|
} else {
|
||||||
Picasso.with(getContext())
|
ImageLoader.with(getContext())
|
||||||
.load(imageUrl)
|
.load(imageUrl)
|
||||||
.error(R.drawable.icon_home_empty_firefox)
|
.error(R.drawable.icon_home_empty_firefox)
|
||||||
.into(imageView);
|
.into(imageView);
|
||||||
|
@ -149,7 +149,7 @@ public class TopSitesGridItemView extends RelativeLayout {
|
|||||||
updateType(TopSites.TYPE_BLANK);
|
updateType(TopSites.TYPE_BLANK);
|
||||||
updateTitleView();
|
updateTitleView();
|
||||||
setLoadId(Favicons.NOT_LOADING);
|
setLoadId(Favicons.NOT_LOADING);
|
||||||
Picasso.with(getContext()).cancelRequest(mThumbnailView);
|
ImageLoader.with(getContext()).cancelRequest(mThumbnailView);
|
||||||
displayThumbnail(R.drawable.top_site_add);
|
displayThumbnail(R.drawable.top_site_add);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -192,7 +192,7 @@ public class TopSitesGridItemView extends RelativeLayout {
|
|||||||
if (changed) {
|
if (changed) {
|
||||||
updateTitleView();
|
updateTitleView();
|
||||||
setLoadId(Favicons.NOT_LOADING);
|
setLoadId(Favicons.NOT_LOADING);
|
||||||
Picasso.with(getContext()).cancelRequest(mThumbnailView);
|
ImageLoader.with(getContext()).cancelRequest(mThumbnailView);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateType(type)) {
|
if (updateType(type)) {
|
||||||
@ -233,7 +233,7 @@ public class TopSitesGridItemView extends RelativeLayout {
|
|||||||
}
|
}
|
||||||
mThumbnailSet = true;
|
mThumbnailSet = true;
|
||||||
Favicons.cancelFaviconLoad(mLoadId);
|
Favicons.cancelFaviconLoad(mLoadId);
|
||||||
Picasso.with(getContext()).cancelRequest(mThumbnailView);
|
ImageLoader.with(getContext()).cancelRequest(mThumbnailView);
|
||||||
|
|
||||||
mThumbnailView.setScaleType(SCALE_TYPE_THUMBNAIL);
|
mThumbnailView.setScaleType(SCALE_TYPE_THUMBNAIL);
|
||||||
mThumbnailView.setImageBitmap(thumbnail);
|
mThumbnailView.setImageBitmap(thumbnail);
|
||||||
@ -251,7 +251,7 @@ public class TopSitesGridItemView extends RelativeLayout {
|
|||||||
mThumbnailView.setBackgroundColor(bgColor);
|
mThumbnailView.setBackgroundColor(bgColor);
|
||||||
mThumbnailSet = true;
|
mThumbnailSet = true;
|
||||||
|
|
||||||
Picasso.with(getContext())
|
ImageLoader.with(getContext())
|
||||||
.load(imageUrl)
|
.load(imageUrl)
|
||||||
.noFade()
|
.noFade()
|
||||||
.error(R.drawable.favicon)
|
.error(R.drawable.favicon)
|
||||||
|
@ -278,6 +278,7 @@ gbjar.sources += [
|
|||||||
'home/HomePagerTabStrip.java',
|
'home/HomePagerTabStrip.java',
|
||||||
'home/HomePanelPicker.java',
|
'home/HomePanelPicker.java',
|
||||||
'home/HomePanelsManager.java',
|
'home/HomePanelsManager.java',
|
||||||
|
'home/ImageLoader.java',
|
||||||
'home/MultiTypeCursorAdapter.java',
|
'home/MultiTypeCursorAdapter.java',
|
||||||
'home/PanelAuthCache.java',
|
'home/PanelAuthCache.java',
|
||||||
'home/PanelAuthLayout.java',
|
'home/PanelAuthLayout.java',
|
||||||
|
@ -13,6 +13,7 @@ jar.sources += [
|
|||||||
'src/tests/BrowserTestCase.java',
|
'src/tests/BrowserTestCase.java',
|
||||||
'src/tests/TestDistribution.java',
|
'src/tests/TestDistribution.java',
|
||||||
'src/tests/TestGeckoSharedPrefs.java',
|
'src/tests/TestGeckoSharedPrefs.java',
|
||||||
|
'src/tests/TestImageDownloader.java',
|
||||||
'src/tests/TestJarReader.java',
|
'src/tests/TestJarReader.java',
|
||||||
'src/tests/TestRawResource.java',
|
'src/tests/TestRawResource.java',
|
||||||
'src/tests/TestSuggestedSites.java',
|
'src/tests/TestSuggestedSites.java',
|
||||||
|
@ -0,0 +1,205 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
package org.mozilla.gecko.browser.tests;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.test.mock.MockResources;
|
||||||
|
import android.test.RenamingDelegatingContext;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.distribution.Distribution;
|
||||||
|
import org.mozilla.gecko.home.ImageLoader.ImageDownloader;
|
||||||
|
|
||||||
|
public class TestImageDownloader extends BrowserTestCase {
|
||||||
|
private static class TestContext extends RenamingDelegatingContext {
|
||||||
|
private static final String PREFIX = "TestImageDownloader-";
|
||||||
|
|
||||||
|
private final Resources resources;
|
||||||
|
private final Set<String> usedPrefs;
|
||||||
|
|
||||||
|
public TestContext(Context context) {
|
||||||
|
super(context, PREFIX);
|
||||||
|
resources = new TestResources();
|
||||||
|
usedPrefs = Collections.synchronizedSet(new HashSet<String>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Resources getResources() {
|
||||||
|
return resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SharedPreferences getSharedPreferences(String name, int mode) {
|
||||||
|
usedPrefs.add(name);
|
||||||
|
return super.getSharedPreferences(PREFIX + name, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearUsedPrefs() {
|
||||||
|
for (String prefsName : usedPrefs) {
|
||||||
|
getSharedPreferences(prefsName, 0).edit().clear().commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
usedPrefs.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestResources extends MockResources {
|
||||||
|
private final DisplayMetrics metrics;
|
||||||
|
|
||||||
|
public TestResources() {
|
||||||
|
metrics = new DisplayMetrics();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DisplayMetrics getDisplayMetrics() {
|
||||||
|
return metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDensityDpi(int densityDpi) {
|
||||||
|
metrics.densityDpi = densityDpi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestDistribution extends Distribution {
|
||||||
|
final List<String> accessedFiles;
|
||||||
|
|
||||||
|
public TestDistribution(Context context) {
|
||||||
|
super(context);
|
||||||
|
accessedFiles = new ArrayList<String>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getDistributionFile(String name) {
|
||||||
|
accessedFiles.add(name);
|
||||||
|
|
||||||
|
// Return null to ensure the ImageDownloader will go
|
||||||
|
// through a complete density lookup for each filename.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAccessedFiles() {
|
||||||
|
return Collections.unmodifiableList(accessedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetAccessedFiles() {
|
||||||
|
accessedFiles.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestContext context;
|
||||||
|
private TestResources resources;
|
||||||
|
private TestDistribution distribution;
|
||||||
|
private ImageDownloader downloader;
|
||||||
|
|
||||||
|
protected void setUp() {
|
||||||
|
context = new TestContext(getApplicationContext());
|
||||||
|
resources = (TestResources) context.getResources();
|
||||||
|
distribution = new TestDistribution(context);
|
||||||
|
downloader = new ImageDownloader(context, distribution);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void tearDown() {
|
||||||
|
context.clearUsedPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void triggerLoad(Uri uri) {
|
||||||
|
try {
|
||||||
|
downloader.load(uri, false);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore any IO exceptions.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkAccessedFiles(String[] filenames) {
|
||||||
|
List<String> accessedFiles = distribution.getAccessedFiles();
|
||||||
|
|
||||||
|
for (int i = 0; i < filenames.length; i++) {
|
||||||
|
assertEquals(filenames[i], accessedFiles.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkAccessedFilesForUri(Uri uri, int densityDpi, String[] filenames) {
|
||||||
|
resources.setDensityDpi(densityDpi);
|
||||||
|
triggerLoad(uri);
|
||||||
|
checkAccessedFiles(filenames);
|
||||||
|
distribution.resetAccessedFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAccessedFiles() {
|
||||||
|
// Filename only.
|
||||||
|
checkAccessedFilesForUri(Uri.parse("gecko.distribution://file"),
|
||||||
|
DisplayMetrics.DENSITY_MEDIUM,
|
||||||
|
new String[] {
|
||||||
|
"mdpi/file.png",
|
||||||
|
"xhdpi/file.png",
|
||||||
|
"hdpi/file.png"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Directory and filename.
|
||||||
|
checkAccessedFilesForUri(Uri.parse("gecko.distribution://dir/file"),
|
||||||
|
DisplayMetrics.DENSITY_MEDIUM,
|
||||||
|
new String[] {
|
||||||
|
"dir/mdpi/file.png",
|
||||||
|
"dir/xhdpi/file.png",
|
||||||
|
"dir/hdpi/file.png"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sub-directories and filename.
|
||||||
|
checkAccessedFilesForUri(Uri.parse("gecko.distribution://dir/subdir/file"),
|
||||||
|
DisplayMetrics.DENSITY_MEDIUM,
|
||||||
|
new String[] {
|
||||||
|
"dir/subdir/mdpi/file.png",
|
||||||
|
"dir/subdir/xhdpi/file.png",
|
||||||
|
"dir/subdir/hdpi/file.png"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDensityLookup() {
|
||||||
|
Uri uri = Uri.parse("gecko.distribution://file");
|
||||||
|
|
||||||
|
// Medium density
|
||||||
|
checkAccessedFilesForUri(uri,
|
||||||
|
DisplayMetrics.DENSITY_MEDIUM,
|
||||||
|
new String[] {
|
||||||
|
"mdpi/file.png",
|
||||||
|
"xhdpi/file.png",
|
||||||
|
"hdpi/file.png"
|
||||||
|
});
|
||||||
|
|
||||||
|
checkAccessedFilesForUri(uri,
|
||||||
|
DisplayMetrics.DENSITY_HIGH,
|
||||||
|
new String[] {
|
||||||
|
"hdpi/file.png",
|
||||||
|
"xxhdpi/file.png",
|
||||||
|
"xhdpi/file.png"
|
||||||
|
});
|
||||||
|
|
||||||
|
checkAccessedFilesForUri(uri,
|
||||||
|
DisplayMetrics.DENSITY_XHIGH,
|
||||||
|
new String[] {
|
||||||
|
"xhdpi/file.png",
|
||||||
|
"xxhdpi/file.png",
|
||||||
|
"mdpi/file.png"
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
checkAccessedFilesForUri(uri,
|
||||||
|
DisplayMetrics.DENSITY_XXHIGH,
|
||||||
|
new String[] {
|
||||||
|
"xxhdpi/file.png",
|
||||||
|
"hdpi/file.png"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user