Brion VIBBER has submitted this change and it was merged.

Change subject: Volley images
......................................................................


Volley images

Add support for using Volley to load remote images instead of UIL

1. Gives us Caching
2. Load images at full resolution, rather than hardcode them. This is done by 
trying to fetch an image at highest width possible for the particular view. If 
it 500s, we assume that the image is smaller than the requested width and just 
request the full size image
3. Created a MediaWikiImageView, to which you can pass a Media object and it 
will display it. Takes care of sizing, etc. Optionally you can also specify a 
view to use as the 'loading' view.

TODO:
Loading from content:// URIs still use UIL. Need to write a Volley HTTP Stack 
that can fake responses for content:// URIs.

GitHub: https://github.com/wikimedia/apps-android-commons/pull/1
Change-Id: Ia21a7b19fefa552d5a0b013085d0f5f1f80dc5ff
---
M commons/pom.xml
M commons/res/layout/fragment_media_detail.xml
M commons/res/layout/layout_contribution.xml
M commons/src/main/java/org/wikimedia/commons/CommonsApplication.java
A commons/src/main/java/org/wikimedia/commons/MediaWikiImageView.java
M 
commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java
M commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java
M pom.xml
8 files changed, 298 insertions(+), 37 deletions(-)

Approvals:
  Brion VIBBER: Verified; Looks good to me, approved



diff --git a/commons/pom.xml b/commons/pom.xml
index 7adf9e8..c66d489 100644
--- a/commons/pom.xml
+++ b/commons/pom.xml
@@ -53,6 +53,12 @@
           <version>4.4.0</version>
       </dependency>
       <dependency>
+          <groupId>com.android</groupId>
+          <artifactId>volley</artifactId>
+          <version>1.0</version>
+      </dependency>
+
+      <dependency>
           <groupId>de.keyboardsurfer.android.widget</groupId>
           <artifactId>crouton</artifactId>
           <version>1.7</version>
diff --git a/commons/res/layout/fragment_media_detail.xml 
b/commons/res/layout/fragment_media_detail.xml
index d56a4c6..a8112d9 100644
--- a/commons/res/layout/fragment_media_detail.xml
+++ b/commons/res/layout/fragment_media_detail.xml
@@ -22,11 +22,10 @@
                android:visibility="gone"
                />
 
-    <ImageView android:id="@+id/mediaDetailImage"
+    <org.wikimedia.commons.MediaWikiImageView 
android:id="@+id/mediaDetailImage"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:scaleType="fitCenter"
-               android:visibility="gone"
             />
 
     <RelativeLayout
diff --git a/commons/res/layout/layout_contribution.xml 
b/commons/res/layout/layout_contribution.xml
index f436e96..1c7dcf0 100644
--- a/commons/res/layout/layout_contribution.xml
+++ b/commons/res/layout/layout_contribution.xml
@@ -16,7 +16,7 @@
               android:typeface="serif"
               android:layout_gravity="end|bottom"
             />
-    <ImageView android:id="@+id/contributionImage"
+    <org.wikimedia.commons.MediaWikiImageView 
android:id="@+id/contributionImage"
                android:layout_width="fill_parent"
                android:layout_height="240dp"
                android:scaleType="centerCrop"
diff --git 
a/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java 
b/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java
index 6e1fc47..e88d05b 100644
--- a/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java
+++ b/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java
@@ -14,6 +14,8 @@
 import android.net.Uri;
 import android.os.Build;
 
+import android.support.v4.util.LruCache;
+import com.android.volley.RequestQueue;
 import 
com.nostra13.universalimageloader.cache.disc.impl.TotalSizeLimitedDiscCache;
 import 
com.nostra13.universalimageloader.cache.memory.impl.LimitedAgeMemoryCache;
 import com.nostra13.universalimageloader.core.ImageLoader;
@@ -28,6 +30,8 @@
 import org.apache.http.impl.client.*;
 import org.apache.http.params.CoreProtocolPNames;
 import org.wikimedia.commons.data.*;
+
+import com.android.volley.toolbox.*;
 
 // TODO: Use ProGuard to rip out reporting when publishing
 @ReportsCrashes(formKey = "",
@@ -62,6 +66,7 @@
     public static final String FEEDBACK_EMAIL = 
"mobile-feedbac...@lists.wikimedia.org";
     public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App 
(%s) Feedback";
 
+    public RequestQueue volleyQueue;
 
     public static AbstractHttpClient createHttpClient() {
         DefaultHttpClient client = new DefaultHttpClient();
@@ -106,6 +111,35 @@
 
         // Initialize EventLogging
         EventLog.setApp(this);
+
+        volleyQueue = Volley.newRequestQueue(this);
+    }
+
+    private com.android.volley.toolbox.ImageLoader imageLoader;
+    // based off 
https://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
+    // Cache for 1/8th of available VM memory
+    private LruCache<String, Bitmap> imageCache = new LruCache<String, 
Bitmap>((int) (Runtime.getRuntime().maxMemory() / (1024 * 8))) {
+        @Override
+        protected int sizeOf(String key, Bitmap bitmap) {
+            // The cache size will be measured in kilobytes rather than
+            // number of items.
+            return bitmap.getByteCount() / 1024;
+        }
+    };
+
+    public com.android.volley.toolbox.ImageLoader getImageLoader() {
+        if(imageLoader == null) {
+            imageLoader = new 
com.android.volley.toolbox.ImageLoader(volleyQueue, new 
com.android.volley.toolbox.ImageLoader.ImageCache() {
+                public Bitmap getBitmap(String key) {
+                    return imageCache.get(key);
+                }
+
+                public void putBitmap(String key, Bitmap bitmap) {
+                    imageCache.put(key, bitmap);
+                }
+            });
+        }
+        return imageLoader;
     }
     
     public MWApi getApi() {
diff --git 
a/commons/src/main/java/org/wikimedia/commons/MediaWikiImageView.java 
b/commons/src/main/java/org/wikimedia/commons/MediaWikiImageView.java
new file mode 100644
index 0000000..37fa04d
--- /dev/null
+++ b/commons/src/main/java/org/wikimedia/commons/MediaWikiImageView.java
@@ -0,0 +1,201 @@
+/**
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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.wikimedia.commons;
+
+import android.content.Context;
+import android.graphics.drawable.BitmapDrawable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.ImageLoader;
+import com.android.volley.toolbox.ImageLoader.ImageContainer;
+import com.android.volley.toolbox.ImageLoader.ImageListener;
+
+
+public class MediaWikiImageView extends ImageView {
+
+    private Media mMedia;
+
+    private ImageLoader mImageLoader;
+
+    private ImageContainer mImageContainer;
+
+    private View loadingView;
+
+    public MediaWikiImageView(Context context) {
+        this(context, null);
+    }
+
+    public MediaWikiImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public MediaWikiImageView(Context context, AttributeSet attrs, int 
defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setMedia(Media media, ImageLoader imageLoader) {
+        this.mMedia = media;
+        mImageLoader = imageLoader;
+        loadImageIfNecessary(false);
+    }
+
+    public void setLoadingView(View loadingView) {
+        this.loadingView = loadingView;
+    }
+
+    public View getLoadingView() {
+        return loadingView;
+    }
+
+    private void loadImageIfNecessary(final boolean isInLayoutPass) {
+        loadImageIfNecessary(isInLayoutPass, false);
+    }
+
+    private void loadImageIfNecessary(final boolean isInLayoutPass, final 
boolean tryOriginal) {
+        int width = getWidth();
+        int height = getHeight();
+
+        // if the view's bounds aren't known yet, hold off on loading the 
image.
+        if (width == 0 && height == 0) {
+            return;
+        }
+
+        final String  mUrl;
+        if(tryOriginal) {
+            mUrl = mMedia.getImageUrl();
+        } else {
+            // Round it down to the nearest 320
+            // Possible a similar size image has already been generated.
+            // Reduces Server cache fragmentation, also increases chance of 
cache hit
+            // If width is less than 320, we just use that directly, to avoid 
a case of the Maths
+            mUrl = mMedia.getThumbnailUrl(width <= 320 ? width : (width / 320) 
* 320);
+        }
+
+        // if the URL to be loaded in this view is empty, cancel any old 
requests and clear the
+        // currently loaded image.
+        if (TextUtils.isEmpty(mUrl)) {
+            if (mImageContainer != null) {
+                mImageContainer.cancelRequest();
+                mImageContainer = null;
+            }
+            setImageBitmap(null);
+            return;
+        }
+
+        // Don't repeat work. Prevents onLayout cascades
+        // We ignore it if the image request was for either the current URL of 
for the full URL
+        // Since the full URL is always the second, and
+        if (mImageContainer != null && mImageContainer.getRequestUrl() != 
null) {
+            Log.d("Commons", "Older one is " + mImageContainer.getRequestUrl() 
+ " new one is " + mUrl);
+            if (mImageContainer.getRequestUrl().equals(mMedia.getImageUrl()) 
|| mImageContainer.getRequestUrl().equals(mUrl)) {
+                return;
+            } else {
+                // if there is a pre-existing request, cancel it if it's 
fetching a different URL.
+                mImageContainer.cancelRequest();
+                BitmapDrawable actualDrawable = (BitmapDrawable)getDrawable();
+                if(actualDrawable != null && actualDrawable.getBitmap() != 
null) {
+                    setImageBitmap(null);
+                    if(loadingView != null) {
+                        loadingView.setVisibility(View.VISIBLE);
+                    }
+                }
+            }
+        }
+
+        // The pre-existing content of this view didn't match the current URL. 
Load the new image
+        // from the network.
+        ImageContainer newContainer = mImageLoader.get(mUrl,
+                new ImageListener() {
+                    @Override
+                    public void onErrorResponse(final VolleyError error) {
+                        Log.d("Commons", "Error: or Url " + mUrl + " value is 
" + tryOriginal);
+                        if(!tryOriginal) {
+                            post(new Runnable() {
+                                public void run() {
+                                    loadImageIfNecessary(false, true);
+                                }
+                            });
+                        }
+
+                    }
+
+                    @Override
+                    public void onResponse(final ImageContainer response, 
boolean isImmediate) {
+                        // If this was an immediate response that was 
delivered inside of a layout
+                        // pass do not set the image immediately as it will 
trigger a requestLayout
+                        // inside of a layout. Instead, defer setting the 
image by posting back to
+                        // the main thread.
+                        if (isImmediate && isInLayoutPass) {
+                            post(new Runnable() {
+                                @Override
+                                public void run() {
+                                    onResponse(response, false);
+                                }
+                            });
+                            return;
+                        }
+
+
+                        Log.d("Commons", "No-Error: For Url " + mUrl + " value 
is " + tryOriginal);
+
+                        if (response.getBitmap() != null) {
+                            setImageBitmap(response.getBitmap());
+                            if(loadingView != null) {
+                                loadingView.setVisibility(View.GONE);
+                            }
+                        } else {
+                            Log.d("Commons", "Whelp, fully can not load an 
image at all!");
+                            // We got nothing back, figure out some sort of a 
solution?
+                        }
+                    }
+                });
+
+        // update the ImageContainer to be the new bitmap container.
+        mImageContainer = newContainer;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int 
bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        Log.d("Commons", "Called via onLayout");
+        loadImageIfNecessary(true);
+        // Called via onLayout
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        if (mImageContainer != null) {
+            // If the view was bound to an image request, cancel it and clear
+            // out the image from the view.
+            mImageContainer.cancelRequest();
+            setImageBitmap(null);
+            // also clear out the container so we can reload the image if 
necessary.
+            mImageContainer = null;
+        }
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        invalidate();
+    }
+}
diff --git 
a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java
 
b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java
index 685b55c..e8b076d 100644
--- 
a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java
+++ 
b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java
@@ -22,14 +22,16 @@
 import com.actionbarsherlock.view.Menu;
 import com.actionbarsherlock.view.MenuInflater;
 import com.actionbarsherlock.view.MenuItem;
-import com.nostra13.universalimageloader.core.*;
-import com.nostra13.universalimageloader.core.assist.*;
+
+import com.nostra13.universalimageloader.core.DisplayImageOptions;
 
 import java.io.*;
 import java.util.*;
 
 
+import 
com.nostra13.universalimageloader.core.assist.SimpleImageLoadingListener;
 import org.wikimedia.commons.*;
+import org.wikimedia.commons.R;
 
 public class ContributionsListFragment extends SherlockFragment {
 
@@ -85,17 +87,23 @@
             String actualUrl = TextUtils.isEmpty(contribution.getImageUrl()) ? 
contribution.getLocalUri().toString() : contribution.getThumbnailUrl(320);
 
             if(views.url == null || !views.url.equals(actualUrl)) {
-                ImageLoader.getInstance().displayImage(actualUrl, 
views.imageView, contributionDisplayOptions, new SimpleImageLoadingListener() {
+                if(actualUrl.startsWith("http")) {
+                    MediaWikiImageView mwImageView = 
(MediaWikiImageView)views.imageView;
+                    mwImageView.setMedia(contribution, ((CommonsApplication) 
getActivity().getApplicationContext()).getImageLoader());
+                    // FIXME: For transparent images
+                } else {
+                    
com.nostra13.universalimageloader.core.ImageLoader.getInstance().displayImage(actualUrl,
 views.imageView, contributionDisplayOptions, new SimpleImageLoadingListener() {
 
-                    @Override
-                    public void onLoadingComplete(String imageUri, View view, 
Bitmap loadedImage) {
-                        if(loadedImage.hasAlpha()) {
-                            
views.imageView.setBackgroundResource(android.R.color.white);
+                        @Override
+                        public void onLoadingComplete(String imageUri, View 
view, Bitmap loadedImage) {
+                            if(loadedImage.hasAlpha()) {
+                                
views.imageView.setBackgroundResource(android.R.color.white);
+                            }
+                            views.seqNumView.setVisibility(View.GONE);
                         }
-                        views.seqNumView.setVisibility(View.GONE);
-                    }
 
-                });
+                    });
+                }
                 views.url = actualUrl;
             }
 
diff --git 
a/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java 
b/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java
index 43bb24f..39343d8 100644
--- a/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java
+++ b/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java
@@ -3,13 +3,16 @@
 import android.graphics.*;
 import android.os.*;
 import android.text.*;
+import android.util.Log;
 import android.view.*;
 import android.widget.*;
 import com.actionbarsherlock.app.SherlockFragment;
+import com.android.volley.toolbox.NetworkImageView;
 import com.nostra13.universalimageloader.core.DisplayImageOptions;
-import com.nostra13.universalimageloader.core.ImageLoader;
 import com.nostra13.universalimageloader.core.assist.FailReason;
 import com.nostra13.universalimageloader.core.assist.ImageLoadingListener;
+
+import com.android.volley.toolbox.*;
 
 import org.wikimedia.commons.*;
 
@@ -78,29 +81,38 @@
         }
 
         String actualUrl = TextUtils.isEmpty(media.getImageUrl()) ? 
media.getLocalUri().toString() : media.getThumbnailUrl(640);
-        ImageLoader.getInstance().displayImage(actualUrl, image, 
displayOptions, new ImageLoadingListener() {
-            public void onLoadingStarted(String s, View view) {
-                loadingProgress.setVisibility(View.VISIBLE);
-            }
-
-            public void onLoadingFailed(String s, View view, FailReason 
failReason) {
-                loadingProgress.setVisibility(View.GONE);
-                loadingFailed.setVisibility(View.VISIBLE);
-            }
-
-            public void onLoadingComplete(String s, View view, Bitmap bitmap) {
-                loadingProgress.setVisibility(View.GONE);
-                loadingFailed.setVisibility(View.GONE);
-                image.setVisibility(View.VISIBLE);
-                if(bitmap.hasAlpha()) {
-                    image.setBackgroundResource(android.R.color.white);
+        if(actualUrl.startsWith("http")) {
+            ImageLoader loader = 
((CommonsApplication)getActivity().getApplicationContext()).getImageLoader();
+            MediaWikiImageView mwImage = (MediaWikiImageView)image;
+            mwImage.setLoadingView(loadingProgress); //FIXME: Set this as an 
attribute
+            mwImage.setMedia(media, loader);
+            Log.d("Volley", actualUrl);
+            // FIXME: For transparent images
+        } else {
+            
com.nostra13.universalimageloader.core.ImageLoader.getInstance().displayImage(actualUrl,
 image, displayOptions, new ImageLoadingListener() {
+                public void onLoadingStarted(String s, View view) {
+                    loadingProgress.setVisibility(View.VISIBLE);
                 }
-            }
 
-            public void onLoadingCancelled(String s, View view) {
-                throw new RuntimeException("Image loading cancelled. But 
why?");
-            }
-        });
+                public void onLoadingFailed(String s, View view, FailReason 
failReason) {
+                    loadingProgress.setVisibility(View.GONE);
+                    loadingFailed.setVisibility(View.VISIBLE);
+                }
+
+                public void onLoadingComplete(String s, View view, Bitmap 
bitmap) {
+                    loadingProgress.setVisibility(View.GONE);
+                    loadingFailed.setVisibility(View.GONE);
+                    image.setVisibility(View.VISIBLE);
+                    if(bitmap.hasAlpha()) {
+                        image.setBackgroundResource(android.R.color.white);
+                    }
+                }
+
+                public void onLoadingCancelled(String s, View view) {
+                    throw new RuntimeException("Image loading cancelled. But 
why?");
+                }
+            });
+        }
         title.setText(media.getDisplayTitle());
 
         title.addTextChangedListener(new TextWatcher() {
diff --git a/pom.xml b/pom.xml
index a2ad105..42c177b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -205,10 +205,11 @@
     </profiles>
 
     <repositories>
+
         <repository>
             <id>yuvi.in</id>
-            <name>Yuvi's Maven Repo</name>
-            <url>http://yuvi.in/blog/maven</url>
+            <name>Yuvi's Newer Maven Repo</name>
+            <url>http://yuvi.in/maven</url>
         </repository>
     </repositories>
 

-- 
To view, visit https://gerrit.wikimedia.org/r/68282
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ia21a7b19fefa552d5a0b013085d0f5f1f80dc5ff
Gerrit-PatchSet: 2
Gerrit-Project: apps/android/commons
Gerrit-Branch: master
Gerrit-Owner: SuchABot <yuvipanda+sucha...@gmail.com>
Gerrit-Reviewer: Brion VIBBER <br...@wikimedia.org>
Gerrit-Reviewer: Yuvipanda <yuvipa...@gmail.com>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to